自学内容网 自学内容网

初识C++ (三)

如果很迷茫的话,就学习吧

引用

一. 引用的概念

“引用(Reference)是 C++ 相对于C语言的又一个扩充。引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据。

具体是什么意思呢?

我们这里来举个例子

比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。

 那你叫铁牛 他会答应你

你叫黑旋风 他也会答应你

代码演示

我们下面来写一段代码试试

int main()
{
int a = 10;
int& b = a;
return 0;
}

这段代码是什么意思呢?

我们假设这个a是李逵

那么这个b就是黑旋风的意思了!

我们打印这两个变量的地址来看看

它们的地址一样的 都是李逵!

注意:引用类型必须和引用实体是同种类型的

比如说像我们这样子 

这里就是一个错误代码

二. 引用特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体
1. 引用在定义时必须要初始化 

这里如果不初始化就会报错 不用多讲了

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

我们有代码如下

int main()
{
int a = 10;
int& b = a;
int& c = a;

cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}

这里它们的地址全部都一模一样 就可以说明它们都是一个地址的别名了

3. 引用一旦引用一个实体,再不能引用其他实体

这句话是什么意思呢?

就拿我们的c来说

他已经引用了a了 还能不能引用其他变量呢

int main()
{
int a = 10;
int& b = a;
int& c = a;
int d = 20;
c = d;

cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}

我们可以发现 它们的地址是没有变化的 那么

c=d;

这一步代码 到底改变了什么呢?

我们画图来看看

我们来验证下我们的理论正确不正确

这里就可以发现 a b c的值全部都变成20了

三. 使用场景

1 . 做参数
2 . 做返回值

1. 做参数

我们来看下面的代码

要求: 交换两个变量的值

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

}

可是上面这段代码真的能够交换两个变量的值嘛?

看过我的这两篇博客的同学应该知道 答案是 不能

函数栈帧(上)-CSDN博客

函数栈帧(下)-CSDN博客

为什么呢?

因为x y只是我们要交换的函数的临时拷贝

交换它们的值并不会对要交换的值有什么影响

那么结合我们今天学到的知识

同学们有没有想到一种巧妙的解法呢?

没错! 就是引用传参

我们写出下面这样子的代码

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

}

将它们的别名传进去 就可以啦

思考题:
我们都知道 在单链表头插尾插的时候 为了防止头指针为空的情况 我们就需要传递一个二级指针进去
这样子很麻烦
那么使用我们的引用机制如何修改它呢?

答案就是! 将指针的别名(引用)作为参数传递进去 那么修改引用参数是不是就可以了?

如果不能理解的话这样子

李逵吃饱了是不是就等于黑旋风吃饱了?

我们之后再来看以下代码

#include <time.h>
struct A { int a[100000]; };

void TestFunc1(A a) {}

void TestFunc2(A& a) {}

void TestRefAndValue()
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();

// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();

// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
TestRefAndValue();

return 0;
}

 

我们可以发现使用两个函数运行的时间竟然相差很多!

这是为什么呢?

这里就设计到一个传值和传引用的区别(大家类比下传值和传址)
传值需要将整个a拷贝一份
而我们使用引用的话不会拷贝 时间就差在这里 

2. 做返回值 

还是一样 我们先来看这么两段代码

int Count()
{
int n = 0;
n++;
//...
return n;
}

int& Count()
{
static int n = 0;
n++;
//...
return n;
}

这两段代码的返回值都是1

但是它们返回的方式可不同

我们先来看第一个代码

int Count()
{
int n = 0;
n++;
// ...
return n;
}

当我们调用count()这个函数的时候 它会开辟一个新的内存空间在这里临时空间里面设置一个n变量

将n的值加加

到了这一步的时候

return n;

将n的值放到临时变量(有可能是寄存器)里面返回

再来看看你第二个代码

int& Count()
{
static int n = 0;
n++;
//...
return n;
}

前面的过程几乎一样

注意! 这时候它的返回参数是 int&

也就是说 它直接是把n的别名(引用)返回过来了!

不知道大家能不能看出来区别

一个是返回到临时变量中 一个值直接返回n的别名(引用)的值

那么这里就引出一个很危险的操作!

int& Count()
{
   int n = 0;
   n++;
   // ...
   return n; }

我们将static去掉 这个时候n彻底变成局部变量了!

这个时候我们打印一下试试看

咦?

竟然还是1 难道说局部全局变量没有影响嘛?

当然不是!!!

我们再来写出以下代码

int& Count()
{
int n = 0;
n++;
//...
return n;
}
void test()
{
cout << "hello world" << endl;
}
int main()
{
int& ret = Count();
cout << ret << endl;
test();
cout << ret << endl;
return 0;
}

我们发现! 竟然ret变成随机值了!

这是为什么呢?

因为我们实际上得到的ret是n的别名 但是呢在函数结束调用之后所有的参数就被销毁了(这其中也包括n) 当我们运行另外的一个函数来调用栈空间的时候 有可能就将n地址的内容改变了 所以说造成了这个现象

那么这里就引用出两个问题

内存销毁后空间还在吗?

空间还在 但是已经不属于我们了

内存销毁后我们还能访问嘛? 

可以访问 但是里面的数据的读写我们都不能确定

结论 

1 出了函数作用域,返回变量不存在了,不能用引用返回,因为引用返回的结果是未定义的。
2 出了函数作用域,返回变量存在,才能使用引用返回。

优点 

 可以修改返回值

 比如说

 如果说我们使用 int 来接受ret的话那么只能够每次都给ret赋值了

四. 传值传引用的效率比较

参考做参数 使用场景1中的举例

五. 常引用

这里牢记一个概念就好
我们引用一个变量的时候所具有的权限只能小于等于该变量

例如

int main()
{
//a具有读写功能
int a = 10;
int& b = a;
//可以
const int& b = a;
//权限缩小 可以

const int c = 20;
int& d = c;
//不可以 权限放大了
const int& e = c;
//可以 权限相等 平移
    return 0;

}
右值为常数问题

常数是不可以被改变的 所以说没有写权限

其实不是不能引用而是权限不匹配

如果一定要可以用以下方式写代码

 const int& a = 10;
// 读写权限匹配
cout << a << endl;

 以上就是关于c++引用博主一些浅薄的理解啦
如果出现错误希望大佬们指正!


原文地址:https://blog.csdn.net/Zbldx/article/details/143785991

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!