当前位置: 首页 > article >正文

【C++11(上)】—— 我与C++的不解之缘(三十)

一、C++11

这里简单了解一下C++发展好吧:

C++11C++的第二个大版本,也是自C++98以来最重要的一个版本。

它引入了大量的更改,它曾被人们称为C++0x,因为它被期待在2010年之前发布;但在2011年8月12日才被采纳。

C++03C++11花了8年时间,这是迄今为止最长的版本间隙;自C++11起C++就有规律的每三年更新一次。

在这里插入图片描述

二、列表初始化

C++98中的{}

在之前C++98中是支持对一般数组和结构体使用{}来初始化的

struct A
{int _x;int _y;
};
int main()
{int arr1[] = { 1,2,3,4,5 };//数组使用{}进行初始化int arr2[3] = { 0 };A a = { 2,2 };//结构体使用{}进行初始化return 0;
}

这些在C语言当中都是支持的。

C++11中的{}

  • C++11中的{},想统一初始化方式,想要实现一切对象皆可以使用{}初始化;{}初始化也称为列表初始化。
  • 内置类型支持使用{}进行初始化;自定义类型也支持,但是自定义类型支持的本质是类型转化,会产生临时对象,最后被优化成了直接构造
  • {}在初始化的过程中,也可以省略掉=
class Date
{
public:Date(int year = 1, int month, int day):_year(year),_month(month),_day(day){cout << "Date(int year = 1,int month = 1,int day = 1" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date(const Date& d)" << endl;}
private:int _year;int _month;int _day;
};int main()
{//C++11//内置类型支持{}初始化int a = { 1 };//自定义类型也支持{}初始化//本质上是{2025,3,31}构造一个Date对象,再进行拷贝构造//编译器优化二合一成{2025,3,31}直接构造Date d1 = { 2025,3,31 };//这里d2引用的就是{2025,3,31}构造的临时对象const Date& d2 = { 2025,3,31 };//C++98中支持单参数时 隐式类型转换,可以不写{}Date d3 = { 2025 };Date d4 = 2025;//可以省略掉=int b{ 2 };Date d5{ 2025 };const Date& d6{ 2006,7,20 };//只有使用{}时,才可以省略=//Date d7 2025;return 0;
}

C++11列表初始化的本意就是想要实现一个统一的初始化方式(无论是内置类型还是自定义类型

在有些场景还是能够带来很大的便利的,就比如容器push/insert多参数构造对象时,{}就十分方便。

int main()
{vector<Date> v;Date d = { 2025,3,31 };v.push_back(d);v.push_back(Date(2025, 1, 1));//匿名对象//相比较于有名对象和你们对象,这里使用{}还是很便利的v.push_back({ 2025, 1, 1 });return 0;
}

C++11中的std::initializer_list

在上面描述中,我们Date类之所以能够使用{2025,3,31}来初始化,那是因为其实现了三个参数的构造函数(都有缺省值);

那我们vector是否也能使用{}来初始化呢?

显然是不可以的(如果要用N个值去初始化,那就需要有N个参数的构造函数);

vector<int> v1 = {1 , 2 , 3 , 4 , 5}。(这里如果在自己的编译器上试一下,可能是支持这样的,那是编译器进行了优化处理)

C++11提出了一个initializer_list类,这个类的本质就是开辟一个数组,然后将数据拷贝过来;std::initializer_list内部存在两个指针分别指向数组的开始和结束位置。

initializer_list是支持迭代器遍历的。

容器支持了一个std::initializer_lis的构造函数,支持多个值构成的{x1,x2,x3...}进行初始化。

STL的容器支持任意多个值构成的{x1,x2,x3...}进行初始化都是通过initializer_list进行支持的

在这里插入图片描述

什么意思呢?

在这里插入图片描述

可以看到,auto arr = {1 , 2 , 3 , 4 , 5};它是类型匹配成initializer_list的,其中存在两个指针(_First_Last)。

那如何实现STL中容器支持任意多个值{x1 , x2 , x3...}进行初始化呢?

在这里插入图片描述

可以看到STL容器在C++11的中支持了initializer_list的构造函数,这样我们就可以使用任意多个值进行初始化了。

int main()
{std::initializer_list<int> mylist;mylist = { 10, 20, 30 };cout << sizeof(mylist) << endl;//这里begin和end返回的是initializer_list对象中存在的两个指针_First和_Last// 这里连个指针和i的地址接近,这个数组在栈上int i = 0;cout << mylist.begin() << endl;cout << mylist.end() << endl;cout << &i << endl;//{}列表中可以有任意个值//下面第一个是v1的直接构造(编译器将构造和拷贝构造优化成直接构造)// 第二个v2是 {1,2,3,4,5}先构造成initializer_list,在调用vector的构造,再进行拷贝构造// v2 : 构造临时对象 + 临时对象拷贝 优化成了直接构造vector<int> v1({ 1,2,3,4,5 });vector<int> v2 = { 1,2,3,4,5 };const vector<int>& v3 = { 1,2,3,4,5 };//这里pair对象的{} 吃石化和map的initializer_list的构造相结合map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} };// initializer_list版本支持的赋值v1 = { 10,20,30,40,50 };return 0;
}

三、右值引用和移动语义

C++98(我们之前的学习中),我们已经了解到引用这个概念;

C++11新增了右值引用这个概念,我们之前学习的引用就叫做左值引用

这里无论左值引用还是右值引用,都是给对象其别名。

1. 左值和右值

  • 左值是一个表示数据的表达式(例如:变量名/解引用的指针),一般是持久的,存储在内存中,我们可以用获取它的地址;左值可以出现在赋值符号的左边,也可以出现赋值符号的右边。定义时const修饰后的左值,不能给它赋值,但是可以取它的地址
  • 右值也是一个表达数据的表达式,常见的右值有(字面值常量表达式求值过程中创建的临时变量匿名对象等),右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址
int main()
{//左值可以取地址//p,b,c,*p,str,str[0]都是左值int* p = new int(0);int b = 1;const int& c = b;*p = 3;string str = "12345";str[0] = '0';cout << &b << endl;cout << (void*)&str[0] << endl;//&str[0]是char* ,不强转成void* 会被当成字符串输出//常见的右值double x = 1.1, y = 2.2;10;//常量值x + y;//表达式计算产生的临时变量fmin(x, y);//函数传值返回的返回值string("11111");//匿名对象return 0;
}

左值的英文简写为lvalue,右值的英文简写为rvalue;(这里可以认为是left valueright value的缩写)

在现代C++中,lvalue被解释为loactor value的缩写,可以理解为在内存中、有明确存储地址,可以取地址的对象;而rvalue被解释为read value,可以理解为那些可以提供数据值,但是不能寻址,(临时变量字面值常量、存储于寄存器的变量)

也就是说左值和右值的主要区别就在于是否取地址。

2. 左值引用和右值引用

  • 左值引用:Type& r1 = x,左值引用是给左值取别名;
  • 优质引用:Type&& rr1 = y,右值引用就是给右值取别名。

我们之前使用const Type&是可以引用临时变量和匿名对象(右值)的,这里:

  • 左值引用不能直接引用右值,但是const左值引用可以引用右值
  • 右值引用不能直接引用左值,但是可以引用mave(左值)

move是库里面的一个函数模版,其作用就是将左值转换成功右值,其本质上是强制类型转换,还设计到一部分引用折叠(这里就简单理解成强制类型转换。

这里有一个需要注意的点:

  • 变量表达式都是左值属性,一个右值被右值引用绑定后,这个右值引用变量表达式的属性是左值;

什么意思呢?就比如:int&& r = 10;(其中10 是常量值是右值,我们定义了右值引用变量r,这个r是一个左值。

  • 从语法层面看,左值引用和右值引用都是取别名,没有开空间;但从汇编角度看,底层都是指针实现的,没有什么区别。
int main()
{int* p = new int(0);int b = 1;const int c = b;*p = 10;string s("111111");s[0] = 'x';double x = 1.1, y = 2.2;//左值引用int& r1 = b;int*& r2 = p;int& r3 = *p;string& r4 = s;char& r5 = s[0];//右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);string&& rr4 = string("11111");//左值引用不能直接引用右值,const左值引用可以引用右值const int& rx1 = 10;const double& rx2 = x + y;const double& rx3 = fmin(x, y);const string& rx4 = string("11111");//右值引用不能直接引用左值,但可以引用move(左值)int&& rrx1 = move(b);int*&& rrx2 = move(p);int&& rrx3 = move(*p);string&& rrx4 = move(s);string&& rrx5 = (string&&)s;// b、r1、rr1都是变量表达式,都是左值cout << &b << endl;cout << &r1 << endl;cout << &rr1 << endl;// rr1是右值引用表达式,rr1属性是左值int& r6 = r1;// int&& rrx6 = rr1;int&& rrx6 = move(rr1);return 0;
}

3. 延长生命周期

右值引用也可以用来延长临时对象的生命周期,const左值引用也可以用来延长临时对象的生命周期,但是这些对象不能被修改。

int main()
{string s1 = "666666";const string& s2 = s1 + s1;//const 左值引用延长临时对象生命周期//s2 += "999";//const左值引用不能修改string&& s3 = s1 + s1;//右值引用延长临时对象生命周期s3 += "999";//可以进行修改cout << s1 << endl;cout << s2 << endl;return 0;
}

4. 左值和右值的参数匹配

C++98中,我们使用const左值引用来作为函数的参数,这样在传参时,左值和右值都可以匹配到const左值引用

C++11有了右值引用以后,现在我们就可以重载左值引用const 左值引用右值引用作为参数的func函数;那这样实参是左值就会匹配func(左值引用)、实参是const左值就会匹配func(const 左值引用)、实参是右值就会匹配func(右值引用)

这里有一个感觉非常奇怪的点就是:右值引用变量在用于表达式时属性是左值(它存在一定有它存在的道理,在后面了解右值引用的使用就会直到这个设计的作用)。

void func(int& x)
{cout << "左值引用" << endl;
}
void func(const int& x)
{cout << "const 左值引用" << endl;
}
void func(int&& x)
{cout << "右值引用" << endl;
}
int main()
{int i = 1;const int ci = 2;func(i); //调用func(int& x)func(ci); //调用func(const int& x)func(3); //调用func(int&& x),如果没有就调用func(const int& x)func(std::move(i)); //调用func(int&& x),如果没有就调用func(const int& x)int&& x = 1;func(x); //右值引用变量的属性是左值,调用func(int& x)func(std::move(x)); //x是左值,move(x)是右值,调用func(int&& x)return 0;
}

5. 右值引用和移动语义的使用

说了那么多关于右值引用的语法,那这些有什么用呢?

左值引用的使用

先来看一下左值引用的使用:

之前我们使用左值引用主要就是在函数中左值引用传和左值引用返回值 减少拷贝,同时做到可以修改实参和修改返回值的作用。

我们使用左值引用就已经解决了大多数场景的拷贝效率问题,但是总有一些场景是不能使用传左值引用返回(这里就比如下面的addString函数generate函数),显然是不能进行左值引用返回的;(就只能进行传值返回)

C++11设计出了右值引用能够使用右值引用返回解决这一问题吗?显然不能。

这里问题的本质是:返回的是一个局部对象,函数结束之后这个对象就析构销毁了,右值引用返回也无法改变它被析构销毁的事实。

// 传值返回需要拷⻉
string addStrings(string num1, string num2) {string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;// 进位int next = 0;while (end1 >= 0 || end2 >= 0){int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1)str += '1';reverse(str.begin(), str.end());return str;
}
//这里进行传值返回的代价就非常大了
vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);for (int i = 0; i < numRows; ++i){vv[i].resize(i + 1, 1);}for (int i = 2; i < numRows; ++i){for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}return vv;
}

移动构造和移动赋值

右值引用也不能解决传值返回拷贝的问题,C++11也提出了移动构造移动赋值

  • 移动构造是一种构造函数,类似于拷贝构造函数;
  • 移动构造要求第一个参数是该类类型的引用,于拷贝构造不同的是,移动构造要求这个参数是右值引用(如果却在其他参数,额外参数必须有缺省值)
  • 移动赋值的一个赋值运算符的重载,于拷贝赋值构成函数重载,类似于拷贝赋值;
  • 移动赋值函数要求第一个参数是该类类型的引用,且要求这个参数是右值引用。

这里移动构造移动赋值对于vector/string这样进行深拷贝的类,或者包含深拷贝的成员变量的类才有意义。

因为移动构造和移动赋值,它们本质上是要掠夺右值引用引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去重新开辟资源,从而移动构造和移动赋值效率更高。

移动构造和移动赋值实现的本质就是交换右值对象的资源。

//移动构造
string(string&& s)
{swap(s);
}
//移动赋值
string& operator=(string&& s)
{swap(s);return *this;
}

这里,右值是 临时对象、匿名对象等,右值引用引用的对象,马上就被析构释放了;

移动构造和移动赋值的本质就是要将这些即将释放的资源进行重新利用。

三、移动构造和移动赋值解决传值返回的问题

这部分内容有点多,现在来详细了解

这里为了能够方便观察到拷贝构造和移动构造的次数,就使用自己实现的string

namespace HL
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)-构造" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string& s):_str(nullptr){cout << "string(const string& s) -- 拷⻉构造" << endl;reserve(s._capacity);for (auto ch : s){push_back(ch);}}// 移动构造string(string&& s){cout << "string(string&& s) -- 移动构造" << endl;swap(s);}string& operator=(const string& s){cout << "string& operator=(const string& s) -- 拷⻉赋值" <<endl;if (this != &s){_str[0] = '\0';_size = 0;reserve(s._capacity);for (auto ch : s){push_back(ch);}}return *this;}// 移动赋值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动赋值" << endl;swap(s);return *this;}~string(){cout << "~string() -- 析构" << endl;delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];if (_str){strcpy(tmp, _str);delete[] _str;}_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity *2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}size_t size() const{return _size;}
private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;
};string addStrings(string num1, string num2) {string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;// 进位int next = 0;while (end1 >= 0 || end2 >= 0){int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1)str += '1';reverse(str.begin(), str.end());return str;}
}

使用的string如上述所示。

只存在拷贝构造,没有移动构造

在没有右值引用和移动构造之前,我们都是使用拷贝构造,我们来看一下它进行传值返回时的拷贝消耗。

现在假设这样的场景(只有拷贝构造)

int main()
{HL::string ret = HL::addStrings("11111", "2222");cout << ret.c_str() << endl;return 0;
}

在这里插入图片描述

博主这里使用的是visual Studio 2022,可以看到传值返回时没有调用拷贝构造啊;

这是因为编译器进行了优化将多次拷贝构造进行了优化。

这里如果编译器不进行优化,那就是两次拷贝构造

在这里插入图片描述

vs2019 debug环境下编译器对拷贝的优化,会将两次拷贝构造优化成一次(上图右边所示)

vs2022vs2019 release下,编译器优化就会十分恐怖了,会直接将str对象的构造,str拷贝构造的临时对象,临时对象拷贝构造ret对象,三次拷贝构造合三为一,变成直接构造。(如下图所示
在这里插入图片描述

这里对str取地址可以发现和ret地址是一样的:

在这里插入图片描述

那也就是说,编译器究极优化以后,str其实就是ret的一个别名(引用)。

这里空口无凭,现在在linux的环境下使用g++ test.cpp -fno-elide-constructors(关闭编译器的优化),来看一下运行结果:

在这里插入图片描述

可以看到确实是两次拷贝构造

存在移动构造

其实编译器的优化已经很好了,如上述,它将多次拷贝构造合成一次;

但是如果只有拷贝构造,那效率就非常依赖编译器了,如果编译器进行了优化,那效率相对还是很高的,但如果编译器没有进行优化那就十分的坑了。

并不是所有的编译器都会进行优化,所有现在来看一下有了移动构造以后,传值问题如何解决

		// 移动构造string(string&& s){cout << "string(string&& s) -- 移动构造" << endl;swap(s);}

这里先来看一下linux不使用编译器优化的运行结果:

在这里插入图片描述

在这里插入图片描述

先来看一下编译器vs2019 debug下进行的优化,将两次移动构造优化成一次:

在这里插入图片描述

最后我们再来看一下编译器的究极优化:

在这里插入图片描述

对比上面三种情况:

编译器不进行优化,那也就两次移动构造;我们知道移动构造就是交换一下资源,并没有开辟新的空间和释放旧的空间这些浪费,效率还是很高的;

如果编译器进行优化,那就更好了,效率会更高;(但是这里编译器不进行优化,效率就已经很高了,这样我们就不再依赖于编译器来解决传值返回的拷贝构造问题)。

只存在拷贝赋值,没有移动赋值

有了移动构造,我们降低了上述传值返回进行拷贝构造的消耗,但如果我们传值返回不是调用的拷贝构造,而是拷贝赋值呢?

int main()
{HL::string ret("hello");//先创建了一个变量//进行一系列操作cout << ret.c_str() << endl;//然后对其进行赋值ret = HL::addStrings("11111", "2222");cout << ret.c_str() << endl;return 0;
}

现在有上述场景;(现在我们先看只存在拷贝赋值的情况下)

如果编译器不进行优化的情况下,那就是一次拷贝构造,一次拷贝赋值(linux下测试一下

在这里插入图片描述

在这里插入图片描述

vs2022vs2019 release下,编译器会对这样的拷贝构造和拷贝赋值进行优化,会将其造优化成一次拷贝赋值。

在这里插入图片描述

在这里插入图片描述

存在移动赋值

当存在了移动构造和移动赋值以后,这里我们编译器不优化的情况下,这里也就只有一次 移动构造和一次 移动赋值

在这里插入图片描述

在这里插入图片描述

**在vs2022vs2019 release**下,编译器会对这样的移动构造和移动赋值进行优化,会将其造优化成一次移动赋值。
在这里插入图片描述

这里总结一下:

在只有拷贝构造和拷贝赋值的情况下,传值返回拷贝的消耗是很大的;编译器虽然做出了一些优化,将多次构造合成一次构造,但这样就会取决于编译器(编译器优化了消耗就低,编译器不优化消耗就很大)。

有了移动构造和移动拷贝之后,虽然传值返回还是会调用多次构造,但是移动构造和移动赋值只是进行资源的交换,效率是很快的,就是编译器不进行优化,两次、三次移动构造效率还是很快;这样我们程序的效率就不会取决于编译器,(编译器优化了更好,不优化效率也不会很低,消耗都很小)。

右值引用和移动语义在传参中的提效

在上述过程中,其实提效不是很明显,因为我们使用的是string

但是如果我们使用是vector<string>或者list<string>那就可想而知,提效就很明显了。

我们在STL文档中可以看到,C++11之后容器的pushinsert系列的接口都增加了右值引用的版本

  • 当是实参是一个左值时,容器就会调用左值版本,调用拷贝构造进行拷贝,将对象拷贝到容器空间中的对象。
  • 如果实参是一个右值,容器就会调用右值版本,调用移动构造,将右值对象的资源转移到容器空间的对象上。

这里还存在一个emplace系列的接口,这个在了解可变参数模板以后再了解这一系列的接口。

这里就不演示list<string>或者vector<string>这些场景了好吧

四、类型分类

C++11之后,对类型进行了进一步划分:

  • 右值被划分成了纯右值(pure value,简称prvalue)和将亡值(expiring value,简称xvalue)。
  • 纯右值:字面值常量和求值结果相当于字面值,或者一个不具名的临时对象。(比如10truenullptr或者类似于str.subsr(1,3)str1+str2传值返回函数的调用、或者整型a+ba++等。
  • 将亡值:指返回右值引用的函数的调用表达式和转化成右值引用的转换函数的调用表达式(比如mave(x)static_cast<X&&>(x)
  • 泛左值:(generalized value,简称glvalue),泛左值包含将亡值左值

这里简单了解一下,详细可以看一下 值类别和 Value category

五、引用折叠

C++11中,我们不能直接定义引用的引用,比如int& && r = i这样会报错,但是如果我们通过模版或者typedef类型操作,我们就可以构成引用的引用(这样不会报错)

当我们通过模版或者typedef中的类型操作,构成引用的引用时,C++11给出了关于引用折叠的规则:

  • 右值引用的右值引用折叠成右值引用;
  • 其他的都折叠成了左值引用。
int main()
{typedef int& lref;typedef int&& rref;int n = 0;lref& r1 = n; // 左值引用和左值引用 折叠为 左值引用lref&& r2 = n; //右值引用和左值引用 折叠为 左值引用rref& r3 = n; //左值引用和右值引用 折叠为 左值引用rref&& r4 = 1; //右值引用和右值引用 折叠为 右值引用return 0;
}

当然我们也可以通过类模版,来构成引用折叠

template<class T>
void fun1(T& x)
{}template<class T>
void fun2(T&& x)
{}
int main()
{int n = 0;//显示实例化fun1<int>(n);fun1<int>(0);//errfun1<int&>(n);fun1<int&>(0);//errfun1<int&&>(n);fun1<int&&>(0);//errfun2<int>(n);//errfun2<int>(0);fun2<int&>(n);fun2<int&>(0);//errfun2<int&&>(n);//errfun2<int&&>(0);return 0;
}

在这里插入图片描述

根据上图我们会发现,左值引用版本的模板,无论我们给的是左值还是右值,它都会实例化成左值版本;而右值引用版本,我们给的是左值,它就实例化成左值版本;给的是右值就实例化成右值版本。

在有些地方,也将右值版本的模版参数叫做万能引用。

这里我们是显示实例化,现在来看一下编译器根据我们传值来实例化生成的

template<class T>
void func(T&& x)
{int a = 0;T b = a;//x++;cout << &a << endl;cout << &b << endl;
}
int main()
{func(10);//右值int a;func(a);//左值func(std::move(a));//右值const int b = 8;func(b);//const 左值func(std::move(b));//const 右值return 0;
}

在这里插入图片描述

六、完美转发

在上述func(T&& x)函数模版中,我们传左值以后实例化的是左值引用的func函数,传右值以后实例化的是右值引用的func函数。

但是在右值的属性时,曾提到右值引用表达式变量的属性是左值,那也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值,那我们把一个右值引用变量x传给函数func,那么匹配的是左值引用版本的func函数;(这不符合逻辑啊

C++11对上述情况,通过了完美转发函数:

template<class T> T&& forward(typename remove_reference<T>::type& arg)

完美转发forword是一个函数模版,它主要还是通过引用折叠的方式实现。

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<class T>
void func(T&& t)
{Fun(t);//Fun(std::forward<T>(t));
}
int main()
{func(10); // 右值int a;func(a); // 左值func(std::move(a)); // 右值const int b = 8;func(b); // const 左值func(std::move(b)); // const 右值return 0;
}

在这里插入图片描述

就如上述,不管我们func实例化的是左值引用还是右值引用版本,它再次调用Fun函数,都是调用左值引用版本的

这是因为,不论实例化的是左值引用还是右值引用版本的,我们的变量t它的属性都是左值,那调用的移动是参数是左值的Fun函数。

我们要想通过func调用Fun实现左值引用调用左值引用,右值引用调用右值引用的,那就要通过forword来实现。

在这里插入图片描述

到这里本篇内容就结束了,希望对你有所帮助。

制作不易,感谢大佬的支持。

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

相关文章:

【C++11(上)】—— 我与C++的不解之缘(三十)

一、C11 这里简单了解一下C发展好吧&#xff1a; C11是C的第二个大版本&#xff0c;也是自C98以来最重要的一个版本。 它引入了大量的更改&#xff0c;它曾被人们称为C0x&#xff0c;因为它被期待在2010年之前发布&#xff1b;但在2011年8月12日才被采纳。 C03到C11花了8年时间…...

【多线程初阶】wait() notify()

文章目录 协调多个线程间的执行顺序join 和 wait 区别sleep 和 wait 区别 wait()方法线程饿死调用 wait()唤醒 wait() notify()方法wait() 和 notify() 需对同一对象使用确保先 wait ,后 notify多个线程在同一对象上wait notify随机唤醒一个wait notifyAll()方法应用 wait() 和…...

安全-JAVA开发-第二天

Web资源访问的流程 由此可见 客户访问JAVA开发的应用时 会先通过 监听器&#xff08;Listener&#xff09;和 过滤器&#xff08;Filter&#xff09; 今天简单的了解下这两个模块的开发过程 监听器&#xff08;Listener&#xff09; 主要是监听 我们触发了什么行为 并进行反应…...

Python基础:文件简单操作

&#x1f343;引言 手把手带你快速上手Python Python基础专栏 一、&#x1f343;文件是什么 变量是把数据保存到内存中. 如果程序重启/主机重启, 内存中的数据就会丢失。 要想能让数据被持久化存储, 就可以把数据存储到硬盘中. 也就是在文件中保存。 通过文件的后缀名, 可以看…...

深度学习项目之RT-DETR训练自己数据集

RT-DETR 1.模型介绍&#x1f4cc; 什么是 RT-DETR &#xff1f;&#x1f4d6; 核心改进点&#x1f4ca; 结构示意&#x1f3af; RT-DETR 优势⚠️ RT-DETR 缺点&#x1f4c8; 应用场景&#x1f4d1; 论文 & 官方仓库2.模型框架3.Yaml配置文件4.训练脚本5.训练完成截图6.总结…...

以太网帧结构和封装【二】-- IP头部信息

1字节 byte 8比特 bit 【位和比特是同一个概念】 比特/位&#xff0c;字节之间的关系是&#xff1a; 位&#xff08;Bit&#xff09; 中文名&#xff1a;位&#xff08;二进制位&#xff09;。 英文名&#xff1a;Bit&#xff08;Binary Digit 的缩写&#xff09;。 含义&…...

mysql 悲观锁和乐观锁(—悲观锁)

适合悲观锁的使用场景&#xff1a; 悲观锁更适合在&#xff0c;写操作较多、并发冲突高、业务需要强一致性等场景下使用悲观锁。 如何使用悲观锁&#xff1a; 悲观锁主要通过以下两个 SQL语句实现&#xff1a; 1、SELECT...FOR UPDATE; 这个语句会在所查询中的数据行上设置排…...

Promtail采集服务器本地日志存储到Loki

✅ 一、前提条件 已安装 Loki 服务 日志文件目录可访问&#xff08;如 /var/log&#xff09; 具备 sudo 权限 &#x1f9e9; 二、下载 Promtail 二进制文件 # 替换为你想要的版本 VERSION"3.5.1"# 创建目录 sudo mkdir -p /opt/promtail cd /opt/promtail# 下载并…...

python第31天打卡

import numpy as np from tensorflow import keras from tensorflow.keras import layers, optimizers, utils, datasets# 数据加载和预处理函数 def load_and_preprocess_data():(x_train, y_train), (x_test, y_test) datasets.mnist.load_data()# 重塑并归一化图像数据x_tr…...

4.1 HarmonyOS NEXT原生AI能力集成:盘古大模型端侧部署与多模态交互实战

HarmonyOS NEXT原生AI能力集成&#xff1a;盘古大模型端侧部署与多模态交互实战 在HarmonyOS NEXT的全场景生态中&#xff0c;原生AI能力成为连接设备、服务与用户的核心纽带。通过盘古大模型端侧轻量化部署、多模态交互技术及环境感知系统&#xff0c;开发者能够构建"主…...

学习STC51单片机27(芯片为STC89C52RCRC)

每日一言 你读过的书、走过的路、流过的汗&#xff0c;终将成就独一无二的你。 硬件&#xff1a;LCD1602液晶显示 非标协议外设 概述 LCD1602&#xff08;Liquid Crystal Display&#xff09;是一种工业字符型液晶&#xff0c;能够同时显示 1602 即 32 字符(16列两行) 那我…...

PAT-甲级JAVA题解(更新中...)

使用JAVA语言进行算法练习,但是有些会出现运行超时情况. 题目链接A1001A1001-PAT甲级JAVA题解 AB FormatA1005A1005-PAT甲级JAVA题解 Spell It RightA1006A1006-PAT甲级JAVA题解 Sign In and Sign OutA1011A1011-PAT甲级JAVA题解World Cup BettingA1012A1012 PAT甲级JAVA题解 …...

Deep Chat:重塑人机对话边界的开源智能对话框架—— 让下一代AI交互无缝融入你的应用

在AI助手泛滥的今天&#xff0c;开发体验碎片化、功能扩展性差、多模态支持不足成为行业痛点。由开发者Ovidijus Parsiunas发起的开源项目 Deep Chat&#xff08;https://github.com/OvidijusParsiunas/deep-chat&#xff09;&#xff0c;正以模块化设计 全栈兼容性颠覆传统聊…...

DA14531_beacon_大小信标设备开发

蓝牙信标是一款通过广播指定蓝牙信号&#xff0c;实现信标信号扫描、识别和获得辅助信息的电子产品。 不同品名的蓝牙信标采用相同的 UUID 和广播信号格式&#xff0c;但在 MAC 地址、工作寿命、体积和广播周期上有所差异。 小武编程巧用DA14531开发一款蓝牙信标....

【算法训练营Day06】哈希表part2

文章目录 四数相加赎金信三数之和四数之和 四数相加 题目链接&#xff1a;454. 四数相加 II 这个题注意它只需要给出次数&#xff0c;而不是元组。所以我们可以分治。将前两个数组的加和情况使用map存储起来&#xff0c;再将后两个数组的加和情况使用map存储起来&#xff0c;ke…...

Word双栏英文论文排版攻略

word写双栏英文论文的注意事项 排版首先改字体添加连字符还没完呢有时候设置了两端对齐会出现这样的情况&#xff1a; 公式文献 等我下学期有时间了&#xff0c;一定要学习Latex啊&#xff0c;word写英文论文&#xff0c;不论是排版还是公式都很麻烦的&#xff0c;而Latex一键就…...

乡村三维建模 | 江苏农田无人机建模案例

测绘是农田建设的基础工作&#xff0c;测绘的质量和效率直接影响农田建设的进度和成果。传统的人工测量、地面测量等测绘手段&#xff0c;存在效率低、精度差、受环境影响大、成本高等缺点&#xff0c;难以满足高标准农田建设的要求。而无人机倾斜摄影技术具有高效、精确、灵活…...

2025 5 月 学习笔记

计算高斯半径&#xff0c;用于生成高斯热图 这个的意义是什么 有什么作用&#xff1f; 14 核心意义&#xff1a;平衡定位精度与检测鲁棒性 在基于热图的目标检测方法&#xff08;如CenterNet、CornerNet等&#xff09;中&#xff0c;计算高斯半径的核心意义在于​​在精确…...

SpringBoot(七) --- Redis基础

目录 前言 一、Redis入门 二、Redis常用数据类型 三、Redis常用命令 1. 字符串操作命令 2. 哈希操作命令 3. 列表操作命令 4. 集合操作命令 5. 有序集合操作命令 6.通用命令 四、在Java中操作Redis 前言 Redis是一个基于内存的key-value结构数据库&#xff0c;有以下…...

Oracle 故障实例 - 通过备份恢复到某时间点 故障恢复

一、环境和故障描述 1.数据库版本&#xff1a;oracle 11g , linux &#xff1b;OA系统的后台数据库。 2. 同事在做正式机数据迁移到测试机时&#xff0c;不小心删除了正式机的数据。 导致大量生产数据丢失&#xff0c;系统故障。 3.万幸的是正式机每日都做了数据备份&#x…...

滑动智能降级:Glide优化加载性能的黑科技

简介 在移动应用开发中,图片加载性能直接关系到用户体验,尤其在列表快速滑动场景下,如何平衡流畅度与流量消耗成为开发者面临的核心挑战。本文将深入探讨Glide库的智能降级技术,通过滑动速度动态调整图片加载策略,实现流量节省35%、首屏加载速度提升40%的显著效果。我们将…...

【前端并发请求控制:必要性与实现策略】

前端并发请求控制&#xff1a;必要性与实现策略 一、引言 在现代 Web 开发中&#xff0c;处理大量异步请求是一个常见场景。虽然浏览器和服务器都有其并发限制机制&#xff0c;但前端实现并发控制仍然具有其独特的价值和必要性。本文将深入探讨这个话题。 二、现有的并发限制…...

LeetCode 139. 单词拆分(Word Break) - 动态规划深度解析

文章目录 问题描述动态规划解法解法核心思路完整代码实现关键代码解析1. 数据结构初始化2. 动态规划数组3. 核心循环逻辑4. 子串区间理解(关键)示例演算复杂度分析算法优化点总结本文详细解析LeetCode 139题"单词拆分"的动态规划解法,涵盖核心思路、代码实现、区间…...

@Prometheus动态配置管理-ConsulConfd

文章目录 动态配置管理 Consul Confd**一、目标****二、架构组件****三、环境准备****四、配置 Consul**1. 注册监控目标&#xff08;服务发现&#xff09;2. 存储告警规则&#xff08;KV 存储&#xff09; **五、配置 Confd**1. 监控目标模板配置2. 告警规则模板配置 **六、配…...

CentOS7 + JDK8 虚拟机安装与 Hadoop + Spark 集群搭建实践

前言 在大数据时代&#xff0c;Hadoop 和 Spark 是两种非常重要的分布式计算框架。本文将详细介绍如何在 CentOS7 JDK8 的虚拟机环境中搭建 Hadoop Spark 分布式集群&#xff0c;包括 Spark Standalone 和 Hadoop Spark on YARN 两种模式&#xff0c;并提供具体的代码示例。…...

从OSI到TCP/IP:网络协议的演变与作用

个人主页&#xff1a;chian-ocean 文章专栏-NET 从OSI到TCP/IP&#xff1a;网络协议的演变与作用 个人主页&#xff1a;chian-ocean文章专栏-NET 前言网络发展LANWAN 协议举个例子&#xff1a; 协议的产生背景 协议的标准化OSI模型参考OSI各个分层的作用各层次的功能简介 TCP/…...

Stream流性能分析及优雅使用

文章目录 摘要一、Stream原理解析1.1、Stream总概1.2、Stream运行机制1.2.1、创建结点1.2.1、搭建流水线1.2.3、启动流水线 1.3、ParallelStream 二、性能对比三、优雅使用3.1 Collectors.toMap()3.2 findFirst()&#xff0c;findAny()3.3 增删元素3.4 ParallelStream 四、总结…...

iOS 电子书听书功能的实现

在 iOS 应用中实现电子书听书&#xff08;文本转语音&#xff09;功能&#xff0c;可以通过系统提供的 AVFoundation 框架实现。以下是详细实现步骤和代码示例&#xff1a; 核心步骤&#xff1a; 导入框架创建语音合成器配置语音参数实现播放控制处理后台播放添加进度跟踪 完整…...

【和春笋一起学C++】(十七)C++函数新特性——内联函数和引用变量

C提供了新的函数特性&#xff0c;使之有别于C语言。主要包括&#xff1a; 内联函数&#xff1b;按引用传递变量&#xff1b;默认参数值&#xff1b;函数重载&#xff08;多态&#xff09;&#xff1b;模版函数&#xff1b; 因篇幅限制&#xff0c;本文首先介绍内联函数和引用…...

GitHub 趋势日报 (2025年06月02日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 1339 prompt-eng-interactive-tutorial 1080 courses 624 onlook 596 system-desi…...