c++引用

c++比较珍惜关键字,所以没有定义引用的关键字,所以选择共用符号来表示引用:&

引用就是给变量定义一个别名,引用符号放在类型的后面,变量名的前面。这是一个新的语法。

int  a = 0;

int&  b = a;

int&  c = b;

a、b和c的地址是相同的。

引用的价值,在函数传指针,对数据进行修改的时候,可以使用引用

void swap(int& x, int& y)//c++的引用,指向的对象不可修改;jave的引用,指向的对象可以修改

{

    int tmp = x;

    x = y;

    y = tmp;

}

int main()

{

    int a = 0;

    int b = 10;

    swap(a, b);

    cout << a << " " << b << endl;

    return 0;

}

还有一种情况

typedef struct SinListNode

{

        SLTDateType val;

        struct SinListNode* next;

}SinListNode, *pSinListNode;//*pSinListNode是struct SinListNode对应的指针类型,就是SinListNode对应的指针类型

//这里的*pSinListNode是类型名称,不是变量

void SinListPushBack_C(SinListNode** pphead, int x)//这是正常的C语言单链表尾插

{

    SinListNode* newnode = (SinListNode*)malloc(sizeof(SinListNode));

    if(*pphead == NULL)

    {

        *pphead = newnode;

    }

}

void SinListPushBack_CPP(SinListNode*& phead, int x)//c++引用版本的单链表尾插

//还有一种形式:void SinListPushBack_CPP(pSinListNode& phead, int x),这是同时利用typedef和引用的版本。

{

    SinListNode* newnode = (SinListNode*)malloc(sizeof(SinListNode));

    if(phead == NULL)

    {

        phead = newnode;

    }

}

int main()

{

    SinListNode* phead = NULL;

    //SinListPushBack_C(&phead, 1);

    SinListPushBack_CPP(phead, 1);

    return 0;

}

引用的特性:

1.引用必须在定义的时候初始化,也就是说不能定义一个引用,然后在后面确定它指向的位置。

2.一个变量可以有多个引用

3.引用一旦选择一个实体,就不能引用其他实体。

int main()

{

    int a = 0;

    int& b = a;

    

    const int x = 200;

    const int& y = x;//int 类型取int类型的别名,const int类型取const int类型的别名是合理的

    

    int c = 20;

    const int& d = c;//但是,int类型取const int类型的别名也是可以通过的

    return 0;

}

这就涉及里取别名的一个原则:对引用的变量,权限只能缩小,不能放大

const是修饰常变量的,也就是说,const修饰的是不可更改的。权限从可读取可修改到只能读取,权限变小了,所以编译时不会报错。如果const int& y = x前没有const,y引用x时,前面没有const,可以对x值进行修改,权限从只读到可修改可读取,权限放大,语法报错。

下面来介绍一些神奇的代码:

int main()

{

    const int& c = 20;//常量也是可以取别名的,但是前面必须加const,因为常量不可修改

    double a = 2.22;

    int x = a;

    const int& b = a;//int& b = a;是有语法错误的

    return 0;

}

虽然会丢失数据,但为什么加上const编译就可以通过?

c++延伸了C语言的特性,存在隐式类型转换,因为浮点数和整数储存机制的不同,所以两者只能转换,不能简单粗暴的提升,截断。所以double类型赋值给int类型,从语法的层面上来讲,会产生一个临时变量,取出double的整数部分,放到临时变量中。然后再将临时变量给int类型的变量。临时变量具有常属性,不能被修改。所以int& b = a;编译不通过的原因在于,b引用的是中间的临时变量。可以调试一下,b引用的是中间的临时变量,b的地址和a的地址是不一样的。b不是对a的引用。

为什么int x = a;是正常的?

因为变量之前的赋值只是值的拷贝,不会影响原来的值;而指针和引用是可以影响对象的。x拷贝的是中间的临时变量,x不会对临时变量产生影响,而int& b = a;中的b是可以对临时变量产生影响的,所以语法报错。

具体用途在类和对象的时候会讲到(类和对象需要频繁的使用引用),这里简单的提一下,在函数传参的时候会用到。

void func(int& x)

{

}

当参数为double类型,常量,const修饰的整型变量等,就无法进行函数传参。

修改为这样就可以正常传参:

void func(const int& x)

{

}

后面在学到模板时也会用到。

引用的价值主要在作参数和作返回值方面

当引用参数时:

void swap(int& a, int& b)

{

    int tmp = a;

    a = b;

    b = tmp;

}

int main()

{

    int a = 10;

    int b = 20;

    swap(a, b);

    return 0;

}

在书写上可以节省一点时间,在功能上产生优化(比如不需要对参数开辟空间了,以及其他的方面的优化,引用传参比普通传参的效率高很多很多,和指针传参的效率差不多,在其他方面比指针更优)

当作返回值时:

要理解引用作返回值首先要理解一般情况下的返回值

int reta()

{

    static int a = 1;

    return a;

}

int main()

{

    int ret = reta();

    return 0;

}

由前面的知识可知,a是由寄存器保存的(相当于int tmp = a)。也就是寄存器充当中间变量的作用,中间变量的类型就是返回值的类型,将a先传给中间变量,再被接收。设置中间变量的原因:函数结束,函数中的变量就销毁了,静态变量有很多限制,要少用。在ret前加上&,编译会不通过,原因在于临时变量具有常属性,权限放大了。这样也能证明临时变量的存在。

int& reta()

{

    static int a = 1;

    return a;

}

int main()

{

    int ret = reta();

    return 0;

}

返回值类型为int&的意义在于,产生的中间变量类型变成了int&。相当于int& tmp = a。此时tmp没有开辟空间。tmp再给ret就相当于a直接给ret。传引用返回的意义就是,返回的是a的别名。如果ret前加上&,ret和a的地址就是一样的。这里只考虑传值返回和传引用返回的区别,不考虑变量空间销不销毁。

int& reta()

{

    int a = 1;

    return a;

}

int main()

{

    int& ret = reta();

    return 0;

}

这个代码肯定是不行的,a已经销毁了。正常情况下只有static修饰的情况下才有引用返回(感觉有点鸡肋)

但是还有这种情况:

int& reta()

{

    int a = 1;

    return a;

}

int main()

{

    int ret = reta();

    return 0;

}

这样也会报警告,a是一个局部变量。引用的本质是指针,相当于返回局部变量地址了。

如果函数函数返回时,出了函数作用域,返回对象还在,没有还给系统,就可以使用引用返回;如果已经还给系统了,就必须使用传值返回。

下面来用一个样例来说明:

int& add(int x, int y)

{

    int c = x + y;

    return c;

}

int main()

{

    int& ret = add(1, 2);

    add(3,4);

    cout << "add(1 ,2) :" << ret << endl;

    return 0;

}

这段代码的结果是打印出:add(1 ,2) :7

原因在于返回的是c的引用,也就是说ret的值是add函数中c的那块空间所对应的值。在add(3,4)函数中,c的那块空间的值被改成了7,所以打印出来的是7。如果多调用几次cout << "add(1 ,2) :" << ret << endl;打印出来的将会是随机值。原因在于:第一次调用cout函数取参数ret,为7,打印7。然后第二次取参数,ret所在空间已经被cout函数栈帧覆盖了一次,ret空间中的数据产生了变化,也就打印出随机值。

这个样例只是说明返回对象还给系统后再使用引用返回是不安全的。在int c = x + y;前加上static也只是ret的值稳定,不会被后面的函数栈帧覆盖,add(3,4)还是会让ret变成7,因为对应空间存储的值改变了。

如果返回值超过寄存器的大小,会在上一层的栈帧中找一个位置储存下来。了解一下就可以。

总结一下:

int& add(int x, int y)

int& ret = add(1, 2);

根据返回值和接受变量有无&,共4种情况:

1.

int& add(int x, int y)

int& ret = add(1, 2);

正常,ret指向的空间就是返回值所在的空间,返回值在函数结束后如果销毁,会报警告。ret对应值所在空间会因被后面函数栈帧覆盖而产生变化。

2.

int add(int x, int y)

int& ret = add(1, 2);

报错,函数返回值的中间变量具有常属性,给ret就发生权限变大的现象了

3.

int& add(int x, int y)

int ret = add(1, 2);

如果返回值在函数结束后销毁,报警告,返回的是已经销毁的变量。ret是变量已经被赋值就不会产生变化。

4.

int add(int x, int y)

int ret = add(1, 2);

正常的函数使用。

第二种情况,在int& ret前加上const就不会报错(中间变量是寄存器,那ret引用的不就是寄存器的空间吗?那ret的值是不是会变化?),const修饰的变量可能在静态区,可能在栈区,也可能在只读区,具体情况不知道。

引用和指针的不同点:

1. 引用在定义时必须初始化,指针没有要求

2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

3. 没有NULL引用,但有NULL指针

4. 在sizeof中含义不同:引用结果为引用类型的大小(const int&大小为4),但指针始终是地址空间所占字节个数(32位占4字节)

5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

6. 有多级指针,但是没有多级引用

7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

8. 引用比指针使用起来相对更安全

9.引用是一个变量的别名,指针是一个地址

引用可以说是对指针的改进。在汇编代码中引用和指针是一样的实现方式。

lea——load  effective  address加载有效地址

ra确实有进行a地址的储存的步骤,但是引用变量在使用时,编译器会自动访问地址对应的值。所以ra中存储的才是a的值,而不是a的地址。

应用要到类和对象中了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值