初识C++ 三
- 引用
- 一. 引用的概念
- 代码演示
- 二. 引用特性
- 1. 引用在定义时必须要初始化
- 2. 一个引用可以有多个实体
- 3. 引用一旦引用一个实体,再不能引用其他实体
- 三. 使用场景
- 1. 做参数
- 2. 做返回值
- 内存销毁后空间还在吗?
- 内存销毁后我们还能访问嘛?
- 结论
- 优点
- 四. 传值传引用的效率比较
- 五. 引用与重载函数
- 六. 常引用
- 右值为常数问题
- 总结
如果有什么想要去做的事情就立马去做
全部做完了收收心 继续投入学习!
引用
一. 引用的概念
“引用(Reference)是 C++ 相对于C语言的又一个扩充。引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据。
具体是什么意思呢?
我们这里来举个例子
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
那你叫铁牛 他会答应你
你叫黑旋风 他也会答应你
代码演示
我们下面来写一段代码试试
int main()
{
int a = 10;
int& b = a;
return 0;
}
这段代码是什么意思呢?
我们假设这个a是李逵
那么这个b就是黑旋风的意思了!
我们打印这两个变量的地址来看看
它们的地址一样的 都是李逵!
注意:引用类型必须和引用实体是同种类型的
比如说像我们这样子
这里就是一个错误代码
二. 引用特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
1. 引用在定义时必须要初始化
这里如果不初始化就会报错 不用多讲了
2. 一个引用可以有多个实体
我们有代码如下
int main()
{
int a = 10;
int& b = a;
int& c = a;
//char& c = a; 不能改变类型
printf("%p\n", &a);
printf("%p\n", &b);
printf("%p\n", &c);
return 0;
}
这里它们的地址全部都一模一样 就可以说明它们都是一个地址的别名了
3. 引用一旦引用一个实体,再不能引用其他实体
这句话是什么意思呢?
就拿我们的c来说
他已经引用了a了 还能不能引用其他变量呢
我们可以发现 它们的地址是没有变化的 那么
c=d;
这一步代码 到底改变了什么呢?
我们画图来看看
我们来验证下我们的理论正确不正确
这里就可以发现 a b c的值全部都变成20了
三. 使用场景
1 . 做参数
2 . 做返回值
1. 做参数
我们来看下面的代码
要求: 交换两个变量的值
void swap(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
可是上面这段代码真的能够交换两个变量的值嘛?
看过我的这两篇博客的同学应该知道 答案是 不能
函数栈帧(上)
函数栈帧(下)
为什么呢?
因为x y只是我们要交换的函数的临时拷贝
交换它们的值并不会对要交换的值有什么影响
那么结合我们今天学到的知识
同学们有没有想到一种巧妙的解法呢?
没错! 就是引用传参
我们写出下面这样子的代码
void swap(int& x, int& y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
将它们的别名传进去 就可以啦
思考题:
我们都知道 在单链表头插尾插的时候 为了防止头指针为空的情况 我们就需要传递一个二级指针进去
这样子很麻烦
那么使用我们的引用机制如何修改它呢?
答案就是! 将指针的别名(引用)作为参数传递进去 那么修改引用参数是不是就可以了?
如果不能理解的话这样子
李逵吃饱了是不是就等于黑旋风吃饱了?
我们之后再来看以下代码
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
A a;
int begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1(a);
int end1 = clock();
int begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2(a);
int 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()
{
static int n = 0;
n++;
// ...
return n; }
int Count()
{
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 难道说局部全局变量没有影响嘛?
当然不是!!!
我们再来写出以下代码
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中的举例
五. 引用与重载函数
我们来看以下代码
这两个函数显然是构成重载函数的
但是我们却不建议这么写
因为很有可能造成歧义
比如说你打出下面的代码
test(10);
那么你究竟是想调用谁呢?
六. 常引用
这里牢记一个概念就好
我们引用一个变量的时候所具有的权限只能小于等于该变量
例如
int main()
{
// a具有读写能力
int a = 10;
int& b = a;
// 可以
const int& b = a;
// 权限缩小 可以
const int c = 20;
int& d = 20;
// 权限放大 不可以
const int& d = 20;
// 权限相同 可以
return 0;
}
右值为常数问题
常数是不可以被改变的 所以说没有写权限
其实不是不能引用而是权限不匹配
如果一定要可以用以下方式写代码
const int& a = 10;
// 读写权限匹配
cout << a << endl;
总结
以上就是关于c++引用博主一些浅薄的理解啦
如果出现错误希望大佬们指正!
阿尼亚 哇酷哇酷!