彻底理解C/C++指针

简介: 彻底理解C++指针.pdf 推荐阅读pdf版本,原因是从WPS复制粘贴到ChinaUnix后格式有些丢了。 目录 目录 1 1. 概念 1 1.1.
img_e25d4fb2f8de1caf41a735ec53088516.png彻底理解C++指针.pdf 推荐阅读pdf版本,原因是从WPS复制粘贴到ChinaUnix后格式有些丢了。

目录

目录 1

1. 概念 1

1.1. 双指针 1

1.2. 指针数组 1

1.3. 数组指针 1

1.4. 常见指针定义解读 1

2. 区别 2

3. 兼容性 2

4. 为何列数须相等? 2

5. “1”的含义 3

6. 回归本质 3

7. “*”和“[]” 7

 

1. 概念

1.1. 双指针

指向一个指针的指针。

1.2. 指针数组

由指针值组成的数组,也就是说数组的每个元素值的数据类型均为指针类型,如:int* p[2];

1.3. 数组指针

指向一个数组的指针。

1.4. 常见指针定义解读

int *p;

p为指向int值的指针,也可以说是指向一维数组的指针,假如有一个一维数组:int m[8],则可:p = m;

int *p[8];

p为一个一维数组,数组元素为int*类型,它和数组int p[8]都是同一类型,只不过一个元素类型为int*,一个是int

int (*p)[8];

p为一个指向二维数据的指针,数组元素为int类型,假如有二维数据:int m[1][8],则可:p = m;

int (*p)();

p为一个指向函数的指针,假设有一个函数:int foo(),则可:p = foo;

 

下面两个了?

int (**pa)[8];

int (**pb)();

不用怕,只是多了个*,也就是指向指针的指针。假设有:int m[1][8]; int (*p)[8] = m;,则:pa = &p

2. 区别

 

 

行数

列数

说明

int** p1;

双指针

不固定

不固定

列数和行数都不确定,而且每行可以列数不等。

int* p2[3];

指针数组

固定

不固定

3行,每行多少列不确定,而且每行可以列数不等

int (*p3)[3];

数组指针

不固定

固定

3列,多少行不确定。

3. 兼容性

int** p1;

int* p2[3];

int (*p3)[3];

int p4[2][3];

int p5[3];

 

// 兼容性

p1 = p2;

p3 = p4;

p3 = &p5; // p5的列数必须和p3的列数相同

 

p1 = p2; // 两者列数均不确定,可兼容

 

“列数相等”或“列数不确定”是兼容的提前条件,如上述的p3p4p5三者的列数均相同。

4. 为何列数须相等?

指针支持加减操作,比如:

int m[3][3];

int (*pm)[3] = m + 1;

 

上述第二行的m是指二维数组“int m[3][3];”在内存中的首地址,如:0x7fff82521370。而这个“1”是指这个二维数组一行的大小,也就是“int m[3];”的大小。因此,pm的值为:0x7fff82521370 + 12 = 0x7fffd5afd94c。

 

如果列数不相等,则加减操作无法进行,因此需要“列数相等”。假设:

int** b1;

int** b2 = b1 + 1;

 

上述中的“1”实际是多少?这个就要看b1的类型是什么?在这里,b1是一个双指针,也就是指向指针的指针。本质上就是一个指针,因此在32位平台上它的值是4,在64位平台上它的值是8。

5. “1”的含义

对于“p+1”中的“1”,其含义依p所指向的类型而不同。

定义

 

所指向类型

1”的含义

int* p;

p+1

p指向int类型

sizeof(int)

(*p)+1

*p”不是指针

即为表面意义的数字1

(&p)+1

&p”指向“int*”类型

sizeof(int*)

int** p;

p+1

p指向“int*”类型

sizeof(int*)

(*p)+1

*p”指向int类型

sizeof(int)

(**p)+1

**p”不是指针

即为表面意义的数字1

int m[5];

m+1

m是个地址,指向int类型

sizeof(int)sizeof(m[0])

&m+1

&m是个地址,指向int[5]类型

sizeof(m)

int*** p;

p+1

p指向“int**”类型

sizeof(int**)

(*p)+1

*p”指向“int*”类型

sizeof(int*)

(**p)+1

**p”指向int类型

sizeof(int)

(***p)+1

***p”已不是指针

即为表面意义的数字1

int mm[2][3];

mm+1

mm是个地址,指向int[3]类型

sizeof(m[0]),即为4*3=12

6. 回归本质

指针的加减操作,实际是对地址的操作,而解引用“*”是取所在地址的数据,数组下标操作“[]”也是取所在地址的数据。

彻底理解指针,最关节是理解内存是啥。内存有两个基本属性:一是地址,二是数据。对于“int *p;”,p是地址,“*p”是数据。对于“加减”操作,要区分是对地址,还是数据的“加减”操作,对于“int* p;”,则“p+1”是地址的加减操作,而“(*p)+1”则是数据的加减操作。

 

在x86_64环境,指针大小为8字节,int类型为4字节。注意下图中的虚线框,它们要么是未初始化的内存,或者对它们的访问是越界访问。总之一句话:虚线框的内存地址是确定的,但存储在这些地址上的数据是不能保证的。

 

上图对应的C++代码:

// g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

#include 

#include 

 

int main()

{

    using namespace std;

    

    int a;

    int b;

    cout 

    

    int m[] = { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xA0 };

    int* p = m;

    int** pp = (int**)m;

    

    cout 

         

         

         

         

         

         

         

         

    

    cout 

         

         

         

         

    cout 

         

         

         

         

    cout 

         &p=" &

         

         

         

    

    cout 

         

         

         

         

pp = &p;

    cout 

    

    cout 

         

         

         

         

    cout 

         

         

         

         

    cout 

         

         

         

         

    return 0;

}

 

上图对应的C++代码运行结果(由在线编译器http://coliru.stacked-crooked.com/编译运行):

&a=0x7fff55631998, &b=0x7fff55631994

 

&m[0]=0x7fff55631c30

&m[1]=0x7fff55631c34

&m[2]=0x7fff55631c38

&m[3]=0x7fff55631c3c

&m[4]=0x7fff55631c40

&m[5]=0x7fff55631c44

&m[6]=0x7fff55631c48

&m[7]=0x7fff55631c4c

 

pp=0x7fff55631c30

pp+1=0x7fff55631c38

pp+2=0x7fff55631c40

 

*pp=0x200000001

*(pp+1)=0x400000003

*(pp+2)=0x600000005

 

&p=0x7fff55631988

p=0x7fff55631c30

p+1=0x7fff55631c34

p+2=0x7fff55631c38

 

*p=1

*(p+1)=2

*(p+2)=3

 

pp = &p;

 

pp=0x7fff55631988

pp+1=0x7fff55631990 // 这个地址值是确定的,但上面的数据是啥则不好说

pp+2=0x7fff55631998

 

*pp=0x7fff55631c30

(*pp)+1=0x7fff55631c34

(*pp)+2=0x7fff55631c38

 

*pp=0x7fff55631c30

*(pp+1)=0x0

*(pp+2)=0x0

7. “*”和“[]”

假设有:int** pp;,则“**pp”和“pp[0][0]”作用相同,实际上都是:*((*p)+0)。对于二维数组“mm[行][列]”,“mm[2][3]”效果和“*((*(mm+2))+3)”相同。

 

如果这样定义:

int mm[][4] = { {0x01,0x02,0x03,0xA1}, {0x04,0x05,0x06,0xA2}, {0x07,0x08,0x09,0xA3} };

int** qq = mm;

 

则编译时会告警“cannot convert 'int (*)[4]' to 'int**' in initialization”,原因是违背了本文第三节“兼容性”要求,正确的定义是:

int mm[][4] = { {0x01,0x02,0x03,0xA1}, {0x04,0x05,0x06,0xA2}, {0x07,0x08,0x09,0xA3} };

int (*qq)[4] = mm;

 

这个时候,“*((*(mm+2))+3)”、“*((*(qq+2))+3)”和“mm[2][3]”效果相同。如果要强来:

int mm[][4] = { {0x01,0x02,0x03,0xA1}, {0x04,0x05,0x06,0xA2}, {0x07,0x08,0x09,0xA3} };

int** yy = (int**)mm;

 

cout 

cout 

 

可以看到地址值完全是两个不同的了:

((*(mm+2))+3)=0x7fff8f58c27c

((*(yy+2))+3)=0x500000010

 

因为地址值yy和mm是相同的,所以仍然可以通过取巧使用yy来取得“mm[2][3]”的数据:

1) “mm+2”中的“2”,实则是“sizeof(m[0])*2”

2) “(*(mm+2))+3”中的“3”,实则是“sizeof(m[0][0])*3”

3) “m[0]”大小为“4*4=16”,“m[0][0]”大小为“sizeof(int)=4”,不难计算出“(*(mm+2))+3”相对于“mm”,地址偏移了“32+12=44”

4) 因此只需要将“yy”偏移“44”即可达到目的

5) 在x86_64上“(int**)((unsigned long)(yy+6)-4)”值和“(*(mm+2))+3”相同

6) 可以通过“*(int*)((unsigned long)(yy+6)-4)”取得“m[2][3]”的值。


相关文章
|
存储 安全 算法
【C++智能指针 相关应用】深入探索C++智能指针:跨进程、动态库与最佳实践
【C++智能指针 相关应用】深入探索C++智能指针:跨进程、动态库与最佳实践
61 5
|
29天前
|
存储 安全 C++
在C++指针和引用
在C++指针和引用
|
1月前
|
安全 程序员 Linux
【C++】—— c++11之智能指针
【C++】—— c++11之智能指针
|
25天前
|
JSON JavaScript 前端开发
C++ 智能指针与 JSON 处理:高级编程技巧与常见问题解析
C++ 智能指针与 JSON 处理:高级编程技巧与常见问题解析
255 0
|
11天前
|
存储 C++
C++指针
C++指针
|
22天前
|
存储 编译器 C语言
【c++】类和对象(二)this指针
朋友们大家好,本节内容来到类和对象第二篇,本篇文章会带领大家了解this指针
【c++】类和对象(二)this指针
|
23天前
|
存储 编译器 C语言
【C++练级之路】【Lv.2】类和对象(上)(类的定义,访问限定符,类的作用域,类的实例化,类的对象大小,this指针)
【C++练级之路】【Lv.2】类和对象(上)(类的定义,访问限定符,类的作用域,类的实例化,类的对象大小,this指针)
|
25天前
|
存储 安全 数据库连接
【C++智能指针】深入探究C++智能指针:自定义删除器的设计与选择
【C++智能指针】深入探究C++智能指针:自定义删除器的设计与选择
82 0
|
25天前
|
存储 安全 编译器
【C++ 函数设计的艺术】深挖 C++ 函数参数的选择 智能指针与 std::optional:最佳实践与陷阱
【C++ 函数设计的艺术】深挖 C++ 函数参数的选择 智能指针与 std::optional:最佳实践与陷阱
106 0
|
25天前
|
安全 算法 程序员
【C++ 空指针的判断】深入理解 C++11 中的 nullptr 和 nullptr_t
【C++ 空指针的判断】深入理解 C++11 中的 nullptr 和 nullptr_t
47 0

热门文章

最新文章