【C++】引用&指针

  在C语言中,函数有两种传参方式:传值和传址。
  以传值方式进行传参,在函数调用过程中会生成一个临时变量用形参代替,然后把实参的值传递给新产生的临时变量即形参。这样传参虽然避免了函数调用的副作用,却无法改变形参的值;而传址方式,即传实参的地址(指针),则可以改变实参的值。如下实例:

void Swap(int x, int y)
{
    int tmp = x;
    x = y;
    y = tmp;
}
int main()
{
    int a = 10;
    int b = 20;
    Swap(a, b);//传值调用
    cout << "a:"<< a << endl;
    cout << "b:"<< b << endl;
    system("pause");
    return 0;
}

这里写图片描述
采用传值调用函数,外部的实参a和b并未改变。下面看传址调用:

void Swap(int* x, int* y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}
int main()
{
    int a = 10;
    int b = 20;
    Swap(&a, &b);//传址调用
    cout << "a:"<< a << endl;
    cout << "b:"<< b << endl;
    system("pause");
    return 0;
}

这里写图片描述
此时,即交换了a和b的值。虽然指针可以解决问题,但是它并不安全也不友好,所以引用应运而生。
  那什么是引用呢?它有什么特点?该如何使用?一起来看!
  *引用*
  定义:引用是为一个已经定义的变量取一个别名,而并不是定义一个新的变量,因此,编译器不会为引用开辟新的内存空间,它和它所引用的变量共用一块内存空间。
  格式:类型& 引用变量名=已定义的变量;
  【注意】类型必须和已定义变量的类型相同。比如:

int num = 5;
int& tmp = num;

  特点:
  1.引用在定义时必须初始化。

int num = 5;
int& tmp;//这样的代码不能通过编译

这里写图片描述
怎么理解呢?比如说,一个别名“秃子”,它必须是某人或某个动物的别名,不能没有“归属”。
  2.一个变量可以有多个引用。

//情况1:
int a = 1;
int& b = a;
int& c = a;
//情况2:
int a = 1;
int& b = a;
int& c = b;

上述两种情况产生的效果是一样的,都是为变量a取了两个别名b和c,区别是:情况1是直接地;而情况2是间接地(b是a的别名,c是b的别名,则c也是a的别名)
  3.引用一旦绑定了一个实体,就不能再改变为其他变量的引用。

int a = 1;
int b = 2;
int& c = a;
int& c = b;

这里写图片描述
比如现实生话中,两个或者多个人的别名相同,都叫“胖子”,当叫其中一个人时,其他别名为“胖子”的都答应,这是不允许的。
  const引用(也称“常引用”)
  常引用用来引用常量。

const int v1 = 4;
const int& v2 = v1;//常引用

常量具有常性,只有常引用才可以引用常量。

double v3 = 3.3;
int v4 = v3;//当类型不相同时,发生隐式类型转换,产生一个临时变量,
int& v5 = v3;//即:v5引用的是这个具有常性的临时变量

这里写图片描述
图解:
这里写图片描述
int& v5=v3;其中,v5可能会修改该临时变量(非法操作),所以可以加const使v5成为常引用:

double v3 = 3.3;
int v4 = v3;
const int& v5 = v3;

  引用的作用
  1.引用作为参数
  上面我们已经知道,一个函数通过传值方式传参无法改变外部实参的值,而通过传指针的方式达到了目的。其实,在C++中以传引用的方式传参也可以改变外部实参。

void Swap(int& x, int& y)
{
    int tmp = x;
    x = y;
    y = tmp;
}
int main()
{
    int a = 10;
    int b = 20;
    Swap(a, b);//传引用
    cout << "a:"<< a << endl;
    cout << "b:"<< b << endl;
}

这里写图片描述
形参为实参的引用,则形参是实参的别名。
  当不希望改变函数外部实参的值时,尽量使用常引用(const 类型& 形参名)传参,这样传参还可以减少内存空间的开辟。
  2.引用作为返回值(深度解析)
  首先来看一段代码:
  代码1:

int Add(int x, int y)
{
    int z = x + y;
    return z;
}
int main()
{
    int a = 3;
    int b = 4;
    int& c = Add(a, b);
    cout << "c = " << c << endl;
    system("pause");
    return 0;
}

这样的代码能通过编译吗?来看结果:
这里写图片描述
上述代码中,我想用引用类型的变量c接收函数Add的返回值,但却没有通过编译。事实上,Add函数并未用z直接返回,而是在中间生成了一个临时变量(也叫“匿名变量”)作为函数的返回值,而临时变量都具有常性,所以引用变量c不能接收函数的返回值,根据上面所讲到的,可以将c改为常引用变量:const int& c=Add(a,b)。那为什么不能用z作表达式的返回值呢?因为,每个变量都有自己的生命周期,z的生命周期只是其所在函数的生命周期,当Add函数调用结束时,函数栈帧已经销毁,z的生命周期已经到了,此时就不能再访问z。
  代码2:

int& Add(int x, int y)
{
    int z = x + y;
    return z;
}
int main()
{
    int a = 3;
    int b = 4;
    int& c = Add(a, b);
    cout << "c = " << c << endl;
    system("pause");
    return 0;
}

这里写图片描述
可以看到虽然通过编译,但有一个警告。再来分析:Add函数的返回值是引用类型,main()中也以引用变量来接收其返回值;Add函数并不是直接以z作返回值,因为一旦出了该函数的作用域,z就不存在了,当然也没有像代码1一样生成临时变量,因为若生成了临时变量,c是不能作为临时变量的引用的;此处可以理解为:int& x(x是为了便于理解设置的,其类型是引用),用z初始化x,x是z的别名,而c是x的别名,则可理解为c是z的别名,main()中c已非法,因为z已不存在。
  因此,通过代码2可得出以下结论:
  (1)不要返回一个临时变量的引用(它出了作用域已不存在);
  (2)若出了其作用域还存在(该对象在全局作用域或是静态的情况下 ),尽量返回引用,这样可以更高效且可以节省空间;
  (3)若函数的返回值是以参数传入,此时它的生命周期是大于函数的生命周期的,可以返回引用。
  【通过汇编对比传值和传址返回】
  这里写图片描述
  
  这里写图片描述
可见:引用在底层(汇编层)的实现和指针一模一样
  引用和指针的区别
  相同点:二者底层的处理方式相同。
  不同点:
  1.引用定义时必须初始化,指针可以不初始化(正常情况下需初始化为NULL);
  2.引用一旦被定义就不能再修改,指针可以在任何时候指向任何一个同类型对象;
  3.自加或自减的含义不同:引用++/–,其值+1/-1;而指针++/–,要视其指向对象的类型而定,++/–后指针的指向会发生改变;
  4.没有多级引用,但可以有多级指针;
  5.sizeof(引用):引用类型的大小;sizeof(指针):地址空间所占字节数;
  6.寻址方式不同:引用通过编译器寻址;指针是手动寻址;
  7.指针比引用更灵活,引用比指针使用起来相对更安全;
  8.普通的引用变量相当于常指针:int* const p=&a 和 int& ra=a 是等价的。 
  关于引用以及对比指针和引用就讲到这。由于博主水平有限,若博友们发现其中有误,望指出。
  
  

猜你喜欢

转载自blog.csdn.net/Sunshine_R9H15Chen/article/details/79548611
今日推荐