指针理解C部分

目录

1.二级指针

2.指针数组

        2.1指针数组的定义和表现形式

        2.2指针数组模拟实现二维数组

        2.2.1二维数组

        2.2.2使用指针数组模拟实现二维数组

3.字符指针

2.数组指针

3.二维数组传参

4.函数指针

        4.1函数指针变量的定义和创建 

         4.2函数指针变量的使用

        4.3两段有趣的代码

        4.4typedef关键字

5.函数指针数组


1.二级指针

        我们说,指针其实就是一个存放一个变量地址的变量,那么,指针的本质其实还是一个变量。既然指针是一个变量,那么这个变量就应该有属于它的地址,既然有一个存放它的地址,那么我们也可以套娃把这个地址放在另外应该指针;里面,那么我们就称这个存放指针地址的另外一个指针为二级指针

        我们可以使用一个生动形象的图来表示这样的关系:

        这样是不是更好理解了呢?

        指针,无非就是一种专门储存地址的变量,再用另外一个指针储存这个变量,就构成了二级指针。我们下面看一下指针的初始化结构:

int a = 10 ;//创建一个变量a
int * p = &a ;//创建一个一级指针p储存a的地址
int * * pp = &p ;//创建一个二级指针储存一级指针的地址

我们在前面讲过,在指针这部分,使用类比推理法是一个很有力的工具,我们再使用类比推理法来解析一下创建二级指针的结构:

        这就是一个二级指针,对于二级指针的运算其实和一级指针是互通的,也就是通过解引用操作符*回到上一级,通过取地址操作符取得地址,*和& 仍然可以相互抵消。比如:我们使用**pp其实就是得到了a的值,因为这里*pp表示通过解引用操作符找到一级指针p,然后*(*pp)就是通过对这个一级指针p解引用得到a的值。

        在这里我们就可以这样使用这种二级指针两次解引用看一下能不能打印出变量的值:

在这里我们可以清楚的看到 a == *p  ==  **pp  == 10

2.指针数组

        2.1指针数组的定义和表现形式

        我们在之前学过数组,数组里面每个元素都是整形的叫做整形数组,数组里面每个元素都是浮点数的叫做叫做浮点数数组,那么一个数组里每个元素都是指针的就是指针数组。并且,每个指针都应该是一个储存地址的变量单元格,所以指针数组本质上其实是每个元素里面都储存了一个指针(地址变量)。

        这样我们其实就可以使用一个指针指向几个指针构成一个指针数组:

	int a = 10;
	int* pa = &a;
	int b = 20;
	int* pb = &b;
	int c = 30;
	int* pc = &c;
	int d = 40;
	int* pd = &d;
	int* arr[4] = { pa,pb,pc,pd };
	//或者我们可以使用另外一种表现形式
	//int* arr[4] = { &a, &b, &c, &d };

        要理解这个指针数组其实也不难,指针数组本质其实就是数组,有整形(int)数组,有浮点型(double)数组,有字符型(char)数组,也就可以有指针数组。当然,指针数组是要细分的,指针数组还可以分为:整形指针(int*)数组,浮点型指针(double*)数组,字符型指针(char*)数组......

         既然一个指针数组可以存放多个地址,那么我们其实也可以存放一个多个数组的首元素地址,前面我们又知道:一个数组的首元素地址我们通常使用这个数组名来代替,那么我们就可以写出下面的代码:

	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 1,5,8,9,5 };
	int arr3[5] = { 3,6,8,5,1 };
	int arr4[5] = { 3,8,7,6,4 };
	int arr5[5] = { 7,3,5,6,4 };
	int* parr[5] = { arr1,arr2,arr3,arr4,arr5 };

        这样就是指针数组的两种表现形式,下面的这个看起来的话其实是有点像二维数组,一会儿我们就会讲到,其实用指针数组储存的这几个数组的长度可以各不相同,这也是使用二维数组无法实现的一个功能,但是,数组长度各不相同我们就要考虑数组越界的问题了。

        这样,其实各不相同的所谓“二维数组”的存在其实就非常抽象,因为它似乎起不到什么作用。但其实,我们总有一天写一些比较特别的代码的时候,我们就会用到这样“奇怪”的代码,学会这样的表现形式,会让我们在后面的学习过程中更加得心应手,我们可以运用更加广泛的思维去写出代码,没准现在你看起来“没什么用”的代码形式会帮你一个大忙!

        2.2指针数组模拟实现二维数组

        2.2.1二维数组

        在我们学习这个指针数组模拟实现二维数组之前,我们先回顾一下二维数组的定义:

        数组的元素都是内置类型的,如果我们把⼀维数组做为数组的元 素,这时候就是⼆维数组,⼆维数组作为数组元素的数组被称为三维数组,⼆维数组以上的数组统称 为多维数组。

        给定一个二维数组arr[3][5]就表示一个数组有3行5列,也就是说:前面的arr[3]表示这个数组有3行,后面的[5]表示这个数组有5列,准确的说,一个二维数组是按照行“分割”的:

        像这样,我们把这个3行5列的二维数组按照行“分割”,其实就可以看成3部分:arr[0]、arr[1]、arr[2],这同时也是这个数组的行号,然后就是这三部分每个部分都有一个“头”,这就是每行的首元素。

        比如我们需要找到第2行第4列的元素,我们就要先通过行号找到arr[1]也就是第二行,然后就是第4列元素,我们就得到了arr[1][3],这里面存储的就是我们想要的元素。

        或者我们从地址的方向理解的话,其实数组无非就是一系列连续存放的元素罢了,这里我们使用一段代码打印一下一个3行5列的每个元素的代码:

        在这里我们可以看到:二维数组确实是连续存放的,并且以行作为分割 标志,一一存放数组中的每一个元素。所以我可以用下面的图示表示一段二维数组:

这样就可以想象出一个二维数组的大概形式了,我们的计算机想要找到一个二维数组的元素时,先通过寻找行找到这个行首元素的地址,然后再通过列定位到这个行里面的元素,然后实现二维数组元素的调用。

        2.2.2使用指针数组模拟实现二维数组

        通过上面我们对二维数组的回顾,我们也大概了解了一个二维数组的构成,接下来我们可以使用指针数组来模拟实现这个二维数组。需要注意的是,我们是使用指针数组模拟实现一个二维数组,这个模拟实现的数组其实从根本上来说并不是一个二维数组。

        那么我们就可以先列一下我们的思路:

        首先我们需要实现一个模拟的二维数组,我们就需要通过定义几个分开的区域表现行的特性,这里我们用几个相应的一维数组来实现,然后再通过这个数组的列找到我们想要的元素,那么,其实我们就可以列出下面的实现原理图:

这里,parr就是一个指针数组,里面储存着arr1,arr2,arr3,也就是这三个数组的首元素地址,然后arr1,arr2,arr3其实模拟的就是一个二维数组的1、2、3行,这样我们就得到了这个数组的行和列,写一下代码:

	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int* parr[3] = {arr1,arr2,arr3};

        我们就通过一段指针数组模拟实现了一个二维数组,之所以是模拟实现,其实就是这几个一维数组之间其实并不是连续存放的,它们直接有一定的空隙,所以这个模拟实现的二维数组并不能等同于二维数组。

        我们想要通过这个模拟实现的二维数组找到第2行第3列的元素就可以使用p[1]首先找到这个对应“行”的首元素的地址,然后就是找到这个对应“列”的地址。

        我们在前面对数组曾经剖析过这样的结论:一个arr数组配上[ k ] 时,其实就是通过首元素地址找到了第k个元素,这里计算机会把把 arr[ k ] == *( arr + k )  ,得到arr元素的地址加上相应的元素间隔,得到了我们要求的元素。

        在这里我们又需要用到这方面的知识,我们使用parr[ 1 ]就得到了一个地址,这个就是arr2的地址,在我们的模拟二维数组里面就是第二行的首元素地址,在计算机里面就是  *(parr + 1)。我们在想要找到第3列的元素,我们就要通过得到的这个parr[ 1 ]再得到第3列的元素,在计算机里表示就是*(*(parr + 1)+ 2 ),写出来就是parr[1][2],这样我们就得到了模拟实现的二维数组的第2行第3列的元素,用代码实现一下就是:

3.字符指针

        我们接触了很多指针,它们的类型都是int *类型的,其实除了int * 型的指针,还有很多种类型的指针,这里我们主要介绍 char * 型的指针。

        char * 型指针就是字符指针,字符又分为单字符和字符串,这就是组成我们字符指针变量的两种小分类。

        单字符使用:

char ch = 'G';//定义一个字符,储存一个单字符'G'
char * pc = &ch ;//定义一个字符指针,储存上面字符变量ch的地址

        字符串使用:

	char* pstr = "Hello World";
	printf("%s\n", pstr);

这里我们运行一下这个通过字符串指针打印的结果:

         很明显的发现,我们打印成功了,但是你觉得这个指针pstr里面储存的是整个字符串的地址还是第一个元素的地址呢?

这里我们就可以使用打印pstr的地址和pstr +1的地址:

        这个非常好用的方法很直观的表示了我们pstr其实代表的是字符串里面第一个元素’H‘的地址,假设我们这个指针里面储存的是整个字符串,我们输出的pstr和pstr+1之间的差距就是整个字符串的长度了,所以我们可以推断出pstr其实代表的是字符串里面第一个元素’H‘的地址。

        在《剑指offer》这本书里面收录了一段这样的代码:

#include <stdio.h>
int main()
{
 char str1[] = "Hello World";
 char str2[] = "Hello World";
 const char *str3 = "Hello World";
 const char *str4 = "Hello World";
 if(str1 ==str2)
 printf("str1 and str2 are same\n");
 else
 printf("str1 and str2 are not same\n");

 if(str3 ==str4)
 printf("str3 and str4 are same\n");
 else
 printf("str3 and str4 are not same\n");

 return 0;
}

我们运行一下结果:

我们发现,这里面的str1和str2其实是不一样的地址,而str3和str4其实是同一个地址,我们再使用打印地址验证这个结论:

        可以看到,其实str1和str2就是两个内容相同的两个不同的字符串,str3和str4是两个不同的字符指针指向了同一个字符串,这里我们的计算机内存中开辟了三个空间存储同样内容的三个字符串“Hello World”,只有字符串指针可以实现两个指指针指向同一个元素的功能,做个图示理解就是:

2.数组指针

        数组指针和指针数组是两个东西,按照语法来说的话这两个名字都该加上一个“的”,也就是变成:数组的指针和指针的数组,前者本质上是一个指针,是一个存放数组的指针,后者本质上是一个数组,是存放指针的数组,这就是区别。

        我们要学习数组指针,就要从我们之前学习过的其他指针入手,比如前面学的整形指针(int*)和上面的字符指针(char*),整形指针是储存整形数据的指针,字符指针就是储存字符白变量的指针,所以我们也可以推出数组指针就是储存数组的指针

如果我们拿出下面的代码,让你分辨一下哪个才是数组指针,看一下你能不能分辨出来:

int *p1[10];
int (*p2)[10];

答案其实是:上面的是指针数组,下面的是数组指针,这里我们使用一个图解分析一下:

        其实,这就涉及到一个优先结合的顺序问题了。 

        首先,结合的顺序应该是()>[]>*

1)一般来说一个指针数组int * p [10],应该是p优先和[10]结合,表示了p[10]是一个数组,然后就是int 和 * 结合,表示这个p[10]数组里面每一个元素都是int * 类型的指针。也就是说我们的变量p是一个数组,数组里面每个元素都是一个指针(即地址)。

2)再来看看数组指针 int (*p) [10] ,其实就是优先级被()改变了,我们就要先把*和p结合,这样我们就得到了一个指针*p,然后我们的(*p)和[10]结合,表示了一个数组(*p)[10],前面的int 修饰这个数组,表示我们的数组里面每个元素都是int 类型的。也就是说变量p其实是一个指针,指针指向的是一个有10个元素的数组,数组里面的每个元素是一个int整形。

        我们的主体由变量名字p决定,p是什么,我们的这个东西就是什么。

        还是不理解的话,我们就可以举例子:

int arr[10]={1,2,3,4,5,6,7,8,9,10};
int (*p)[10]=&arr;

        我们可以使用这个数组指针直接打印出这个数组的每一个元素,这就是和指针数组不一样的地方,我们看到前面我们介绍的指针数组是每个元素都是一个地址,它可以用来指向多个数组首元素地址;但是我们这个数组指针更加“专一”,数组指针只可以指向一个数组,然后每个元素都是这个数组的元素并且一一对应。

3.二维数组传参

        当我们使用一个二维数组并把它传给一个函数的时候,我们会这样写:

#include <stdio.h>
void test(int arr[3][5], int r, int c)
{
	int i = 0;
	int j = 0;

	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}

        可以看到,我们使用二维数组传参时传入了一个二维数组和这个数组的行和列的长度

        其实二维数组被行分成几个部分,比如arr[3][5]表示有3行5列,所以我们把这个数组分成3部分,每部分是5个元素。把二维数组的数组名分开来理解,就是arr[3][5]中arr[3]其实就可以看作这个二维数组的数组名,然后这个数组名arr[0],arr[1],arr[2]其实又可以看作是5个一维数组的首元素地址,其实就是三个指针。这三个指针

       现在我们有一个一维数组在主函数中,我们需要使用test函数打印出这个arr数组里的全部元素,我们在test函数里输入的参数就要有数组 arr[](注意:这里不能是arr,如果是arr就不能表示一个数组,这样表示的就是一个普通的整形变量),数组长度 len ,在主函数中有一个arr[5],所以我们在主函数传入的参数就是arr,5.这里的arr表示的就是数组的首元素地址(当然也可以用&arr[0]替换)。

#include <stdio.h>
void test(int arr[], int len)
{
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	test(arr, 5);
	return 0;
}

        那么我们可以使用 数组作为test函数的形参,我们是不是也可以使用一个指针作为形参呢?答案其实是肯定的。我们的确可以使用一个指针作为形参,而我们传入的参数还可以是这个首元素地址,因为我们test函数里面需要的其实就是这个形参指针里面存储的地址,就可以改成下面这样:

#include <stdio.h>
void test(int *p, int len)
{
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%d ", p[i]);
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	test(arr, 5);
	return 0;
}

        同理,我们现在使用一个二维数组传参,我们想要在主函数里面定义一个二维数组arr[3][5],我们要使用一个test函数打印出这个二维数组里面的所有元素,我们就要传入一个arr[ ][5]表示数组(注意,这里不能只有一个arr[ ][ ],二维数组传参必须带上每行的元素个数),r表示行,c表示列,我们除了这样传参之外还可以把这个arr[ ][5]前面的arr[ ]用一个指针代替,也就是用指针指向前面的每行首元素,而且每行必须要有5个元素,所以我们就可以使用数组指针,一个数组指针指向第一行,打印完成这行之后再让数组指针指向下一行的首元素地址,再次重复打印操作...这样我们就可以得到打印完的全部元素,下面是实现代码:

#include <stdio.h>
void test(int (*p)[5],int r,int c)
{
	int i = 0;
	int j = 0;

	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ",p[i][j]);//这里也可以写成*(*(p+i)+j)
		}
		printf("\n");//换行
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}

        这样我们就只要在主函数传入首行首元素地址就可以得到整个二维数组的全部元素的打印。

所以,二维数组传参,形参部分也可以写出数组形式,也可以写成指针形式。

4.函数指针

        4.1函数指针变量的定义和创建 

        我们知道了数组指针是指向一个数组的指针,那么我们就可以知道函数指针其实就是指向一个函数的指针。

        有人可能会疑问:函数也有地址吗?每次其实函数也是有地址的,不信?这里打印一个函数地址看看:

        在这里我们可以看到,不仅函数名取地址可以打印出地址,我们就直接使用一个函数名也可以打印出地址。

        那么,既然这个函数有地址的话,我们就可以使用一个指针存放这个地址,这就要创建一个指针变量了。和数组指针类似又不同,我们需要使用()把函数指针名放起来,还要再在外面加上函数的格式,比如上面的test函数,我们要把这个test函数的地址放在一个函数指针里面的话,就要像下面这样操作:

	void (*p)() = &test;//也可以直接写成test

这样就是创建一个函数指针的格式,我们也可以打印出来这个地址:

其实三者是一样的。

如果换成别的函数又要怎么做呢?这里我们定义一个Add函数用来表示两个数相加,返回值为int,就可以写出下面的代码:

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*p)(int, int) = &Add;//这里也可以写成&Add
	printf("%p\n", p);
	printf("%p\n", Add);
	printf("%p\n", &Add);
	return 0;
}

打印一下结果: 

再抽象一点:定义一个函数:

void Fuc(int *p,char str,double x);//定义一个函数

那么我们定义的函数指针就可以这样写:

void (*p)(int*,char,double)=&Fuc;//&Fuc也可以写成Fuc

         4.2函数指针变量的使用

        我们可以通过函数指针来调用我们指针指向的这个函数。

        比如我们定义了一个Add函数,我们定义一个函数指针,怎么使用这个函数指针调用这个函数呢?很简单,模仿一下我们使用Add函数的样子就可以了。

        正常情况我们不使用函数指针就可以这样使用:

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%d", Add(1, 2));
	return 0;
}

这样就是简单的一个函数调用和打印。

那么如果使用函数指针怎么使用呢?模仿一下函数调用的格式就好了!

	int (*p)(int, int) = &Add;
	printf("%d\n", (*p)(1, 2));

然后我们在这里把这个*p(1,2)改成p(1,2)其实也是可行的,二者其实是等价的。

那么我们可以使用这些代码写出下面的代码并运行一下:

这样我们就成功使用函数指针调用了这个函数。

        4.3两段有趣的代码

代码1:

(*(void (*)())0)();

代码2:

void (*signal(int , void(*)(int)))(int);

乍一看这两段代码似乎很让人头疼,但其实,只要你好好去分析这两段代码,你会发现其实也没有那么难。

代码1可以理解成这样:

下面这个代码2就简单描述一下:

 这里其实可以使用类型重命名来简化这段代码2

        4.4typedef关键字

        typedefy是用来重命名的关键字,我们可以利用这个关键字将复杂的类型简单化

例如:unsigned int 写起来很麻烦,就可以使用typedefy简化这个unsigned int

//简化前:
unsigned int a = 10 ;
unsigned int b = 20 ;
//...
//简化后:
typedefy unsigned int  uint
//把unsigned int重命名为uint
uint a = 10 ;
uint b = 20 ; 

例如我们想要重命名一个数组指针类型int (*) [5]重命名为parr_t

//简化前:
int arr[5] = { 1,2,3,4,5 };
int(*p)[5] = &arr;
printf("%d"*p[0]);
//简化后
typedefy int (*parr_t)[5] ;
//简化成parr_t
//表达原来的代码:
int arr[5] = { 1,2,3,4,5 };
parr_t p = &arr;
printf("%d"*p[0]);

 把相应东西替换一下就好了

这样我们就实现了代码的简化。在代码量小的时候可能类型重命名很麻烦,但是当代码量增加或者代码复杂性增加的时候,类型重命名就体现出它的优点了

        比如我们上面的代码2,我们就可以把它使用类型重命名简化:

//简化前:
void (*signal(int , void(*)(int)))(int);
//简化后:
typedefy void (*pf_t )(int) ;
//把函数指针类型简化成pf_t
//最后表达原来的代码:
pf_t signal(int , pf_t);

这样就可以逻辑清晰地表达我们的复杂的代码了。

5.函数指针数组

        函数指针数组听起来有点绕口,其实就是一个函数指针的变化更新,把普通的指针变成了指针数组。函数指针数组其实本质上还是一个数组。

我们回顾一下指针数组:

int * arr[10] ;
//一个数组arr[10]每个元素类型是int *

我们再看一下函数指针:

#include <stdio.h>
int Add(int x, int y)
{
    return x + y;
}
int main()
{
    //声明函数指针
    int(*pf)(int,int) = Add;//定义函数指针的时候在等号右边可以使用  &函数名 或 函数名
    //通过函数指针调用函数
    int ret = (*pf)(2, 3);//调用函数指针的时候函数名可以使用 (*pf) 或  pf
    printf("%d\n", ret);
    return 0;
}

        函数指针的本质其实就是一个指针,这个指针指向的是一个函数,我们定义了这个函数指针之后可以通过函数指针调用这个函数。

        同理,我们的函数指针数组其实本质上是一个数组,准确的讲应该是指针数组,这个指针数组指向的是一个函数数组,我们在定义了函数指针数组之后也可以通过指针数组对应的项调用对应项的函数。

        也就是说,我们从函数指针的单个指针指向一个函数变成了多个指针(就是指针构成的数组)指向多个函数(就是函数构成的数组)

#include <stdio.h>
int Add(int x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
int Div(int x, int y)
{
    return x / y;
}
int Mul(int x, int y)
{
    return x * y;
}
int main()
{
    //声明函数指针数组
    int(*pfArr[4])(int, int) = { Add,Sub,Div,Mul };//可以是pfArr[4]也可以是pfArr[]
    //通过函数指针数组调用函数
    int ret1 = pfArr[0](6 ,2);
    int ret2 = pfArr[1](6, 2);
    int ret3 = pfArr[2](6, 2);
    int ret4 = pfArr[3](6, 2);
    printf("6+2 = %d\n", ret1);
    printf("6-2 = %d\n", ret2);
    printf("6/2 = %d\n", ret3);
    printf("6*2 = %d\n", ret4);
    return 0;
}

 像上面的这个就是定义了一个函数指针数组pfArr[4] ,指向加、减、乘、除 四个函数,我们通过这个函数指针数组调用各个函数,这样就得到了我们的想要的计算值:

        其实使用这样的函数指针数组是一种简化代码很好的方法,同时函数指针数组也可以在一种数据结构——转移表 里面用来优化程序,提高计算效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/321235.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【NI国产替代】USB‑7846 Kintex-7 160T FPGA,500 kS/s多功能可重配置I/O设备

Kintex-7 160T FPGA&#xff0c;500 kS/s多功能可重配置I/O设备 USB‑7846具有用户可编程FPGA&#xff0c;可用于高性能板载处理和对I/O信号进行直接控制&#xff0c;以确保系统定时和同步的完全灵活性。 您可以使用LabVIEW FPGA模块自定义这些设备&#xff0c;开发需要精确定时…

NLP论文阅读记录 - 2022 | WOS 一种新颖的优化的与语言无关的文本摘要技术

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.前提三.本文方法四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 A Novel Optimized Language-Independent Text Summarization Techni…

Linux系统编程(十):线程同步(下)

参考引用 UNIX 环境高级编程 (第3版)嵌入式Linux C应用编程-正点原子 1. 为什么需要线程同步&#xff1f; 线程同步是为了对共享资源的访问进行保护 共享资源指的是多个线程都会进行访问的资源&#xff08;如&#xff1a;全局变量&#xff09; 保护的目的是为了解决数据一致性…

前端对接电子秤、扫码枪设备serialPort 串口使用教程

因为最近工作项目中用到了电子秤&#xff0c;需要对接电子秤设备。以前也没有对接过这种设备&#xff0c;当时也是一脸懵逼&#xff0c;脑袋空空。后来就去网上搜了一下前端怎么对接&#xff0c;然后就发现了SerialPort串口。 Serialport 官网地址&#xff1a;https://serialpo…

C# 静态代码织入AOP组件之肉夹馍

写在前面 关于肉夹馍组件的官方介绍说明&#xff1a; Rougamo是一个静态代码织入的AOP组件&#xff0c;同为AOP组件较为常用的有Castle、Autofac、AspectCore等&#xff0c;与这些组件不同的是&#xff0c;这些组件基本都是通过动态代理IoC的方式实现AOP&#xff0c;是运行时…

Mysql-redoLog

Redo Log redo log进行刷盘的效率要远高于数据页刷盘,具体表现如下 redo log体积小,只记录了哪一页修改的内容,因此体积小,刷盘快 redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快Redo log 格式 在MySQL的InnoDB存储引擎中,redo log(重做日志)被用…

【EMC专题】浪涌的成因与ICE 61000-4-5标准

什么是浪涌? 浪涌是一种无法预料的瞬态电压或电流尖峰,由附近的电子产品或是环境导致。 了解浪涌非常重要,因为浪涌有可能会导致设备的电气过应力损坏,造成系统故障等。 对于系统设计来说,重要的一点是我们如果无法控制浪涌的产生,那么只能通过将瞬态峰值电流导入到地,…

Mysql查询与更新语句的执行

一条SQL查询语句的执行顺序 FROM&#xff1a;对 FROM 子句中的左表<left_table>和右表<right_table>执行笛卡儿积&#xff08;Cartesianproduct&#xff09;&#xff0c;产生虚拟表 VT1 ON&#xff1a;对虚拟表 VT1 应用 ON 筛选&#xff0c;只有那些符合<join_…

Kafka消费全流程

Kafka消费全流程 1.Kafka一条消息发送和消费的流程图(非集群) 2.三种发送方式 准备工作 创建maven工程&#xff0c;引入依赖 <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>3.3.1&l…

UDS 诊断通讯

UDS有哪些车型支持 UDS(统一诊断服务)协议被广泛应用于汽车行业中,支持多种车型。具体来说,UDS协议被用于汽车电子控制单元(ECU)之间的通讯,以实现故障诊断、标定、编程和监控等功能。 支持UDS协议的车型包括但不限于以下几种: 奥迪(Audi)车型:包括A3、A4、A5、A6…

C++I/O流——(4)文件输入/输出(第一节)

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 含泪播种的人一定能含笑收获&#xff…

外部晶振、复位按键、唤醒按键、扩展排针原理图详解

前言&#xff1a;本文对外部晶振、复位按键、唤醒按键、扩展排针原理图详解。本文使用的MCU是GD32F103C8T6 目录 外部晶振原理图 复位按键、唤醒按键原理图 扩展排针部分原理图 ​外部晶振原理图 如下图&#xff0c;两个外部晶振&#xff0c;分别是8M&#xff08;主晶振&a…

git的三种状态概念

git的三种状态 Git 有三种状态&#xff0c;你的文件可能处于其中之一&#xff1a; 已提交&#xff08;committed&#xff09;、已修改&#xff08;modified&#xff09; 和 已暂存&#xff08;staged&#xff09;。 已修改表示修改了文件&#xff0c;但还没保存到数据库中。 …

Rust-函数

简介 Rust的函数使用关键字fn开头。 函数可以有一系列的输入参数&#xff0c;还有一个返回类型。 函数体包含一系列的语句(或者表达式)。 函数返回可以使用return语句&#xff0c;也可以使用表达式。 Rust编写的可执行程序的入口就是fn main()函数。 以下是一个函数的示例…

案例121:基于微信小程序的作品集展示系统设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

解决ERROR 24680 --- [ main] o.a.catalina.core.AprLifecycleListener 报错:

1.报错全称&#xff1a; ERROR 24680 --- [ main] o.a.catalina.core.AprLifecycleListener : An incompatible version [1.2.32] of the Apache Tomcat Native library is installed, while Tomcat requires version [1.2.34] 2.解决方案&#xff1a; 步骤一 在…

高创新!EI论文复现+改进:聚合温度调控策略的综合能源系统/微电网/虚拟电厂多目标优化调度程序代码!

程序考虑供热的热惯性&#xff0c;并根据室内供热效果进行柔性供热&#xff0c;发挥热温度负荷的“储能”能力&#xff1b;针对普适性参数的室内空调进行集群研究&#xff0c;深入剖析温度设定值调整导致负荷波动的机理&#xff0c;并提出一种新的温度调整方法&#xff0c;平抑…

「 典型安全漏洞系列 」03.跨站请求伪造CSRF详解

引言&#xff1a;CSRF&#xff08;Cross-Site Request Forgery&#xff0c;跨站请求伪造&#xff09;是一种攻击技术&#xff0c;通过使用用户的身份进行不诚实地操作&#xff0c;恶意用户可以在受害者&#xff08;目标&#xff09;的机器上执行一些未授权的操作。这可能会危及…

行业分享----dbaplus174期:美团基于Orchestrator的MySQL高可用实践

记录 MySQL高可用方案-MMM、MHA、MGR、PXC https://blog.csdn.net/jycjyc/article/details/119731980 美团数据库高可用架构的演进与设想 https://tech.meituan.com/2017/06/29/database-availability-architecture.html

推荐一款通过ssh连接linux服务的开源工具WindTerm

文章目录 前言WindTerm介绍WindTerm使用主密码和锁屏总结 前言 工作一入门便是游戏服务器开发&#xff0c;所以常常有连接Linux服务器的需求&#xff0c;之前用的最多的是Xshell&#xff0c;最近这个软件个人版只能免费使用一个月了&#xff0c;超过时间会提示更新无法正常使用…