C++——引用和指针

一. 引用的概念及用法

1. 概念

        引用是给一个已存在的变量起一个别名,而不是定义一个新变量

2. 引用的使用格式

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

3. 引用的特点

(1)一个变量可起多个别名

(2)引用必须初始化

(3)引用变量只能在初始化的时候引用一次,之后不能改变为再去引用别的变量

4. 引用的代码实现

#include <iostream>
using namespace std;

int main()
{
    int a = 1;
    int& b = a;
    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;
    cout<<"a address"<<&a<<endl;
    cout<<"b address"<<&b<<endl;
        
    //可以对一个引用变量再引用,是别名的别名
    int&c = b;
    cout<<"c="<<c<<endl;
    //修改引用变量,对应的原变量的值随之改变
    c = 4;
    cout<<"a="<<a<<" b="<<b<<" c="<<c<<endl;
    cout<<"address "<<"a:"<<&a<<" b:"<<&b<<" c:"<<&c<<endl;
    return 0;
}

具体运行结果为


        可以看出引用只是在起别名。a,b的值与地址都一样。改变引用变量c的值,被引用变量的值也会随之改变。

5. const引用

(1)非const的变量可以被const变量引用

(2)const变量不可以被非const的变量引用

        我们可以这样去理解,const变量我们可以认为它是只读的,非const变量我们可以认为它是可读可写的。一个只读的const的变量,我给别人之后,被它怎么能去写呢;同样的,一个可读可写的变量,我给别人之后,它只读是可以的。总结一句话就是“定义别名时,权限可以缩小,但是不能放大”。

#include <iostream>                                                                                                                                   
using namespace std;

int main()
{
    //int a = 5;
    //const int& b = a;
    //cout<<"a="<<a<<" b="<<b<<endl;
    //a = 6;
    //cout<<"a="<<a<<" b="<<b<<endl; 
    ////常量不能被修改
    ////b = 7;//这句是错误的

    ////定义别名时,权限不能放大,只能缩小
    //const int a = 5;
    ////int& b = a;//这句错误,只读变量不能转化为可写可读的
    //const int&b = a;//只有常属性可以引用常量
    //cout<<"a="<<a<<" b="<<b<<endl;
        
    double a = 1.1;
    //a赋值给b时要生成一个临时变量,b其实引用的是那个带有常性的临时变量,而不是a,所以不能赋值
    //int& b = a;//这句是错误的
    const int& b = a;
    cout<<"a="<<a<<" b="<<b<<endl;
    
    return 0;
}

        具体运行结果为:


二. 引用作为参数

1. 引用作为参数的代码实现

        在C语言的学习中,我们可以知道两种常用的函数传参方式,分别是:传值、传地址。在这里,我们学习的引用也可以作为参数传递。

        就拿常见的交换两个变量的值的swap函数来举例,我们知道传递并不能交换两个变量的值,但是传地址可以。对于传值来讲,因为传值给函数,形参是实参的一份临时拷贝,在swap函数中,两个变量的值确实交换了,但是它们交换的仅是形参。它是临时变量,出了swap函数,它们就被销毁了,实际上要交换的两个变量的值并未改变。而对于传地址来讲,在swap函数中接收到的参数就是两个要交换变量的地址,swap函数可以通过地址访问到两个变量,并且出了swap函数,这两个变量依旧在,通过访问地址可以实现交换它们的值。这里的引用传参也可以实现两个变量值的交换。原因就是引用是给变量起别名,对引用变量作修改,被引用的变量也会随之修改,这边在上面我们已经验证过了。具体代码实现如下:

#include <iostream>
using namespace std;

//(1)值传递->交换不了两个数的值
void Swap1(int a, int b)
{
    int ret = a;
    a = b;
    b = ret;
}

//(2)引用传递->可以交换两个数的值
//->可以提高效率
void Swap2(int& a, int& b)
{
    int ret = a;
    a = b;
    b = ret;
}

//(3)指针传递->可以交换两个数的值
void Swap3(int* a, int* b)
{
    int ret = *a;
    *a = *b;
    *b = ret;
}

int main()
{
    int a = 10;                                                                                                                                       
    int b = 20;
    cout<<"a="<<a<<" b="<<b<<endl;

    Swap1(a, b);
    cout<<"a="<<a<<" b="<<b<<endl;
    
    int& c = a;
    Swap2(a, b);
    cout<<"a="<<a<<" b="<<b<<endl;
    
    Swap3(&a, &b);
    cout<<"a="<<a<<" b="<<b<<endl;
    
    return 0;                                                                                                                                         
}

运行结果如下:


2. 测试引用传递和值传递的效率

        在数据较大时,引用传递的效率高于值传递。

编写测试代码如下:

#include <iostream>
#include <time.h>
using namespace std;
                                                                                                                                                      
struct Bigdata
{
    int arr[1000];
};

void DealBigdata(Bigdata& x)
{
    x.arr[0] = 0;
    x.arr[1] = 1;
    x.arr[2] = 2;
}

void DealBigdata1(Bigdata x)
{
    x.arr[0] = 0;
    x.arr[1] = 1;
    x.arr[2] = 2;
}

int main()
{
    Bigdata bd;
    struct timespec ts;
    //在windows下也可以用GetTickCount()函数
    clock_t begin = clock();//该函数用于获取从系统启动到执行到该句的毫秒数
    int  i = 0;
    for(; i<1000000; i++)
    {
        DealBigdata(bd);
    }
    clock_t end = clock();
    cout<<"引用传递:"<<end-begin<<endl;
    clock_t begin1 = clock();
    for(i=0; i<1000000; i++)
    {
        DealBigdata1(bd);
    }
    clock_t end1 = clock();
    cout<<"值传递:"<<end1-begin1<<endl;
   
    return 0;
}

        运行结果如下:


        从上图,我们可以明显的看到,引用传递的效率要高于值传递。

注意:我们有一个建议是说,若并不想函数内改变传入的引用变量的值,尽量使用const传参,如下所示:

void readata(const int& a)
{
    int ret = a;                                                                                                                                      
}      

三. 引用作返回值

1. 引用作返回值、值作返回值

(1)用“引用”返回适用于:出作用域,return的值依旧在

(2)用”值”返回适用于:出作用域,return的值不在了

编写测试代码如下:

#include <iostream>
using namespace std;

//(1)传值作返回值
//->尽量用于出作用域,返回的变量不在了的情况
int Add(int a, int b)
{
    int ret = a + b;
    //带回返回值的并不是ret,因为出该函数ret已经销毁,开辟了临时变量,复制了ret返回
    return ret;
}

//(2)传引用作返回值
//->尽量用于出作用域,返回的变量还在的情况
int ret1;
int& Add1(int a, int b)
{
    ret1 = a + b;
    //带回返回值的并不是ret,因为出该函数ret已经销毁,开辟了临时变量,复制了ret返回
    return ret1;
}

int main()
{
    int c = Add(3, 4);
    cout<<c<<endl;
    int d = Add1(3, 4);                                                                                                                               
    cout<<d<<endl;
    return 0;
}

运行结果如下:


        对于用值返回来讲,因为出了作用域后,返回的值就被销毁,其实是有临时变量复制保存了要返回的值,带回去返回的。若是比较小的变量返回,一般是寄存器复制充当临时变量的角色;对于较大的变量,一般是要临时变量保存压栈带回的。这里只是简单介绍,想要深入了解的可以自行再去查阅一下。

2. 若引用变量已经引用一个固定的变量,只要变量的值修改,引用变量的值必须修改

        具体情况我们通过代码分析,测试代码如下:

#include <iostream>                                                                                                                                   
using namespace std;

int ret;

int& Add(int a, int b)
{
    ret = a + b;
    return ret;
}

int main()
{
    int& c = Add(20, 30);
    cout<<c<<endl;
    Add(200, 300);
    cout<<c<<endl;
    return 0;
}

        运行结果如下:


        我们可以看到,在第一次调用add函数之后,用了引用变量c接收返回值ret,第一次调用结果为50。但是在第二次调用add函数之后,我们并没有用引用变量c去接收返回值,但是c的值依旧改变为了500。造成这样的原因就是,在c接收返回值ret的时候,c已经是ret的别名了,只要ret改变c就随之改变。虽然第二次调用并没拿c接收返回值,但是只要调用了add函数,最后都返回了ret,ret也改变了,c当然一起改变。

四. 汇编层看引用的特性

1. 通过汇编看传值返回和传引用返回


        我们可以看出,引用是起了一个别名,但是它是通过指针实现的。

结论:

(1)不要返回一个临时变量的引用;

(2)若返回对象出了当前作用域还在,最好用引用返回,因为这样更高效。

五. 引用和指针的区别

(1)引用只能在定义时初始化一次,之后不能改变为指向其它的变量(从一而终);指针变量的值可变,可指向其他变量;

(2)引用必须指向有效的变量;指针可以为空;

(3)sizeof引用得到的所指向变量的大小;sizeof指针得到的是对象地址的大小;

(4)引用++表示所指变量的值+1;指针++表示+所指变量类型的大小;

(5)相对而言,引用比指针更安全。

总结:

        指针比引用更加地灵活,但是也更危险。在使用指针的时候,一定要检验指针是否为空。指针所指地址释放之后最好置为0,否则存在野指针的问题。


(1)在32位的操作系统下,只可能有32位的程序,所以指针的大小只可能是4字节;

(2)在64位的操作系统下,可能有32/64位的程序,对应指针的大小为4/8字节。






猜你喜欢

转载自blog.csdn.net/lycorisradiata__/article/details/80944013