【C++11(上)】—— 我与C++的不解之缘(三十)
一、C++11
这里简单了解一下C++
发展好吧:
C++11
是C++
的第二个大版本,也是自C++98
以来最重要的一个版本。它引入了大量的更改,它曾被人们称为
C++0x
,因为它被期待在2010年之前发布;但在2011年8月12日才被采纳。
C++03
到C++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 value
和right 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
环境下编译器对拷贝的优化,会将两次拷贝构造优化成一次(上图右边所示)在
vs2022
和vs2019 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
下测试一下)
在vs2022
和vs2019 release
下,编译器会对这样的拷贝构造和拷贝赋值进行优化,会将其造优化成一次拷贝赋值。
存在移动赋值
当存在了移动构造和移动赋值以后,这里我们编译器不优化的情况下,这里也就只有一次 移动构造和一次 移动赋值。
**在vs2022
和vs2019 release
**下,编译器会对这样的移动构造和移动赋值进行优化,会将其造优化成一次移动赋值。
这里总结一下:
在只有拷贝构造和拷贝赋值的情况下,传值返回拷贝的消耗是很大的;编译器虽然做出了一些优化,将多次构造合成一次构造,但这样就会取决于编译器(编译器优化了消耗就低,编译器不优化消耗就很大)。
有了移动构造和移动拷贝之后,虽然传值返回还是会调用多次构造,但是移动构造和移动赋值只是进行资源的交换,效率是很快的,就是编译器不进行优化,两次、三次移动构造效率还是很快;这样我们程序的效率就不会取决于编译器,(编译器优化了更好,不优化效率也不会很低,消耗都很小)。
右值引用和移动语义在传参中的提效
在上述过程中,其实提效不是很明显,因为我们使用的是
string
;但是如果我们使用是
vector<string>
或者list<string>
那就可想而知,提效就很明显了。
我们在STL
文档中可以看到,C++11
之后容器的push
和insert
系列的接口都增加了右值引用的版本
- 当是实参是一个左值时,容器就会调用左值版本,调用拷贝构造进行拷贝,将对象拷贝到容器空间中的对象。
- 如果实参是一个右值,容器就会调用右值版本,调用移动构造,将右值对象的资源转移到容器空间的对象上。
这里还存在一个
emplace
系列的接口,这个在了解可变参数模板
以后再了解这一系列的接口。
这里就不演示list<string>
或者vector<string>
这些场景了好吧。
四、类型分类
在C++11
之后,对类型进行了进一步划分:
- 右值被划分成了纯右值(
pure value
,简称prvalue
)和将亡值(expiring value
,简称xvalue
)。 - 纯右值:字面值常量和求值结果相当于字面值,或者一个不具名的临时对象。(比如
10
、true
、nullptr
或者类似于str.subsr(1,3)
、str1+str2
传值返回函数的调用、或者整型a+b
、a++
等。 - 将亡值:指返回右值引用的函数的调用表达式和转化成右值引用的转换函数的调用表达式(比如
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发展好吧: C11是C的第二个大版本,也是自C98以来最重要的一个版本。 它引入了大量的更改,它曾被人们称为C0x,因为它被期待在2010年之前发布;但在2011年8月12日才被采纳。 C03到C11花了8年时间…...
python如何把列表中所有字符变成小写
在Python中,你可以使用列表推导式(list comprehension)结合字符串的.lower()方法,将列表中的所有字符串元素转换为小写。以下是一个示例: # 定义一个包含字符串的列表 strings ["Hello", "WORLD"…...
DEAP数据集介绍
DEAP数据集介绍 0. 数据集摘要1. 文件列表2. 文件详细信息2.1 Online_ratings2.2 Video_list2.3 Participant_ratings2.4 Participant_questionnaire2.5 Face_video.zip2.6 Data_original.zip2.7 Data_preprocessed_matlab.zip 和 Data_preprocessed_python.zip 3. References欢…...
基于RDK X3的“校史通“机器人:SLAM导航+智能交互,让校史馆活起来!
视频标题: 【校史馆の新晋顶流】RDK X3机器人:导览员看了直呼内卷 视频文案: 跑得贼稳团队用RDK X3整了个大活——给校史馆造了个"社牛"机器人! 基于RDK X3开发板实现智能导航与语音交互SLAM技术让机器人自主避障不…...
JavaScript基础-window.localStorage
在现代Web开发中,数据存储对于提升用户体验至关重要。window.localStorage 是一种简单而强大的客户端存储机制,允许网页以键值对的形式持久化保存数据。与 sessionStorage 不同,localStorage 中的数据不会因浏览器标签页关闭或刷新而丢失&…...
在航电系统中提高可靠性的嵌入式软件设计
1.总线余度设计 数据传输采用双余度总线设计,CANFD为主,RS485为备。发送方将相同的数据分别通过双总线来发送,接收方优先处理主线数据。由于总线上数据频率固定,可设置定时器监控主总线的数据,当定时器超时后ÿ…...
H.266/VVC SCC技术学习:块差分脉冲编码调整(block differential pulse coded modulation, BDPCM)
近年来,屏幕内容视频广泛用于多媒体应用,例如远程桌面,屏幕共享等。由于屏幕内容视频的特性与自然视频有较大区别,VVC中使用了帧内块复制(intra block copy, 即IBC), 调色板(Palette)࿰…...
网络编程—Socket套接字(TCP)
上篇文章: 网络编程—Socket套接字(UDP)https://blog.csdn.net/sniper_fandc/article/details/146923670?fromshareblogdetail&sharetypeblogdetail&sharerId146923670&sharereferPC&sharesourcesniper_fandc&sharefro…...
数据结构:二叉树(三)·(重点)
二叉树的存储结构 ⼆叉树⼀般可以使⽤两种结构存储,⼀种顺序结构,⼀种链式结构。 顺序结构 顺序结构存储就是使⽤数组来存储,⼀般使⽤数组只适合表⽰完全⼆叉树,因为不是完全⼆叉树会有 空间的浪费,完全⼆叉树更适合…...
StyleTTS 2:文本到语音(Text-to-Speech, TTS)模型
StyleTTS 2 是一种先进的文本到语音(Text-to-Speech, TTS)模型,通过结合风格扩散(style diffusion)和对抗训练(adversarial training),利用大规模语音语言模型(Speech La…...
痉挛性斜颈康复路,饮食要点来相助
痉挛性斜颈患者除了接受正规治疗,合理饮食对缓解症状、促进康复也至关重要。 高蛋白质食物是饮食中的重点。像鸡蛋,富含人体必需的氨基酸,其组成与人体组成模式接近,易于吸收。每天吃 1 - 2 个鸡蛋,能为身体补充修复肌…...
谷歌 Gemini 2.5 Pro 免费开放
2025 年 3 月 30 日,谷歌宣布将最新的 Gemini AI 旗舰模型 Gemini 2.5 Pro 免费向所有 Gemini 应用用户开放。以下是关于此次免费开放的一些具体信息1: 背景:此前,Gemini 2.5 Pro 仅向支付 19.99 美元月费的 Gemini Advanced 用户…...
规则引擎Drools
1.规则引擎概述 1.1 什么是规则引擎 规则引擎 全称为业务规则管理系统,英文名为BRMS,规则引擎的主要思想是将应用程序中的业务决策部分分离出来,并使用预定义的语义模块编写业务规则,由用户或开发者在需要时进行配置和管理。 需…...
第三季:挪威
挪威 挪威是北欧的一个国家,位于斯堪的纳维亚半岛的西部。以下是关于挪威的详细介绍: 地理位置与自然环境 位置:挪威位于北欧,东邻瑞典,东北与芬兰和俄罗斯接壤,西濒挪威海,北临巴伦支海。地…...
搜索与图论 树的深度优先遍历 树的重心
树的一种特殊的图,无环连通图 图还分为有向图,无向图 但是无向图其实也是特殊的有向图 (a指向b,b也指向a,每个连接节点都如此,则是无向图) 那我们只需要讨论有向图 有向图的分类 邻接矩阵 …...
ORA-09925 No space left on device 问题处理全过程记录
本篇文章关键字:linux、oracle、审计、ORA-09925 一、故障现像 朋友找到我说是他们备份软件上报错。 问题比较明显,ORA-09925,看起来就是空间不足导致的 二、问题分析过程 这里说一下逐步的分析思路,有个意外提前说一下就是我…...
Java开发者の模型召唤术:LangChain4j咏唱指南(三)
Java开发者の模型召唤术:LangChain4j咏唱指南(三) 往期回顾: Java开发者の模型召唤术:LangChain4j咏唱指南(一)Java开发者の模型召唤术:LangChain4j咏唱指南(二) 上两期博客中简单的为大家介绍了 langchain4j是什么、java 集成…...
【leetcode100】动态规划Java版本
70. 爬楼梯 题目 思考的时候觉得情况很多,无从下手,卡在了找推导公式这一步。 看了随想录后知道以简单的三个阶梯来推导dp公式,为什么不是四个,五个为一组呢?因为题目要求的只能爬1个阶梯,或者2个阶梯&…...
RSA和ECC在密钥长度相同的情况下哪个更安全?
现在常见的SSL证书,如:iTrustSSL都支持RSA和ECC的加密算法,正常情况下RAS和ECC算法该如何选择呢?实际上在密钥长度相同的情况下,ECC(椭圆曲线密码学)通常比RSA(Rivest-Shamir-Adle…...
YOLO 获取 COCO 指标终极指南 | 从标签转换到 COCOAPI 评估 (训练/验证) 全覆盖【B 站教程详解】
✅ YOLO 轻松获取论文 COCO 指标:AP(small,medium,large )| 从标签转换到 COCOAPI 评估 (训练/验证) 全覆盖 文章目录 一、摘要二、为什么需要 COCO 指标评估 YOLO 模型?三、核心挑战与解决方案 (视频教程核…...
【算法竞赛】dfs+csp综合应用(蓝桥2023A9像素放置)
目录 一、 题目 二、思路 (1)算法框架选择 (2)剪枝策略 具体来说就是: 三、代码 (1) 数据读取与初始化 (2) 检查当前填充是否符合要求 (3) 递归 DFS 进行填充 (4) 读取输入 & 调用 DFS (5) 完整代码 一…...
3D点云配准RPM-Net模型解读(附论文+源码)
RPM-Net 总体流程代码数据预处理模型计算 α α α和 β β β特征提取变换矩阵计算损失 论文链接:RPM-Net: Robust Point Matching using Learned Features 官方链接:RPMNet 老规矩,先看看效果。 看看论文里给的对比图 总体流程 在学…...
23种设计模式-行为型模式-命令
文章目录 简介问题解决代码核心设计优势 总结 简介 命令是一种行为设计模式, 它能把请求转换为一个包含与请求相关的所有信息 的独立对象。这个转换能让你把请求方法参数化、延迟请求执行或把请求放在队列里,并且能实现可撤销操作。 问题 假如你正在开…...
ngx_cpystrn
定义在 src\core\ngx_string.c u_char * ngx_cpystrn(u_char *dst, u_char *src, size_t n) {if (n 0) {return dst;}while (--n) {*dst *src;if (*dst \0) {return dst;}dst;src;}*dst \0;return dst; } ngx_cpystrn 函数的作用是安全地将源字符串(src&#x…...
常用的国内镜像源
常见的 pip 镜像源 阿里云镜像:https://mirrors.aliyun.com/pypi/simple/ 清华大学镜像:https://pypi.tuna.tsinghua.edu.cn/simple 中国科学技术大学镜像:https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣镜像:https://pypi.doub…...
【小沐杂货铺】基于Three.JS绘制太阳系Solar System(GIS 、WebGL、vue、react)
🍺三维数字地球系列相关文章如下🍺:1【小沐学GIS】基于C绘制三维数字地球Earth(456:OpenGL、glfw、glut)第一期2【小沐学GIS】基于C绘制三维数字地球Earth(456:OpenGL、glfw、glut)第二期3【小沐…...
Navicat17详细安装教程(附最新版本安装包和补丁)2025最详细图文教程安装手册
目录 前言:为什么选择Navicat 17? 一、下载Navicat17安装包 二、安装Navicat 1.运行安装程序 2.启动安装 3.同意“协议” 4.设置安装位置 5.创建桌面图标 6.开始安装 7.安装完成 三、安装补丁 1.解押补丁包 2.在解压后的补丁包目录下找到“w…...
记忆宫殿APP:全方位脑力与思维训练,助你提升记忆力,预防老年痴呆
记忆宫殿APP,一款专业的记忆训练软件,能去帮你提升自己的记忆能力,多样的训练项目创新的记忆方法,全方面帮你去提升你的记忆能力。 记忆宫殿APP有丰富的记忆训练项目,如瞬间记忆、短时记忆、机械记忆等,以…...
SpringBoot+Spring+MyBatis相关知识点
目录 一、相关概念 1.spring框架 2.springcloud 3.SpringBoot项目 4.注解 5.SpringBoot的文件结构 6.启动类原理 二、相关操作 1.Jar方式打包 2.自定义返回的业务状态码 3.Jackson 4.加载配置文件 5.异常处理 三、优化配置 1.简化sql语句 2.查询操作 复杂查询 一…...
【力扣hot100题】(050)岛屿数量
一开始还以为会很难很难(以为暴力搜索会时间超限要用别的办法),没想到并不难。 我最开始是用vector<vector<bool>>记录搜索过的地域,每次递归遍历周围所有地域。 class Solution { public:vector<vector<char…...
Opencv计算机视觉编程攻略-第九节 描述和匹配兴趣点
一般而言,如果一个物体在一幅图像中被检测到关键点,那么同一个物体在其他图像中也会检测到同一个关键点。图像匹配是关键点的常用功能之一,它的作用包括关联同一场景的两幅图像、检测图像中事物的发生地点等等。 1.局部模板匹配 凭单个像素就…...
pat学习笔记
two pointers 双指针 给定一个递增的正整数序列和一个正整数M,求序列中的两个不同位置的数a和b,使得它们的和恰好为M,输出所有满足条件的方案。例如给定序列{1,2,3,4,5,6}和正整数M 8,就存在268和358成立。 容易想到࿱…...
MoE Align Sort在医院AI医疗领域的前景分析(代码版)
MoE Align & Sort技术通过优化混合专家模型(MoE)的路由与计算流程,在医疗数据处理、模型推理效率及多模态任务协同中展现出显著优势,其技术价值与应用意义从以下三方面展开分析: 一、方向分析 1、提升医疗数据处理效率 在医疗场景中,多模态数据(如医学影像、文本…...
大数据概念介绍
这节课给大家讲一下大数据的生态架构, 大数据有很多的产品琳琅满目, 大家看到图上就知道产品很多, 那这些产品它们各自的功能是什么, 它们又是怎么样相互配合, 来完成一整套的数据存储, 包括分析计算这样的一些任务, 这节课就要给大家进行一个分析, 那我们按照数据处理…...
Linux(2025.3.15)
1、将/etc/passwd,/etc/shadow,/etc/group文件复制到/ceshi; 操作: (1)在根目录下创建ceshi目录; (2)复制; 结果: 2、找到/etc/ssh目录下ssh开头的所有文件并将其复制到…...
centosububntu设置开机自启动
一、centos 1.将脚本放到/etc/rc.d/init.d路径下 2.给脚本授权 sudo chmod -R 777 脚本名称 3.添加脚本至开机启动项中 sudo chkconfig --add 脚本名称 4.开启脚本 sudo chkconfig 脚本名称 on 5.查看开机启动项中是否包含该脚本 ls /etc/rc.d/rc*.d 二、ubuntu 1.在…...
基于大模型与动态接口调用的智能系统(知识库实现)
目录 引言 1、需求背景 2、实现原理 3、实现步骤 3.1 构建知识库接口调用提示模板 3.2 动态接口配置加载 3.3 智能参数提取链 3.4 接口智能路由 3.5 建议生成链 3.6 组合完整工作流 3.7 展示效果 总结 引言 在医疗信息化快速发展的今天,我们开发了一个智能问诊系…...
23种设计模式-行为型模式-迭代器
文章目录 简介问题解决代码设计关键点: 总结 简介 迭代器是一种行为设计模式,让你能在不暴露集合底层表现形式(列表、栈和树等)的情况下遍历集合中所有的元素。 问题 集合是编程中最常使用的数据类型之一。 大部分集合使用简单列表存储元素。但有些集…...
【Java集合】ArrayList源码深度分析
参考笔记: java ArrayList源码分析(深度讲解)-CSDN博客 【源码篇】ArrayList源码解析-CSDN博客 目录 1.前言 2. ArrayList简介 3. ArrayList类的底层实现 4. ArrayList的源码Debug 4.1 使用空参构造 (1)开始De…...
ISIS单区域抓包分析
一、通用头部报文 Intra Domain Routing Protocol Discriminator:域内路由选择协议鉴别符:这里是ISIS System ID Length:NSAP地址或NET中System ID区域的长度。值为0时,表示System ID区域的长度为6字节。值为255时,表…...
关键业务数据如何保持一致?主数据管理的最佳实践!
随着业务规模的扩大和系统复杂性的增加,如何确保关键业务数据的一致性成为许多企业面临的重大挑战。数据不一致可能导致决策失误、运营效率低下,甚至影响客户体验。因此,主数据管理(Master Data Management,简称MDM&am…...
ISIS单区域配置
一、什么是ISIS单区域 ISIS(Intermediate System to Intermediate System,中间系统到中间系统)单区域是指使用ISIS路由协议时,所有路由器都位于同一个区域(Area)内的网络配置。 二、实验拓扑 三、实验目的…...
Visual Basic语言的物联网
Visual Basic语言在物联网中的应用 引言 物联网(IoT)作为一种新兴的技术趋势,正在深刻地改变我们的生活方式与工业制造过程。在众多编程语言中,Visual Basic(VB)凭借其简单易用的特性,逐渐成为…...
【小沐杂货铺】基于Three.JS绘制三维数字地球Earth(GIS 、WebGL、vue、react)
🍺三维数字地球系列相关文章如下🍺:1【小沐学GIS】基于C绘制三维数字地球Earth(456:OpenGL、glfw、glut)第一期2【小沐学GIS】基于C绘制三维数字地球Earth(456:OpenGL、glfw、glut)第二期3【小沐…...
Vite环境下解决跨域问题
在 Vite 开发环境中,可以通过配置代理来解决跨域问题。以下是具体步骤: 在项目根目录下找到 vite.config.js 文件:如果没有,则需要创建一个。配置代理:在 vite.config.js 文件中,使用 server.proxy 选项来…...
嵌入式Linux开发环境搭建,三种方式:虚拟机、物理机、WSL
目录 总结写前面一、Linux虚拟机1 安装VMware、ubuntu18.042 换源3 改中文4 中文输入法5 永不息屏6 设置 root 密码7 安装 terminator8 安装 htop(升级版top)9 安装 Vim10 静态IP-虚拟机ubuntu11 安装 ssh12 安装 MobaXterm (SSH)…...
React项目在ts文件中使用router实现跳转
前言: 默认你已经进行了router的安装,目前到了配置http请求的步骤,在配置token失效或其他原因,需要实现路由跳转。在普通的 TypeScript 文件中无法直接使用Router的 useNavigate Hook。Hook 只能在 React 组件或自定义 Hook 中调用…...
Java中的正则表达式Lambda表达式
正则表达式&&Lambda表达式 正则表达式和Lambda表达式是Java编程中两个非常实用的特性。正则表达式用于字符串匹配与处理,而Lambda表达式则让函数式编程在Java中变得更加简洁。本文将介绍它们的基本用法,并结合示例代码帮助理解。同时要注意&…...
【idea设置文件头模板】
概述 设置模板,在创建java类时,统一添加内容(作者、描述、创建时间等等自定义内容),给java类添加格式统一的备注信息。 1、在settings 中找到File and Code Templates 选择File Header 2、模板内容示例 /*** Author hweiyu* Descriptio…...
我与数学建模之顺遂!
下面一段时期是我一段真正走进数模竞赛的时期。 在大二上学期结束之后,就开始张罗队友一起报名参加美赛,然后同时开始学LaTeX和Matlab,当时就是买了本Matlab的书,把书上的例题还有课后题全部做完了,然后用latex将书上…...