• 彻底理解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; // 两者列数均不确定,可兼容

    列数相等”或“列数不确定”是兼容的提前条件,如上述的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 <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#学习笔记:多态与隐藏,覆盖
    HTML与XML数据的结合小总结
    基于角色(RoleBased)的表单验证
    去除C++String的首尾空格
    Bash Shell中命令行选项/参数处理
    linux shell 执行多个命令的几种方法
    gdb 多线程调试
    linux 时间戳及时间差计算
    oracle sql日期比较
    在Fedora 14上安装Sun JDK 6
  • 原文地址:https://www.cnblogs.com/aquester/p/11469872.html
Copyright © 2020-2023  润新知