左值引用与右值引用

embedded/2024/9/22 17:58:58/

目录

右值 与 左值

右值引用

右值引用的用处

move

引用折叠

forward


右值 与 左值

c++11增加了一个新的类型,右值引用,记作:&& 。

● 左值是指在内存中有明确的地址,我们可以找到这块地址的数据(可取地址)。

● 右值是只提供数据,无法找到地址(不可取地址)。

○ 所有有名字的变量都是左值,而右值是匿名的。

一般情况下位于等号左边的是左值,位于等号右边的是右值,但是也可以出现左值给左值赋值的情况。

c++11中右值分为两种情况:一个是将亡值,另一个是纯右值。

● 纯右值:非引用返回的临时变量,运算表达式产生的临时变量,原始字面量,lambda表达式等。

● 将亡值:与右值引用相关的表达式,比如:T&& 类型函数的返回值,std::move()的返回值等。

右值引用

右值引用就是对右值引用的类型。因为右值是匿名的,所以我们只能通过引用的方式找到它。无论是左值引用还是右值引用都必须被初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。通过右值引用,该右值所占的内存又可以被使用。

int&& value = 520; // 右值引用,520是字面量,是右值class Test
{
public:Test(){cout << "构造函数" << endl;}Test(const Test& a){cout << "拷贝构造函数" << endl;}
};Test getObj()
{return Test();
}int main()
{int a1;//int &&a2 = a1;        // 报错 右值引用不能被左值初始化//Test& t1 = getObj();   // 右值不能初始化左值引用Test && t2 = getObj(); //函数返回的临时对象是右值,可以被引用const Test& t3 = getObj();// 常量左值引用是万能可以接收左值,右值,常量左值,常量右值const int& t3 = a1; //被左值初始化return 0;
}

右值引用的用处

在c++用对象初始化对象时会调用拷贝构造,如果这个对象占用堆内存很大,那么这个拷贝的代价就是非常大的,在某些情况,如果想要避免对象的深拷贝,就可以使用右值引用进行性能的优化。

class Test
{
public:Test() : m_num(new int(100)){cout << "构造函数" << endl;}Test(const Test& a) : m_num(new int(*a.m_num)){cout << "拷贝构造函数" << endl;}~Test(){delete m_num;}int* m_num;
};Test getObj()
{Test t;return t;
}int main()
{Test t = getObj();cout << "t.m_num: " << *t.m_num << endl;return 0;
};

这段代码在调用Test t = getObj(); 的时候调用了拷贝构造函数,对返回的临时对象进行了深拷贝得到了对象t,在getObj函数中创建的对象虽然进行了内存申请操作,但是没有使用就被释放掉了。如果我们在函数结束后仍然可以利用在函数里面申请的空间就极大的节省了创建对象和释放对象的时间。这个操作就需要我们的右值引用来完成。

右值引用具有移动语义,移动语义可以将堆区资源,通过浅拷贝从一个对象转移到另一个对象这样就能减少不必要的临时对象的创建,拷贝以及销毁,大幅度提高性能。

class Test
{
public:Test() : m_num(new int(100)){cout << "构造函数" << endl;}Test(const Test& a) : m_num(new int(*a.m_num)){cout << "拷贝构造函数" << endl;}// 添加移动构造函数,参数是右值引用Test(Test&& a) : m_num(a.m_num) {a.m_num = nullptr;cout << "移动构造函数" << endl;}~Test(){delete m_num;cout << "析构函数" << endl;}int* m_num;
};Test getObj()
{Test t;return t;
}int main()
{Test t = getObj(); // 因为getObj 返回的是右值,所以调用移动构造函数cout << "t.m_num: " << *t.m_num << endl;return 0;
};

● 在上面的代码中添加了 移动构造函数(参数为右值引用类型),这样在进行 Test t = getObj();并没有调用构造函数进行深拷贝,而是调用的(浅拷贝)移动构造,提高了性能。

● 本例子中,getObj()返回值是一个右值,在进行赋值操作的时候如果  等号 右边是一个右值,那么移动构造函数就会被调用。

结论:需要动态申请大量的资源的类,应该设计移动构造,提高程序的效率。需要注意的是在提供移动构造的同时,一般也会提供左值引用拷贝构造函数,左值初始化新对象时会走拷贝构造。

move

c++11添加了右值引用,却不能左值初始化右值引用,在一些特定的情况下免不了需要左值初始化右值引用(用左值调用移动构造),如果想要用左值初始化一个右值引用需要借助std::move() 函数。move() 函数可以将左值转换为右值。

#include<iostream>
using namespace std;
class Test
{
public:int a = 3;int* m_num = &a;Test() : m_num(new int(100)){cout << "构造函数" << endl;}Test(const Test& other) : m_num(new int(*other.m_num)){cout << "拷贝构造函数" << endl;}// 添加移动构造函数Test(Test&& a) : m_num(a.m_num){a.m_num = nullptr;cout << "移动构造函数" << endl;}~Test(){delete m_num;cout << "析构函数" << endl;}};
Test getObj()
{Test t;return t;
}
int main()
{Test t = getObj();Test t2 = move(t); //此处调用移动构造,因为move返回一个右值return 0;
};

引用折叠

c++中,并不是所有情况下&&都代表右值引用,在模板和自动类型推导(auto)中,如果是模板参数需要指定为T&&,如果是自动类型推导需要指定为auto &&,这两种情况下&&被称作 未定的引用类型。另外 const T &&表示 一个右值引用,不是未定引用类型。

template<typename T>
void fun(T&& param)
{work(forward<T>(param))
}
int main()
{fun(10); //对于 f(10) 来说传入的实参 10 是右值,因此 T&& 表示右值引用int x = 1;fun(x);//对于 f(x) 来说传入的实参是 x 是左值,因此 T&& 表示左值引用return 0;
}

因为T&& 或者auto&&这种未定引用类型作为参数时,有可能被推导成右值引用,也有可能被推导为左值引用,在进行类型推导时右值引用会发生变化,这种变化被称作引用折叠。折叠规则如下:

● 通过右值 推导T&&或者auto&& 得到的是一个右值引用类型,const T &&表示 一个右值引用

● 通过非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推导T&& 或者auto&&得到的是一个左值引用类型

int main()
{int&& a1 = 1;       //右值推导为               右值引用auto&& bb = a1; //右值引用推导为       左值引用auto&& bb1 = 2; //右值推导为              右值引用int a2 = 1;        int &a3 = a2;          //左值推导为                      左值引用auto&& cc = a3;    //左值引用推导为               左值引用auto&& cc1 = a2; //左值推导为                       左值引用const int& s1 = 1; // 常量左值引用const int&& s2 = 1;// 常量右值引用auto&& dd = s1;    // 常量左值引用推导为              左值引用auto&& ee = s2;   // 常量右值引用推导为               左值引用return 0;
}

forward

右值引用类型是独立于值的,一个右值引用作为函数参数的形参时,在函数内部转发该参数给内部其他函数时,他就变成了一个左值(当右值被命名是编译器会认为他是个左值),并不是原来的类型了。如果按照参数原来的类型转发到另一个函数,可以使用c++11的  std::forward()函数,该函数实现的功能称之为完美转发。

// 函数原型
template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept;template <class T> T&& forward(typenameremove_reference<T>::type&& t) noexcept;// 精简之后的样子
std::forward<T>(t);

● 当T为左值引用类型时,t将会被转换为左值

● 当T不是左值引用类型时,t将被转换为T类型的右值

#include <iostream>
using namespace std;template<typename T>
void printValue(T& t)
{cout << "左值引用: " << t << endl;
}template<typename T>
void printValue(T&& t)
{cout << "右值引用:" << t << endl;
}template<typename T>
void testForward(T && v)
{printValue(v);printValue(move(v));printValue(forward<T>(v));cout << endl;
}int main()
{testForward(520);int num = 1314;testForward(num);testForward(forward<int>(num));testForward(forward<int&>(num));testForward(forward<int&&>(num));return 0;
}

http://www.ppmy.cn/embedded/7804.html

相关文章

Promise面试题

promise与 fetch、async/await_fetch async 获取结束标志-CSDN博客 手写promise A、catch、finally、all、allsettled、any、race-CSDN博客 【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理) - 掘金 目录 每隔1秒输出1,2,3 红黄绿灯交替亮 按序执行数组 …

怎么用手机远程控制电脑 远程控制怎么用

怎么用手机远程控制电脑&#xff1a;远程控制怎么用 在这个科技日新月异的时代&#xff0c;远程控制电脑已经成为了很多人的需求。有时&#xff0c;我们可能在外出时突然需要访问家中的电脑&#xff0c;或者在工作中需要远程操控办公室的电脑。这时&#xff0c;如果能用手机远…

FFmpeg合并音视频文件操作备忘(mac版)

利用NDM嗅探插件从B站下载下来的文件是音视频分开的&#xff0c;用剪辑软件合并时发现导出时文件都特别大&#xff0c;于是使用FFmpeg处理 环境&#xff1a; MBP M1芯片版 系统 macOS Sonama 14.4.1 操作步骤&#xff1a; 一、官方下载链接&#xff1a;https://evermeet.cx/…

IDEA2024配置RunDashBoard(Services)面板

IDEA2024配置RunDashBoard(Services)面板 新版本的IDEA没有RunDashBoard&#xff0c;取而代之的是Services面板&#xff0c;不需要配置workspace.xml文件; 本文教你简单的方法就能一个SpringBoot的Main运行多次&#xff0c;方便调试。 1、配置启动类 导航栏&#xff0c;Edit…

ElasticSearch中使用向量和关键词联合检索

注&#xff1a;案例测试数据及其索引构建详见&#xff1a;ElasticSearch中使用bge-large-zh-v1.5进行向量检索&#xff08;一&#xff09;-CSDN博客 中的第三部分。 假设任务场景为&#xff1a;用“新疆”向量检索相关的数据&#xff0c;同时需要匹配关键词“巴州”。 首先获取…

【JavaWeb】Day51.Mybatis动态SQL

什么是动态SQL 在页面原型中&#xff0c;列表上方的条件是动态的&#xff0c;是可以不传递的&#xff0c;也可以只传递其中的1个或者2个或者全部。 而在我们刚才编写的SQL语句中&#xff0c;我们会看到&#xff0c;我们将三个条件直接写死了。 如果页面只传递了参数姓名name 字…

springboot日志使用 SLF4J+Logback 实现(springboot默认的日志实现),日志打印到控制台及日志输出到指定文件

还是直接上代码 Slf4j 这玩意 默认支持 不用引入 yml 配置文件 # 日志配置 如果配置了xml 这个就不生效了 xml优先级最高 #logging: # file: # path: /home/logs # 日志目录地址 # name: /home/logs/skeleton.log # max-size: 1KB # 设置日志大小的最大大小 1…

J8 inceptionv1

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 卷积神经网络大家族中有很多经典的网络&#xff0c;前面已经学习resnet,densenet相关网络&#xff0c;今天学习一种更久远的一种网络GoogLenet 网络结构…