- 重载运算符:教会C++如何对类类型的对象执行内置运算符的操作。
- 重载规则:
大多数运算符重载函数都可以定义为全局函数或成员函数。
定义为全局函数时,参数个数和参数类型与运算符的运算对象数及运算对象类型完全相同,返回值类型是运算结果的类型。用友元函数重载。
C++规定隐含参数this是运算符的第一个参数。
因此把一个一元运算符重载为成员函数时,该函数没有形式参数,运算对象是当前调用函数的对象,即this指针指向的对象。
而把二元运算符重载为成员函数时,该函数只有一个形式参数,左运算数是当前对象,右运算符是形式参数。
运算符重载成员函数应该设计为公有的成员函数。 - 友元函数重载运算符:
实现 对象 = operator @ (对象,对象); // 用友元函数重载+、*、<、==的有理数类的定义 class Rational { friend Rational operator+(const Rational &r1, const Rational &r2); //+运算符重载 friend Rational operator*(const Rational &r1, const Rational &r2); //*运算符重载 friend bool operator<(const Rational &r1, const Rational &r2); //<运算符重载 friend bool operator==(const Rational &r1, const Rational &r2); //==运算符重载 private: int num; int den; void ReductFraction(); public: Rational(int n = 0, int d = 1) { num = n; den = d; ReductFraction();} void display() const { cout << num << '/' << den;} }; // 重载成友元函数的+、*、<、==重载函数的实现 Rational operator+(const Rational &r1, const Rational &r2) { Rational tmp; tmp.num = r1.num * r2.den + r2.num * r1.den; tmp.den = r1.den * r2.den; tmp.ReductFraction(); return tmp; } Rational operator*(const Rational &r1,const Rational &r2) { Rational tmp; tmp.num = r1.num * r2.num; tmp.den = r1.den * r2.den; tmp.ReductFraction(); return tmp; } bool operator<(const Rational &r1, const Rational &r2) { return r1.num * r2.den < r1.den * r2.num; } bool operator==(const Rational &r1, const Rational &r2) { return r1.num == r2.num && r1.den == r2.den;}
- 一元运算符成员函数重载元素符(+=、-=):
----------------------待更------------------------- - 二元运算符成员函数重载运算符:
实现:对象 = 对象 .operator @ (对象);
// 用成员函数重载+、*、<、==后的有理数类的定义
class Rational {
private:
int num;
int den;
void ReductFraction();
public:
Rational(int n = 0, int d = 1) { num = n; den = d; ReductFraction();}
Rational operator+(const Rational &r1) const; //+运算符重载
Rational operator*(const Rational &r1) const; //*运算符重载
bool operator<(const Rational &r1) const; //<运算符重载
bool operator==(const Rational &r1) const; //==运算符重载
void display() const { cout << num << '/' << den;}
};
// 有理数类的运算符重载函数的实现,运算符被重载成成员函数
Rational Rational::operator+(const Rational &r1) const
{
Rational tmp;
tmp.num = num * r1.den + r1.num * den;
tmp.den = den * r1.den;
tmp.ReductFraction();
return tmp;
}
Rational Rational::operator*(const Rational &r1) const
{
Rational tmp;
tmp.num = num * r1.num;
tmp.den = den * r1.den;
tmp.ReductFraction();
return tmp;
}
bool Rational::operator<(const Rational &r1) const
{ return num * r1.den < den * r1.num; }
bool Rational::operator==(const Rational &r1) const
{ return num == r1.num && den == r1.den;}
- 特殊运算符重载:
赋值运算符(=)、下标运算符([ ])、函数调用运算符(( ))和成员访问运算符(->)必须重载为成员函数,因为其第一个运算对象必须是相应类的对象,定义成成员函数可以保证第一个运算对象的正确性,编译器能检测出错误。
- 赋值运算符(=):二元运算符,由于第一个运算数必须是类的对象,因此C++规定赋值运算符必须重载为成员函数。形式参数是同类对象的常量引用,返回类型是同类对象的引用。
一般而言,需要自定义复制构造函数的类也需要自定义赋值运算符重载函数(动态内存泄漏及对象析构问题)
Rational r2 = r1; //调用复制构造函数
r1 = r3 ; //调用赋值运算符重载函数
注意:赋值运算符重载函数必须返回左边对象的引用。// DoubleArray类的赋值运算符重载函数的定义 DoubleArray &DoubleArray::operator=(const DoubleArray &right) { if (this == &right) return *this; //防止自己复制自己 delete [ ] storage; //归还空间 low = right.low; high = right.high; storage = new double[high - low + 1]; //根据新的数组大小重新申请空间 for (int i=0; i <= high - low; ++i) storage[i] = right.storage[i]; //复制数组元素 return *this; }
- 下标运算符([ ]):二元运算符,第一个运算数是当前对象,第二个运算数是下标值,运算结果是对应数组元素的引用。
形式:数组元素类型 &operator[](int 下标);
返回值:该下标对应的数据元素的引用。
调用形式:DoubleArray对象 array[i],系统执行函数调用 array.operator[](i),返回变量storage[i-low]的引用。表示array[i]等价于storage[i-low],即可输入输出。// DoubleArray类的下标运算符重载函数的定义 double & DoubleArray::operator[](int index) { if (index < low || index > high) {cout << "下标越界"; exit(-1); } return storage[index - low]; }
- 函数调用运算符(( )):二元运算符,第一个运算对象是函数名,第二个参数是一个表达式表列,运算结果是函数的返回值,实现把类的对象当做函数来使用。
形式:函数返回值 operator()(形式参数表列);// DoubleArray类中的函数调用运算符重载函数的定义 DoubleArray operator()(int start, int end, int lh) { if (start > end || start < low || end > high ) // 判断范围是否正确 { cout << "下标越界"; exit(-1); } DoubleArray tmp(lh, lh + end - start); // 为取出的数组准备空间 for (int i = 0; i < end - start + 1; ++i) tmp.storage[i] = storage[start + i - low]; return tmp; }
成员访问运算符(->):
-----------------待更----------------------自增自减运算符(++、--):可重载为成员运算符或友元函数,但是更多是成员函数。
作为内置运算符,即可当前缀使用,也可当后缀使用。
用法:C++规定后缀运算符重载函数接受一个额外的int型的形式参数(无作用)以区分前缀。
重载为友元函数代码:
-----------------待更----------------------
重载为成员函数代码区别:前缀的++、--是引用返回,因为离开函数后,当前对象仍然存在,所以用引用返回。
后缀是值返回,返回一个局部对象,离开函数已经消亡
Rational &Rational::operator++() // 前缀++ { num += den; return *this; } Rational Rational::operator++(int x) // 后缀++ { Rational tmp = *this; num += den; return tmp; } Rational &Rational::operator--() // 前缀-- { num -= den; return *this; } Rational Rational::operator--(int x) // 后缀-- { Rational tmp = *this; num -= den; return tmp; }
- 输入输出运算符重载:
输出运算符重载:
流插入运算符<<是二元运算符,第一个参数是ostream类的对象,由于函数将右边对象值插入左边对象,因此用引用传递。第二个对象是对当前类的常量对象的引用。返回值是第一个参数的引用,即ostream类的一个引用。由于第一个参数是ostream类的对象,所以不能重载为成员函数,必须重载为全局函数。
输入运算符重载:ostream &operator<<(ostream &os, const Rational &obj){ os<<obj.num<<'/'<<obj.den; return os; }
流提取运算符>>是二元运算符,第一个参数是输入流对象的引用。第二个形式参数是对要读入的对象的非常量引用,该形参必须是非常量,因为输入运算符重载函数的目的是要将输入读入此对象。返回值为同一个流的引用。输入运算符也必须重载为全局函数。istream &operator>>(istream &in, Rational &obj){ in>>obj.num>>obj.den; obj.ReductFraction(); return in; }
- 自定义类型转换函数:
- 内置类型到类类型的转换:
通过类的构造函数实现。只要某个类有一个单个参数的构造函数,C++就会执行参数类型到类对象的隐式转换。
不希望编译器执行这种隐式转换,禁止隐式转换是在构造函数前加关键词explicit。
通常除非有明显的需要隐式转换的理由,否则单个参数的构造函数应该被设为explicit,以避免不必要错误。当需要转换时,用户可以显式地构造函数: 对象名 = 类名(参数)
代码实例:
-----------------待更-------------------- - 类类型到其他类型的转换:
将类类型的对象自动转换为内置类型或其他类类型。
类型转换函数不指定返回类型,因为返回类型就是目标类型名。类型转换函数没有形式参数,被转换的对象就是当前对象,因为这个函数不会修改当前对象值,因此是一个常量成员函数。
利用类型转换函数实现隐式转换可以避免各类运算符的重载,从而减少代码量
即使没有重载减法运算符,但可以执行r - 1.5操作,也可以执行r1 = r2 - r3;即都转换为double类型,执行减法。operator double()const{ return (double(num)/den) }
- 编程规范:
- 类包含指针类型的数据成员时,则不能用默认的赋值运算符重载及默认复制构造函数,应该设置相应构造及重载函数。
- 输入重载函数的第二个参数是当前类对象的引用,输出重载时,第二个参数是当前类对象的const引用。
- 类型转换函数可以减少重载函数,但是也会带来二义性,如5.5 + r1就会出现编译错误,既可能是将5.5构造成一个对象,也有可能是将r1转换为double类型,故类型转换函数一般都用explicit限定,显示调用。
- 运算符的各类操作都是通过操作它的数据成员实现的,当把运算符重载为全局函数时,应该将此函数声明为友元函数。
- 由于成员函数有一个隐含的参数:this指针,所以重载为成员函数时,参数个数比运算符的运算对象少1,this指针指向的对象是运算符的第一个运算对象,因此第一个运算对象是当前类对象的运算符通常被重载为成员函数。