彻底理解C++指针
目录
目录 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; // 两者列数均不确定,可兼容 |
“列数相等”或“列数不确定”是兼容的提前条件,如上述的p3、p4和p5三者的列数均相同。
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 <stdio.h> #include <iostream> int main() { using namespace std; int a; int b; cout << "&a=" << &a << ", &b=" << &b << endl << endl; int m[] = { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xA0 }; int* p = m; int** pp = (int**)m; cout << "&m[0]=" << &m[0] << endl << "&m[1]=" << &m[1] << endl << "&m[2]=" << &m[2] << endl << "&m[3]=" << &m[3] << endl << "&m[4]=" << &m[4] << endl << "&m[5]=" << &m[5] << endl << "&m[6]=" << &m[6] << endl << "&m[7]=" << &m[7] << endl; cout << endl << "pp=" << pp << endl << "pp+1=" << pp+1 << endl << "pp+2=" << pp+2 << endl; cout << endl << "*pp=" << *pp << endl << "*(pp+1)=" << *(pp+1) << endl << "*(pp+2)=" << *(pp+2) << endl; cout << endl << "&p=" << &p << endl << "p=" << p << endl << "p+1=" << p+1 << endl << "p+2=" << p+2 << endl; cout << endl << "*p=" << *p << endl << "*(p+1)=" << *(p+1) << endl << "*(p+2)=" << *(p+2) << endl; pp = &p; cout << endl << "pp = &p;" << endl; cout << endl << "pp=" << pp << endl << "pp+1=" << pp+1 << endl << "pp+2=" << pp+2 << endl; cout << endl << "*pp=" << *pp << endl << "(*pp)+1=" << (*pp)+1 << endl << "(*pp)+2=" << (*pp)+2 << endl; cout << endl << "*pp=" << *pp << endl << "*(pp+1)=" << *(pp+1) << endl << "*(pp+2)=" << *(pp+2) << endl; 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 << "((*(mm+2))+3)=" << ((*(mm+2))+3) << endl; cout << "((*(yy+2))+3)=" << ((*(yy+2))+3) << endl; |
可以看到地址值完全是两个不同的了:
((*(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指针之五:指针和字符串 基础概念 字符串可以分配到内存的不同区域,通常使用指针来支持字符串操作.字符串是以ASCII字符NUL结尾的字符序列.ASCII字符NUL表示为\0.字符串通常 ...
- 深入理解C指针之六:指针和结构体
原文:深入理解C指针之六:指针和结构体 C的结构体可以用来表示数据结构的元素,比如链表的节点,指针是把这些元素连接到一起的纽带. 结构体增强了数组等集合的实用性,每个结构体可以包含多个字段.如果不用结 ...
- 深入理解C指针之四:指针和数组
原文:深入理解C指针之四:指针和数组 数组是C内建的基本数据结构,数组表示法和指针表示法紧密关联.一种常见的错误认识是数组和指针完全可以互换,尽管数组名字有时可以当做指针来用,但数组的名字不是指针.数 ...
- 深入理解C指针之三:指针和函数
原文:深入理解C指针之三:指针和函数 理解函数和指针的结合使用,需要理解程序栈.大部分现代的块结构语言,比如C,都用到了程序栈来支持函数的运行.调用函数时,会创建函数的栈帧并将其推到程序栈上.函数返回 ...
- 深入理解C指针之一:初识指针
原文:深入理解C指针之一:初识指针 简单来说,指针包含的就是内存地址.理解指针关键在于理解C的内存管理模式.C里面有三种内存: ①.静态全局内存(生命周期从程序开始到程序结束,全局变量作用域是全局,静 ...
- 深入理解C指针之二:C内存管理
原文:深入理解C指针之二:C内存管理 内存管理对所有程序来说都很重要.有时候内存由运行时系统隐式的管理,比如为变量自动分配内存.在这种情况下,变量分配在它所处的函数的栈帧上(每个函数都有它自己的栈帧, ...
- C 真正理解二级指针
本文转载自CSDN博主liaoxinmeng,做数据结构时遇到指针方面的问题,想了许久,因此我觉得很有必要复习一下二级指针及其使用 正文如下: 指针是C语言的灵魂,我想对于一级指针大家应该都很熟悉,也 ...
- C语言指针使用小记 (深入理解C指针 读后小记)
最近正值过年在家,新年初一,闲暇时间无事可做便把以前看过的书籍整理了一下,顺手也把这本“深入理解C指针”的书重新读了一遍,这本书总体感觉比较简单,但是还是不免有些地方是平时没有想到过或者没有注意到的, ...
- 《深入理解C指针》
<深入理解C指针> 基本信息 原书名:Understanding and using C pointers 作者: (美)Richard Reese 译者: 陈晓亮 丛书名: 图灵程序设计 ...
- 深入理解C指针----学习笔记
深入理解C指针 第1章 认识指针 理解指针的关键在于理解C程序如何管理内存,指针包含的就是内存地址. 1.1 指针和内存 C程序在编译后,以三种方式使用内存: 1. 静态. ...
随机推荐
- SUSE12Sp3-使用Docker导入镜像并安装redis,zookeeper,kafka
首先在另外一台联网电脑拉取最新的redis,zookeeper,kafka镜像 docker pull redis docker pull zookeeper docker pull wurstmei ...
- linux部署安装SRS流媒体服务器教程
这段时间一直在搞RTMP流媒体直播项目,期间踩过很多坑,刚开始是用的nginx-rtmp作为流媒体转发服务器,但是效果并不尽人意,推拉流不稳定,特别是拉流,速度特别慢,平均要十多秒才能拉到流,并且交互 ...
- Linux下mongoDB下载与安装
原文连接:(http://www.studyshare.cn/blog/details/1168/0)一.下载 官网下载:点击下载 选择合适的版本 百度网盘下载:点击下载 提取码:rm12 此处提供 ...
- 2019 阿里java面试笔试总结 (含面试题解析)
本人5年开发经验.18年年底开始跑路找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.滴滴等公司offer,岗位是Java后端开发,因为发展原因最终选择去了阿里,入职一年时间了,也成为了面试官, ...
- jQuery---jQ动画(普通,滑动,淡入淡出,自定义动画,停止动画),jQuery的事件,jQ事件的绑定/解绑,一次性事件,事件委托,事件冒泡,文档加载
jQuery---jQ动画(普通,滑动,淡入淡出,自定义动画,停止动画),jQuery的事件,jQ事件的绑定/解绑,一次性事件,事件委托,事件冒泡,文档加载 一丶jQuery动画 show,hide, ...
- MFC中窗口重绘
搬家于CSDN 2015-05-14 MFC提供了三个函数用于窗口重绘 InvalidateRect(&Rect) Invalidate() UpdateWindow() 当需要更新或者重绘窗 ...
- js数组与字符串类型相同方法的比较
数组和字符串有很多相似的对方,比如数组和字符串都有以下方法: concat indexOf lastIndexOf slice includes 鉴于toString及valueOf方法基本类型都有, ...
- 解决Ubuntu18.10 网络图标经常消失连不上网问题
我不知道是什么原因,Ubuntu虚拟机经常会出现无法上网的问题? 此时右上角没有网络标志,Settings->NetWork也只有VPN一项,不知道咋用. 在网上终于找到了方法,亲测有效:htt ...
- Fiddler抓包工具简介
1.Fiddler工具: 个人认为Fiddler实际上是一款在浏览器与服务器之间设置代理,对两者之间的通信会话(数据包)进行抓取和处理的一款工具: Fiddler是一个http协议调试代理工具,它能够 ...
- php mysql 按照指定年月查找数据 数据库create_time为时间戳
$moneyloglist = MoneyLog::where(['user_id' => $this->auth->id]) ->where($where) ->whe ...