第11章 使用类
1. 运算符重载
class Time
{
private:
int h;
int m;
public:
Time operator+(const Time & t) const; // + 重载
};
Time Time::operator+(const Time & t) const // 定义
{
...
}
int main()
{
Time t1, t2;
Time total;
total = t1 + t2; // 调用重载的 +
total = t1.operator+(t2); // 与上面等价
return 0;
}
成员函数的重载运算符的左操作数是调用运算符的对象,右操作数是参数。非成员函数的重载运算符,左操作数是第一个参数,右操作数是第二个参数。
运算符重载不必是类的成员函数,且重载的运算符的操作数至少一个是用户定义的类型(防止用户重载标准类型下的运算符),重载的运算符不能违反它原来的句法规则(即二元运算符不能重载成一元,优先级也不能改变),不能创建新的运算符。
不能重载的运算符:
运算符 | 说明 |
sizeof | sizeof运算符 |
. | 成员运算符 |
.* | 成员指针运算符 |
:: | 作用域运算符 |
?: | 条件运算符 |
typeid | 一个RTTI运算符 |
const_cast | 强制类型转换运算符 |
dynamic_cast | 强制类型转换运算符 |
reinterpret_cast | 强制类型转换运算符 |
static_cast | 强制类型转换运算符 |
只能通过成员函数重载的运算符:
运算符 | 说明 |
= | 赋值运算符 |
() | 函数调用运算符 |
[ ] | 下标运算符 |
-> | 通过指针访问类成员运算符 |
重载运算符最好不改变原运算符的意义,例如 * 运算符,不要重载成交换两个类对象的数据成员。
一元运算符重载:
class D
{
private:
int a;
int b;
public:
D& operator-(); // 取负运算符,运算符在操作数前边,无参数
D& operator++(); // 前置递增重载,运算符在操作数前边,无参数
D operator++(int); // 后置递增重载,运算符在操作数后面,重载函数的参数有个int
};
D& D::operator-()
{
a = -a;
b = -b;
return *this;
}
D& D::operator++()
{
++a;
++b;
return *this;
}
D D::operator(int)
{
++(*this);
return *this;
}
2. 友元函数
友元函数在类中声明,友元函数中的该类的参数,能访问类的私有成员。友元函数不是类的成员函数,不能用成员运算符 “ . ” 调用,作用域也不在类内。
友元函数原型要在类中声明,声明前面加上关键字 friend:
friend Time operator*(double m, const Time & t); // 要在类中声明
Time operator*(double mul, const Time & t) // 定义时不加 Time:: 因为不是类的成员函数
{ // 不加关键字 friend
Time result;
long totalm = t.h * 60 * mul + t.m * mul; // 调用了参数t的私有数据m,h
result.h = totalm / 60;
result.m = totalm % 60;
return result;
}
3. 类的自动转换和强制类型转换
假定有一个类,构造函数为对象的三个私有数据赋值:
class Stonewt
{
private:
enum {Lbs_per_stn = 14};
int stone;
double pds_left;
double pounds;
public:
Stonewt(double lbs);
Stonewt(int stn, double lbs);
Stonewt();
~Stonewt();
};
可以这样强制转换:
Stonewt maCat; // 调用默认构造函数
naCat = 19.6; // 使用构造函数Stonewt(double)转换19.6
程序会调用构造函数 Stonewt(double) 创建一个临时对象,将19.6作为初始化值,然后逐成员将临时对象的内容复制到 myCat 中。这个过程叫隐式转换。只有接受一个参数的构造函数才能作为转换函数。Stonewt 类中,接受两个参数的构造函数,若 double 形参是默认参数的,则也可以作为转换函数,来匹配 int 型转换。
可以通过在声明构造函数时,添加关键字 explicit 关闭隐式转换,但还是可以强制转换:
explicit Stonewt(double lbs);
Stonewt myCat;
myCat = 19.6; // 非法
myCat = Stonewt(19.6); // 合法
myCat = (Stonewt) 19.6; // 合法
若没有explicit 关键字,则隐式转换还可以在以下情况自动执行:将 Stonewt 对象初始化 double 值;将 double 值赋给 Stonewt 对象;将 double 值传递给接受 Stonewt 类型参数的函数时;返回值被声明为 Stonewt 的函数试图返回 double 值时;在上述任意一种情况下,使用可转换为 double 类型的内置类型时。
最后一条的意思是:函数的返回值是 double 类型,但是函数 return 了 Stonewt 类型对象。
Stonewt Jumbo(7000); // 将int转换为double后,使用Stonewt(double)
Jumbo = 7300; // 将int转换为double后,使用Stonewt(double)
只有不存在二义性时才会进行这种而不转换,即,当还有一个构造函数 Stonewt(long) 时,int 可以转换成 double 或 long,这种情况下会报错。
也可以进行相反的转换,通过 c++ 特殊的运算符函数—转换函数:
operator typeName();
typeName 是要转换的类型。转换函数必须是类方法;转换函数不能指定返回类型;转换函数不能有参数。可以在 Stonewt 类声明中添加两个转换函数:
operator int() const; // 声明
operator double() const; // 声明
Stonewt::operator int() const // 定义,转换函数是类方法
{
return int (pounds + 0.5);
}
使用转换函数时:
Stonewt pop(9, 2.8); // 调用构造函数Stonewt(int, double)
double p_wt = pop; // 隐式调用转换函数
std::cout << int (pop) << std::endl; // 显示调用转换函数
与上面提到的类似,当不存在二义性时,转换也会自动执行。
强制转换:
long go = (double) pop;
long go = int (pop); // 这两种形式的强制转换,都会调用转换函数
转换函数也可以通过 explic 关键字关闭隐式转换。
可以不定义转换函数,而自定义一个类方法来实现类型转换,这样会省却一些隐式类型转换造成的麻烦。
类的类型转换总结:
只有一个参数的构造函数,可以用来将该参数类型的值转换成类对象,使用 explicit 关键字可以关闭隐式转换;
特殊的类的成员函数—转换函数,可以将类转换成其他类型,转换函数没有返回类型,没有参数,同样可以用 explicit 关闭隐式转换。
在类型转换中,函数参数存在转换条件时才会执行,调用对象不会被转换。有 + 运算符重载:
Stonewt operator+(const Stonewt & s) const; // 成员函数重载
// 或者友元函数的形式,只能存一
friend Stonewt operator+(const Stonewt &s1, const Stonewt &s2)
Stonewt JennySt(9, 12);
double BennySt(12, 8);
Stonewt total;
total = JennySt + BennySt; // total = JenntSt.operator+(BennySt)
// 或者 total = operator+(JennySt, BennySt)
这两种情况,由于BenntSt 是参数,可以通过构造函数将 BenntSt 隐式转换成类对象。如果类方法中,还有转换函数 operator double();将会造成二义性而报错。
total = BennySt + JennySt; // 成员函数:total = BennySt.operator+(JennySt);
// 友元函数:total = operator+(BennySt, JennySt);
这种情况,友元函数时,BennySt 是参数,可以通过构造函数将其转换为类对象,而成员函数时,BennySt 不是参数,无法转换,会报错。
解决这种问题的方法,一是在重载加法时,采用友元函数;或者在成员函数重载运算符的基础上,再重载一个运算符函数,来符合加法操作数顺序问题。