先了解一些名词的实质意义:
临时变量:True temporary objects in C++ are invisible — they don't appear in your source code. They arise whenever a non-heap object is created but not named.--《More Effective C++》
真正的临时变量是不可见的(平时所创建的一般是局部变量),每当创建非堆对象但未命名时,它们就会出现[1]
void fun(const string& str)
{}
int main(int argc, char* argv[])
{
char a[] = "Local Variable";//临时变量并不可见,这里时局部变量
fun(a); // 有类型转换
return 0;
}
这里fun函数要const string&类型参数,实参确是char*,所以编译器会进行类型转换,创建了一个string类型的临时变量(对象),并用a进行初始化,之后将此变量传给函数fun,
如果是这样:
void fun(string& str)
{}
int main(int argc, char* argv[])
{
char a[] = "Local Variable";//临时变量并不可见,这里时局部变量
fun(a); // 有类型转换,err
return 0;
}
此时,若创建一个临时变量,如果在函数内部对这个临时变量进行修改就会产生误会(我明明传的是这个,出来居然不是),所以这里就发挥了const的作用,既然不想更改就明确说明用const
可能有人会说那为什么不直接用值传递?--看下面情况一
产生临时变量的三种情况:1.By Value方式传值,2.参数const类型,3.类型转换[1]
一:以By Value的方式传值。
我们都知道,引用类型和指针类型传递的都是地址,可以直接对地址中存放的数据进行操作,而以传值的方式传递参数,就会在heap中重新分配一个临时区域,将实参中的数据拷贝到临时区域中,而你对这份数据进行的任何的操作都不会影响实参的内容,因为实参跟形参只是内容相同,分别在两块不同的内存中。而引用和指针操作的是同一块内存,所以形参修改后,实参也修改了。
二:参数为const的类型。
因为常量是不能修改,在只需要实参中的数据,而不需对实参进行修改时,或是禁止对实参进行修改时,把形参定义为const类型,系统会产生一个临时变量,就能起到保护数据的作用,如在函数strlen中,修改参数的值行吗?本来只是想得到实参的长度,结果在函数中被修改了,那得到得实参长度还是真实的吗。
如果你程序中的数据到处都可以被修改,那是多么的可怕(所以我们讨厌全局变量),所以const还是有它存在的价值。
三:类型转换的时候会产生临时变量。
真是糟糕啊,在用类型转换带来便利的同时,产生临时变量就是我们承担的损失。
如将一个short类型转换成int类型,他们占用的内存不一样,如果不产生临时变量,那不就short类型和int类型占用的字节数不就一样了吗,sizeof不就坑爹了吗。
C++语言禁止为非常量引用产生临时对象。同时证明引用类型传参不会产生临时变量,如char[]转换成string会报错,他们都是引用类型。
临时变量不能作为非const引用参数,不是因为他是常量,而是因为c++编译器的一个关于语义的限制。如果一个参数是以非const引用传入,c++编译器就有理由认为程序员会在函数中修改这个值,并且这个被修改的引用在函数返回后要发挥作用。但如果你把一个临时变量当作非const引用参数传进来,由于临时变量的特殊性,程序员并不能操作临时变量,而且临时变量随时可能被释放掉,所以,一般说来,修改一个临时变量是毫无意义的,据此,c++编译器加入了临时变量不能作为非const引用的这个语义限制,意在限制这个非常规用法的潜在错误。
临时变量只能通过Const引用来指向,因为临时变量不可修改[2]
接着[3]
#include<iostream>
using namespace std;
class Complex{
public:
Complex() :_real(0), _imaginary(0) {}
Complex(double real, double imaginary) :_real(real), _imaginary(imaginary) {}
Complex(Complex& cp) :_real(cp._real), _imaginary(cp._imaginary) {}
friend Complex getConjugate(Complex cp);
private:
double _real; //实部
double _imaginary; //虚部
};
//求共轭复数
//为Complex的友元
Complex getConjugate(Complex cp){
cp._imaginary = -cp._imaginary;
return cp;
}
int main()
{
Complex a(1.0, -5.0);
Complex b = getConjugate(a); // Error : no match
return 0;
}
error C2440: 'initializing': cannot convert from 'Complex' to 'Complex'
Cannot copy construct class 'Complex' due to ambiguous copy constructors or no available copy constructor
Complex b = getConjugate(a);调用了拷贝构造,但是拷贝构造中形参是Complex& cp,根据上面的解释,这样是不允许的,所以需要const Complex& cp
还有上面等式调用了两次拷贝构造,第一次是传参创建了临时对象需要用a这个旧对象初始化新对象(即那个临时对象),第二次则是给b赋值(这个很好理解)
终于到了讨论标题内容的时候了
先添加两端代码:是前置++后置++的两种实现方式
1.友元函数实现
#include <iostream>
using namespace std;
class Complex
{
friend Complex &operator++(Complex &c); //前置++
friend Complex operator++(Complex &c, int);//后置++, 多了一个占位符
public:
Complex(int x = 0, int y = 0) :a(x), b(y) {}
void display() const{
cout << a << " + " << b << "i" << endl;
}
private:
int a, b;
};
//前置++,先加后用
Complex &operator++(Complex &c){
c.a++;
c.b++;
return c;
}
//后置++, 先用后加
Complex operator++(Complex &c, int){
Complex tmp = c;
c.a++;
c.b++;
return tmp;
}
int main()
{
Complex c(1, 2);
//前置++, 先加后用
Complex tmp = ++c; //函数原型: operator++(Complex &c);
tmp.display();
c.display();
//后置++, 先用后加,多了一个占位符参数int,表示后置操作
Complex tmp2 = c++; //函数原型: operator++(Complex &c, int);
tmp2.display();
c.display();
return 0;
}
2.成员函数实现
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Plusplus
{
public:
friend ostream& operator<<(ostream&os, const Plusplus&myint);
Plusplus(int val){
mNum = val;
}
/**拷贝构造,用户定义了普通构造,系统则不提供无参构造,但提供拷贝构造
Plusplus(const Plusplus&plusplus)
{
mNum = plusplus.mNum;
}
*/
//前置++运算符重载
Plusplus& operator++(){
//先++,再返回对象,返回值类型是引用类型
++mNum;
return *this;
}
//后置++运算符重载
Plusplus operator++(int){
//先返回一个临时对象,再++,返回值类型是值类型
Plusplus tmp(*this);
++mNum;
return tmp;
}
private:
int mNum;
};
ostream& operator<<(ostream&os,const Plusplus&myint){
os << myint.mNum;
return os;
}
void test(){
/**
有参构造调用的三种方式
* Plusplus myint = 10;
* Plusplus myint = Plusplus(10);
*/
Plusplus myint(10);
cout << ++myint << endl;
cout << myint++ << endl;
cout << myint << endl;
}
int main()
{
test();
//VS中能引用匿名对象,err,需要右值引用
//Plusplus &my = Plusplus(10);
system("pause");
return EXIT_SUCCESS;
}
上面两端代码中有两点不明白:[4]
1.前置++跟后置++实现用的是同一个函数名通过形参不同(形参中多了一个int作为占位符)重载实现的,这里面估计编译器做了优化当调用param++的时候转化成了operator++(param,0),当++param则会转化成operator++(c)再调用对应的函数。
2.为什么友元函数实现方式中函数形参比成员函数多一个参数:因为友元函数是全局函数,其中并没有指向类对象的this指针,所以要另外引入一个引用该类对象的形参传递数据
[1] C++中的临时变量 https://blog.csdn.net/wxn704414736/article/details/79972570
[2] C++临时变量的生命周期 https://www.cnblogs.com/catch/p/3251937.html
[3] C++11移动改语义探讨--从临时对象到右值引用 https://blog.csdn.net/y1196645376/article/details/54911331
[4] C++成员运算符重载和友元运算符重载的比较(以++,--运算符为例)https://blog.csdn.net/ayangya/article/details/78901294