C++语法—引用

引用变量

概念

简单理解就是对一个已存在的变量起别名,与那个已存在的变量共用一块内存空间。
用法:已存在变量的类型 & 引用变量名 = (引用实体)已存在变量

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

在上面这个示例代码中,b是a的引用变量,我们可以通过a去输出1,也可以通过b来输出1,相当于给张三这个人起了个绰号叫张山,无论是叫张山还是张三代指都是一个人。

语法规定

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

引用与指针的区别

虽然在语法概念上引用就是一个,没有独立空间,和其引用实体共用同一块空间,但是底层实现是有空间,因为引用是按照指针方式来实现的

int main()
{   
    int a = 1;
    int& ra = a;
    int b = 2;
    ra = b;
    //这里只是把b的值赋值给ra,不是改变引用对象
    //&ra = &b;//引用一旦给定初值无法改变方向
    cout << b << endl;
    cout << ra << endl;

    //而指针可以改变指向
    int* pa = &a;
    pa = &b;
    (*pa)++;
    cout << b << endl;
    return 0;
}

引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

引用的使用场景

传参

在这里插入图片描述swap1是传值传参,在swap1中,a和b 不过是main函数的拷贝,所以改变swap1的a和b,不能改变main函数的a和b。而swap2是传址传参,在swap2中,a和b是main函数中a和b的别名,所以能够改变他,而且传引用传参不会发生拷贝构造,也会提高效率

常引用参数、临时变量

其实由上面的例子来看,swap1的传值引用会产生临时匿名对象,然后再拷贝到函数参数中,会调用该类的拷贝构造。
但是在某些传引用传参也会产生临时匿名对象(在被const修饰的前提之下,为什么?因为临时匿名对象具有常性,所以只有加const才能引用他)

  1. 实参的类型正确,但实参是右值
  2. 发生隐式类型转换

比如:

double refcube(const double &ra)
{
	return ra*ra*ra;
}
double side = 3.0;
long edge = 5L;
double c1 = refcube(side); // ra is side 
double c2 = refcube(edge);// edge是long类型,发生隐式类型转换
double c3 = refcube(7.0);// 7.0是字面量是右值
double c7 = refcube(side + 10.0); // side + 10.0是将亡值 也是右值的一种

总结:哪几种场景会产生临时变量??

  1. 隐形类型转换 比如:intb=0 float a=b b在赋值到a时候编译器会产生临时变量再去赋值给a
  2. 函数传值传参
  3. 函数返回值

函数返回

int& Add1(int a, int b)
{
    int c = a + b;
    return c;
}

int Add2(int a, int b)
{
    int c = a + b;
    return c;
}

int main()
{
    int& ret1 = Add1(1, 2);//ret1是Add1中c的引用
    int ret2 = Add1(1, 2);//隐式类型转换

    int ret3 = Add2(1, 2);//返回的时候把Add2的临时里面对象给拷贝到ret3
    const int& ret4 = Add2(1, 2);//把他的临时匿名对象给引用了
    cout << "ret1 is :" << ret1 << endl;
    cout << "ret2 is :" << ret2 << endl;
    cout << "ret3 is :" << ret3 << endl;
    cout << "ret4 is :" << ret4 << endl;
    return 0;
}

在这里插入图片描述
为什么得出以上结果?
ret1是Add1中c的引用,c是add函数中的变量,生命周期会在离开add函数的时候销毁,所以引用的虽然是c,但是c已经在函数返回的时候还给内存了,所以是随机值。
注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
ret2是3 本质上其实是 int c = 3 返回变量 int temporary& = c; ret2 = temporary。其实也是拷贝构造
ret3是典型的传值返回,返回的时候把Add2的临时里面对象给拷贝到ret3
ret4是引用类型,它把Add2的返回值引用了,因为临时匿名对象具有常性,所以要用const修饰

右值引用

字面量、常量、变量

字面量:顾名思义就是我们人在读这个变量的,可以里面懂的他代指的含义。const int a = 16 16就是字面量,而被const修饰的只读的变量就是常量。如果a没有被const修饰那就是变量。

左值、右值、将亡值

左值:是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。

int main()
{
	// 以下的p、b、c、*p都是左值
	int* p = new int(0);
	int b = 1;
	const int c = 2;
	// 以下几个是对上面左值的左值引用
	int*& rp = p;
	int& rb = b;
	const int& rc = c;
	int& pvalue = *p;
	return 0;
}

右值:也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。

int main()
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值
	10;
	x + y;
	fmin(x, y);
	// 以下几个都是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
	10 = 1;
	x + y = 1;
	fmin(x, y) = 1;
	return 0;
}

将亡值:即将还给内存的值,一些临时匿名对象。

概念

之前学习的引用都是左值引用,而右值引用就是对右值起别名
右值引用语法: 引用类型 && 引用名 = 右值

右值引用的作用

其实左值引用也可以对右值取别名,比如:a+b表达式是右值,我们可以这样写const int& ret2 = (a + b); 因为临时匿名对象具有常性,我们加上const就可以对他引用了,也可以使用右值引用 int&& ret2 = (a + b); 如果你要使用右值引用引用左值的话可以使用move函数,他会返回一个右值给引用变量int a = 1; int&& b = move(a);
其实对于已经存在左值引用的情况下,为什么要弄一个右值引用呢?

void func(const int& a)
{
	cout << "void func(int& a)" << endl;
}

//void func(int&& a)
//{
//	cout << "void func(int&& a)" << endl;
//}

int main()
{
	int a = 0;
	int b = 1;
	func(a);
	func(a + b);
}
void func(int& a)
{
	cout << "void func(int& a)" << endl;
}

void func(int&& a)
{
	cout << "void func(int&& a)" << endl;
}

int main()
{
	int a = 0;
	int b = 1;
	func(a);
	func(a + b);
}

右值引用的第一个意义是在之前只有左值引用的时候,如果我们对右值引用要在前面+const 去修饰参数,可读性不强,难区分左右值传参,而现在多了右值引用我们可以利用传参不同构成函数重载,来增加明确的可读性。
再看一段代码:

int main()
{
	Jaxsen::string s1("hello");
	Jaxsen::string ret1 = s1;
	Jaxsen::string ret2 = (s1+'!');
	return 0;
}

ret1 = s1上,本质是s1调用了拷贝构造把ret1深拷贝到ret1中,然后我们再去看ret2 = (s1 + '!' ),首先s1 +'!'是一个右值,对于这个右值我们是否有必要对他进行深拷贝呢?在ret1 = s1我们之所以要做深拷贝是因为避免同一个变量指向同一块内存,导致我改变ret1,s1也会跟着改变。但是在s1 +'!'这个右值表达式中产生出来的结果是一个将亡值,它的生命周期将会在这个表达式运算完就还给内存了,我们根本不用去担心访问冲突的关系。所以移动拷贝就出来了。

移动拷贝

什么是移动拷贝?
简单的理解就是把那些临时匿名对象,没有名字的变量,用右值引用变量管理那块要还给操作系统的内存。省去深拷贝,提高了效率。
注意:右值引用的根本作用不是减少拷贝,大部分的减少拷贝左值引用已经完成了

string operator+(char ch)
{
	string tmp(*this);
	tmp += ch;
	return tmp;
}

在这里插入图片描述
在上面代码执行的时候,可以观察出ret2的地址和operator+中的tmp是一个地址。
左值引用解决直接减少拷贝,相当于传指针,不过没有指针那么复杂。而左值引用没有解决的是函数内的局部对象不能用引用返回的问题。
看下面场景:

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> vv;
        vv.resize(numRows);//给定开辟有多少个vector<int>
        //给每一个vector<int>开辟空间
        for(int i = 0;i<vv.size();i++)
        {
            vv[i].resize(i+1);
        }
        //把杨辉三角的每一行的首元素和尾元素置为1
        for(int i = 0;i<numRows;i++)
        {
            vv[i][0] = 1;
            vv[i][vv[i].size()-1] =1;
        }
        //执行逻辑:上一行的前一位+上一行的当前位 =当前位
        for(int i = 2;i < numRows;i++)
        {
            for(int j = 1;j < vv[i].size() - 1;j++)
            {
                vv[i][j] = vv[i-1][j-1]+vv[i-1][j];
            }
        }
        return vv;   
    }
};

在这样的一个杨辉三角问题中,我们需要在generate函数中构建临时对象vv,然后还传值返回,在传值返回的时候深拷贝构建临时匿名对象,去复制给外面的接收又要深拷贝,而且这返回值根本不用担心访问冲突的问题,没有必要做深拷贝。所以右值这就是引用的第二个意义。

move

int main()
{
	string s1("hello");
	cout << "第一次赋值" << endl;
	string s2 = s1;
	cout << s1 << endl;
	cout << s2 << endl;

	cout << "第二次赋值" << endl;
	string&& s3 = move(s1);
	cout << s1 << endl;
	cout << s3 << endl;

	cout << "第三次赋值" << endl;
	string s4 = move(s1);
	cout << s1 << endl;
	cout << s4 << endl;
	return 0;
}

在这里插入图片描述
为什么会有这样的输出,请解释赋值语句的含义?
第一次赋值语句是将对象s1赋值给s2,发生深拷贝把s1的值拷贝一份给s2,相当于在内存中开多一块空间存放与s1一样的值,不过是s2管。
第二次赋值语句是将对象s1move成了右值,然后用右值引用变量去给该值起别名,s3和s1都有管理这片内存的权限,和左值引用没有什么区别。
第三次是s1变成右值交给s4去管理,发生了移动拷贝,把s1原本管理的内存交给s4去管理,s1被置为了null。所以不能轻易的把左值move右值然后又赋值给其他变量,这样原本的那个左值会失去对该内存的管理。

完美转发

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

// 万能引用(引用折叠):既可以引用左值,也可以引用右值
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}

int main()
{
	PerfectForward(10);           // 右值

	int a;
	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b);		      // const 左值
	PerfectForward(std::move(b)); // const 右值

	return 0;
}

在这里插入图片描述
为什么是这个输出结果?
再看一个案例

int main()
{
	int&& rr1 = 10;
	cout << &rr1 << endl;
	rr1++;
	cout << rr1 << endl;
	return 0;
}

在这里插入图片描述
10是字面量而rr1右值引用变量引用之后就可以取地址和做++运算,得出结论:当右值被右值引用变量引用之后就转化为了左值,或者说右值引用变量是左值。
为什么要这样设计呢?因为只有这样才可以对右值进行操作(移动构造)。
我们再回到上面那一题目,我们可以得出结论,在我们将右值作为函数参数传递给右值引用的时候,那个参数就变成了左值,所以输出的就是左值。

forward

含义:在传参的过程中保留对象原生类型属性
使用方法:forward<原生类型>(变量)
上面修改后:

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

// 万能引用(引用折叠):既可以引用左值,也可以引用右值
template<typename T>
void PerfectForward(T&& t)
{
	Fun(forward<T>(t));
}

int main()
{
	PerfectForward(10);           // 右值

	int a;
	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b);		      // const 左值
	PerfectForward(std::move(b)); // const 右值

	return 0;
}

在这里插入图片描述
注意:在使用forward()的时候,要从最开始层层往下写下来。

移动赋值

和移动构造一样的原理:右值传参对该参数做移动,把这个生命周期极短的临时变量交给右值引用变量管理。
在这里插入图片描述

总结

右值引用解决了,左值引用中函数内变量不能引用返回需要强行深拷贝的场景和传右值作为参数,仍然使用深拷贝的场景。使用右值引用减少了很多没有必要的深拷贝,大大提高了效率

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/884090.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Lab1:虚拟机kolla安装部署openstack,并创建实例

实验内容&#xff1a; 创建并配置虚拟机安装OpenStack创建镜像创建实例类型选择网络配置创建实例 1、选择一个适合你的系统的虚拟机管理软件&#xff1a; VirtualBox &#xff08;推荐&#xff09; VMWare 其他 2、下载 .iso 镜像文件 openstack S 版本 iso 链接&#xff1…

计算机视觉实战项目4(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A*路径规划+单目测距与测速+行人车辆计数等)

往期热门项目回顾&#xff1a; 计算机视觉项目大集合 改进的yolo目标检测-测距测速 路径规划算法 图像去雨去雾目标检测测距项目 交通标志识别项目 yolo系列-重磅yolov9界面-最新的yolo 姿态识别-3d姿态识别 深度学习小白学习路线 AI健身教练-引体向上-俯卧撑计数…

网站设计中安全方面都需要有哪些考虑

网站设计中的安全性是一个多方面的问题&#xff0c;需要从多个角度进行考虑和实施。以下是一些关键的安全考虑因素&#xff1a; 数据加密&#xff1a; 使用SSL&#xff08;安全套接字层&#xff09;证书来建立加密连接&#xff0c;确保数据在传输过程中不被截获。定期更新SSL证…

保障电气安全的电气火灾监控系统主要组成有哪些?

电气火灾是什么&#xff1f; 电气火灾一般是指由于电气线路、用电设备、器具以及供配电设备出现故障性释放的热能&#xff1a;如高温、电弧、电火花以及非故障性释放的能量&#xff1b;如电热器具的炽热表面&#xff0c;在具备燃烧条件下引燃本体或其他可燃物而造成的火灾&…

动态规划入门题目->使用最小费用爬楼梯

1.题目&#xff1a; 2.解析&#xff1a; 做题模式&#xff1a; 步骤一&#xff1a;找状态转移方程 步骤二&#xff1a;初始化 步三&#xff1a;填表 步骤四&#xff1a;返回-> dp[n] dp[i]表示到达 i 位置最小花费 逻辑&#xff1a;要爬到楼顶先找到 i 位置 &#xff0c; 要…

如何在谷歌浏览器上玩大型多人在线游戏

在如今的数字时代&#xff0c;谷歌浏览器已经成为了许多人上网冲浪的首选工具。除了浏览网页、观看视频之外&#xff0c;你还可以在谷歌浏览器上畅玩各种大型多人在线游戏。本文将为你详细介绍如何在谷歌浏览器上玩大型多人在线游戏的步骤。 &#xff08;本文由https://chrome…

PTH原理 补丁+工具

顺着《域渗透攻防指南》4.9的总结记录下。 0x00 PTH简单说明 PTH在内网渗透中用于横向移动。由于NTLM && Kerberos都是采用用户密码的NTLM Hash&#xff0c;所以我们不需要非得拿用户明文口令&#xff0c;拿到hash一样可以。 拿到hash后&#xff0c;可以撞hash&…

【深度学习】03-神经网络01-4 神经网络的pytorch搭建和参数计算

# 计算模型参数,查看模型结构,我们要查看有多少参数&#xff0c;需要先安装包 pip install torchsummary import torch import torch.nn as nn from torchsummary import summary # 导入 summary 函数&#xff0c;用于计算模型参数和查看模型结构# 创建神经网络模型类 class Mo…

nginx+php+postgresql搭建漏洞靶场

经过我多番查找,最终得出一个结论,dvwa暂时不支持 postgresql 本文给大家提供一个思路,千万不要轻易模仿 更新系统包列表 首先,打开终端并更新你的系统包列表: sudo apt updatesudo apt upgrade -y安装必要的软件包 安装Nginx、PHP、PostgreSQL以及一些必要的PHP扩展:…

基于BeagleBone Black的网页LED控制功能(flask+gpiod)

目录 项目介绍硬件介绍项目设计开发环境功能实现控制LED外设构建Webserver 功能展示项目总结 &#x1f449; 【Funpack3-5】基于BeagleBone Black的网页LED控制功能 &#x1f449; Github: EmbeddedCamerata/BBB_led_flask_web_control 项目介绍 基于 BeagleBoard Black 开发板…

搜索引擎onesearch3实现解释和升级到Elasticsearch v8系列(四)-搜索

搜索 搜索内容比较多&#xff0c;onesearch分成两部分&#xff0c;第一部分&#xff0c;Query构建&#xff0c;其中包括搜索词设置&#xff0c;设置返回字段&#xff0c;filter&#xff0c;高亮&#xff1b;第二部分分页和排序。第一部分是映射引擎负责&#xff0c;映射通用表…

HAL+M4学习记录_2

一、Boot配置 内存地址是固定的&#xff0c;代码从0x0000 0000开始&#xff0c;而数据从0x2000 0000开始&#xff0c;F4支持三种不同的boot模式 复位芯片时&#xff0c;在SYSCLK的第4个上升沿BOOT引脚值被锁存&#xff0c;STM32F407通过此时BOOT[1:0]引脚值选择Boot模式 BOOT1…

深度学习(入门)03:监督学习

1、监督学习简介 监督学习&#xff08;Supervised Learning&#xff09;是一种重要的机器学习方法&#xff0c;它的目标是通过“已知输入特征”来预测对应的标签。在监督学习中&#xff0c;每一个“特征-标签”对被称为样本&#xff08;example&#xff09;&#xff0c;这些样…

物联网行业中模组的AT指令详解以及使用

01 概述 AT 命令&#xff08;AT Commands&#xff09;最早是由发明拨号调制解调器&#xff08;MODEM&#xff09;的贺氏公司&#xff08;Hayes&#xff09;为了控制 MODEM 而发明的控制协议。后来随着网络带宽的升级&#xff0c;速度很低的拨号 MODEM 基本退出一般使用市场&am…

【含文档】基于Springboot+Vue的高校师资管理系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

深入理解 `torch.nn.Linear`:维度变换的过程详解与实践(附图、公式、代码)

在深度学习中&#xff0c;线性变换是最基础的操作之一。PyTorch 提供了 torch.nn.Linear 模块&#xff0c;用来实现全连接层&#xff08;Fully Connected Layer&#xff09;。在使用时&#xff0c;理解维度如何从输入映射到输出&#xff0c;并掌握其具体的变换过程&#xff0c;…

宠物空气净化器有必要买吗?希喂、霍尼韦尔和352哪款更推荐?

国庆假终于要来了&#xff0c;对于我这个上班族而言&#xff0c;除了春节假期最期待的就是这个国庆假&#xff0c;毕竟假期这么长&#xff0c;家里还有一只小猫咪&#xff0c;一直都没时间陪它&#xff0c;终于给我找到时间带它会老家玩一趟了。 我跟我妈说的时候&#xff0c;…

时序必读论文13|ICLR24 “又好又快”的线性SOTA时序模型FITS

论文标题&#xff1a;FITS: Modeling Time Series with 10k Parameters 开源代码&#xff1a;https://anonymous.4open.science/r/FITS/README.md 前言 FITS&#xff08;Frequency Interpolation Time Series Analysis Baseline&#xff09;这篇文章发表于ICLR2024&#xff…

鸿蒙开发(NEXT/API 12)【硬件(Pen Kit)】手写笔服务

Pen Kit&#xff08;手写笔服务&#xff09;是华为提供的一套手写套件&#xff0c;提供笔刷效果、笔迹编辑、报点预测、一笔成形和全局取色的功能。手写笔服务可以为产品带来优质手写体验&#xff0c;为您创造更多的手写应用场景。 目前Pen Kit提供了四种能力&#xff1a;手写…

C++入门day5-面向对象编程(终)

C入门day4-面向对象编程&#xff08;下&#xff09;-CSDN博客 本节是我们面向对象内容的最终篇章&#xff0c;不是说我们的C就学到这里。如果有一些面向对象的基础知识没有讲到&#xff0c;后面会发布在知识点补充专栏&#xff0c;全都是干货满满的。 https://blog.csdn.net/u…