c++ 左值引用 右值引用 统一引用 及 参数引用 & 与&&
右值引用 左值引用 统一引用 引用语义转移 万能引用引用折叠
先了解引用的作用:C++引用的作用_Lao_tan的博客-CSDN博客_c++引用的作用
右值引用_百度百科
C++中rvalue和lvalue详悉_Naruto_Q的博客-CSDN博客_lvalue rvalue
lvalue(left value)、rvalue(right value)是c语言编译过程中的用于描述等号左右值的符号。但是不能直观的通过在等号左边还是右边来判断是lvalue还是rvalue。能够取地址的,有名字的就是左值;反之,不能取地址,没有名字的就是右值。
左值(lvalue)可以是明确的变量。右值(rvalue)可以是临时变量,比如表达式、函数返回值、常量等。
int a = 1;
int b;//下面为左值引用
int &aa0 = a;//ture
int &bb0 = b;//ture
int &aa1 =1;//error
int &aa1 = a+1;//error//下面为右值引用
int &&a1 = a;//error
int &&b1 = b;//error
int &&a2 = 1;//ture
int &&a3 = a+1;//ture
int &&a4 = std::move(a);//ture ,std::move()强制将左值转换成右值
//可以通过这个函数来判断是lvalue还是rvalue。
bool is_r_value(int &&)
{
return true;
}
bool is_r_value(const int &)
{
return false;
}
可以将C++的引用理解为指针常量。引用必须在声明时初始化,声明之外的赋值操作都是对引用所代指地址的内容访问。
C++参数中的& 和&& 都表示引用。左值的引用(lvalue)声明符号为”&”, 为了和左值区分,右值的引用(rvalue)声明符号为”&&”。 右值引用变量初始化之后引用本身变成了lvalue。所以无法进行“&&=&&”,从而达到禁止右值引用隐式传递的目的;但是左值引用可以传递,所以可以进行“&=&”。引用初始化必须满足lvalue = lvalue或者rvalue = rvalue。“const type &” 或“type const &”(比如const int &)被称为万能引用,不管是值、指针、左引用还是右引用,它都能“照单全收”。
不同编辑器表现可能会有差异。比如vs中对类或结构体操作时不一定非要满足lvalue = lvalue或者rvalue = rvalue的原则。
static int t = 0;
class Whore {
public:
char buf[10];
Whore()
{
itoa(t++,buf,10);
cout << "Whore():" << buf<< endl;
}
~Whore()
{
cout << "~Whore():"<<buf << endl;
}
}
void fun(int &a)
{
cout << "fun(int &a)" << endl;
}
void fun(int &&a)//&&用于存放右值(可以是表达式、临时变量等等)的引用
{
cout << "fun(int &&a)" << endl;
}
void fun1(Whore &a)
{
cout << "fun1(Whore &a)" << endl;
}
void fun1(Whore &&a)
{
cout << "fun1(Whore &&a)" << endl;
}
void fun2(Whore &a)
{
cout << "fun2(Whore &a)" << endl;
}
void fun3(int &a)
{
cout << "fun3(int &a)" << endl;
}
int main()
{
int a = 0;
Whore w;
fun(a);
fun(1); //1为匿名临时变量
fun1(w);
fun1(Whore());//Whore()为匿名临时变量,生命周期为fun1(Whore && a)的函数栈。
//fun2(Whore()); //vs编辑器不报错,编译不报错,可执行;qt编辑器报错:(no matching function for call to 'fun2'),不可执行
//fun3(1); //vs编辑器报错:(非常量引用的初始值必须为左值),编译报错:(无法将参数 1 从“int”转换为“int &”),不可执行;qt编辑器报错:(no matching function for call to 'fun3'),不可执行
cout<<"end"<<endl;
int &a0 = a;用变量对左值引用进行初始化,等价于lvalue = lvalue。
a0 = 2;
//int &a1 = 1;//vs编辑器报错:(非常量引用的初始值必须为左值),编译器报错:(无法从“int”转换为“int &”);qt编辑器报错:(error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int') ,qt编译器报错:(无法从“int”转换为“int &”)
//int &a1 = a+1;//vs编辑器报错:(非常量引用的初始值必须为左值),编译器报错:(无法从“int”转换为“int &”);qt编辑器报错:(error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int') ,qt编译器报错:(无法从“int”转换为“int &”)
int &a1 = a0;//用左值引用对左值引用进行初始化,等价于lvalue = lvalue。
int *pa = &a0;
const int &a2 = 1;//该语句在qt和vs中的汇编语言等价于“int &&a2 = 1;”,但是该语句a2指向的地址的内容无法修改。const int & 称为万能引用,不管是值、指针、左引用还是右引用,它都能“照单全收”。
const int &a2_1 = *pa;//右值引用(表达式)
const int &a2_2 = a0;//左值引用
const int &a2_3 = a0+1;//右值引用(表达式)
int &&a3 = 1;//用常数对右值引用进行初始化,等价于rvalue = rvalue。
a3 = 2;
const int &&a4 = 1;
//int &&a5 = a;//用变量对右值引用初始化,等价于rvalue = lvalue,失败。//vs编辑器报错:(无法将右值引用绑定到左值),编译器报错:(无法从“int”转换为“int &&”);qt编辑器报错:(error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int') ,qt编译器报错:(无法从“int”转换为“int &&”)
//int &&a6 = a0;//vs编辑器报错:(无法将右值引用绑定到左值),编译器报错:(无法从“int”转换为“int &&”);qt编辑器报错:(rvalue reference to type 'int' cannot bind to lvalue of type 'int'),qt编译器报错:(无法从“int”转换为“int &&”)
int &&a7 = a+0;//用表达式对右值引用进行初始化,等价于rvalue = rvalue。
int &&a8 = a0+0;//用表达式对右值引用进行初始化,等价于rvalue = rvalue。
//int &&a9 = a3;//用已经初始化的右值引用对右值引用初始化,然而引用初始化之后引用本身变成了lvalue,等式等价于rvalue = lvalue,所以无效。//vs编辑器报错:(无法将右值引用绑定到左值),编译器报错:(无法从“int”转换为“int &&”);qt编辑器报错:(error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int') ,qt编译器报错:(无法从“int”转换为“int &&”)
int &&a10 = a3+0;//用表达式对右值引用进行初始化,等价于rvalue = rvalue。
//const int &&a11 = a0;//vs编辑器报错:(无法将右值引用绑定到左值),编译器报错:(无法从“int”转换为“const int &&”);qt编辑器报错:(error: non-const lvalue reference to type 'const int' cannot bind to a temporary of type 'int') ,qt编译器报错:(无法从“int”转换为“int &&”)
int &a12 = a3;//用已经初始化的右值引用对左值引用初始化,相当于进行lvalue = lvalue操作。
fun(a);
fun(a0);
fun(a3);
//fun(a4);//vs编辑器报错:(没有与参数列表匹配的 重载函数 "fun" 实例),vs编译器报错:(“fun”: 2 个重载中没有一个可以转换所有参数类型);qt编辑器报错:( no matching function for call to 'fun'),编译器报错:(“fun”: 2 个重载中没有一个可以转换所有参数类型)
fun(1+1);
fun(a+1);
fun(a0+0);
fun(a3+1);
return 0;
}
/*输出
Whore():0
fun(int &a)
fun(int &&a)
fun1(Whore &a)
Whore():1
fun1(Whore &&a)
~Whore():1
end
fun(int &a)
fun(int &a)
fun(int &a)
fun(int &&a)
fun(int &&a)
fun(int &&a)
fun(int &&a)
~Whore():0
*/
const int * ,const int *& , int * const , int * const &。无法将int * 转换成const int *&。
无法从“int *”转换为“int *&”?详解C++引用(&)使用方法_代码乌龟的博客-CSDN博客_无法从int?转换为int
int d = 10;
int *p = &d;
const int * p1 = &d;
int d1 = 20;
const int * p2 = &d1;
typedef int * IntT;
typedef int const * IntTC;
const IntT &pi = p;//const IntT 等价于IntT const ,等价于int * const
IntT const &pi1 = p;
int * &pi2 = p;
int * const &pi3 = p;
//const int * &pi4 = p;//vs编辑器报错:无法用 "int *" 类型的值初始化 "const int *&" 类型的引用(非常量限定) ;vs编译器报错: 无法从“int *”转换为“const int *&”
//IntTC &pi5 = p;//同上
const int *&pi6 = p1;
//pi4 = a;//error,vs编辑器报错:表达式必须是可修改的左值
*pi = 1;
//pi = a;//vs编辑器报错:表达式必须是可修改的左值
*pi2 = 1;
pi2 = a;
*pi2 = 1;
//pi2 = a;//error,vs编辑器报错:表达式必须是可修改的左值
pi6 = p2;
//*pi6 = 11;//error,vs编辑器报错:表达式必须是可修改的左值
C++临时变量的生命周期_snail0428的博客-CSDN博客_临时变量生命周期
【C++】拷贝构造函数的调用时机_翼同学的博客-CSDN博客_c++拷贝构造函数什么时候调用
返回引用的意义_陈小淘的博客-CSDN博客_函数返回引用的含义
C++将返回值为引用有什么作用? - 知乎
(右值)引用可以改变临时变量的生命周期!!!一般而言,函数返回类型不能返回右值引用:T &&,返回右值引用在逻辑上不成立,且会造成非法越界访问。返回的左值引用也可能会造成越界访问,需要理解清楚左值引用所引用对象的生命周期。std::forward<T>(var)与std::move(var)两者分别用于对变量var进行转移语义和转换语义,目的都是改变参数的引用类型。
static int t = 0;
class Whore {
public:
char nickname[10];
Whore()
{
_itoa(t++,buf,10);
cout << "Whore():"<< nickname << endl;
}
Whore(Whore & w)
{
char buf[10];
nickname = _itoa(t++, buf, 10);
cout << "Whore(Whore & w):" << nickname << endl;
}
~Whore()
{
cout << "~Whore():"<<nickname << endl;
}
Whore& operator =(const Whore & w)
{
///char buf[10];
//nickname = _itoa(t++, buf, 10);
cout << "Whore& operator =(const Whore & w):" << nickname << ":" << w.nickname << endl;
strcpy(nickname , w.nickname);
return *this;
}
Whore& operator =(Whore && w)
{
///char buf[10];
//nickname = _itoa(t++, buf, 10);
cout << "Whore& operator =(Whore && w):" << nickname <<":"<<w.nickname<< endl;
strcpy(nickname , w.nickname);
return *this;
}
}
Whore fun4()
{
return Whore();
}
Whore fun4_1()
{
Whore w = Whore();
return w;
}
Whore & fun5()//错误示范1,返回临时或局部变量的引用,在函数退出后临时变量会被回收,引用也会变的无效,fun5()之外再访问w的成员造成越界。
{
Whore w = Whore();
return w;//临时或局部变量在函数调用完后会被释放,外部再使用会导致越界访问。
}
Whore && fun5_1()//错误示范2,返回临时或局部变量的引用,在函数退出后临时变量会被回收,引用也会变的无效,fun5_1()之外再访问w的成员造成越界。
{
return Whore();//临时或局部变量在函数调用完后会被释放,外部再使用会导致越界访问。
}
Whore & fun6()//错误示范3,左值引用无法改变变量生命周期
{
Whore wt = Whore();
static Whore & w = wt;//左值引用无法改变局部变量生命周期。
return w;
}
Whore & fun6_1()
{
static Whore & w = Whore();//在vs中能运行成功,在qt中编辑器报错。这里用临时变量初始化左值引用,相当于lvalue = rvalue,正常来讲应该是要报错的。在vs中,这里直接将该临时变量变成全局变量来使用了。
return w;
}
Whore & fun6_2()
{
static Whore && w = Whore();//右值引用可以改变临时变量的生命周期。
return w;
}
Whore fun6_3()
{
static Whore && w = Whore();//右值引用可以改变临时变量的生命周期。
return w;
}
Whore fun7()
{
static Whore w = Whore();
return w;
}
Whore& fun7_1()
{
static Whore w = Whore();
return w;
}
void fun8(Whore & w)
{
cout << "void fun8(Whore & w)" << endl;
}
void fun8(Whore && w)
{
cout << "void fun8(Whore && w)" << endl;
}
int main()
{
/*
//下面代码运行成功。注意fun4与fun4_1返回变量与返回临时变量的差异。
Whore w1 = fun4();//debug 和release下结果不一样,debug中调用构造函数后再调用拷贝构造,release中只调用构造函数。
cout<<w1.nickname<<endl;
Whore w2;
cout<<w2.nickname<<endl;
w2 = fun4();//调用了赋值重载函数,此时临时变量的生命周期在这条语句结束时结束了。
cout<<w2.nickname<<endl;
Whore w3 = fun4_1();//只调用了一次构造函数,等价于Whore ww1 = Whore();此时临时变量的生命周期与ww1等价了。
cout<<w2.nickname<<endl;
Whore &w4 = fun4();
cout<<w4.nickname<<endl;
Whore &w5 = fun4_1();
cout<<w5.nickname<<endl;
Whore &&w6 = fun4();//右值引用拉长了临时变量的生存周期
cout << w6.nickname << endl;
Whore &&w7 = fun4_1();//右值引用拉长了临时变量的生存周期
cout << w7.nickname << endl;
*//*debug输出:
Whore():0
Whore(const Whore &w):1
~Whore():0
1
Whore():2
2
Whore():3
Whore(const Whore &w):4//w2 = fun4();在debug中调用了拷贝构造之后再调用赋值重载函数函数。拷贝构造的作用应该是将fun4中的返回的临时变量先存放到main中一个临时变量值中,然后再main中的临时变量赋值给w2。这个过程设计的内存操作相当的多。在release下有很大的精简。
~Whore():3
Whore& operator =(Whore && w):2:4
~Whore():4
4
Whore():5
4
Whore():6
Whore(const Whore &w):7
~Whore():6
7
Whore():8
8
Whore():9
Whore(const Whore &w):10
~Whore():9
10
Whore():11
11
end
~Whore():11
~Whore():10
~Whore():8
~Whore():7
~Whore():5
~Whore():4
~Whore():1
*//*release 输出: //release 模式下少了很多拷贝构造函数的调用,临时变量在fun4退出后并没有被西沟,意味着都直接延长了临时变量生命周期。在release模式下,右值引用与普通变量基本没有差别
Whore():0
0
Whore():1
1
Whore():2
Whore& operator =(Whore && w):1:2
~Whore():2
2
Whore():3
2
Whore():4
4
Whore():5
5
Whore():6
6
Whore():7
7
end
~Whore():7
~Whore():6
~Whore():5
~Whore():4
~Whore():3
~Whore():2
~Whore():0
*/
/*
//下面代码在qt中失败,在vs中成功。按规则来讲,临时变量是不能赋值给左值引用的,但vs中对类或结构体操作时是可以的。
Whore &w = fun4();
cout << w.nickname << endl;
*/
/*
//下面的代码导致崩溃。fun5返回的是临时变量的引用,而该临时变量的生命周期只在“Whore &w = fun5();”语句之中,在main中通过引用再访问变量的内容时,变量已经被销毁了,所以再访问就是越界访问了。
Whore &w = fun5();
cout<<w.nickname<<endl;
Whore &w1 = fun5_1();
cout<<w.nickname<<endl;
*//*release输出:
Whore():0
~Whore():0//fun5中的临死变量在fun5退出后被析构了,与fun4 的区别在于一个是返回了临时变量或局部,一个是返回了临时或局部变量的引用。
Whore(const Whore &w):1
1
Whore():2
~Whore():2
Whore(const Whore &w):3
3
end
~Whore():3
~Whore():1
*//*debug输出:
Whore():0
~Whore():0
Whore(const Whore &w):1
1
Whore():2
~Whore():2
Whore(const Whore &w):3
3
end
~Whore():3
~Whore():1
*/
/*
//下面代码导致崩溃。fun6中定义了一个全局静态左值引用,并且用一个局部变量初始化左值引用,然而局部变量的生命周期只在fun6的函数栈内,在main中通过fun6返回的引用再访问变量的内容时,变量已经被销毁了,所以再访问就是越界访问了。
Whore &w = fun6();
cout << w.nickname << endl;
Whore &w = fun6_1();
cout<< w.nickname <<endl;
*/
/*下面代码运行成功:展示了返回引用的一种正确用法,返回引用更多的是用在返回对象成员变量或传入参数的成员变量
//fun6中用临时变量初始化右值引用,是合法的,临时变量生命周期也强制变长了。全局静态变量w初始化之后就成了lvalue了然后以引用的形式进行返回。
Whore &w = fun6_2(); //整个过程只有一次在fun6_2中调用一次构造函数。返回引用的正确用法,人为避免调用拷贝构造函数
cout << w.nickname << endl;
*//*debug/release输出:
Whore():0
0
end
~Whore():0
*/
/*下面代码运行成功:都是展示函数返回引用后的错误用法
Whore w1 = fun6_2(); //函数虽然返回引用,但是依然调用拷贝构造函数。
cout << w1.nickname << endl;
//Whore &&w2 = fun6_2()//qt编辑器报错,不能将rvalue绑定到lvalue。
//Whore &w3 = fun6_3(); //qt编辑器报错,不能将lvalue绑定到临时变量
Whore w4 = fun6_3();//将fun6_3返回的右值引用赋值给普通变量,会调用拷贝构造函数。注意fun4的区别,fun4不会调用拷贝构造函数。
cout << w4.nickname << endl;
Whore && w5 = fun6_3(); //将fun6_3返回的右值引用赋值给右值引用,依然会调用拷贝构造函数。
cout << w5.nickname << endl;
*//*debug/release输出:
Whore():0
Whore(const Whore &w):1
1
Whore():2
Whore(const Whore &w):3
3
Whore(const Whore &w):4
4
end
~Whore():4
~Whore():3
~Whore():1
~Whore():2
~Whore():0
*/
/*下面代码运行成功:正确用法
Whore &w = fun7_1();//整个过程只有在fun7_1中一次调用了构造函数。人为避免调用拷贝构造函数
cout<<w.nickname<<endl;
*//*debug/release 输出:
Whore():0
0
end
~Whore():0
*/
/*下面代码运行成功:错误用法
Whore wz = fun7();
cout<<wz.nickname<<endl;
Whore &&w1 = fun7();
cout<<w1.nickname<<endl;
Whore w2 = fun7_1();
cout<<w2.nickname<<endl;
*//*debug/release输出:
Whore():0
Whore(const Whore &w):1
1
Whore(const Whore &w):2
2
Whore():3
Whore(const Whore &w):4
4
end
~Whore():4
~Whore():2
~Whore():1
~Whore():3
~Whore():0
*/
/*
//下面代码运行成功。
Whore w;
fun8(w);
fun8(Whore());
*//*输出:
Whore():0
void fun8(Whore & w)
Whore():1
void fun8(Whore && w)
~Whore():1
end
~Whore():0
*/
cout <<"end"<<endl;
return 0;
}
在C++编程中,了解右值引用并充分利用右值引用,可以减少无意义的拷贝,极大提升程序性能。
int main()
{
string str = "afef";//调用basic_string(const _Elem *_Ptr)
string str0 = str; //调用basic_string(const _Myt& _Right)
cout << "str:" << str << endl << "str0:" << str0 << endl;
string str1;//调用basic_string()
str1 = str;//调用_Myt& operator=(const _Myt& _Right)
cout << "str:" << str << endl << "str1:" << str1 << endl;
string str2;
str2 = std::move(str);//调用_Myt& operator=(_Myt&& _Right) ,remove_reference<_Ty>::type&&move(_Ty&& _Arg)。
//_Myt& operator=(_Myt&& _Right) 中直接夺取了转变为右值引用后的str的内容存储空间,从而减少不必要的拷贝。
cout << "str:"<<str << endl<<"str2:"<< str2 << endl;
string str3 = std::move(str0);//调用basic_string(_Myt&& _Right) ,remove_reference<_Ty>::type&&move(_Ty&& _Arg)
cout << "str0:" << str0 << endl << "str3:" << str3 << endl;
/*输出:
str:afef
str0:afef
str:afef
str1:afef
str:
str2:afef
str0:
str3:afef
*/
return 0;
}
统一引用:条款24:区别开统一引用和右值引用_coolmeme的博客-CSDN博客
引用折叠:主要用于方便编写模板代码,很大程度上减少了因为左右值引用差异而导致的代码的重写
引用折叠和完美转发 - 知乎
引用语义转移std::forward 与 引用语义转换std::move :
// TEMPLATE FUNCTION forward
template<class _Ty> inline
_CONST_FUN _Ty&& forward(
typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT
{ // forward an lvalue as either an lvalue or an rvalue
return (static_cast<_Ty&&>(_Arg));
}
template<class _Ty> inline
_CONST_FUN _Ty&& forward(
typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT
{ // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
return (static_cast<_Ty&&>(_Arg));
}
void tun(int &a)
{
cout << "lvalue" << endl;
}
void tun(int &&a)
{
cout << "rvalue" << endl;
}
//声明了引用参数的重载函数就不能再声明无引用参数的重载函数,比如此处声明了tun(int&)或tun(int&&)
//就不能再声明tun(int)。否则会出现
//编辑器可能报错:有多个 重载函数 "tun" 实例与参数列表匹配
//编译器可能报错:“tun”: 对重载函数的调用不明确
//forward的正确用法,起到语义转移的作用,主要如下:(结合引用折叠,下面的模板函数形式中T &&a也被
//称为万能引用,左值引用右值引用参数都能接收)
template<typename T>
void funx(T &&a)
{
//tun(a); //1
tun(forward<T>(a)); //2
}
//假设T = “int &&”,引用折叠a为int && 。如果调用1,则a因为已经被初始化会被强制转变成左值引用,
//会走tun(int&)的重载分支输出"lvalue";
//但是如2中那样调用forward,虽然会走forward<int &&>(int &)的重载分支,但最终会返回“int && &&”
//类型,引用折叠后等价于“int &&”,结果输出“rvalue”,相当于将rvalue属性转移到tun中,而不是强制转变
//成左值引用。整个过程相当于将已经初始化的右值引用变量(等价于左值引用)转变成右值引用
//假设T = “int &”,引用折叠后a为int&,如果调用1,会走tun(int &)重载分支输出“lvalue”;
//如果调用2,会走forward<int&>(int&)的重载分支,
//最终会返回“int & &&”,引用折叠后等价于“int &”
//假设T = “int”,引用折叠后a为int &&,调用1,会走tun(int &)的分支输出“lvalue”;如果调用2,
//会走forward<int>(int &)重载分支。最终返回“int &&”类型。
int main()
{
int &&a1 = 0;
int &a2 = a1;
int a3 = a1;
a3 = a3 + 1;
//通过在forward中设置断点,会发现下面三个都会走forward(type &)的函数。
forward<int>(a1); //return type 为 int&&
forward<int&>(a1);//return type 为 int & &&,引用折叠后等价于int&
forward<int&>(a2);//return type 为 int & &&,引用折叠后等价于int &
forward<int&>(a3);//return type 为 int & &,引用折叠后等价于int &
forward<int&&>(a1);//return type 为 int && &&,引用折叠后等价于int &&
forward<int&&>(a2);//return type 为int && &&,引用折叠后等价于int &&
forward<int&&>(a3);//return type 为int &&,引用折叠后等价于int &&
//通过在forward中设置断点,会发现下面三个都会走forward(type &&)的函数。
forward<int>(0);//return type 为int &&,引用折叠后等价于int &&
forward<int>(move(a1)); //move是个参数为万能引用的函数模板,意义就是将左值引用右值引用转变为右值引用。
forward<int>(move(a2));
forward<int>(move(a3));
//forward<int&>(0); //error: bad forward call
//forward<int&>(move(a1));//error: bad forward call
//forward<int&>(move(a2));//error: bad forward call
//forward<int&>(move(a3));//error: bad forward call
forward<int&&>(0);//return type 为int && &&,引用折叠后等价于int &&
forward<int &&>(move(a1));//return type 为int && &&,引用折叠后等价于int &&
tun(0); //rvalue
tun(a1); //lvalue a1虽然为右值引用变量,为int && 类型,但是被初始化之后就变成了左值引用
tun(a2); //lvalue
tun(a3); //lvalue
tun(forward<int &&>(a1));//rvalue,
tun(forward<int &&>(a2));//rvalue,
tun(forward<int &&>(a3));//rvalue,
return 0;
}
什么是右值,左值,x值,glvalues和prvalues? 学习之路
cppreference.com关于值类型的详细解读:lvalue,rvalue,xvalue,prvalue,glvalue_杨领well的博客-CSDN博客
更多推荐
所有评论(0)