前言
最近复习c++中,类相关的知识。在做默认构造和拷贝构造相关实验的过程中,遇到了一个很疑惑的问题。后来才发现,是由于编写的时候语法不规范导致的(不应该被修改的实参,引用形式传入时加const限定)。所以在这里记录一下,如果有其他小伙伴遇到了,也能作一个提醒。
问题描述以及解决方案
在visual studio中,定义了如下的拷贝构造函数以及重载运算符=
class Complex{ //复数类的定义
public:
Complex(Complex& c) //拷贝构造
{
cout << "拷贝构造" << endl;
this->real = c.real;
this->image = c.image;
}
Complex& operator=(Complex& c) //重载=
{
cout << "重载=的调用" << endl;
this->real = c.real;
this->image = c.image;
return *this;
}
private:
double real;
double image;
};
上面的代码,咋一看好像没什么问题,而且在visual c++ 6.0中,这样定义出来的拷贝构造函数和重载出来的=操作符是可以正常使用的。但是如果放在visual studio中,就会出现很诡异的错误。
首先是拷贝构造的问题:
Complex c4采用的是拷贝构造来进行初始化,但是会发现编译器提示你没有适当的拷贝构造函数可以使用,但是明明前面已经定义了。
这样的问题也会出现在重载运算符=上:
它提示你没有Complex = Complex的“=”运算符可以使用。我当时看到的时候,就真的:)
而解决的办法在就像在前言中讲到的那样,给这两个函数定义的形参前加上const修饰后,上面的两个错误都会消失。
所以你会发现,解决方法就只是用了一句话来说明,但是前面却说了一大堆,我也纠结了快一下午,只能说确实心累。 (其实搜 "重载=的注意事项" ,大部分文章都会告诉你要加const (捂脸) )
默认构造函数中默认参数的使用
这里再补充一点关于默认参数的使用。基于上面的复数类Complex,当要求你实现:复数与复数之间、复数与整数之间、复数与浮点数之间的加法运算时,你会如何去定义重载+函数?
正常的思路就是:由于重载运算符+时,形参列表只能是一种数据类型。为此,就需要定义三个运算符+的重载函数。
Complex operator+(int c); //复数与整型
Complex operator+(double c); //复数与双精度浮点数
Complex operator+(const Complex& c); //复数与复数
但是当我们利用好默认构造函数以及隐式类型转换时,可以只通过定义一个复数与复数之间的重载运算符+函数,就完成上面的三个要求。
为此,我们首先需要在复数类的默认构造函数用上默认参数。目的是:当不指定实部和虚部大小时,默认定义出来的复数的实部与虚部都为0。同时,当你只指定一个参数时,(该参数无论是int还是double,最终都会被转换成double类型) 构造出来的是只有实部的复数。
Complex(double rel = 0,double img = 0):real(rel),image(img){} //默认构造函数
Complex c2; // c2 = 0 + 0j
Complex c3(1); // c3 = 1 + 0j
于是,我们就可以只用operator+(const Complex& c),来完成上面的三个要求:
Complex c2(3);
Complex c3,c4;
c3 = c2 + 1; //复数与整数
c4 = c3 + 1.5; //复数与浮点数
c2 = c3 + c4; //复数与复数
要理解为什么能这么做,主要就是要理解整数与浮点数作为实参传递给复数类引用的过程。对此,你可以配合着拷贝构造的调用时机来理解。
当实参以值传递的方式给函数参数传值时,它其实会先调用一次拷贝构造函数,然后把拷贝出来的类对象传入函数。当运行c3 = c2 + 1时,也是同理。它本质上是调用operator+函数,实参为整型1,但形参为引用形式的复数类。这种情况下,会调用Complex类中的默认构造函数,利用传入的整型1构造出来一个复数类,然后将这个复数类对象( 1+0j )传入函数中完成运算。
其实在这里,当你重载操作符+时,定义出来的是operator+(Complex& c)的形式,即没有加const.你就会发现,它也无法编译通过,无法完成默认构造函数的调用。
结语
这个故事告诉我们,当使用引用作参数传递时,就一定要多考虑一下,如果它原本的值不应该被改变,就要养成加上const修饰的习惯。关于隐式类型转换,感兴趣的可以参考我的上一篇文章。欢迎带伙交流~