C++学习-引用和指针

内容 关键字
引用的概念及用法 别名,&符号
引用做参数 普通引用,const引用
引用做返回值 临时变量,全局变量做返回值区别
汇编层看引用的特性 栈帧,eax寄存器
引用和指针的区别 sizeof区别,定义区别,自增区别

(一 )引用的概念

什么是“引用”?申明和使用“引用”要注意哪些问题?

  • 引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。
  • 申明一个引用的时候,切记要对其进行初始化。
  • 引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能-再把该引用名作为其他变量名的别名。

下面这种引用就是不合法的

int a=9,c=10;
int &b=a;
int &b=c;
  • 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。

这里写图片描述

引用格式:

类型 & 变量名=已定义过的变量名

引用用法:
  • 普通变量引用
  • const引用
  • 不同类型之间的引用

1)普通变量引用



    int a=10;
    int &b=a;
    int &c=b;
    int &d=c;
    cout<<"&a="<<&a<<endl;
    cout<<"&b="<<&b<<endl;
    cout<<"&c="<<&c<<endl;
    cout<<"&d="<<&d<<endl;

    d=13;
    c=2;
    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;
    cout<<"c="<<c<<endl;
    cout<<"d="<<d<<endl;

运行结果:

[zyc@localhost lesion1]$ ./a.out 

&a=0x7fff1bc0b04c
&b=0x7fff1bc0b04c
&c=0x7fff1bc0b04c
&d=0x7fff1bc0b04c
a=2
b=2
c=2
d=2

结论:

  • 一个 变量可以有多个引用,但是一个引用不对应多个变量;
  • 尽管该变量被引用多次,但是,引用对象的地址都是一样的,即不开辟空间,所以改变其中一个值,其他值跟着改变。

2)const引用

    //类型1
    int d1=4;
    const int &d2=d1;
    d1=5;   //d1改变,d2也会改变
    //d2=6; //d2是常量(const修饰),不能给其赋值

   //类型2
    const int d3=10;
    //int &d4=d3; 
    const int &d4=d3;  //常量具有常属性,只用常引用才能引用常量

    //类型3
    const int &g=10;
    cout<<"g="<<m<<endl;


    cout<<"d1="<<d1<<endl;
    cout<<"d2="<<d2<<endl;
    cout<<"d3="<<d3<<endl;
    cout<<"d4="<<d4<<endl;
    cout<<"g="<<m<<endl;

运行结果:


[zyc@localhost lesion1]$ ./a.out 
d1=5
d2=5
d3=10
d4=10
g=45

总结:

如果变量没有用const修饰,而引用可以用coonst修饰,也可以不用const修饰。这里可以理解为具有可读可写的变量可以向仅可读方向转化,但反过来不行。

如果变量被const修饰,则引用也必须被const修饰,且变量值和引用值都不可以被修改。因为常量具有只读属性。常引用引用常变量。

3)不同类型变量之间的引用

    cout<<"========不同类型之间的引用==========="<<endl;
    double e=1.99998;
    const int &f=e; //e是double类型,f是int类型,f引用e时,要创建一个临时变量
                    //即f引用的是那个带常量的临时变量,所有不能赋值给引用。
    e=2.333333;
    cout<<"e="<<e<<endl;
    cout<<"&e="<<&e<<endl;
    cout<<"f="<<f<<endl;
    cout<<"&f"<<&f<<endl;

运行结果:

[zyc@localhost lesion1]$ ./a.out 
e=2.33333           //打印的是修改过的e值
&e=0x7fff1bc0b038  //e的地址
f=1                //打印的是e的初始值并且是截断的。
&f0x7fff1bc0b050   //f的地址(临时变量的地址),和e的地址不一样,

结论:当引用类型和定义变量类型不一致时,会产生一个临时变量。改变变量的值,引用值并不会改变。
这里写图片描述

(二)引用做参数

以交换两数来举例


值传递版本

#include<iostream>
using namespace std;
void Swap(int x,int y)
{
    int  tmp=x;
    x=y;
    y=tmp;

}
int main()
{
    int a=1;
    int b=3;
    cout<<"交换前"<<a<<","<<b<<endl;
    Swap(a,b);
    cout<<"交换后"<<a<<","<<b<<endl;
    return 0;
}

运行结果:

[zyc@localhost lession_quote]$ ./a.out 
交换前1,3
交换后1,3

结论:并没有实现交换的功能


引用版本

#include<iostream>
using namespace std;

void Swap(int &x,int& y)
{
    int  tmp=x;
    x=y;
    y=tmp;

}
int main()
{
    int a=1;
    int b=3;
    cout<<"交换前"<<a<<","<<b<<endl;
    Swap(a,b);
    cout<<"交换后"<<a<<","<<b<<endl;
    return 0;
}

运行结果

[zyc@localhost lession_quote]$ ./a.out 
交换前1,3
交换后3,1

结论:可以实现交换功能


指针版本

#include<iostream>
using namespace std;

void Swap(int *x,int* y)
{
    int  tmp=*x;
    *x=*y;
    *y=tmp;

}
int main()
{
    int a=1;
    int b=3;
    cout<<"交换前"<<a<<","<<b<<endl;
    Swap(&a,&b);
    cout<<"交换后"<<a<<","<<b<<endl;
    return 0;
}

运行结果

[zyc@localhost lession_quote]$ ./a.out 
交换前1,3
交换后3,1

结论:可以实现交换功能


综合对比:

值传递:

失败原因分析:使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本,我们在Swap里面的交换动作实际上是对副本的操作,所以不会对实参进行交换,就好像你建立了一个world文档,然后又将其备份了,接下来的操作全是在备份里更改,主文档依然还是主文档,并没有改变。

指针传递

成功原因:将实参地址传递给形参,那么在Swap函数中的操作机会按照地址寻址的方式找到实参,然后在进行交换,实质是对实参的更改。

引用传递

成功原因:传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

(三)引用作函数返回值

(1)以引用返回函数值,定义函数时需要在函数名前加&
(2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。

以下面程序为例来解析引用作函数返回值

#include <stdio.h>

int &Add(int x,int y)       //引用返回
//int Add(int x,int y)      //值返回
{
     int ret=x+y;
     return ret;
}
void Test()
{


    int sum=Add(1,2);

}
int main()
{

    Test();
    return 0;
}

运行结果

[zyc@localhost lession_quote]$ ./a.out 
4

分析过程:


查看栈帧

main函数栈帧(传值返回和传值返回相同)

这里写图片描述

Test函数栈帧(传值返回和传值返回相同)

这里写图片描述

Add函数栈帧(此时传值返回和传值返回不相同,注意区别)

  • 传值返回

这里写图片描述

  • 传引用返回

这里写图片描述

这里注意,由于ret是在Add函数里面定义的,当Add函数调用完毕,函数栈帧销毁,ret也会被操作系统回收,放在eax里面的地址就会变成野指针,容易造成程序bug。

比如上面程序就会爆出下面警告

add.cpp: In functionint& Add(int, int)’:
add.cpp:12: warning: reference to local variable ‘ret’ returned
(12:警告:引用本地变量' ret '返回)

程序更正:
将上面的局部变量ret变成全局变量,即可消除警告

这里写图片描述

总结:

1)不要反悔一个临时变量的引用。就比如刚才的局部变量ret。
因为局部变量会随着函数栈帧的销毁而回收。存在eax里面的地址就会变成野指针。

2)如果返回对象的生命周期不随函数栈帧的销毁而释放,则建议使用传引用返回。更高效。

(三)引用和指针的区别

1)参数使用情况

使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用”*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰.

2)其他方面

  • 引用只能在定义时,初始化一次,之后不能使其指向其他变量,但是指针可以改变其指向,我们可以理解为引用是从一而终的,而指针是 随机应变的。

  • 引用必须指向有效的 变量,而指针可以指向NULL

  • 指针和引用自增和自减意义不一样。

  • sizeof(指针对象)和sizeof(引用对象)的意义不同,sizeof(指针对象)求的是对象地址的大小,sizeof(引用对象)求的是对象占的字节大小。

测试代码

#include<iostream>
using namespace std;
int main()
{
    int a=1;
    int *p=&a;
    int &b=a;
    //测试自增区别
    cout<<"++b="<<++b<<endl;
    cout<<"p="<<p<<endl;

    cout<<"++p="<<++p<<endl;
    //测试sizeof区别
    cout<<"sizeof(p)="<<sizeof(p)<<endl;
    cout<<"sizeof(b)="<<sizeof(b)<<endl;
    return 0;
}

运行结果

[zyc@localhost lession_quote]$ ./a.out 
//测试自增
++b=2
p=0x7fffa36fa51c
++p=0x7fffd3fc9910

//测试sizeof
sizeof(p)=8
sizeof(b)=4

结论:
指针自增加1,定义对象地址一次加四个字节,引用自增加1,就是定义对象加一

sizeof(指针)如果是32位程序就是4字节,如果是64程序就是8字节。


32位程序是指程序的虚拟地址空间是4G(地址编号要到429467296,刚好四个字节可以装下这些地址),64位程序同理。

猜你喜欢

转载自blog.csdn.net/zgege/article/details/80901182