实例代码:
//试看下面的代码:
#include <iostream>
using namespace std;
void f(int &a)
{
cout << "f(" << a << ") is being called" << endl;
}
void g(const int &a)
{
cout << "g(" << a << ") is being called" << endl;
}
int main()
{
int a = 3, b = 4;
f(a + b); //编译错误,把临时变量作为非const的引用参数,传递给int &a了
g(a + b); //OK,把临时变量作为const&传递是允许的
}
上面的两个调用之前,a+b的值会存在一个临时变量中,因为a+b是一个表达式,本质上属于一个没有名字的变量,编译器会自动生成一个临时变量储存a+b的值,当把这个临时变量传给f时,由于f的声明中,参数是int&,不是常量引用,所以产生以下编译错误:
const_ref.cpp: In function `int main()':
const_ref.cpp:14: error: invalid initialization of non-const reference of type '
int&' from a temporary of type 'int'
const_ref.cpp:4: error: in passing argument 1 of `void f(int&)'
关于临时变量的内容,推荐博客:
临时变量不能作为非const类型引用形参的实参 - jiayouwyhit - 博客园www.cnblogs.com/jiayouwyhit/p/3681224.html
而在g(a+b)中,由于g定义的参数是const int&,编译通过。 问题是为什么临时变量作为引用参数传递时,必须是常量引用呢?
很多人对此的解释是临时变量是常量,不允许赋值,改动,所以当作为非常量引用传递时,编译器就会报错。这个解释在关于理解【临时变量】不能作为【非const引用参数】这个问题上是可以的,但不够准确。事实上,临时变量是可以被作为左值(L value)并被赋值的,请看下面的代码:
#include <iostream>
using namespace std;
class CComplex {
friend CComplex operator+(const CComplex &cp1, const CComplex &cp2);
friend ostream& operator<<(ostream &os, const CComplex &cp);
private:
int x;
public:
CComplex(){}
CComplex(int x1) {
x = x1;
}
};
CComplex operator+(const CComplex &cp1, const CComplex &cp2){
CComplex cp3;
cp3.x = cp1.x + cp2.x;
return cp3; //返回的cp3其实是一个函数内部的临时变量,在外部并没有名字,由(a+b)接收。
}
ostream& operator<<(ostream &os, const CComplex &cp){
os << cp.x;
return os;
}
int main(){
CComplex a(2), b(3), c(4);
cout << (a + b) << endl;
cout << ((a + b) = c) << endl; //临时对象作为左值
return 0;
}
上面的程序编译通过,而且运行结果是:
5
4
临时变量确实被赋值,而且成功了。上述的例子说明了临时变量本身其实是可以被修改的。问题出在引用上,也就是说编译器不允许引用一个非const类型的临时变量。
The compiler won’t allow the reference toward a temporary variable which is not constant.
所以,【临时变量】不能作为【非const引用参数】,不是因为他是常量,**而是因为c++编译器的一个关于语义的限制。**如果一个参数是以非const引用传入,**c++编译器就有理由认为程序员会在函数中修改这个值,并且这个被修改的引用在函数返回后要发挥作用。**但如果你把一个临时变量当作非const引用参数传进来,由于临时变量的特殊性,程序员并不能操作临时变量,而且临时变量随时可能被释放掉,所以,一般说来,修改一个临时变量是毫无意义的,据此,c++编译器加入了临时变量不能作为非const引用的这个语义限制,意在限制这个非常规用法的潜在错误。
以上内容源自博客:
c++中临时变量不能作为非const的引用参数 - Dec-Fth - 博客园www.cnblogs.com/faith0217/articles/4076296.html
在了解了以上内容之后,可以看下面一个例子:
#include <iostream>
using namespace std;
class A{
private:
int length;
public:
A(){
length = 100;
}
A(int len){
length = len;
}
void setLength(int len){
length = len;
}
A operator+(const A& temp){
int total_length = this->length + temp.length;
return A(total_length);
}
int getLength(){
return length;
}
};
int main(){
A a = A();
A b = A(10);
A c = a+b;
cout<<"this length of c is: "<<c.getLength()<<endl;
return 0;
}
若将其中的:
A operator+(const A& temp){
//code
return A(total_length);
}
修改为如下的代码:
A& operator+(const A& temp){
//code
return A(total_length);
}
则会报错:
error: cannot bind non-const lvalue reference of type 'A&' to an rvalue of type 'A'
return A(total_length);
^
这是因为构造一个对象A(total_length),这个对象并没有名字,而是作为一个临时变量返回的,但是返回的类型确实一个引用,根据前文提到的C++语法规定,我们无法将【引用】与一个【非const的临时变量】进行绑定,所以会像这样报错。
需要注意的是,第一个例子中,f(a+b)实际上是将a+b作为一个整体传递进去,编译器会自动分配一个临时变量(temp = a+b)。而重载的操作符+的工作原理是将this指针(指向第一个操作数a)和第二个操作数(对象b)的引用传递进函数,这一过程并不涉及临时变量。
函数return引用类型的语法规范 #
由于函数本身处在的栈空间会在执行完之后被释放,所以返回的引用不可能指向本地栈空间内的变量,所以要么是该函数外部的变量,要么是函数内部动态分配的内存空间中的变量。
下面是一个错误实例(really nasty code):
A* & operator+(const A& temp){ //肯定不会有人这么写的,权当测试了
//code
A* temp = new A(total_length);
A* & res = temp;
return res;
}
int main(){
A a = A(100);
A b = A(10);
A* & c = a+b
}
可以看到,即使我们采用了动态内存分配,还是会报错。我们尝试返回*一个指针变量的引用,但是由于指针变量本身依旧是本地变量,指针指向的堆空间不会被销毁,但是指针变量本身是会随着return被释放的。
综上所述,一个函数想要以一个引用作为返回值,需要满足被引用的变量不会随着函数的结束而被释放,例如:
//将 int getLength(){return length;} 改为
int& getLength(){ return length;}
其中length作为对象本身的数据,在main结束之后才会消亡,所以可以放心地返回length的引用,这种做法常见于数据类型为向量的情况中,为了避免大规模的数据拷贝,类的接口函数往往会返回自身数据成员的引用。
当然当我们重载操作符的时候,往往希望返回一个类的对象,假如这个类的大小足够大以至于直接返回对象本身开销很大,还可以进行如下操作:
A* operator+(const A& temp){
//code
A* temp = new A(total_length);
return temp;
}
假如没有动态分配内存,实际上编译器会做一个bitwise的拷贝,将class A的一个对象拷贝到函数调用者(如main函数)的内存空间中:
//some functions defined here
int main(){
A instance = f(); //f()返回:return A(); 那么A()这个对象会被bitwise拷贝到变量instance的空间中。
}