技术交流QQ群:1027579432,欢迎你的加入!

1.Cpp中的指针

  • 每个变量都有一个内存位置,每一个内存位置都定义了可使用&地址运算符访问的地址,它表示了变量在内存中的一个地址
    int var1;
        int var2[10];
        cout << "变量var1的地址:" << &var1 << endl;  // 访问变量var1的在内存中的位置,使用&地址运算符,获得变量在内存中的地址
        cout << "变量var2的地址:" << &var2 << endl;

2.什么是指针?

  • 指针也是一个变量,它存放的值是另一个变量的地址,即变量在内存中的位置,即内存中的直接地址。与其他变量或常量一样,在使用指针存储其他变量地址之前,必须对指针进行声明,指针变量声明的一般形式是:
    数据类型 *指针变量名;
  • 在上述指针的指针变量声明过程中,数据类型必须是一个有效的C++数据类型,用来声明指针变量的星号*与乘法中使用的星号是相同的。但是,在上面的声明语句中,星号是用来指定一个变量是指针,有效的指针变量的声明是:
    int *p;  
        float *p1;
        double *ptr;
        char *p2;
  • 由于所有指针变量中存放的都是另一个变量在内存中的地址,所以不管指针指向的是int,float,char还是其他数据类型,指针变量的值实际数据类型都是一样的!都是一个代表内存地址的十六进制数。不同数据类型的指针变量之间唯一的区别是:指针所指向的变量或常量的数据类型不同。32位机系统中,指针变量所占的字节数是sizeof(*p) = 4个字节。

3.C++中使用指针

  • 指针变量使用时,会频繁使用以下几个操作:定义一个指针变量,把另外一个变量的地址赋值个指针,访问指针变量中可用地址的值,这些操作都是通过*运算符来返回位于操作数所指定地址的变量的值。
    int var = 20;  // 普通变量的初始化
        int *ip;  // 指针变量的声明
        ip = &var;   // 用指针变量存储普通变量var的地址
        cout << "var的值是:" << var << endl;
        cout << "普通变量var在内存中的地址: " << &var << endl;
        cout << "指针变量ip在内存中的地址: " << &ip << endl;
        cout << "通过访问指针变量ip中存放的普通变量的地址来得到普通变量var的值: " << *ip << endl;

4.Cpp中指针详解

  • C++中与指针有关的概念如下表:


    与指针有关的概念.png

4.1 C++中的NULL指针

  • 在指针变量声明时,如果没有明确的地址可以赋值时,为指针变量赋值一个NULL值是一个很好的习惯。NULL指针是一个定义在标准库中的值为0的常量。如下面的程序:
    int *ptr = NULL;
        cout << "指针变量ptr在内存中的地址:: " << &ptr << endl;
        cout << "ptr的值是: " << ptr << endl;
  • 在大多数的操作系统上,程序不允许访问地址为0的内存,因为该内存是操作系统保留的。然而,内存地址0有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。如果检查一个空指针,需要使用if语句:
    if(ptr)  // 如果ptr非空,则完成
        if(!ptr)  // 如果ptr为空,则完成
  • 因此,如果所有未使用的指针都被赋予空值,同时避免使用空指针,就可以防止误用一个未初始化的指针。很多时候,未初始化的变量存有一些垃圾值,导致程序难以调试。

4.2 指针的算术运算

  • 指针变量中存放的是一个用数值表示另一个变量的地址,可以对指针变量执行算术运算。对指针进行四个算术运算:++ -- - +,假设ptr是一个指向地址是1000的整型指针变量,是一个32位整数,对该指针执行算术运算ptr++后,ptr的指向位置是1004。因为ptr每增加一次,它都指向下一个整数位置,即当前位置往后移动4个字节。这个运算在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。如果ptr执向的是一个地址是1000的char型字符,上面的运算会导致指针指向位置1001,因为下一个字符位置是1001。
  • 递增一个指针:在程序中使用指针代替数组,因为指针变量可以递增,而数组不能递增,因为数组名是一个指针常量,如下面的例子:
    int a[MAX] = {10, 100, 200};
        int *pp;
        pp = a;
        for (int i = 0; i<MAX; i++){
            cout << "变量a[" << i << "]的地址是: " << &a[i] << endl;
            cout << "变量a[" << i << "]的地址是: " << pp << endl;
            cout << "变量a[" << i << "]的值是: " << a[i]  << endl;
            cout << "通过访问指针变量pp中存放的普通变量的地址来得到数组元素的值: " << *pp << endl;
            cout << "------------------------------------------------\n";
            pp++;  // 移动到下一个位置
        }
  • 递减一个指针:对指针进行递减运算,即把值减去其指向的数据类型的字节数,如下所示:
    pt = &a[MAX - 1];
        for (int i = MAX - 1; i >= 0; i--){
            cout << "变量a[" << i << "]的地址是: " << &a[i] << endl;
            cout << "变量a[" << i << "]的地址是: " << pt << endl;
            cout << "变量a[" << i << "]的值是: " << a[i]  << endl;
            cout << "通过访问指针变量pt中存放的普通变量的地址来得到数组元素的值: " << *pt << endl;
            cout << "------------------------------------------------\n";
            pt--;  // 移动到下一个位置
        }
  • 指针的比较:指针可以用关系运算符进行比较,如果p1和p2指向两个相关的变量,如同一个数组中的不同元素,则可以对p1和p2进行大小比较。
    tt = a;
        int j = 0;
        while (tt <= &a[MAX - 1]){
            cout << "变量a[" << j << "]的地址是: " << tt << endl;
            cout << "变量a[" << j << "]的值是: " << a[j] << endl;
            tt++;
            j++;
        }

4.3 指针vs数组

  • 指针与数组是密切相关的,在很多情况下两个是可以互换的。例如,通过一个指向数组开头的指针,可以通过使用指针的算术运算或数组的索引来访问数组,如下面例子:
    int a[MAX] = {10, 100, 200};
        int *pp;
        pp = a;
        for (int i = 0; i<MAX; i++){
            cout << "变量a[" << i << "]的地址是: " << &a[i] << endl;
            cout << "变量a[" << i << "]的地址是: " << pp << endl;
            cout << "变量a[" << i << "]的值是: " << a[i]  << endl;
            cout << "通过访问指针变量pp中存放的普通变量的地址来得到数组元素的值: " << *pp << endl;
            cout << "------------------------------------------------\n";
            pp++;  // 移动到下一个位置
        }
  • 然而,指针与数组并不是完全可以互换的,如下面的例子:
    int b[MAX] = {1, 11, 111};
        cout << "b = " << b << endl;
        cout << "*b = " << *b << endl;
        for (int i = 0; i < MAX; i++){
            *b = i;   // 改变的是数组第一个元素的值 
            // b++;   数组名是一个指针常量,不能进行修改
            *(b + 2) = 555;
        }
    // 不是说数组内的值不能修改,而是只要b的值(地址)不变,那么相应位置的数值改变了,也不会影响该数组的首地址。

4.4 指针数组

  • 引入:一个由3个整数组成的数组
    int c[MAX] = {6, 66, 666};
        for (int i = 0; i < MAX; i++){
            cout << "c[" << i << "]的值是: " << c[i] << endl;
        }
  • 可能有一种情况是:希望数组存储的是指向int/char或者其他数据类型的指针,下面是一个指向整数的指针数组的声明:
    int * ptr[3];
  • 把ptr声明为一个数组,由3个整数指针组成。因此,ptr中的每个元素,都是指向int类型的指针,见下面的例子:
    int cc[MAX] = {6, 66, 666};
        int *pc[MAX];  // 定义一个指针数组
        for (int i = 0; i < MAX; i++){
            pc[i] = &cc[i];  // 由于pc数组中的每个元素都是指向int类型的指针,所以这里赋值为cc[i]的每个元素的地址
        }
        for (int i = 0; i < MAX; i++){
            cout << "cc[" << i << "]的地址是: " << pc[i] << endl;
            cout << "cc[" << i << "] = " << *pc[i] << endl;
        }
  • 也可以用一个指向字符的指针数组来存储一个字符串列表,如下面例子:
    const char *names[MAX] = { 
            "Curry", 
            "Harden", 
            "Durant",
        }; // const char * names[MAX]是指针数组,它的本质是存储指针的数组,存储的元素是char类型的指针
        for (int i = 0; i < MAX; i++){
            cout << "names[" << i << "] = "<< names[i] << endl; // 输出字符串的值
            cout << "*names[" << i << "]的值是: " << *names[i] << endl; // 输出指针所指向的字符串的首地址的值
            cout << "names[" << i << "]的地址是: " << &names[i] << endl; // 输出指针所指向的字符串的首地址
        }

4.5 指向指针的指针(多级间接寻址)

  • 指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针中存储的是另一个变量的地址,当再定义一个指向指针的指针时,第一个指针变量就包含了第二个指针变量的地址,第二个指针变量存储的是另一个变量的地址。


    指向指针的指针.png
  • 一个指向指针的指针变量必须进行声明,即在变量名前加上两个**,下面是一个指向int型的指针的指针:
    int val = 3000;
        int *p1 = &val;
        int **p2 = &p1;  // 指向int类型的指针的指针
        int ***p3 = &p2;
        cout << "val的地址是: " << &val << endl;
        cout << "指针p1的地址是: " << &p1 << endl;
        cout << "指向指针p1的指针p2的地址是: " << &p2 << endl;
        cout << "指针指针p2的指针p3的地址是: " << &p3 << endl;
        cout << "--------------------------------------------------------\n";
        cout << "指针p3中存放的是指针p2的地址: " << p3 << endl;
        cout << "指针p2中存放的是指针p1的地址: " << p2 << endl;
        cout << "指针p1中存放的是变量val的地址: " << p1 << endl;
        cout << "*p3得到的是p2的值即p1的地址: " << *p3 << endl;
        cout << "*(*p3)得到的是p1的值即a的地址: " << *(*p3) << endl;
        cout << "*(*(*p3))得到的是a的值: " << *(*(*p3)) << endl;

4.6 传递指针给函数

  • C++中允许传递指针给函数,只需要简单地声明函数参数为指针类即可,下面的实例是传递一个long型指针给函数,并在函数内改变这个值:
    void getSeconds(unsigned long *par){
            *par = time(NULL);  // 获取当前秒数
        }
        // 传递指针给函数
        unsigned long sec;
        getSeconds(&sec);
        // 输出实际值
        cout << "number of seconds: " << sec << endl;
  • 能接收指针作为参数的函数,也可以接收数组作为参数,如下所示:
    double getAverage(int *arr, int size){
            int i, sum = 0;
            double avg;
            for (i = 0; i < size; i++)
                sum += arr[i];
            avg = double(sum) / size;
            return avg;
        }
    
        int balance[5] = {1, 2, 3, 4, 5};
        double avg;
        // 传递一个指向数组的指针作为参数
        avg = getAverage(balance, 5);
        cout << "平均值: " << avg << endl;

4.7 从函数返回指针

  • 从函数返回指针,需要声明一个返回指针的函数,如下所示:
    int * myFunction(){
            语句;
        }
  • 此外,C++不支持在函数外部返回局部变量的地址,除非定义的是局部变量static变量。
  • 返回从函数返回指针的实例如下:
    // 要生成和返回随机数的函数
        int * getRandom(){
            static int r[10];
            // 设置种子
            srand((unsigned)time(NULL));
            for (int i=0; i<10;i++){
                r[i] = rand();
                cout << "r[" << i << "]=" << r[i] << endl;
            }
            return r;
        }
        // 从函数返回一个指针
        int *ppg;
        ppg = getRandom();   // 调用子函数,从子函数中返回一个指针
        for (int i = 0; i < 10; i++)
            cout << "*(ppg + " << i << ") = " << *(ppg + i) << endl;

5.指针小结

  • 指针的本质是变量,可以是各种数据类型,定义一个指针"*ip",其中"ip"需要赋于一个地址(可以用&符号获取其他变量的地址再赋值给 ip),而"*ip"是一个具体的值,即读取地址后获得的值;&符号的意思是取地址,也就是返回一个对象在内存中的地址, *符号的意思是取得一个指针所指向的对象,也就是如果一个指针保存着一个内存地址,那么它就返回在那个地址的对象
    #include <iostream>
        using namespace std;
    
        int main()
        {
            int var = 20;
            int *ip;
            ip = &var;
    
            cout << "var的值:";
            cout << var << endl;
    
            cout << "变量ip的储存地址:";
            cout << ip << endl;
    
            cout << "指针*ip的值:";
            cout << *ip << endl; 
            return 0;
        }
  • C++中允许声明指向函数的指针,被称为函数指针。函数指针的声明类似于函数的声明,只不过将函数名变成了(*指针名),定义方式: int (*fp)(int a); // 定义了一个指向函数(这个函数参数仅仅为一个int类型,函数返回值是int类型)的指针fp
    int func(int b);
        {
            cout << b;
            return ++b;
        }
        // 定义一个函数指针
        int(*p)(int);
        p = func;
        // 通过函数指针来调用函数
        (*p)(5);