C语言指针问题快速解惑_第1页
C语言指针问题快速解惑_第2页
C语言指针问题快速解惑_第3页
C语言指针问题快速解惑_第4页
C语言指针问题快速解惑_第5页
已阅读5页,还剩5页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、C语言指针问题快速解惑(一)1、int a3 = 2,5,8;int* p = a;把数组名赋值给指针表示将数组的首元素的地址赋予此指针。2、int a3 = 2,5,8;int* p = &a0;a0是a数组的首元素,而&则是取地址运算符,所以“ &a0”取得的同样是a数组的首元素的地址,因此这段代码的含义和代码段是一致的。3、char * c1 = Hello;char c26 = World;这两句不都是声明一个字符串吗?有什么区别吗?Hello是一个字符串,然后让char指针c1指向这个字符串的首元素的地址。第二句是声明一个长度为6的char类型数组,并且设置数组的初始值为World。

2、注意末尾还有一个隐藏的“0”,所以长度是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个元素

4、printf(%d,p2-p1);/输出2 6、指针之间的大小比较指向同一个数组的两个指针之间进行大小的比较是有意义的。比较结果为地址高低的比较:例子:int a15=3,5,6,8,2;int* p1 = a1;int* p2 = &a13;p1+;printf(%dn,p2p1);/输出0,因为p2的地址比p1高2位 p1=p1+2;/p1在数组内向高位移动两位 printf(%dn,p2=p1);/输出1 ,因为p1和p2的位相等 C语言指针问题快速解惑(二)7、数组做为参数传递给函数可以将数组做为传递给函数,比如下面的代码就是将传入输入的每个元素乘以2:void makeDoule(i

5、nt 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的指针,那么在函数内部对arr进行操作的话

6、就会改变a1数组。所以makeDoule函数也可以写成下面的样子,这两个函数是等价的:void makeDoule(int* arr,int len)int i=0;for(i=0;ilen;i+)arri= arri*2;当然写成下面的形式更符合指针的使用习惯,因此推荐这种用法:void makeDoule(int* arr)int i=0;for(i=0;i5;i+)/arr+i指向的值设置为arr+i指向的值的二倍。*(arr+i)= *(arr+i)*2;有的同学可能会问,为什么makeDoule函数还需要传递数组的长度len呢?makeDoule函数这样写不就行了吗?void mak

7、eDoule(int* arr)int i=0;for(i=0;i5;i+)arri= arri*2;这样写在这个函数中是没有问题的。但是这个makeDoule函数不一定为只为a1服务,函数的最重要的特征是可以复用,也就是可以被其他地方调用,如果我们想用makeDoule函数为另外一个长度为20的数组进行“每个元素乘以2”的操作,那么将数组长度5写死在函数中就会有问题了。那么为什么不在函数内部计算数组的长度呢?省得还传递一个len参数void makeDoule(int arr)int i=0;int len = sizeof(arr)/sizeof(int);for(i=0;ilen;i+)

8、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”。为什么呢?运行下面的程序试一试:void test(int arr)printf(%dn,sizeof(arr)/sizeof(int);int main(int argc, char *argv)int a15=3,5,6,8,2;

9、printf(%dn,sizeof(a1)/sizeof(int);test(a1);运行结果竟然是:51为什么在main函数中计算数组的大小是5,在test函数中计算数组arr的大小就变成了1了呢?在C语言中可以通过sizeof的方式取得一个数组的尺寸,这是我们已经知道的。但是一旦把这个数组传递给函数的时候,到了函数内部使用的就是指向这个数组的指针了,虽然在test函数中arr声明的是数组,但是这里的arr只是一个指针而已了,arr本质上只是一个int类型的指针,而int类型的指针的大小是4,所以sizeof(arr)/sizeof(int)的结果就是1。这点是经常容易犯错的,需要特别注意。

10、如果对指针还有什么不清楚的,可以到这个网址查询指针的学习资料:url ,指针是比较不好理解的,因此学习过程中要多试验,多思考,不要气馁。8、多维数组的指针设有一个二维数组int a34=0,1,2,3,4,5,6,7,8,9,10,11 (1)语言允许把一个二维数组分解为多个一维数组来处理。因此数组 a 可分解为三个一维数组,即 a0,a1,a2。每一个一维数组又含有四个元素。例如 a0数组,含有 a00,a01,a02,a03四个元素。(2)从二维数组的角度来看,a 是二维数组名,a 代表整个二维数组的首地址,也是二维数组 0 行的首地址。特别注意a+1表示第1行的首地址,而不是第0行第第1

11、列的地址,这是初学者最容易犯错的地方。同样a1也是第1行一维数组的数组名和首地址。所以a+1、*(a+1)、1是等价的。(4)在二维数组中不能把&ai理解为元素 ai的地址,因为ai不是一个数组元素,ai是一种地址计算方法,它本身就表示数组 a 第 i 行首地址。所以&ai和 ai是等价的。这一点也是初学者最容易犯错的地方。(5)从上边的分析我们得知a0+1是 a0的 1 号元素首地址,由此可得出 ai+j 则是一维数组 ai的 j 号元素首地址,它等于&aij。由 ai=*(a+i)得 ai+j=*(a+i)+j。由于*(a+i)+j 是二维数组 a 的 i 行 j 列元素的首地址,所以,该

12、元素的值等于*(*(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);程序输出如下:66两个表达式都输出a12的值。*(a+1)则表示二维数组a的第1行,也就是等价于a1。a1、*(a+1)都表示数组的第1行。所以*(a1+2)和*(*(a+1)+2)都表示数组的第1行的第2个元素的值。有的同学会问了,既然“*(a+1)”和“a+1”是等价的,为什么“printf(%dn,

13、*(a+1)+2)”输出结果是错误的呢?编译器在编译“*(a+1)+2)”的时候会把“(a+1)+2”优化成“a+3”,因此“*(a+1)+2)”就变成了“*(a+3)”,也就是a数组第3个一维数组的首地址,显然这个只是一个地址,并不是我们想像中的a12的值,所以输出了一个非常大的数。为了避免编译器的这种误解,建议大家表示“二维数组a的第1行”的时候用a1或者*(a+1),而尽量不要用(a+1)因为很容易出错。C语言指针问题快速解惑(三)9、函数指针 在语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使

14、该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。 函数指针变量定义的一般形式为: 函数的返回值的类型(*指针变量名)(参数列表); 其中“参数列表”可以省略,不过建议明确标明“参数列表”。例子:void PrintIt(int i)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+

15、)myAction(arr);上面的程序遍历数组arr的所有元素,并且打印每个元素。有的同学会说,这样做有什么意义吗?把“myAction(arr)”直接换成“PrintIt(arr)”不就得了吗?这么替换在这里是非常合理,也是非常正确的,但是有一天我发现很多地方都要遍历数组做不同的事情,为了避免每次都写for循环,我将遍历数组的功能抽取到一个单独的公共函数中完成void eachItem(int* pArray,int len,void (*action)(int)int i = 0;for(i=0;ilen;i+)/调用函数指针action(*(pArray+i);注意函数eachItem

16、的最后一个参数为函数指针类型。这样main函数就可以简化成如下的样子了:void PrintIt(int i)printf(%dn,i);int main(int argc, char *argv)int arr5 = 3,5,8,2,1;int len = sizeof(arr)/sizeof(int);eachItem(arr,len,PrintIt);以后在其他的地方想对int数组做其他处理,那么只要写不同的函数就可以了,比如说要将数组中的奇数输出:void PrintOdd(int i)if(i%2)=1)printf(%d是奇数n,i); 在main函数中如下调用即可:eachIte

17、m(arr,len,PrintOdd);可以看到通过函数指针,将循环遍历算法和具体的处理算法隔离了,实现了代码的复用。函数指针在编程中有非常多的应用。函数指针有时候又被称为回调,在事件机制、模板算法等场合有着广泛应用,因此一定要掌握好。MFC、STL等流行的框架中都大量的应用了函数指针,在的C语言也能干大事系列在线教学中也将进一步讲解函数指针的更生动的应用。附录:本小节用到的代码:void PrintIt(int i)printf(%dn,i);void PrintOdd(int i)if(i%2)=1)printf(%d是奇数n,i); void eachItem(int* pArray,int len,void (*action)(int)int i = 0;for(i=0;i成员名 应该注意(*pstu)两侧的括号不可少,因为成员符“.”的优先级高于“*”。12、结构指针变量作函数参数允许用结构变量作函数参数进行整体传送。但是这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。13、动态存储分配注:“语言中不允许动态数组”是C89标准中的规定,所以TC、VC6等C89等老的编译器会有这个问题,新的C99中已经不存在这个问题。语言中不允许动态数组类型。例如下面的

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论