指针是C语言中非常重要的一块内容,也是比较难理解的一块内容。我们需要反复学习和巩固才能理解。之前分享过指针相关的笔记,但是比较复杂。本篇笔记总结了指针相关的内容,包含了很多指针相关的基础知识点。笔记有点长,大家可以存起来慢慢看。复杂类型说明以下内容主要来自《让你不再害怕指针》:要理解指针,或多或少都会出现复杂类型,所以我先介绍一下如何完全理解一个复杂类型,其实很简单。一个类型中会有很多个运算符,它们也和普通表达式有相同的优先级,它们的优先级和运算优先级一样,所以我总结了原则:从变量名开始,根据运算符优先级组合,步骤通过步骤分析。我们先从一个简单的类型开始慢慢分析:intp;这是一个普通的整型变量。诠释*p;先从P开始,先和*结合,所以说明P是一个指针。然后和int结合表示指针指向的内容的类型是int类型。所以P是一个返回整数数据的指针。诠释p[3];先从P开始,先用[]组合,表示P是一个数组,再用int组合,表示数组中的元素都是整数,所以P是由整数数据组成的数组。整数*p[3];先从P开始,先和[]结合,因为它的优先级比*高。所以P是一个数组,然后加上*,就表示数组中的元素是指针类型。然后和int结合表示指针指向的内容的类型是整型,所以P是一个由返回整型数据的指针组成的数组。整数(*p)[3];先从P开始,先和*结合,说明P是指针再和[]和"()"结合(这一步可以忽略,只是改变优先级)。说明指针指向的内容是一个数组,再结合int,说明数组中的元素都是整数。所以P是指向整数数据数组的指针。诠释**p;先以P开头,再与*组合,表示指针指向的元素是指针,再与int组合,表示指针指向的元素是整型数据。由于二级指针以上的指针在复杂类型中很少用到,所以后面更复杂的类型就不考虑多级指针了,最多只考虑一级指针。整数p(整数);从P开始,先与()结合,说明P是一个函数,然后进入()进行分析,说明该函数有一个整型变量的参数,再与外面的int结合,说明该函数返回值是一个整型数据。整数(*p)(整数);从P开始,先和指针结合,说明P是指针,再和()结合,说明指针指向一个函数,再和()中的int结合,说明函数是int类型参数,然后与最外层的int组合。说函数的返回类型是一个整数,所以P是一个指针,指向一个接受一个整数参数并返回一个整数的函数。差不多就这样吧,我们任务那么多,了解了这几个类型之后,其他类型对我们来说也是小菜一碟。但是,我们一般不会使用过于复杂的类型,这样会大大降低程序的可读性,请谨慎使用,以上类型足够我们使用了。分析指针的方法指针是一种特殊的变量,它存储的值被解释为内存中的一个地址。找出一个指针,需要找出指针的四个方面:指针的类型、指针指向的类型、指针的值(指针指向的内存区域)、内存指针本身占用的区域。我们分开来分解。例如声明几个指针:(1)int*ptr;(2)字符*指针;(3)int**ptr;(4)整数(*ptr)[3];(5)整数*(*ptr)[4];1.指针的类型从语法上看,只需要在指针声明语句中去掉指针名,剩下的就是指针的类型。这是指针本身的类型。我们看一下例1中各个指针的类型:(1)int*ptr;//指针的类型是int*(2)char*ptr;//指针的类型是char*(3)int**指针;//指针类型为int**(4)int(*ptr)[3];//指针类型为int(*)[3](5)int*(*ptr)[4];//指针指针的类型为int*(*)[4]2.指针指向的类型当你通过指针访问指针指向的内存区时,指针指向的类型决定了指针的内容编译器会将其视为某物的内存区域。从语法上看,只需要去掉指针声明语句中的指针名称和名称左边的指针声明符*,剩下的就是指针指向的类型。例如:(1)int*ptr;//指针指向的类型是int(2)char*ptr;//指针指向的类型为char(3)int**ptr;//指针指向的类型类型为int*(4)int(*ptr)[3];//指针指向的类型是int()[3](5)int*(*ptr)[4];//指针指向指针的类型是int*()[4]在指针的算术运算中,指针指向的类型有很大的作用。3.指针的值指针的值就是指针本身存储的值,这个值会被编译器当作一个地址,而不是一般的值。在32位程序中,所有类型的指针的值都是32位整数,因为32位程序中所有的内存地址都是32位长。指针指向的内存区是从指针的值所代表的内存地址开始,长度为sizeof(指针指向的类型)的一块内存区。以后我们说一个指针的值为XX,就相当于说这个指针指向一块地址以XX开头的内存区域;当我们说一个指针指向某个内存区域时,就相当于说这个指针的值就是这个内存区域的首地址。指针指向的内存区域和指针指向的类型是两个完全不同的概念。例1中,指针指向的类型已经存在,但是由于指针还没有初始化,所以它指向的内存区域不存在,或者没有意义。以后遇到指针就应该问:这个指针是什么类型的?指针指向什么类型?这个指针指向哪里?(重点说明)。4.指针本身占用的内存区域指针本身占用多少内存?您只需要使用函数sizeof(指针类型)对其进行测试即可。在32位平台上,指针本身占用4个字节的长度。指针本身占用内存的概念在判断一个指针表达式(稍后解释)是否为左值时很有用。指针的算术运算指针可以加或减一个整数。指针这个操作的意义和通常的值加减的意义不一样,单位就是单位。这在内存中体现为:相对于这个指针向后偏移了多少个单位或者向前偏移了多少个单位,其中单位与指针变量的类型有关。在32bit环境下,int类型占4个字节,float类型占4个字节,double类型占8个字节,char类型占1个字节。[注意]一些处理整数的操作不能用于处理指针。例如,您可以将两个整数相乘,但不能将两个指针相乘。示例程序#includeintmain(void){inta=10,*pa=&a;浮动b=6.6,*pb=&b;charc='a',*pc=&c;双d=2.14e9,*pd=&d;//初始值printf("pa0=%d,pb0=%d,pc0=%d,pd0=%d\n",pa,pb,pc,pd);//加法pa+=2;铅+=2;电脑+=2;PD+=2;printf("pa1=%d,pb1=%d,pc1=%d,pd1=%d\n",pa,pb,pc,pd);//减法运算pa-=1;铅-=1;电脑-=1;PD-=1;printf("pa2=%d,pb2=%d,pc2=%d,pd2=%d\n",pa,pb,pc,pd);return0;}运行结果为:pa0=6422268,pb0=6422264,pc0=6422263,pd0=6422248pa1=6422276,pb1=6422272,pc1=6422265,pd1=6422264pa2=6422272,pb2=64222=64,pc266422256分析:举例说明pa0→pa1→pa2的过程,其他类似。pa0+2*sizeof(int)=pa1,pa1-1*sizeof(int)=pa2。因为pa是int类型的指针,所以加减运算都是以4字节为单位向前向后偏移(即sizeof(int))。看下图:如图:pa1指向的地址比pa0指向的地址后8个字节,pa2指向的地址比pa1指向的地址前4个字节。从这个示例程序中也可以看出,连续定义的变量的内存存储可能靠得很近,也可能比较分散。数组与指针的关系数组与指针关系密切,常见的有三种组合:数组指针指针数组二维数组指针1、数组指针数组指针:指向数组的指针。如:intarr[]={0,1,2,3,4};int*p=arr;//也可以写成int*p=&arr[0]即p,arr,&arr[0]都指向数组的开头,也就是第0个元素的地址。如果一个指针p指向数组arr[]的开头,那么p+i就是数组第i个元素的地址,即&arr[i],那么*(p+i)就是数组的值数组的第i个元素,即arr[i]。同样,如果指针p指向数组的第n个元素,那么p+i就是第n+1个元素的地址;不管p指向数组的多少个元素,p+1总是指向下一个元素,p-1也总是指向前一个元素。下面的例子证实了这一点:运行结果为:0,1,2,3,42,指针数组指针数组:数组中的每个元素都是一个指针。如:inta=1,b=2,c=3;int*arr[3]={&a,&b,&c};示例程序:#includeintmain(void){inta=1,b=2,c=3;//Defineapointerarrayint*arr[3]={&a,&b,&c};//也可以不指定长度直接写int*parr[]//Defineapointerpointertoarrayofpointersint**parr=arr;printf("%d,%d,%d\n",*arr[0],*arr[1],*arr[2]);printf("%d,%d,%d\n",**(parr+0),**(parr+1),**(parr+2));return0;}在第一个printf()语句中,arr[i]表示获取第i个元素的值,它是一个指针,需要在前面加一个*来获取它指向的数据,即是*arr[i]的形式。第二条printf()语句中,parr+i表示第i个元素的地址,*(parr+i)表示获取第i个元素的值(该元素为指针),**(parr+i)表示获取第i个元素指向的数据。指针数组也可以与字符串数组结合使用,参见以下示例:#includeintmain(void){char*str[3]={"helloC","helloC++","helloJava"};printf("%s\n%s\n%s\n",str[0],str[1],str[2]);return0;}运行结果为:helloChelloC++helloJava3,二维数组指针二维数组指针:指向二维数组的指针。例如:inta[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};整数(*p)[4]=a;a[3][4]表示一个3行4列的二维数组,它的所有元素都在内存中连续存储。请看下面的程序:#includeintmain(void){inta[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};诠释我,j;for(i=0;i<3;i++){for(j=0;j<4;j++){printf("a[%d][%d]=%d\n",i,j,&a[i][j]);}}return0;}运算结果为:a[0][0]=6422216a[0][1]=6422220a[0][2]=6422224a[0][3]=6422228a[1][0]=6422232a[1][1]=6422236a[1][2]=6422240a[1][3]=6422244a[2][0]=6422248a[2][1]=6422252a[2][2]=6422256a[2][3]=6422260可以看出每个元素的地址相差4个字节,即每个元素在内存中都是连续的,都是连续存储的。根据以上定义,可以得出以下四个结论:(1)p指向数组a的开头,也就是第一行;p+1前进一行,指向第二行。(2)*(p+1)表示取第二行元素(整行元素)。(3)*(p+1)+1表示第二行第二个元素的地址。(4)((p+1)+1)表示第2行第二个元素的值,综上所述,可以得出如下结论:a+i==p+i*(a+i)==*(p+i)a[i][j]==p[i][j]==*(a[i]+j)==*(p[i]+j)==*(*(a+i)+j)==*(*(p+i)+j)以上是数组与指针的三种常见组合。指针和数组的区别数组和指针在大多数情况下是等价的,例如:intarray[10]={0,1,2,3,4,5,6,7,8,9},value;value=array[0];//也可以写成:value=*array;value=array[3];//也可以写成:value=*(array+3);value=array[4];//也可以写成:value=*(array+4)但是也有不等价的时候,比如下面三种情况:数组名不能变,但是指向数组的指针可以变.字符串指针指向的字符串中的字符不能改变,但字符数组中的字符可以改变。在计算数组长度时,可以通过借用数组名得到数组的长度,但是不能通过借用指针得到数组的长度。1、不同的是指向数组名的指针不能改变,而指向数组的指针是可以改变的。请看下面的代码:#includeintmain(void){inta[5]={0,1,2,3,4},*p=a;字符我;//数组遍历方法一for(i=0;i<5;i++){printf("a[%d]=%d\n",i,*p++);}//数组遍历方法2for(i=0;i<5;i++){printf("a[%d]=%d\n",i,*a++);}return0;}数组遍历方法一:使用指针遍历数组元素,*p++等价于*(p++),即每次将指针指向的地址向后移动一个单位,然后取上的值地址被占用。这里的一个单位是sizeof(int)字节。数组遍历方法二:使用数组名自增来遍历数组元素,编译报错如下:error:valuerequiredasincrementoperand因为数组名的指向不能改变,使用自增operatortoauto-increment会改变它的指向,这是错误的,数组名只能指向数组的开头。但可以改为如下遍历方式:for(i=0;i<5;i++){printf("a[%d]=%d\n",i,*(a+i));}这可以正确迭代数组元素。因为*(a+i)等价于a[i]。2、不同的是,字符串指针指向的字符串中的字符不能改变,而字符数组中的字符是可以改变的。请看下面的代码://字符串定义方法1charstr[]="happy";//字符串定义方法2char*str="happy";字符串定义方式一:字符串中的字符是可以改变的。例如,您可以使用类似str[3]='q'的语句来更改其中的字符。原因是:这样定义的字符串存放在全局数据区或栈区,是可读可写的。字符串定义方式二:字符串中的字符不可改变。原因是:这样定义的字符串存放在常量区,不能修改。3.区别3在计算数组长度时,可以借用数组名得到数组的长度,但是不能借用指针得到数组的长度。请看下面的代码:#includeintmain(void){inta[]={0,1,2,3,4},*p=a;字符长度=0;//求数组长度方法一printf("方法一:len=%d\n",sizeof(a)/sizeof(int));//求数组的长度方法二printf("方法二:len=%d\n",sizeof(p)/sizeof(int));return0;}运算结果方法一:len=5方法二:len=1求数组长度方法一:借用数组名求数组长度,数组有5个元素,即正确的。第二种求数组长度的方法:用指针求数组长度,得到的长度为1,报错。原因是:p只是一个指向int类型的指针,编译器不知道它指向的是整数还是数组。sizeof(p)获取的是指针变量p本身占用的字节数,不是整个数组占用的字节数。接下来需要注意数组名的一个问题:如果声明了一个数组TYPEarray[n],数组名是一个常量指针,指针的值是不可修改的,即类似的表达式array++是错误的。指针函数与函数指针函数与指针的组合顺序不同,含义也不同,即指针函数与函数指针的含义不同。1.指针函数指针函数的本质就是一个返回值为指针的函数。例子如下:int*pfun(int,int);由于“*”的优先级低于“()”,所以pfun首先与后面的“()”组合,也就是说pfun是一个函数。即:int*(pfun(int,int));然后再和前面的“*”组合起来,说明这个函数的返回值是一个指针。由于前面有一个int,也就是说pfun是一个返回值为整型指针的函数。指针函数示例程序如下:#include//这是一个指针函数的声明int*pfun(int*arr,intn);intmain(void){intarray[]={0,1,2,3,4};intlen=sizeof(数组)/sizeof(数组[0]);诠释*p;诠释我;//调用指针函数p=pfun(array,len);for(i=0;iintadd(inta,intb);intmain(void){int(*fptr)(int,int);//定义一个函数指针intres;fptr=添加;//函数指针fptr指向函数add/*通过函数指针调用函数*/res=(*fptr)(1,2);//等价于res=fptr(1,2);printf("a+b=%d\n",资源);return0;}intadd(inta,intb){returna+b;}程序运行结果如下:其中,函数指针在嵌入式软件开发中应用广泛,其有两种常见用途:调用函数和制作函数参数。以上就是关于指针的一些基础知识的总结。如有错误,请指出!谢谢