详解C++中指针(*)、取地址(&)、解引用(*)与引用(&)的区别 (完整代码)

来源:互联网 发布:c语言中如何判断闰年 编辑:程序博客网 时间:2024/06/14 21:13

一、初步了解——指针与取地址

先看程序:

[cpp] view plaincopy
  1. #include<cstdio>  
  2.   
  3. int main(void)  
  4. {  
  5.     int num = 7;  
  6.     int *p = &num;  
  7.     printf("%d 的地址是 %p\n", num, p);  
  8.     return 0;  
  9. }  
上面int *p定义了一个指向int类型指针p(我们使用*符号把p声明为指针),并初始化p使其指向int类型的变量num,这里&num中的&是取地址操作符,当&作用于一个对象上时,它返回了该对象的地址。

所以这里指针p指向了num所对应的地址。(我测试时输出了0028FF1C)


二、如何使用指针?——解引用与指针赋值

[cpp] view plaincopy
  1. #include<cstdio>  
  2.   
  3. int main(void)  
  4. {  
  5.     int num = 7;  
  6.     int *p = &num;  
  7.     printf("数值%d所在的地址是 %p\n", num, p);  
  8.     printf("指针p所指向的地址为 %p , 该地址上所保存的值为%d\n", p, *p);  
  9.     *p = 100;  
  10.     printf("指针p所指向的地址为 %p , 该地址上所保存的值为%d\n", p, num);  
  11.     return 0;  
  12. }  

注意这里*操作符为解引用操作符,它返回指针p所指的对象(左值)。

我们可以对*p赋值(对左值赋值),从而改变p所指的地址上所保存的值,从而改变此地址所存储的变量num的值。(num的值变为100)

当然,我们也可以给指针p赋值,使其指向另外一个地址(这样就改变了在解引用时获取的左值):

[cpp] view plaincopy
  1. #include<cstdio>  
  2.   
  3. int main(void)  
  4. {  
  5.     int num = 7, another = -5;  
  6.     int *p = &num;  
  7.     p = &another;  
  8.     printf("%d\n", *p);//-5  
  9.     return 0;  
  10. }  

三、引用

从某种意义上来说,引用完全有别于上面说介绍的内容:

[cpp] view plaincopy
  1. #include<cstdio>  
  2.   
  3. int main(void)  
  4. {  
  5.     int val = 7, val2 = 999;  
  6.     int &refval = val, &refval2 = val2; ///引用必须要初始化,使其绑定到一个变量上  
  7.     ///修改引用的值将改变其所绑定的变量的值  
  8.     refval = -12;  
  9.     printf("%d %d\n", val, refval);//-12,refval的值和val一样  
  10.       
  11.     ///将引用b赋值给引用a将改变引用a所绑定的变量的值,  
  12.     ///引用一但初始化(绑定),将始终绑定到同一个特定对象上,无法绑定到另一个对象上  
  13.     refval = refval2;  
  14.     printf("%d %d\n", val, refval);//999  
  15.     return 0;  
  16. }  

但是可以用int *&refp = p;的方式将一个指针引用和一个指针绑定起来。

PS:如果还是不理解,可以把引用认为是一个变量的别名,就和#define ...,typedef ...很像。


使用引用有何优点?

在传参的时候,使用指针传参,编译器需要给指针另行分配存储单元,存储一个该指针的副本,在函数中对这个副本进行操作;使用引用传参,编译器就不需要分配存储空间和保存副本了,函数将直接对实参进行操作。所以使用引用使得程序的效率更高。


四、补充

a)指向指针的指针

[cpp] view plaincopy
  1. #include<cstdio>  
  2.   
  3. int main(void)  
  4. {  
  5.     int val = 7;  
  6.     int *p = &val;  
  7.     int **p2 = &p;///**声明一个指向指针的指针  
  8.     printf("val的值为%d %d",*p,**p2);///**p2为两次解引用,可看做*(*p2)  
  9.     return 0;  
  10. }  

由于指针也要占用内存空间存放其值,所以我们也可以定义一个指向指针的指针。


b)指针与数组

指针和迭代器非常像,我们可以说指针就是数组的迭代器。

数组具有较好的可读性,指针具有更强的灵活性。一般,对某些多维数组中非连续的元素的随机访问用下标表示比较方便,当按递增(减)顺序访问数组时,使用指针快捷而且方便。

在某些情况下使用指针更好,与数组相比,它可以1. 节省空间(指针只占用4字节空间) 2. 省去了调用memset()的时间

[cpp] view plaincopy
  1. #include<cstdio>  
  2.   
  3. int val[100];  
  4.   
  5. int main(void)  
  6. {  
  7.     for (int i = 0; i < 100; ++i)  
  8.         val[i] = i;  
  9.     int *p = val;///近似理解为 int *p = &val[0];  
  10.     ///隐式指针转换:数组自动转换为指向第一个元素的指针  
  11.     printf("%d\n", *p); ///指针p指向val的第一个元素,即val[0]  
  12.       
  13.     int t = 100;  
  14.     while (t--)  
  15.         ///可以直接对指针进行加减运算,就和迭代器一样  
  16.         printf("%d\n", *(p++));///输出0~99  
  17.           
  18.     ///指针可以做差:  
  19.     int *p2 = &val[10], *p3 = &val[20];  
  20.     printf("%d\n", p3 - p2); //10  
  21.     printf("%d\n", p2 - p3); //-10  
  22.       
  23.     ///还可以比比较大小:  
  24.     printf("%d\n", p2 < p3 ? p3 - p2 : p2 - p3); //10  
  25.     return 0;  
  26. }  

PS:不将数组转换为指针的例外情况为有:&数组,sizeof(数组),以及用数组对数组的引用进行初始化时。

由于sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用:

[cpp] view plaincopy
  1. char arr[sizeof(int) * 10]; // ok  


对于多维数组,只要把指向指针的指针理解好了就行:

[cpp] view plaincopy
  1. #include<cstdio>  
  2.   
  3. int val[100][100];  
  4.   
  5. int main(void)  
  6. {  
  7.     val[2][1] = 666;  
  8.     ///如何用指针取出val[2][1]?  
  9.     printf("%d", *(*(val + 2) + 1));  
  10.     return 0;  
  11. }  


c)指针与字符串

请看这篇文章中的“字符串赋值”部分。


d)危险!探测数组外面的世界

“输出流?”

[cpp] view plaincopy
  1. #include<cstdio>  
  2.   
  3. int INCORRECT_getIntlen(int *num)  
  4. {  
  5.     int *p = num;  
  6.     printf("%x %p\n", *p, p);  
  7.     while (*p++)///不合理的判断条件  
  8.         printf("%x %p\n", *p, p);  
  9.     return (p - num);  
  10. }  
  11.   
  12. int main(void)  
  13. {  
  14.     int num[20];  
  15.     int *p[20];///不创建指针数组造成的最终结果也不同  
  16.     for (int i = 0; i < 20; ++i)  
  17.     {  
  18.         num[i] = i + 1;  
  19.         p[i] = &num[i];  
  20.     }  
  21.     for (int i = 0; i < 20; ++i)  
  22.     {  
  23.         //printf("%x\n", num[i]);  
  24.         printf("%x\t%p\n", num[i], p[i]);  
  25.     }///两个printf造成的最终结果不同  
  26.     ///数组越界造成的后果是未知的  
  27.     puts("");  
  28.     printf("%d\n", INCORRECT_getIntlen(num));  
  29.       
  30.     ///推荐下面这种做法  
  31.     printf("%d\n"sizeof(num) / sizeof(int));  
  32.     return 0;  
  33. }  

PS:指针的长度。在32位的编译环境中,sizeof(p)的结果都是4,也就是一个地址编码的长度。


e)数组作为函数参数传递

虽然很多时候数组名被调用时会被转换成指针,但是不可否认的是它的确是特殊类型,并且在若干地方某些操作并不会发生,如使用&,sizeof,以及初始化赋值给引用时。

在定义的时候:
int function(int *a);
int function(int *a[]);
int function(int *a[10]);
这三种定义,会被编译器解析成int function(int *a);也就是说从编译器开始,已经成为指针了,在函数体内部没有办法知道数组的长度。

这时C++中的vector就派上用场了。

[cpp] view plaincopy
  1. #include<cstdio>  
  2. #include<vector>  
  3. using namespace std;  
  4.   
  5. int getVectorSize(vector<int> v)  
  6. {  
  7.     return v.size();  
  8. }  
  9.   
  10. int main(void)  
  11. {  
  12.     vector<int> v;  
  13.     v.push_back(1);  
  14.     v.push_back(2);  
  15.     printf("%d\n",getVectorSize(v));  
  16.     return 0;  
  17. }  

但若你不想用vector,有以下三种方法:

1. 仿照字符串,加一个结束标记符(比如null)

2. 传递指向数组的第一个元素的指针和最后一个元素的下一个位置的指针(就像我们在排序时调用的sort(a,a+n)一样)

3. 将第二个形参定义为数组的大小(size_t size),传递的值可以为:sizeof(a)/sizeof(*a)

0 0
原创粉丝点击