C++引用目录:
一、引用的概念
二、引用的特性
2.1引用在定义的时候必须初始化
2.2一个变量可以有多个引用
2.3引用一旦引用了一个实体,就不可以再引用其他实体
三、常引用(带const的引用)
3.1临时变量有常性不能修改(传值返回,隐式/强制类型转换时产生)
3.2指针/引用在赋值中,权限可以缩小,但是不能放大
3.3常引用做参数
3.4缺省参数如何引用?
四、引用的使用场景
4.1做参数(减少拷贝提高效率,实形一体)
4.2做返回值(减少拷贝提高效率,修改返回值)
4.2.1内存空间销毁意味着什么?& 访问销毁的内存空间会怎样?
4.2.2引用做返回值提高效率
4.2.3引用做返回值可被修改
五、引用和指针的区别
小知识:vscode查看汇编代码:
在调试控制器的位置输入:
-exec disassemble /m
一、引用的概念
基本形式为:类型& 引用变量名(对象名) = 引用实体
引用就是给一个已经存在的变量取别名:引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
引用的符号和我们C语言中取地址的符号一样,为 &;在某类型名的后面加上引用符号 (&) 就变为了引用类型。设计引用的目的是简化指针的使用,但是引用不能代替指针 (实际上引用的底层是用指针实现的)
难点正是符号与地址符相同,比如见到*&就蒙圈,而且一定得记住在类型后面的都不是取地址,都是起别名
void TestRef()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
a和ra全部指向这个空间
注意:引用类型必须和引用实体是同种类型的(这个原因在常引用的权限放大与缩小会讲解原因)
二、引用的特性
2.1引用在定义的时候必须初始化
注意:const int&b=10;这个也是可以通过的,这个也算是初始化(至于为什么加const请看下面常引用的知识)
2.2一个变量可以有多个引用
int a = 10;
int& b = a;
int& c = a;
int& d = a;
此时,b、c、d都是变量a的引用
2.3引用一旦引用了一个实体,就不可以再引用其他实体
这句话的意思也间接表明,C++引用代替不了指针,因为指针是可以更改链接地址的
(在Java和python中,引用是可以更改的)
int a = 10;
int& b = a;
此时,b已经是a的引用了,b不能再引用其他实体。如果你写下以下代码,想让b转而引用另一个变量c:
int a = 10;
int& b = a;
int c = 20;
b = c;//你的想法:让b转而引用c
但该代码并没有随你的意,该代码的意思是:将b引用的实体赋值为c,也就是将变量a的内容改成了20
三、常引用(带const的引用)
常引用就是在引用类型前面用 const 来进行修饰;和 const 修饰变量一样,被 cosnt 修饰的引用只能够读,而不能够写,即不能修改引用变量的值
3.1临时变量有常性不能修改(传值返回,隐式/强制类型转换时产生)
C++ 中的临时变量指的是那些由编译器根据需要在栈上产生的,没有名字的变量。主要的用途主要有两类,临时变量都是具有常性的
1) 函数的返回值
这个图片很重要,一定得看:
我们再举一个不用引用接收的例子:
在C++中,如果一个函数返回一个临时变量(通过传值返回),那么这个临时变量是一个右值,它不能被修改。`Add(a,b)` 返回一个临时变量,因此不能直接对其进行赋值操作。如果想要修改结果,可以将其存储在一个变量中,然后对变量进行操作。
int Add(int a, int b) {
return a + b;
}
int main() {
int a = 0;
int b = 3;
int result = Add(a, b);
result += 1;
return 0;
}
我还是有个小问题,为什么const类型可以传给int类型的呢??(单纯赋值权限不说放大放小)
只有引用和指针需要考虑,其他的没问题的,所以在这里面就是最单纯的:
int给const错(因为const只可以在定义的时候初始化)
const给int对(可以将一个const类型的值赋给一个非const类型的变量,不能将一个非const类型的值赋给一个const类型的变量,因为这会破坏了const的只读性质)
但在下面的引用赋值的时候,小心混淆!
int& a=10 这是const给int 是权限放大 这是不对的(原来const只读,结果int鬼子入侵,当然不行)
int b=2;const int& c=b; 这是int给const 是权限放小 合理(原来我大大咧咧,然后被妻管严)
2) 类型转换时的中间变量
int main()
{
int d=10;
int i=(int)d;//强制类型转换,并不是改变了变量d,而是产生临时变量
int i=d;//隐式类型转换,也是产生了临时变量
double d=12.34;
const int& ri=d;//这里引用的实体其实就是从double d 到int类型转换中间的临时变量
return 0;
}
把这张图吃透,const关于临时变量的问题就可以告一段落了。题外话结束,进入正题
当然,这段代码是不可以运行的,因为有大量的重定义
3.2指针/引用在赋值中,权限可以缩小,但是不能放大
int main()
{
const int a = 10;
//int& ra = a; //该语句编译时会出错,a为常量,权限放大
const int& ra = a;//正确,权限平移
//int& b = 10; //该语句编译时会出错,10为常量,权限的放大
const int& b = 10;//正确,权限平移
int c=10;
const int& rc=c; //正确,权限缩小
return 0;
}
权限只能被缩小和平移,不能被放大
注: 这里的权限指的是读和写的权限,且只针对于指针和引用
3.3常引用做参数
a.一般引用做参数都是用常引用,也就是const+引用,如果不用const会有可能产生权限放大的问题,而常引用既可以接收只读的权限,又可以接收可读可写的权限。
b.常引用做参数并不是为了修改参数,而是为了减少拷贝提高效率。
当然,并不是所有的参数加const就好,比如我们的swap函数,如果加上const反而会适得其反
3.4缺省参数如何引用?
缺省参数如果想做为引用的话,必须用常引用,因为缺省参数是一个常量,是不允许被修改的,只可以读
void func(const int& N = 10)
{
}
四、引用的使用场景
4.1做参数(减少拷贝提高效率,实形一体)
在调用函数时,形参是要做拷贝的,在它所在的函数栈帧里面,所以如果你要是传值调用,那必然在调用函数时,会做一份实参的临时拷贝,如果你是传址调用,指针变量也要开辟自己的空间,所以这些都是对程序性能的消耗
引用可以直接改变实参,作为输入性参数及输出型参数可以不再传递指针;比如下面的 Swap 函数,我们在 Swap 函数内部交换的其实就是两个实参的值,不用再像以前一样需要传递实参的指针
但如果我们用引用做参数就不会有这些问题了,因为操作系统并不会给引用变量单独开辟一块空间,并且引用变量也不需要对实参进行拷贝,那就可以减少拷贝提高效率。
并且由于引用实质上就是实参本身,那么它也可以作为输出型参数,对函数外面的实参进行修改
//单链表
typedef int SLTDataType; //数据类型重命名
typedef struct SListNode //链表的一个节点
{
SLTDataType data;
struct SListNode* next; //存放下一个节点的地址
}SLTNode;
//在头部插入数据
void SListPushFront(SLTNode*& rphead, SLTDataType x) //引用做形参,直接操作plist
{
SLTNode* newNode = BuySLTNode(x); //开辟新节点
newNode->next = *rphead;
*rphead = newNode;
}
在单链表的头插等操作,由于我们之前实现的单链表是不带头的,所以我们需要传递 plist 的指针,方便插入第一个数据时改变 plist,而现在,我们可以直接使用 plsit 的引用即可,不用再传递二级指针了,从而使代码变得更易理解。当然,我们不能把 SListNode 中的 next 指针也设计为引用,因为尾结点的 next 是在不断改变的,而引用一旦引用一个实体,就不能再引用其他实体,这也从侧面说明了引用不能代替指针,只能简化指针
4.2做返回值(减少拷贝提高效率,修改返回值)
我们先来探讨局部变量空间销毁的问题
4.2.1内存空间销毁意味着什么?& 访问销毁的内存空间会怎样?
内存空间销毁并不是把这块内存空间撕烂了,永久性的毁灭这块儿空间,内存空间是不会消失的,他会原封不动的在那里,只不过当内存空间销毁之后,他的使用权不属于我们了,我们存到里面的数据不被保护了,有可能发生改变了
销毁之后,我们依然可以访问到这块空间,只是访问的行为是不确定的,我们对空间的数据进行读写的行为是无法预料的
把空间当作酒店,把数据当作苹果:
a.苹果没丢 b.房间没丢,但是成了随机水果 c.苹果被覆盖成固定水果
示例一:
int& Func()
{
static int n = 0;
n++;
printf("&n:%p\n", &n);
return n;
}
int main()
{
int ret = Func();
cout << ret << endl;
printf("&ret:%p\n", &ret);
int& rret = Func();
printf("&rret:%p\n", &rret);
return 0;
}
n的地址和rret相同,说明引用返回的是变量n本身,而不仅仅是n的值
在 Func 函数中,n 由于是静态变量,而静态变量在静态区中开辟空间,不在栈区上开辟空间,所以当 Func 函数的栈帧销毁后 n 并不会被销毁;
同时,我们用引用做返回值,相当于直接将 n 这个变量返回给了函数调用者,所以,当我们再用一个引用来接收的话,就等价于给 n 再起了一个别名;
示例二:(注:下面对引用的使用方式有部分是错误示范,一定得跟着博主一起慢慢吃透)
分析:对于左侧代码,离开Count函数栈帧,变量n会被销毁,返回的别名指向的是一块被销毁后的变量空间名,这时如果使用返回后的别名,出现的结果是无法预料的(上方举的apple例子),由于n变量被销毁,所以一定不可以用引用返回(但是不会有语法编译错误,一定得小心)
对于右侧代码,变量n不会被销毁,存放在的是静态区,所以可以使用引用返回
分析:函数都已经被销毁,返回值为什么还可以被带出来呢?是因为在执行return语句前,系统会自动创建一个临时变量temp,这个变量的存储地址是根据return内容的大小分配的,可能是寄存器,也可能是堆,我们无法预知,接着操作系统会把即将要被释放的变量的值赋值给这个临时变量,通过临时变量将返回值带出来
#include<iostream>
using namespace std;
int& Count()
{
int n = 0;//n存放在栈区
n++;
//...
return n;
}
void Func()
{
int x=100;
}
int main()
{
int &ret = Count();
cout << ret << endl;
cout << ret << endl;
Func();
cout << ret << endl;
return 0;
}
分析:
1、Func 函数中的 n 变量是局部变量,函数调用完成后被销毁,但是我们这里是引用做返回值,所以返回的是 n 那块栈区空间;
2、ret 是Func 函数返回值的引用,而函数返回值是局部变量 n 的引用,所以我们第一次打印的是 n 的值1;
3、我们在打印 ret 的时候调用了 printf 函数,该函数使用了Count被销毁的空间,所以 n 空间中原本的数据被覆盖,而 ret 是 n 的引用,所以打印 ret 就是打印 printf 函数中原本属于 n 的那块内存空间的数据,所以打印出来的是一个随机值。
4、我们调用了Func函数,然后发现Func中的 x 变量的地址和原本 n 的地址相同,说明 Cover 函数又使用了 printf 函数的空间,且 x 的空间恰好位于之前 n 的位置,所以打印 ret 的结果为100;
空间销毁的问题解决完毕,继续给出一段引用做返回值的错误代码,继续理解分析:
4.2.2引用做返回值提高效率
根据上面传值返回得知会创建临时变量,所以传引用返回提高效率是显而易见的
4.2.3引用做返回值可被修改
下面这段代码给大家演示了C++中利用引用作为返回值来修改返回值的场景。
将数组中的偶数全部扩大二倍
int& change(int* arr,int i)
{
return arr[i];
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0; i < 10; i++)
{
if (arr[i] % 2 == 0)
{
change(arr, i) *= 2;
}
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] << " ";
}
}
总结:
出了函数作用域返回变量不存在了,不能用引用返回因为引用返回的结果是未定义的
出了函数作用域返向变量存在,才能用引用返回
五、引用和指针的区别
语法概念上:引用就是一个别名,没有独立空间,和其引用实体共用同一块空间
底层实现上:引用实际是有空间的,因为引用是按照指针方式来实现的
我们调试代码,然后转到反汇编后可以发现,引用和指针的汇编代码是完全一样的,即引用的底层实际上就是指针
引用和指针的不同点(9大点)
- 引用概念上定义一个变量的别名,指针存储一个变量地址;
- 引用在定义时必须初始化,指针没有要求;
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体;
- 没有NULL引用,但有NULL指针;
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节);
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小;
- 有多级指针,但是没有多级引用;
- 访问实体方式不同,指针需要显式解引用,引用编译器自己底层处理;
- 引用比指针使用起来相对更安全。
文章来源:https://www.toymoban.com/news/detail-708853.html
希望这篇费劲心思的c++引用全文可以对你们有所帮助!!! 加油!!文章来源地址https://www.toymoban.com/news/detail-708853.html
到了这里,关于【C++】C++引用 (引用不会?详细解决引用诸多细节!)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!