• c++ 指针与引用


    1. c++指针

    指针是对象, 跟普通对象一样, 它有地址&p和存储的值p, 与普通对象的区别是p存储的值是其它对象的地址.
    要得到指针指向的对象的值, 需要使用解引用操作符: *p

    指针p涉及的写法:
    p : 指针p这个对象存储的值, 是指针p指向的对象的地址 .
    p: 指针p指向的对象的值, ""是解引用操作符.
    &p: 指针p自己的地址.

    指针p涉及的概念:
    指针常量: 该指针是一个常量, 该指针的值(所存储的地址)是不可改变的.
    常量指针: 该指针指向的对象是一个常量, 该指针指向的对象是不可改变的.

    1.1 简单指针

    #include <iostream>
    
    int main()
    {
        using std::cout;
        using std::endl;
    
        int a = 5;
        int *p   ; //声明一个指针
        p = &a   ; //指针指向变量a
    
        cout << " a=" << a << endl; // a=5       , 常规变量
        cout << "&a=" <<&a << endl; //&a=0x61FF08, 取a的地址
    
        cout << " p=" << p << endl; // p=0x61FF08, 指针变量p, 存储的是a的地址
        cout << "*p=" <<*p << endl; //*p=5       , 指针指向的内容.
    }
    

    1.2 空指针NULL

    #include <iostream>
    using namespace std;
    int main()
    {
        //如果指针未初始化, 则它可能有垃圾值, 导致程序难以调试.
        //指针变量声明时, 没有确切地址可以赋值, 可以给它一个NULL, 称为空指针.
        //NULL指针在标准库中定义, 值为0;
        //地址0有特别意义, 表明指针指向一个不可访问的内存位置, 就认为指针不指向任何东西.
        int *ptr = NULL;
        cout << "ptr=" << ptr << endl; //ptr=0
    
        return 0;
    }
    

    1.3 指针的算术运算

    可以对指针进行四种算术运算: ++, --, +, -.
    指针可以用关系运算符比较: ==, <, >.

    #include <iostream>
    
    using namespace std;
    const int MAX=3;
    
    int main()
    {
        int var[MAX] = {100, 110, 200}; //一个整型数组
        int *ptr; //一个整型指针
    
        ptr = var; //指针指向数组, 实际上数组名代表的就是数组的第0元素的地址.
        for(int i=0; i<MAX; i++)
        {
            cout << "var[" << i << "]=" << *ptr << ", Addr=" << ptr << endl;
            ptr++; //指针自增, int占四个字节, 所以++后值增加4.
            //var[0]=100, Addr=0x61fef4
            //var[1]=110, Addr=0x61fef8
            //var[2]=200, Addr=0x61fefc
        }
        cout << endl;
    
        ptr = &var[MAX-1]; //指针指向数组最后元素的地址.
        for(int i=MAX-1; i>=0; i--)
        {
            cout << "var[" << i << "]=" << *ptr << ", Addr=" << ptr << endl;
            ptr--; //指针自减, int占四个字节, 所以--后值减少4.
            //var[2]=200, Addr=0x61fefc
            //var[1]=110, Addr=0x61fef8
            //var[0]=100, Addr=0x61fef4
        }
        cout << endl;
    
        //ptr = var; //指针指向第0个元素, 等价于ptr = &var[0];
        int i = 0;
        while(ptr<=&var[MAX-1]) //指针还没指到最后一个元素
        {
            cout << "var[" << i << "]=" << *ptr << ", Addr=" << ptr << endl;
            ptr++;
            i++;
            //var[0]=100, Addr=0x61fef4
            //var[1]=110, Addr=0x61fef8
            //var[2]=200, Addr=0x61fefc
        }
    }
    

    1.4 指针与数组

    常用语法

    int var[MAX] = {100, 110, 200}; //一个整型数组
    int *ptr; //一个整型指针
    
    ptr =  var     ; //指针指向数组, 实际上数组名代表的就是数组的第0元素的地址, 打印ptr或var都能打印出数组地址.
                     //注意, 此处虽然ptr=var, 但ptr与var并不完全等价(var是常量, 不能自增),
    
    //取元素地址
    ptr = &var[0]  ; //指针指向数组, 指向第0个元素的地址, 等价于ptr=var.
    ptr = &var[n-1]; //指针指向数组最后一个元素.
    
    //指针自增自减
    ptr++;           //指针自增, 指向下一个元素.
    ptr--;           //指针自减, 指向上一个元素.
    var++;           //注意, 会报错, 不能使用数组名自增, var是一个常量.
    
    //取元素内容
    *var ;           //在数组名前加*, 获取第0个元素的内容.
    *(var+2);        //取第二个元素的内容.
    var[2];          //同上
    ptr[2];          //同上
    
    *ptr ;           //指针指向的当前元素的内容(根据ptr指针大小, 可以取到所有元素, 超过数组边界会取到垃圾值).
    

    1.5 由指针组成的数组(数组的元素是指针)

    #include <iostream>
    
    using namespace std;
    const int MAX=4;
    
    int main()
    {
        int var0=100, var1=200, var2=300, var3=400;
    
        //ptr是一个数组, 数组的元素是指针.
        int * ptr[MAX] = {&var0, &var1, &var2, &var3};
    
        for(int i=0; i<MAX; i++)
        {
            cout << "ptr[" << i << "]=" << ptr[i] << ", *ptr[" << i << "]=" << *ptr[i] << endl;
        }
    
        // ptr[i], 数组的元素, 本例中元素都是指针
        //*ptr[i], 数组的元素指向的内容
        //打印如下内容
        //ptr[0]=0x61ff08, *ptr[0]=100
        //ptr[1]=0x61ff04, *ptr[1]=200
        //ptr[2]=0x61ff00, *ptr[2]=300
        //ptr[3]=0x61fefc, *ptr[3]=400
    
        return 0;
    }
    

    1.6 指向指针的指针

    #include <iostream>
    
    using namespace std;
    int main()
    {
        int var;
        int *ptr;
        int **pptr; //声明"指向指针的指针"
    
        var = 3000;
        ptr = &var;
        pptr = &ptr; //给"指向指针的指针"赋值
    
        cout << "  var = " <<   var  << endl;
        cout << " *ptr = " <<  *ptr  << endl;
        cout << "**pptr= " << **pptr << endl; //使用**pptr访问真正的值
        //打印:
        //  var = 3000
        // *ptr = 3000
        //**pptr= 3000
    }
    

    1.7 指针作为函数的参数

    //传递简单指针

    #include <iostream>
    
    using namespace std;
    
    void add1(int *p); //指针作为参数, p是个指针, 可以认为参数类型是(int *), 指向int的指针
    
    int main()
    {
        int a = 5;
    
        cout << "before: a=" << a << endl;
        add1(&a); //调用函数时, 给的实参是指针或地址.
        cout << "after : a=" << a << endl; //在函数体中对参数*p的修改, 会体现在a上
        //打印:
        //before: a=5
        //after : a=6
    }
    
    void add1(int *p)
    {
        *p += 1; //给参数增加1, 使用*p获取真实值, 修改*p会修改实参.
    }
    

    //传递数组指针给函数

    #include <iostream>
    using namespace std;
    
    double getAverage(int * arr, int size);//函数参数一是一个指针
    
    int main()
    {
        int scores[5] = {1, 2, 3, 4, 6};
        double avg;
    
        avg = getAverage(scores, 5); //scores是数组名, 同时也是个指针, 所以可以直接传给函数.
        //以下三行代码, 把scores赋值给一个指针在传给函数, 效果跟直接传scores相同.
        //int *pscores;
        //pscores = scores;
        //avg = getAverage(pscores, 5);
    
        cout << "avg=" << avg << endl;
    
        return avg;
    }
    
    //计算平均数
    double getAverage(int * arr, int size)
    {
        int sum = 0;
        double avg;
    
        for(int i=0; i<size; i++)
        {
            sum += arr[i]; //arr相当于数组名, arr[i]是第i个元素.
        }
    
        avg = double(sum)/size;
    
        return avg;
    }
    

    //传递vector指针作为参数

    #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std;
    
    void set_value(vector<vector<string>> *pvec, int i, int j, string vlu) //形参加上*表示该参数是指针
    {
        // pvec[i][j]       = vlu; //error
        //*pvec[i][j]       = vlu; //error
        //*pvec.at(i).at(j) = vlu; //error, 解引用失败, 原因不明.
    
        // pvec->at(i)[j]   = vlu; //pass , 函数体中直接使用该指针.
           pvec->at(i).at(j)= vlu; //pass , pvec是指针, 所以第一级不能用".", 需要用"->"
    }
    
    int main()
    {
        vector<vector<string>> vec;
    
        vec.push_back({"00", "01", "02", "03"});
        vec.push_back({"10", "11", "12"      });
        vec.push_back({"20", "21"            });
        vec.push_back({"30"                  });
    
        set_value(&vec, 0, 2, "xx"); //调用函数时传入&vec, vec的地址
        set_value(&vec, 2, 1, "xx");
    
        for(auto iter0=vec.begin(); iter0!=vec.end(); iter0++)
        {
            for(auto iter1=iter0->begin(); iter1!=iter0->end(); iter1++)
            {
                cout << *iter1 << " ";
            }
            cout << endl;
        }
        //打印vector, v[0][2]和v[2][1]在set_value中被修改为xx:
        //00 01 xx 03
        //10 11 12
        //20 xx
        //30
    }
    

    1.8 从函数返回指针

    注意: C++不支持在函数外返回局部变量的地址,除非定义局部变量为 static变量。

    #include <iostream>
    #include <ctime>
    #include <cstdlib>
    
    using namespace std;
    
    const int MAX = 10;
    
    int * getRandom(); //函数返回值是指针
    
    int main()
    {
        int *p; //指向整型的指针
    
        p = getRandom(); //将函数返回的指针赋值给p
    
        for(int i=0; i<MAX; i++)
        {
            //cout << "*(p+" << i << ") : " << *(p+i) << ", p[" << i << "] = " << p[i] << endl;
    
            char buffer[100];
            snprintf(buffer, 100, "*(p+%d)=%5d, p[%d]=%5d", i, *(p+i), i, p[i]); // *(p+i)与p[i]效果相同.
            cout << buffer << endl;
        }
        //打印:
        //*(p+0)=25832, p[0]=25832
        //*(p+1)=27689, p[1]=27689
        //*(p+2)=19849, p[2]=19849
        //*(p+3)= 8113, p[3]= 8113
        //*(p+4)=29525, p[4]=29525
        //*(p+5)=21313, p[5]=21313
        //*(p+6)=14175, p[6]=14175
        //*(p+7)=  531, p[7]=  531
        //*(p+8)=29886, p[8]=29886
        //*(p+9)=15510, p[9]=15510
    
        return 0;
    }
    
    int * getRandom()
    {
        static int r[MAX]; //返回的指针不能指向局部变量, 只能把这个变量定义为static变量.
    
        srand( (unsigned)time(NULL) ); //设置随机种子
    
        for(int i=0; i<MAX; i++)
        {
            r[i] = rand();
            cout << "r[" << i << "] = " << r[i] << endl;
        }
        //打印:
        //r[0] = 25832
        //r[1] = 27689
        //r[2] = 19849
        //r[3] = 8113
        //r[4] = 29525
        //r[5] = 21313
        //r[6] = 14175
        //r[7] = 531
        //r[8] = 29886
        //r[9] = 15510
    
        return r; //返回指针, r是数组名, 也是指针.
    }
    

    2. c++引用

    引用是c++新增内容, 类似于指针, 但比指针更方便易用.

    引用的一个优点是它一定不为空(声明的同时就要对它初始化).
    在底层, 引用是通过指针常量(指针的值不可改为)的方式实现的.

    2.1 引用的定义

    引用可以看做是一个数据的别名, 通过引用和原来的名字都能找到这份数据.

    数据类型 &引用名称 = 被引用的数据;
    引用必须在定义的同时进行初始化, 且不能再引用其它数据.

    #include <iostream>
    using namespace std;
    int main(){
        int a = 99;
        int &r = a; //定义一个引用, r与a指代同一份数据
    
        cout << "a=" << a << endl;   //99
        cout << "r=" << r << endl;   //99
        cout << "&a=" << &a << endl; //0x61ff08
        cout << "&r=" << &r << endl; //0x61ff08, r和a的地址相同
    
        r = 47;
        cout << "a=" << a << endl;   //47, 通过引用可以修改原变量数据
        cout << "r=" << r << endl;   //47
    
        const int &r1 = a; //定义一个常引用, 常引用不可修改值.
        r1 = 22; //报错: assignment of read-only reference 'r1'
    }
    

    注意: 定义引用时使用&, 使用引用时不能再加&, 加上&后, &r表示取地址.

    &字符的作用:

    1. 按位与.
    2. 取地址.
    3. 定义引用.

    2.2 引用作为函数参数

    1. 定义或声明函数时, 将函数的形参指定为引用的形式
    2. 调用函数时, 实参和形参绑定在一起, 指代同一份数据.
    3. 在函数体中修改形参数据, 会导致实参数据也被修改.
    4. 语法要点:
      void swap_2(int &rx, int &ry){ //传递引用, 将参数声明为引用类型.
          int tmp;    //tmp不必声明为引用类型
          tmp = rx;
          rx = ry;
          ry = tmp;
      }
      int main()
      {
          int a2=12, b2=34;
          swap_2(a2, b2); //传递参数时直接传递变量, 不需要取地址, 也不需要创建引用.
      }
      

    例子1:

    #include <iostream>
    using namespace std;
    
    void swap_0(int x, int y);
    void swap_1(int *px, int *py);
    void swap_2(int &x, int &y);
    
    int main(){
        int a0=12, b0=34;
        cout << "before: a0=" << a0 << ", b0=" << b0 << endl;
        swap_0(a0, b0);
        cout << "after : a0=" << a0 << ", b0=" << b0 << endl;
        cout << endl;
        //打印如下内容, a0和b0的值没有交换:
        //before: a0=12, b0=34
        //after : a0=12, b0=34
    
        int a1=12, b1=34;
        cout << "before: a1=" << a1 << ", b1=" << b1 << endl;
        swap_1(&a1, &b1); //传递参数时要取变量的地址.
        cout << "after : a1=" << a1 << ", b1=" << b1 << endl;
        cout << endl;
        //打印如下内容, a1和b1的值交换了:
        //before: a1=12, b1=34
        //after : a1=34, b1=12
    
        int a2=12, b2=34;
        cout << "before: a2=" << a2 << ", b2=" << b2 << endl;
        swap_2(a2, b2); //传递参数时直接传递变量, 不需要取地址, 也不需要创建引用.
        cout << "after : a2=" << a2 << ", b2=" << b2 << endl;
        cout << endl;
        //打印如下内容, a1和b1的值交换了:
        //before: a2=12, b2=34
        //after : a2=34, b2=12
    }
    
    void swap_0(int x, int y){
        int tmp;
        tmp = x;
        x = y;
        y = tmp;
    }
    void swap_1(int *px, int *py){ //传递指针
        int tmp;    //tmp不必声明为指针类型
        tmp = *px;
        *px = *py;
        *py = tmp;
    }
    void swap_2(int &rx, int &ry){ //传递引用, 将参数声明为引用类型.
        int tmp;    //tmp不必声明为引用类型
        tmp = rx;
        rx = ry;
        ry = tmp;
    }
    

    例子2: 利用引用传递多维vector

    #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std;
    
    void set_value(vector<vector<string>> &rvec, int i, int j, string vlu) //形参加上&表示该参数是引用
    {
        rvec[i][j] = vlu; //函数体中直接使用rvec,
        //本语句还可以写为rvec.at(i).at(j) = vlu;
    }
    
    int main()
    {
    
        vector<vector<string>> v;
        v.push_back({"00", "01", "02", "03"});
        v.push_back({"10", "11", "12"      });
        v.push_back({"20", "21"            });
        v.push_back({"30"                  });
    
        set_value(v, 0, 2, "xx"); //调用函数时直接传入vector, 不需要传入引用
        set_value(v, 2, 1, "xx");
    
        for (auto iter0=v.begin(); iter0!=v.end(); iter0++)
        {
            for (auto iter1=iter0->begin(); iter1!=iter0->end(); iter1++)
            {
                cout << *iter1 << " ";
            }
            cout << endl;
        }
        //打印vector, v[0][2]和v[2][1]在set_value中被修改为xx:
        //00 01 xx 03
        //10 11 12
        //20 xx
        //30
    }
    

    2.3 函数返回引用

    利用"函数返回引用", 可以把"函数调用"放在"赋值语句"的左边, 表示对"函数返回的引用"赋值

    #include <iostream>
    using namespace std;
    
    double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
    
    double & setValue(int i)
    {
        double & ref = vals[i]; //ref引用vals[i]
        return ref; //返回第i个元素的引用
    }
    
    int main()
    {
        cout << "values before change:" << endl;
        for(int i=0; i<5; i++)
        {
            cout << "vals[" << i << "]=" << vals[i] << endl; 
        }
        //打印如下内容:
        //values before change:
        //vals[0]=10.1
        //vals[1]=12.6
        //vals[2]=33.1
        //vals[3]=24.1
        //vals[4]=50
    
        //函数放在赋值语句的左边, 表示对"函数返回的引用"赋值
        setValue(1) = 20.23; //对vals[1]赋值
        setValue(3) = 70.8 ; //对vals[3]赋值
    
        cout << "values after change:" << endl;
        for(int i=0; i<5; i++)
        {
            cout << "vals[" << i << "]=" << vals[i] << endl; 
        }
        //打印如下内容:
        //values before change:
        //vals[0]=10.1
        //vals[1]=20.23 //值被修改了
        //vals[2]=33.1
        //vals[3]=70.8  //值被修改了
        //vals[4]=50
    
    }
    ```cpp
    
    
    注意: 函数返回的引用不能指向"局部数据(比如函数内的局部变量)", 可以是如下变量:
    1) 全局变量.
    2) 静态变量.
    3) 引用类型的函数参数.
        
    ```cpp
    int x[] = {1, 2, 3, 4};
    
    int & setValue(int i){
        int & ri = x[i];
        return ri; //返回全局变量
    }
    
    int & func(int i){
        static int x  = i*2;
        return x; //返回静态变量
    }
    
    int & add(int a, int b, int c, int & result)
    {
        result = a+b+c;
        return result; //返回函数自己的参数
    }
    

    3 引用和指针的区别

    角度 指针 引用
    占用内存 4字节 4字节
    寻址 允许寻址 &p 返回指针自己的地址 不允许寻址 &r返回的是被引用对象的地址, 不是r的地址, r的地址由编译器掌握
    替代 指针可以替代引用 引用不能替代指针
    解引用 可以解引用*p表示指向的对象 不能解引用, r与其引用的对象等价
    sizeof sizeof 指针: 指针本身的大小 sizeof 引用: 被引用对象的大小
    可否为空 不可
    可否修改 可在任何时候指向另一个对象 不可指向另一个对象
    多级 可以有指向指针的指针, **p合法 引用只能一级, &&r不合法
    自增 指针++: 指向的内存地址自增(指向了其他内容) 引用++: 被引用对象自增
    用作函数参数 依然是"值传递", 特殊点在于传递的值是地址 传递的是实参本身(而不是拷贝副本), 修改形参相当于修改实参.
  • 相关阅读:
    MyBatis通过JDBC生成的执行语句问题
    request.getParameter()、request.getInputStream()和request.getReader()
    Spring 实现数据库读写分离
    图片分布式存储
    切换城市的功能实现
    一直在前进的路上
    test blog
    SSIS 系列
    微信摇一摇优惠券
    扫描二维码实现一键登录
  • 原文地址:https://www.cnblogs.com/gaiqingfeng/p/16364031.html
Copyright © 2020-2023  润新知