《2022年C语言指针问题快速解惑 .pdf》由会员分享,可在线阅读,更多相关《2022年C语言指针问题快速解惑 .pdf(8页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、1、int a3=2,5,8;int*p=a;把数组名赋值给指针表示将数组的首元素的地址赋予此指针。2、int a3=2,5,8;int*p=&a0;a0是 a 数组的首元素,而&则是取地址运算符,所以“&a*0+”取得的同样是a 数组的首元素的地址,因此这段代码的含义和代码段是一致的。3、char*c1=Hello;char c26=World;这两句不都是声明一个字符串吗?有什么区别吗?Hello 是一个字符串,然后让char 指针 c1 指向这个字符串的首元素的地址。第二句是声明一个长度为6 的 char 类型数组,并且设置数组的初始值为World。注意末尾还有一个隐藏的“0”,所以长度
2、是6,而不是5。4、数组指针的加减运算对于指向数组的指针变量可以进行加减运算,比如对于指向数组的指针p,p+、p-、p+2等都是合法的。这里的加减运算并不是针对数组元素的,而是针对指针位置的,比如 p 当前指在首元素上,那么 p+后就指在第二个元素上;比如 p 当前指在第5 个元素上,那么 p=p-2后,p 就指在第 3 个元素上。例子:char*c1=Hello;printf(%cn,*c1);/输出 H c1+;printf(%cn,*c1);/输出 e 5、指针之间的运算两个指针之间可以进行减法运算,只有指向同一个数组的两个指针之间进行减法运算才有意义,而指向不同数组的两个指针之间进行减
3、法运算则没有意义。为什么呢?指针其实就是内存中的地址,两个指针的减法运算计算的就是两个内存地址之间的元素的个数,比如整数指针p1 指向内存地址2008H,整数指针p2 内存地址2000H,而整数占4个字节,所以 p1-p2 的结果就是(2008H-2000H)/4=2,也就是p1 和 p2 之间相差2 个元素。很显然两个指针进行加法运算或者两个指向不同变量的指针进行减法运算都是没有意义的。例子:int a15=3,5,6,8,2;int*p1=a1;/p1是指向第0 个元素的地址int*p2=&a13;/p2指向的是第3 个元素的地址p1+;/p1 指向了第1 个元素printf(%d,p2-
4、p1);/输出 2 6、指针之间的大小比较指向同一个数组的两个指针之间进行大小的比较是有意义的。比较结果为地址高低的比较:例子:int a15=3,5,6,8,2;int*p1=a1;名师资料总结-精品资料欢迎下载-名师精心整理-第 1 页,共 8 页 -int*p2=&a13;p1+;printf(%dn,p2p1);/输出 0,因为 p2 的地址比p1 高 2 位p1=p1+2;/p1 在数组内向高位移动两位printf(%dn,p2=p1);/输出 1,因为 p1 和 p2 的位相等7、数组做为参数传递给函数可以将数组做为传递给函数,比如下面的代码就是将传入输入的每个元素乘以2:void
5、 makeDoule(int arr,int len)int i=0;for(i=0;ilen;i+)arri=arri*2;int main(int argc,char*argv)int a15=3,5,6,8,2;int i=0;int len=sizeof(a1)/sizeof(int);makeDoule(a1,len);for(i=0;ilen;i+)printf(%d,a1i);运行结果:6 10 12 16 4 传递给 makeDoule 函数的是a1,我们知道,一个数组的名字其实就是指向数组首元素的地址。所以makeDoule 函数得到的arr 就是数组a1 的指针,那么在函数
6、内部对arr进行操作的话就会改变a1 数组。所以 makeDoule 函数也可以写成下面的样子,这两个函数是等价的:void makeDoule(int*arr,int len)int i=0;for(i=0;ilen;i+)arri=arri*2;当然写成下面的形式更符合指针的使用习惯,因此推荐这种用法:void makeDoule(int*arr)名师资料总结-精品资料欢迎下载-名师精心整理-第 2 页,共 8 页 -int i=0;for(i=0;i5;i+)/arr+i 指向的值设置为arr+i 指向的值的二倍。*(arr+i)=*(arr+i)*2;有的同学可能会问,为什么 make
7、Doule 函数还需要传递数组的长度len 呢?makeDoule 函数这样写不就行了吗?void makeDoule(int*arr)int i=0;for(i=0;i5;i+)arri=arri*2;这样写在这个函数中是没有问题的。但是这个makeDoule 函数不一定为只为a1 服务,函数的最重要的特征是可以复用,也就是可以被其他地方调用,如果我们想用makeDoule 函数为另外一个长度为20 的数组进行“每个元素乘以2”的操作,那么将数组长度5 写死在函数中就会有问题了。那么为什么不在函数内部计算数组的长度呢?省得还传递一个len 参数void makeDoule(int arr)i
8、nt i=0;int len=sizeof(arr)/sizeof(int);for(i=0;ilen;i+)arri=arri*2;int main(int argc,char*argv)int a15=3,5,6,8,2;int i=0;int len=sizeof(a1)/sizeof(int);makeDoule(a1);for(i=0;ilen;i+)printf(%d,a1i);运行以后程序竟然输出了:6 5 6 8 2 只有第一个元素被“乘以 2”。为什么呢?名师资料总结-精品资料欢迎下载-名师精心整理-第 3 页,共 8 页 -运行下面的程序试一试:void test(int
9、arr)printf(%dn,sizeof(arr)/sizeof(int);int main(int argc,char*argv)int a15=3,5,6,8,2;printf(%dn,sizeof(a1)/sizeof(int);test(a1);运行结果竟然是:5 1 为什么在main 函数中计算数组的大小是5,在 test 函数中计算数组arr 的大小就变成了1了呢?在C 语言中可以通过sizeof的方式取得一个数组的尺寸,这是我们已经知道的。但是一旦把这个数组传递给函数的时候,到了函数内部使用的就是指向这个数组的指针了,虽然在 test 函数中 arr 声明的是数组,但是这里的a
10、rr 只是一个指针而已了,arr 本质上只是一个int 类型的指针,而int 类型的指针的大小是4,所以sizeof(arr)/sizeof(int)的结果就是1。这点是经常容易犯错的,需要特别注意。如果对指针还有什么不清楚的,可以到这个网址查询指针的学习资料:urlhttp:/ ,指针是比较不好理解的,因此学习过程中要多试验,多思考,不要气馁。8、多维数组的指针设有一个二维数组int a34=0,1,2,3,4,5,6,7,8,9,10,11(1)语言允许把一个二维数组分解为多个一维数组来处理。因此数组a 可分解为三个一维数组,即 a0,a1,a2。每一个一维数组又含有四个元素。例如a0数组
11、,含有 a00,a01,a02,a03 四个元素。(2)从二维数组的角度来看,a 是二维数组名,a 代表整个二维数组的首地址,也是二维数组0 行的首地址。特别注意a+1 表示第 1 行的首地址,而不是第 0 行第第 1 列的地址,这是初学者最容易犯错的地方。同样 a1也是第 1 行一维数组的数组名和首地址。所以 a+1、*(a+1)、1是等价的。(4)在二维数组中不能把&ai 理解为元素ai的地址,因为ai不是一个数组元素,ai 是一种地址计算方法,它本身就表示数组a 第 i 行首地址。所以&ai和 ai是等价的。这一点也是初学者最容易犯错的地方。(5)从上边的分析我们得知a0+1 是 a0的
12、 1 号元素首地址,由此可得出ai+j 则是一维数组ai的 j 号元素首地址,它等于&aij。由 ai=*(a+i)得 ai+j=*(a+i)+j。由于*(a+i)+j 是二维数组a 的i 行 j 列元素的首地址,所以,该元素的值等于*(*(a+i)+j)。理解了下面的算法也就理解了多维数组的指针问题:int main(int argc,char*argv)int a34=0,1,2,3,4,5,6,7,8,9,10,11;printf(%dn,*(*(a+1)+2);printf(%dn,*(a1+2);名师资料总结-精品资料欢迎下载-名师精心整理-第 4 页,共 8 页 -程序输出如下:6
13、 6 两个表达式都输出a12 的值。*(a+1)则表示二维数组a 的第 1 行,也就是等价于a1。a1、*(a+1)都表示数组的第1 行。所以*(a1+2)和*(*(a+1)+2)都表示数组的第1 行的第 2 个元素的值。有的同学会问了,既然“*(a+1)”和“a+1”是等价的,为什么“printf(%d n,*(a+1)+2)”输出结果是错误的呢?编译器在编译“*(a+1)+2)”的时候会把“(a+1)+2”优化成“a+3”,因此“*(a+1)+2)”就变成了“*(a+3)”,也就是 a 数组第 3 个一维数组的首地址,显然这个只是一个地址,并不是我们想像中的 a12 的值,所以输出了一个非
14、常大的数。为了避免编译器的这种误解,建议大家表示“二维数组a 的第 1 行”的时候用 a1或者*(a+1),而尽量不要用(a+1)因为很容易出错。9、函数指针在语言 中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针 变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。函数指针变量定义的一般形式为:函数的返回值的类型 (*指针变量名)(参数列表);其中“参数列表”可以省略,不过建议明确标明“参数列表”。例子:void PrintIt(int i)
15、printf(%dn,i);int main(int argc,char*argv)int i=0;int arr5=3,5,8,2,1;void(*myAction)(int);myAction=PrintIt;for(i=0;isizeof(arr)/sizeof(int);i+)myAction(arr);上面的程序遍历数组arr 的所有元素,并且打印每个元素。有的同学会说,这样做有什么意义吗?把“myAction(arr)”直接换成“PrintIt(arr)”不就得了吗?这么替换在这里是非常合理,也是非常正确的,但是有一天我发现很多地方都要遍历数组做不同的事情,为了避免每次都写for
16、循环,我将遍历数组的功能抽取到一个单独的公共函数中完成void eachItem(int*pArray,int len,void(*action)(int)int i=0;名师资料总结-精品资料欢迎下载-名师精心整理-第 5 页,共 8 页 -for(i=0;ilen;i+)/调用函数指针 action(*(pArray+i);注意函数eachItem 的最后一个参数为函数指针类型。这样 main 函数就可以简化成如下的样子了:void PrintIt(int i)printf(%dn,i);int main(int argc,char*argv)int arr5=3,5,8,2,1;int
17、len=sizeof(arr)/sizeof(int);eachItem(arr,len,PrintIt);以后在其他的地方想对int 数组做其他处理,那么只要写不同的函数就可以了,比如说要将数组中的奇数输出:void PrintOdd(int i)if(i%2)=1)printf(%d 是奇数 n,i);在 main 函数中如下调用即可:eachItem(arr,len,PrintOdd);可以看到通过函数指针,将循环遍历算法和具体的处理算法隔离了,实现了代码的复用。函数指针在编程中有非常多的应用。函数指针有时候又被称为回调,在事件机制、模板算法等场合有着广泛应用,因此一定要掌握好。MFC、
18、STL等流行的框架中都大量的应用了函数指针,在 的 C 语言也能干大事系列在线教学中也将进一步讲解函数指针的更生动的应用。附录:本小节用到的代码:void PrintIt(int i)printf(%dn,i);void PrintOdd(int i)if(i%2)=1)printf(%d 是奇数 n,i);名师资料总结-精品资料欢迎下载-名师精心整理-第 6 页,共 8 页 -void eachItem(int*pArray,int len,void(*action)(int)int i=0;for(i=0;i成员名应该注意(*pstu)两侧的括号不可少,因为成员符“.”的优先级高于“*”。
19、12、结构指针变量作函数参数允许用结构变量作函数参数进行整体传送。但是这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。13、动态存储分配注:“语言中不允许动态数组”是 C89标准中的规定,所以 TC、VC6等 C89等老的编译器会有这个问题,新的C99 中已经不存在这个问题。语言中不允许动态数组类型。例如下面的代码是错误的:int n;scanf(%d,&n);名师资料总结-精品资料欢迎下载-名师精心整理-第 7 页,共 8 页 -int an;但是在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据。对于这种问题,用数组的办法很难解决。为了解决上述问题,语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间回收。(1)分配内存空间函数malloc 调用形式:(类型说明符*)malloc(size)功能:在内存的动态存储区中分配一块长度为size字节的连续区域。函数的返回值为该区域的首地址。(2)释放内存空间函数free 调用形式:free(void*ptr);名师资料总结-精品资料欢迎下载-名师精心整理-第 8 页,共 8 页 -
限制150内