指针是C/C++的精华,也是最难的部分。——所有学习C/C++的人都明白这点,当年我初学的时候也是这样。但是,现在再回想指针,我却很难回忆它究竟难在哪儿。应该说这就叫“难者不会,会者不难”吧。“饱汉不知饿汉饥”是有一定的道理的,即使饱汉曾经饿过。
本书中规中矩地讲解了指针的概念、定义与初始化、操作等。正如上面提到的“饱汉不知饿汉饥”,我似乎很健忘,以至于不记得指针的难点在哪儿了。
指针的灵活性可以把大量的工作化繁为易,前提是必须首很把足够繁的指针弄懂。听起来有点像绕口令,事实就是这样,你现在把难懂的东西弄懂了,日后可以把难事化简,大事化小。
从VB过来的人一定会熟悉“值传递”和“地址传递”这两个概念,实际上,“地址传递”这种说法正是为了弥补VB没有指针却有类似的需要才发明的。我认为C/C++程序员要想深入理解指针,首先要抛弃这个概念。在C/C++程序中,即使在函数调用中传递指针,也不能说“地址传递”,还应该说是值传递,只不过这次传递的值有点特殊,特殊在于借用这个值,可以找到其它值。就好像我给你一把钥匙一样,你通过钥匙可以间接获得更多,但是我给你的只不过是钥匙。
我前阵子曾写过一篇关于指针的文章,之所以写那篇文章,是因为看到一大堆初学者在论坛上提问。通过对他们提的问题的分析,我总结了几点。下面,首先就先引用我自己写的《关于指针》中的片段吧(完整的文章请到我的个人主页查找):
一、指针就是变量:
虽然申明指针的时候也提类型,如:
char *p1;
int *p2;
float *p3;
double *p4;
.....
但是,这只表示该指针指向某类型的数据,而不表示该指针的类型。说白了,指针都是一个类型:四字节无符号整数(将来的64位系统中可能有变化)。
二、指针的加减运算很特殊:
p++、p--之类的运算并不是让p这个“四字节无符号整数”加一或减一,而是让它指向下一个或上一个存储单元,它实际加减的值就是它所指类型的值的size。
比如:
char *型指针,每次加减的改变量都是1;
float *型的指针,每次加减的改变量都是4;
void *型指针无法加减。
还要注意的是:指针不能相加,指针相减的差为int型。
正是因为指针有着不同于其它变量的运算方式,所以,在任何时候用到指针都必须明确“指针的类型”(即指针所指的变量的类型)。这就不难理解为什么函数声明时必须用“int abc(char *p)”而调用的时候却成了“a = abc(p);”这样的形式了。
三、用指针做参数传递的是指针值,不是指针本身:
要理解参数传递,首先必须把“形参”与“实参”弄明白。
函数A在调用函数B时,如果要传递一个参数C,实际是在函数B中重新建立一个变量C,并将函数A中的C值传入其中,于是函数B就可以使用这个值了,在函数B中,无论有没有修改这个C值,对于函数A中的C都没有影响。函数B结束时,会将所有内存收回,局部变量C被销毁,函数B对变量C所做的一切修改都将被抛弃。
以上示例中,函数A中的变量C称为“实参”,函数B中的变量C被称为“形参”,调用函数时,会在B函数体内建立一个形参,该形参的值与实参的值是相同的,但是形参的改变不影响实参,函数结束时,形参被销毁,实参依然没有发生变化。
指针也是一个变量,所以它也符合以上的规定,但是,指针存放的不仅仅是一个值,而是一个内存地址。B函数对这个地址进行了改动,改动的并不是形参,而是形参所指的内存。由于形参的值与实参的值完全相同,所以,实参所指的内存也被修改。函数结束时,虽然这个形参会被销毁,指针的变化无法影响实参,但此前对它所指的内存的修改会持续有效。所以,把指针作为参数可以在被调函数(B)中改变主调函数(A)中的变量,好像形参影响了实参一样。
注意:是“好像”。在这过程中,函数B影响的不是参数,而是内存。
下面再来看刚才的例子:“int abc(char *p)”和“a = abc(p);”。为什么申请中要用*号,因为函数必须知道这是指针;为什么调用时不加*号,因为传递的是“指针值”,而不是“指针所指内存的值”。
四、指向指针的指针:
正因为指针也是一个变量,它一样要尊守形参与实参的规定。所以,虽然指针做参数可以将函数内对变量的修改带到函数外,但是,函数体内对指针本身作任何修都将被丢弃。如果要让指针本身被修改而且要影响函数外,那么,被调函数就应该知道“该指针所在的内存地址”。这时,指针不再是指针,而是“普通变量”。作为参数传递的不是这个“普通变量”,而是指向这个“普通变量”的指针。即“指向指针的指针”。
如果p是一个指向指针的指针,那么*p就是一个指针,我们不妨就把它看成q。要访问q指针所指的内存,只要*q就是了。用初中数学的“等量代换”一换就知道,*q就是**p。
五、指针数组。
之所以要把“指针数组”单独提出来,是因为数组本身就与指针有着千丝万缕的关系。即使你不想用指针,只要你使用了数组,实际就在与指针打交道了。
只要理解了指针本身就是变量,就不难理解“指针数组”,我们可以暂且把它当成普通数组来处理,a[0]、a[1]、a[2]……就是数组的元素,只是,a[0]是一个指针,a[1]、a[2]也是一个指针。那a呢?当然也是指针,但这是两码事。你可以完全无视a的存在,只去管a[0]等元素。*a[0]与*p没有什么本质的区别。
还有一个东西不得不提一下,它比较重要:
指针的定义有两个可取的方式,它们各有优缺点:“int *p;”和“int* p;”是完全等价的,后者的好处是让人体会到p是一个“指向int的”指针,前者会让人误解为*p是一个int型变量(这里没有定义int型变量);但是前者的好处是不会产生混淆,如“int *p, *q;”让人一眼就看出定义了两个指针,而“int* p,q;”会让人误解成定义了两个指针(实际上q不是指针)。
本书中规中矩地讲解了指针的概念、定义与初始化、操作等。正如上面提到的“饱汉不知饿汉饥”,我似乎很健忘,以至于不记得指针的难点在哪儿了。
指针的灵活性可以把大量的工作化繁为易,前提是必须首很把足够繁的指针弄懂。听起来有点像绕口令,事实就是这样,你现在把难懂的东西弄懂了,日后可以把难事化简,大事化小。
从VB过来的人一定会熟悉“值传递”和“地址传递”这两个概念,实际上,“地址传递”这种说法正是为了弥补VB没有指针却有类似的需要才发明的。我认为C/C++程序员要想深入理解指针,首先要抛弃这个概念。在C/C++程序中,即使在函数调用中传递指针,也不能说“地址传递”,还应该说是值传递,只不过这次传递的值有点特殊,特殊在于借用这个值,可以找到其它值。就好像我给你一把钥匙一样,你通过钥匙可以间接获得更多,但是我给你的只不过是钥匙。
我前阵子曾写过一篇关于指针的文章,之所以写那篇文章,是因为看到一大堆初学者在论坛上提问。通过对他们提的问题的分析,我总结了几点。下面,首先就先引用我自己写的《关于指针》中的片段吧(完整的文章请到我的个人主页查找):
一、指针就是变量:
虽然申明指针的时候也提类型,如:
char *p1;
int *p2;
float *p3;
double *p4;
.....
但是,这只表示该指针指向某类型的数据,而不表示该指针的类型。说白了,指针都是一个类型:四字节无符号整数(将来的64位系统中可能有变化)。
二、指针的加减运算很特殊:
p++、p--之类的运算并不是让p这个“四字节无符号整数”加一或减一,而是让它指向下一个或上一个存储单元,它实际加减的值就是它所指类型的值的size。
比如:
char *型指针,每次加减的改变量都是1;
float *型的指针,每次加减的改变量都是4;
void *型指针无法加减。
还要注意的是:指针不能相加,指针相减的差为int型。
正是因为指针有着不同于其它变量的运算方式,所以,在任何时候用到指针都必须明确“指针的类型”(即指针所指的变量的类型)。这就不难理解为什么函数声明时必须用“int abc(char *p)”而调用的时候却成了“a = abc(p);”这样的形式了。
三、用指针做参数传递的是指针值,不是指针本身:
要理解参数传递,首先必须把“形参”与“实参”弄明白。
函数A在调用函数B时,如果要传递一个参数C,实际是在函数B中重新建立一个变量C,并将函数A中的C值传入其中,于是函数B就可以使用这个值了,在函数B中,无论有没有修改这个C值,对于函数A中的C都没有影响。函数B结束时,会将所有内存收回,局部变量C被销毁,函数B对变量C所做的一切修改都将被抛弃。
以上示例中,函数A中的变量C称为“实参”,函数B中的变量C被称为“形参”,调用函数时,会在B函数体内建立一个形参,该形参的值与实参的值是相同的,但是形参的改变不影响实参,函数结束时,形参被销毁,实参依然没有发生变化。
指针也是一个变量,所以它也符合以上的规定,但是,指针存放的不仅仅是一个值,而是一个内存地址。B函数对这个地址进行了改动,改动的并不是形参,而是形参所指的内存。由于形参的值与实参的值完全相同,所以,实参所指的内存也被修改。函数结束时,虽然这个形参会被销毁,指针的变化无法影响实参,但此前对它所指的内存的修改会持续有效。所以,把指针作为参数可以在被调函数(B)中改变主调函数(A)中的变量,好像形参影响了实参一样。
注意:是“好像”。在这过程中,函数B影响的不是参数,而是内存。
下面再来看刚才的例子:“int abc(char *p)”和“a = abc(p);”。为什么申请中要用*号,因为函数必须知道这是指针;为什么调用时不加*号,因为传递的是“指针值”,而不是“指针所指内存的值”。
四、指向指针的指针:
正因为指针也是一个变量,它一样要尊守形参与实参的规定。所以,虽然指针做参数可以将函数内对变量的修改带到函数外,但是,函数体内对指针本身作任何修都将被丢弃。如果要让指针本身被修改而且要影响函数外,那么,被调函数就应该知道“该指针所在的内存地址”。这时,指针不再是指针,而是“普通变量”。作为参数传递的不是这个“普通变量”,而是指向这个“普通变量”的指针。即“指向指针的指针”。
如果p是一个指向指针的指针,那么*p就是一个指针,我们不妨就把它看成q。要访问q指针所指的内存,只要*q就是了。用初中数学的“等量代换”一换就知道,*q就是**p。
五、指针数组。
之所以要把“指针数组”单独提出来,是因为数组本身就与指针有着千丝万缕的关系。即使你不想用指针,只要你使用了数组,实际就在与指针打交道了。
只要理解了指针本身就是变量,就不难理解“指针数组”,我们可以暂且把它当成普通数组来处理,a[0]、a[1]、a[2]……就是数组的元素,只是,a[0]是一个指针,a[1]、a[2]也是一个指针。那a呢?当然也是指针,但这是两码事。你可以完全无视a的存在,只去管a[0]等元素。*a[0]与*p没有什么本质的区别。
还有一个东西不得不提一下,它比较重要:
指针的定义有两个可取的方式,它们各有优缺点:“int *p;”和“int* p;”是完全等价的,后者的好处是让人体会到p是一个“指向int的”指针,前者会让人误解为*p是一个int型变量(这里没有定义int型变量);但是前者的好处是不会产生混淆,如“int *p, *q;”让人一眼就看出定义了两个指针,而“int* p,q;”会让人误解成定义了两个指针(实际上q不是指针)。
作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:https://jackxiang.com/post/749/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!
评论列表