您的位置:首页 > 编程语言 > C语言/C++

深入理解C++指针

2016-04-12 17:43 351 查看
相信C++中指针是大家比较容易出错和难以理解的地方,本文尝试从内存地址的角度,作一些探讨。

首先让我们来看一段小代码:

1. #include<iostream>
2. using namespace std;
3. void main()
4. {
5. //字符指针指向一个字符串
6. cout << "字符指针测试部分:"<< endl;
7. char*p = "csdn";//定义一个字符型的指针,并用一个字符串初始化
8. cout << p << endl;//输出p所指向的字符串的值,C++中默认输出整个字符串
9. cout << (void*)p << endl;//输出指针p指向的地址
10. cout << *p << endl;//输出p指向地址所存储的字符
11. cout << (void*)&(*p) << endl;//输出该字符的地址
12. cout << *(++p) << endl;//输出p指针指向的地址的下一个地址的存储的值
13. cout << (void*)& *(++p) << endl;//输出该字符的地址
14.
15. cout << "字符数组测试部分:"<< endl;
16. //字符数组存储一个字符串
17. char s[] = "csdn";//定义一个字符型数组,并用一个字符串初始化
18. cout << s << endl;//输出s所指向的字符串的值,C++中默认输出整个字符串
19. cout << (void*)s << endl;//输出该字符数组的首地址
20. cout << *s << endl;//输出s所代表地址的存储的值
21. cout << &s << endl;//输出s所代表的地址
22. cout << s[1] << endl;//输出s的下一个地址的存储值
23. cout << (void*)&s[1] << endl;//输出该字符的地址
24. }
运行结果:




结果分析:

从程序的分析结果来看,我们对字符串的存储可以用两种方式,当我们用指针存储时,p指针指向的是”csdn” 这个字符串的首地址,即是”c” 的地址,我们可以使用指针操作,对字符串中的字符进行访问并输出,当我们使用字符数组进行存储时,我们也可以看到数组名其实就标识这个字符串的首地址,我们同样也可以通过改变数组的下标来对字符串中的字符分别进行访问并输出。
那么,这两者的区别在哪呢?相信细心的亲已经发现,同样是对字符串中下一个字符进行访问,但使用指针存储时,地址偏移量是2个字节,当使用数组存储时,地址偏移是1个字节。而我们知道,字符类型的数据是占用一个字节的,那么问题出在哪呢?还有一个问题是,既然数组名是和一个地址绑定的,那么是否可以将数组名当作一个指针来进行处理呢?细心的亲可能发现,我在对下一个字符的遍历时,对数组和指针是不一样的,这又说明了什么呢?
我们先来看看数组名和指针的区别:
上代码!
用指针进行字符串的存储:
1. #include<iostream>

2. using namespace std;

3. void main()

4. {

5. char*p = "csdn";//定义一个字符型的指针,并用一个字符串初始化

6. cout << (void*)p << endl;//输出指针p指向的地址

7. cout << &p << endl;//输出存储指针变量p的地址空间

8. cout << *(++p) << endl;//输出p指针指向的地址的下一个地址的存储的值

9. cout << (void*)& *(++p) << endl;//输出该字符的地址

10. }

运行结果:



结果分析:

指针p是一个变量,它有独立的存储空间,其存储空间存储的内容就是字符串的第一个字符的地址,我们可以通过指针的自增来完成字符串的遍历,我们也可以输出该指针变量的独立地址。

用字符数组进行存储:

1. #include<iostream>

2. using namespace std;

3. void main()

4. {

5. char s[] = "csdn";//定义一个字符型数组,并用一个字符串初始化

6. cout << (void*)s << endl;//输出指针p指向的地址

7. cout << &s << endl;//输出存储指针变量p的地址空间

8. cout << *(++s) << endl;//输出p指针指向的地址的下一个地址的存储的值

9. cout << (void*)& *(++s) << endl;//输出该字符的地址

10. }

运行结果:





我们将代码修改一下:

#include<iostream>

1. using namespace std;

2. void main()

3. {

4. char s[] = "csdn";//定义一个字符型数组,并用一个字符串初始化

5. cout << (void*)s << endl;//输出指针p指向的地址

6. cout << &s << endl;//输出存储指针变量p的地址空间

7. }

运行结果:



通过以上分析我们知道,数组名a只是编译器在编译运行过程,给数组起始地址绑定的一个临时的标识符,它不是变量,也没有额外的存储空间,它无法进行自增操作,也没法输出额外的地址空间。
在此基础上,我们再来探讨一个更为深刻的问题,通过数组分配的地址空间和通过指针分配地址空间的赋值属性是一样的吗?在此基础上我们更深刻的认识,赋值操作的本质是什么?
保持优良传统,我们仍然通过代码来研究:
1. #include<iostream>

2. using namespace std;

3. void main()

4. {

5. char s[] = "csdn";//定义一个字符型数组,并用一个字符串初始化

6. *s = 'A';//修改字符串第一地址的字符值

7. cout << s << endl;//重新输出字符串

8. char*p = "csdn";//定义一个字符型的指针,并用一个字符串初始化

9. *p = 'A';// 修改字符串第一地址的字符值

10. cout << p << endl; //重新输出字符串

11. }

运行结果:






结果分析:
很明显,程序在执行到第9句时,发生错误,程序中断。这表明通过数组名绑定的地址的值是可以直接修改的,但通过指针指向的地址,是无法直接修改的,编译器会报出写入位置访问冲突。为什么会这样呢,我们不妨再看一段代码:
1. #include<iostream>

2. using namespace std;

3. void main()

4. {

5. char s[] = "csdn";//定义一个字符型数组,并用一个字符串初始化

6. cout << &s << endl;//输出s绑定的地址值

7. char*p = s;//定义一个字符型的指针,并把s绑定的值赋给p

8. cout << (void*)p << endl;//输出指针变量p存储的值

9. *p = 'A';//利用指针变量修改字符串的首字母

10. cout << p << endl;//输出修改后的字符串

11. }

运行结果:



神奇的事情发生了,这次我们竟然通过*p=’A’成功的修改了字符串的第一个字符,看来问题并不是出在指针上,而是出在地址空间本身上。我们知道计算机中的数据都是存储在地址空间上,就相当于一个个的地址单元,我们不妨把它看作图书馆用来存箱包的柜子。显然,我们在使用某个地址空间时,先得进行申请,不是随便一个柜子你都能用。

地址就相当于标识每个柜子的ID,当然有些时候为了方便,你可以在某个柜子上贴上“此柜有主”标签,那么这个柜子就归你所有了,你可以在其中放数据,也可以修改其中的内容,这个标签我们就可以把它看作是一个变量名。那么指针呢,指针是啥,指针可以看作是一个柜子管理员的登记表,这个登记表里面的记录就是一个个柜子的ID,你可以通过这个登记表去找到某个柜子。上面两个例子,在本质上说明一个问题,当使用数组申请一块空间时,这块空间相当于是可以被使用的,因为它是有变量的,已经分配到具体变量了(在字符数组中,其实是a[0],a[1],a[2],a[3],a[4])。但使用指针申请的一块数组,从本质上来说,它并没有分配给某个具体的变量,只是说在管理员的登记本上登记了这些箱子里面有哪些东西,但是这些箱子还是公家的,不能随便动。你可能会反问,它不是属于指针变量P吗?你仔细想想,这肯定是不对的,属于指针变量的地址空间,除了放一个个地址外,难道还可以放其他东西吗?显然这块区域不具有变量的特性,或者干脆把它看作一块常量区,一块由指针指向的常量区。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: