一个简单的将两个对象相加赋值给第三个对象的例子,第一个版本使用成员函数,第二种版本使用运算符重载
class Test
{
private:
int i;
public:
Test(){}
~Test(){}
Test sum(const Test &t) const;
Test operator+(const Test &t) const;
};
Test Test::sum(const Test &t) const
{
Test new = i + t.i;
return new;
}
Test Test::operator+(const Test & t)
{
Test new = i + t.i;
return new;
}
/* 调用者 */
Test t1(1);
Test t2(2);
Test t3 = t1 + t2;
使用重载运算符,隐式调用t1的operator函数,传入显式的t2对象,注意返回值不能是引用。
对于如下这种代码
Test t4 = t1 + t2 + t3;
由于加法是从左向右结合的,因此先执行t1.operator+(t2),返回一个对象,然后此对象调用operator+(t3),得到一个最终对象返回给t4,t1 + t2的时候会返回给一个临时对象,在赋值语句结束之后,这个对象就不需要了,会被析构。
重载限制:
1.必须有一个操作数是用户定义类型,这样可以防止给基本类型重载运算符,比如将“-”重载为两个int相加。
2.重载不能违反原来运算符的句法规则,即操作数和运算符的顺序,不能修改运算符的优先级
3.不能创造新的运算符,比如@
4.有些运算符不能被重载,比如sizeof等
5.多数运算符可以通过成员函数或者非成员函数进行重载,但是有4个只能用成员函数重载,=,(),[],->。
通常,能访问类私有对象的只有通过公有方法,友元可以突破这个限制。友元有3种:
1.友元函数
2.友元类
3.友元成员函数
让函数成为类的友元,就是友元函数,可以访问类的私有对象。友元函数可以解决一个问题,就是二元运算符有一个是基本类型,比如重载减号运算符之后,如下代码:
Test t1(1)
Test t2 = t1 * 10; //valid
Test t2 = 10 * t1; //invalid
因为重载运算符是成员函数,只能从对象调用,所以不能把10放在前面。使用非成员函数可以把10放在前面,但是函数内部无法访问类的私有成员。友元函数不能通过成员运算符调用。
需要反转操作数顺序的时候,可以用友元内容调用成员函数,这是一个小技巧,不用重复编码,如下:
Test operator*(double m, const Test & t)
{
return t * m;
}
友元函数声明时要加friend,定义时不加
类的类型转换:只有一个参数的构造函数允许类和基本类型自动转换,例如
class Test
{
private:
double i;
public:
Test(double);
Test(){}
~Test(){}
};
Test::Test(double d)
{
i = d;
}
int main()
{
Test tmp;
tmp = 19.6;
return 1;
}
若此构造函数后面的参数提供了默认值,那么也可以自动转换。编译器的具体操作是,调用此构造函数创建一个临时对象,将19.6作为初始值,然后逐成员赋值给tmp。这个过程是隐式的,称为隐式转换。给构造函数前面加explicit可以禁止自动转换。这个例子中隐式转换的几种情况:
1.将Test对象初始化为double
2.将double赋值给Test对象
3.将double传递给接收Test对象参数的函数
4.返回值被声明为Test的函数返回double
注意一个问题,将int赋值给Test对象的时候,会提升为double,然后赋值,但是不能有二义性,比如另一个构造函数使用一个参数是long,而int可以提升为long或者double,就会二义性。
上面这种转换是从double转换成对象,但是如下代码会报错:
Test tmp(19.6);
double i = tmp;
只有定义转换函数之后,这样的赋值才能成立,转换函数的要求:
1.转换函数必须是类方法
2.转换函数不能指定返回类型
3.转换函数不能有参数
转换函数的书写格式如下:
class Test
{
private:
double i;
public:
Test(double d){i = d;};
~Test(){}
operator double() const;
};
Test::operator double() const
{
return i;
}
/* 用户调用 */
Test t(19.6);
double i = t; //valid, i is 19.6
c++11允许使用explicit禁止隐式转换函数,但是c++98不支持。应该尽可能少使用隐式转换。
隐式转换和重载加法运算符:如下代码
class Test
{
private:
double i;
public:
Test(){}
Test(double);
~Test(){}
Test operator+(const Test & t) const;
};
Test::Test(double d)
{
i = d;
}
Test Test::operator+(const Test &t) const
{
Test sum;
sum.i = i + t.i;
return sum;
}
/* 调用者 */
Test t1(19.6);
double tmp = 1.2;
Test t2 = t1 + tmp; //valid
Test t2 = tmp + t1; //invalid
第一种赋值可行,因为tmp被隐式转换成Test对象作为函数参数,但是第二种不可行,对一个double调用成员函数是无法被转换为对象的,解决方案有两种,一种是重载加法运算符为友元函数而不是成员函数,另一种方法是定义一个成员函数和一个重载加法运算符的友元,如下:
Test operator+(double n); //成员函数
friend Test operator+(double n, const Test &t); //友元函数
这样每种加法都有严格对应的函数原型,好处是避免了隐式转换,缺点是增加了代码量