lvalue 引用 && rvalue 引用
C++中的每个表达式要么是lvalue要么是rvalue。lvalue表示一个内存位置,而rvalue表示计算表达式的结果。
rvalue引用是对有名称变量的引用,并允许变量表示的内存通过lvalue引用来访问。
rvalue引用是对包含表达式结果的内存位置的引用。
总之,表达式的结果和函数内定义的变量都属于临时变量,即rvalue。
int && num {2*x+3}; //rvalue引用表达式的临时结果
int & num {x=5}; //lvalue引用
常量引用:
void GetSet(int &num) { num += 1; }
Error (active) initial value of reference to non-const must be an lvalue.
void GetSet(const int &num) { num += 1; }
上面代码:只看第一个函数头GetSet(变量)是正确的,但GetSet(常量5)会被报错,因为编译器不允许对常量存在可能潜在的更改行为。
规定常量作为参数传递时需加const,即意味着不可更改。
lvalue引用:
使用lvalue引用形参,可以编写直接访问调用者实参的函数,避免了按值传递中的隐式复制。若不打算修改实参,则只需要给lvalue引用类型使用const修饰符,以避免意外修改参数。
其实,无论是按值传递、按址传递参数或引用都是编译器的规则,我们需要熟悉参数在不同情况下的传递,好的理解方式就是输出地址来观察。
void GetSet(int & num) { num += 1; } int main() { int v = 6; GetSet(v); cout << v<< endl; cin.get(); }
输出结果: 7 ,num变量值通过引用改变了,可以类比,变量的指针传址方式。
rvalue引用:
void GetSet(int && num) { num += 1; } int main() { int v = 6; GetSet(v); cout << v<< endl; cin.get(); }
编译器报错:Error (active) an rvalue reference cannot be bound to an lvalue
可知lvalue不能通过rvalue引用,有rvalue引用形参的函数只能通过rvalue实参来调用。
void GetSet(int && num) { num += 1; cout <<"num="<< num << endl; } int main() { int v = 6; int c = 3; GetSet(v+c); cout <<"v="<< v <<" c="<< c <<endl; GetSet(5); cin.get(); }
上面代码编译通过,结果如下:
编译器会为表达式的结果生成一个临时地址来存储数值。而常量字面值 5 ,被当成一个表达式处理,并且存储在函数引用形参的临时位置。
输出地址可以看出,常量在rvalue引用的时候和表达式一样处理了,即看做临时变量。
函数返回值的引用:
int* GetSet(int num) { num += 1; int result {num}; return & result; } int main() { int v = 6; int *ptr=GetSet(v); cout <<"ptr="<< *ptr << endl; }
本段代码可以运行,但编译器却给出了如下警告:
“Warning C4172 returning address of local variable or temporary”
原因在于:从函数中返回的引用是临时变量的地址,应注意不要从函数中返回局部自动变量的地址。
可以使用动态内存分配为函数中的变量申请内存,由于动态内存分配的内存是一直存在的(在堆中),除非用 delete 销毁。即如下方式:
int* GetSet(int num) { num += 1; int *result{ new int {num} }; return result; } int main() { int v = 6; int *ptr=GetSet(v); cout <<"ptr="<< *ptr << endl;
delete ptr; }
观察下面两部分代码的不同:
一、
double & lowest(double a[], int len) { int j{}; for (int i{ 1 }; i<len; i++) if (a[j]>a[i]) j = i; return a[j]; }
二、
double & lowest(double a[], int len) { int j{}; for (int i{ 1 }; i<len; i++) if (a[j]>a[i]) j = i; return & a[j]; }
引用是赋予存在变量的别名,所以实际返回的是数组元素a[j]的引用,而不是改元素包含的值。a[j]的地址用来初始化要返回的引用,该引用是编译器创建的,因为返回类型是引用。
但返回a[j]和返回&a[j]是不同的。
返回&a[j],则指定的是a[j]地址,那是一个指针。所以,第二部分代码会被报错如下:
Error C2440 'return': cannot convert from 'double *' to 'double &'
因为指针和引用属于不同类型。
Const 在传参引用中的潜规则的:
double refcube(const double &ra) { return ra*ra*ra; } long edge = 5L; double side = 3.0; double c1 = refcube(edge); double c2 = refcube(7.0); double c3= refcube(side+10.0);
上面代码中的函数调用都会产生临时变量:
edge虽然是临时变量,类型不正确,double引用不能指向long。
参数7.0和side+10.0类型虽然正确,但没有名称,编译器将生成一个函数期间调用的匿名临时变量。
而现在如果我们去掉 const ,会发现编译器会报错:
Error:a reference of type "double &" (not const-qualified) cannot be initialized with a value of type "long"
Error:initial value of reference to non-const must be an lvalue
Why?
double refcube(double &ra) 是lvalue引用,要求使用满足类型的左值作为参数。
那么为何 double refcube(double ra) 会通过编译。
因为直接调用和使用const一样编译器会产生 临时变量,只不过使用了 const 意味着不能修改参数。
- 使用const可以避免无意中修改数据。
- 能够处理const和非const实参,否则只能接受非const数据。
- 使用const引用使函数能够正确生成和使用临时变量。
当然此处代码是可以使用rvalue引用或const rvalue引用,但 Long 类型没通过报错如下:
Error:'double refcube(const double &&)': cannot convert argument 1 from 'long' to 'const double &&'