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不合法
自增 指针++: 指向的内存地址自增(指向了其他内容) 引用++: 被引用对象自增
用作函数参数 依然是"值传递", 特殊点在于传递的值是地址 传递的是实参本身(而不是拷贝副本), 修改形参相当于修改实参.
posted @ 2022-06-10 17:28  编程驴子  阅读(73)  评论(0编辑  收藏  举报