第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例
什么是运算符重载 使系统内置的运算符可以用于类类型 例如:+ 运算符能够实现2个对象间的加。例如:类A的对象a1、a2、a3,希望:
问题的提出 把某些事交给系统去做,用户只要知道相加就可 扩充运算符的功能 增强了C++ 语言的可扩充性 使用户定义的类更像系统的内置类型
运算符重载的限制 不是所有的运算符都能重载 重载不能改变运算符的优先级和结合性 重载不能改变运算符的操作数个数 不能创建新的运算符
可以重载的运算符 + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- ->* , -> [] () new delete new[] delete[]
不能重载的运算符 . .* :: ?: sizeof
第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例
运算符重载的方法 运算符重载就是写一个函数解释某个运算符在某个类中的含义 要使得系统能自动找到重载的这个函数,函数名必须要体现出和某个被重载的运算符的联系。 C++中规定,重载函数名为 operator@ 其中,@为要重载的运算符。如要重载“+”运算符,该重载函数名为operator+。要重载赋值运算符,函数名为operator=。
函数原型 运算符的重载不能改变运算符的运算对象数。因此,重载函数的形式参数个数(包括成员函数的隐式指针this)与运算符的运算对象数相同 运算符重载可以重载成成员函数也可以重载成全局函数实现。重载成全局函数时,最好把此函数设为友员函数 如果作为类的成员函数,它的形式参数个数比运算符的运算对象数少1。这是因为成员函数有一个隐含的参数this。在C++中,把隐含参数this作为运算符的第一个参数。 当把一个一元运算符重载成成员函数时,该函数没有形式参数。 把一个二元运算符重载成成员函数时,该函数只有一个形式参数,就是右操作数,当前对象是左操作数。
重载实例 为rational类增加“+”和“*”以及比较的重载函数,用以替换现有的add和multi函数
方案一:重载成成员函数 class Rational { private: int num; int den; void ReductFraction(); public: Rational(int n = 0, int d = 1) { num = n; den = d;} Rational operator+(const Rational &r1) const; Rational operator*(const Rational &r1) const; bool operator<(const Rational &r1) const; bool operator==(const Rational &r1) const; bool operator>(const Rational &r1) const; bool operator<=(const Rational &r1) const; bool operator>=(const Rational &r1) const; bool operator!=(const Rational &r1) const; void display() { 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;
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;} bool Rational::operator>(const Rational &r1) const { return num * r1.den > den * r1.num; } bool Rational::operator<=(const Rational &r1) const { return num * r1.den <= den * r1.num; } bool Rational::operator>=(const Rational &r1) const { return num * r1.den >= den * r1.num; } bool Rational::operator!=(const Rational &r1) const { return !(*this == r1);}
方案二:重载成友员函数 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); friend bool 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); 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;} void display() { 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) tmp.num = r1.num * r2.num; 其他函数实现略
重载后有理数类的使用 int main() { Rational r1(1,6), r2(1,6), r3; r3 = r1 + r2; r1.display(); cout << " + "; r2.display(); cout << " = "; r3.display(); cout << endl; r3 = r1 * r2; r1.display(); cout << " * "; r2.display(); return 0; }
全局函数 vs成员函数 大多数运算符都可以重载成成员函数或全局函数。 赋值(=)、下标([])函数调用(())和成员访问(->)必须重载成成员函数。 具有赋值意义的运算符,如复合的赋值运算符以及++和--,不一定非要定义为成员函数,但最好定义为成员函数。 具有两个运算对象的运算符最好重载为全局函数,这样可以使得应用更加灵活。如果把加运算定义成全局函数,r是有理数类的对象,则2+r是一个合法的表达式。
第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例
几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载
赋值运算符 对任一类,如果用户没有自定义赋值运算符函数,那么系统为其生成一个缺省的赋值运算符函数,在对应的数据成员间赋值。 一般情况下,这个缺省的赋值运算符重载函数能满足用户的需求。但是,当类含有类型为指针的数据成员时,可能会带来一些麻烦。
对DoubleArray类对象执行 array1 = array2的问题 会引起内存泄漏 使这两个数组的元素存放于同一块空间中 当这两个对象析构时,先析构的对象会释放存储数组元素的空间。而当后一个对象析构时,无法释放存放数组元素的空间
赋值运算符“=”的原型 赋值运算符只能重载成成员函数 函数原型: X &X::operator=(const X &source) { // 赋值过程 } 一旦创建了对象x1, x2, 可以用 x1 = x2赋值。
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; }
赋值运算符重载要点 一般来讲,需要自定义拷贝构造函数的类也需要自定义赋值运算符重载函数。 在赋值运算符重载函数中,已经将参数的值赋值给了当前对象,那为什么还需要返回值呢?记住,在C++中,赋值是一个运算,它可以形成一个表达式,而该表达式的结果值就是赋给左边的对象的值。因此,赋值运算符重载函数必须返回赋给左边的对象值。
赋值运算符重载和拷贝构造函数 一般来讲,需要拷贝构造函数的类也需要重载赋值运算符 定义对象时给对象赋初值调用的是拷贝构造函数 程序的语句部分中的赋值语句调用的是赋值运算符重载函数
几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载
下标运算符重载 能否象普通的数组那样通过下标运算操作DoubleArray类的对象,这样可以使DoubleArray类更像一个功能内置的数组。 可以通过重载下标运算符([])来实现 下标运算符是二元运算符,第一个运算数是数组名,第二个运算数是下标值 下标运算符必须重载成成员函数
DoubleArray类的[ ]重载 double & DoubleArray::operator[](int index) { if (index < low || index > high) {cout << "下标越界"; exit(-1); } return storage[index - low]; }
DoubleArray类的使用 定义:DoubleArray array(20, 30); 数组输入: for (i=20; i<=30; ++i) { cout << "请输入第" << i << "个元素:"; cin >> array[i]; } 数组输出: for (i=20; i<=30; ++i) cout << array[i] << '\t';
几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载
函数调用运算符 函数调用运算符()是一个二元运算符。它的第一个运算对象是函数名,第二个参数是形式参数表。运算的结果是函数的返回值。 一个类重载了函数调用运算符,就可以把这个类的对象当做函数来使用
函数调用运算符重载 函数调用运算符必须重载成成员函数 函数调用运算符重载函数的原型为 函数的返回值 operator() (形式参数表);
函数调用运算符重载实例 在DoubleArray类增加一个功能:取数组中的一部分元素形成一个新的数组 例如,在一个下标范围为10到20的数组arr中取出下标为第12到15的元素,形成一个下标范围为2到5的数组存放在数组arr1中,可以调用 arr1 = arr(12, 15, 2)。
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; }
几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载
“++”和“--”重载 ++、- -:是一元操作符 这两个操作符可以是前缀,也可以是后缀。而且前缀和后缀的含义是有区别的。所以,必须有两个重载函数。 问题:两个重载函数有相同的原型 区分方法: 前缀:一元操作符。 后缀:二元操作符。
“++”和“--”重载 cont. 成员函数重载 ++ob重载为:ob.operator++() ob-- 重载为:ob.operator--(int) 友元函数重载 ++ob重载为:operator++(X &ob) ob--重载为:operator--(X &ob, int) 调用时,参数int一般传递给值0。
++、--重载实例 设计一个会报警的计数器类。该计数器从0开始计数,当到达预先设定好的报警值时,计数器会发出报警消息,计数器的值不再增加。
类定义 class Counter { int value; //计数器的值 int alarm; //报警值 public: Counter(int a) {value = 0; alarm = a;} Counter & operator++(); //前缀的++重载 Counter operator++(int); //后缀的++重载 void print() {cout << value << endl; } };
类实现 Counter & Counter::operator++() { if (value == alarm) cout << "已超过报警值\n"; else { ++value; if (value == alarm) cout << "已到达报警值\n"; } return *this; Counter Counter::operator++(int x) { Counter tmp = *this; //保存对象修改前的状态 if (value == alarm) cout << "已超过报警值\n"; return tmp; //返回修改前的状态
类的使用 int main() { Counter cnt(3); //定义一个Counter类的对象,报警值为3 cnt.print(); /显示对象的当前值,此时输出为0 ++cnt; cnt.print(); // 此时输出为1 (++cnt).print(); //调用前缀的++,输出2 (cnt++).print(); //调用后缀的++,当前对象的value已经 //加1,报警。但输出的是2 cnt.print(); //输出值为3 return 0; }
几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载
重载函数的原型设计考虑 参数设计 返回值的类型设计 对于任何函数的参数,如果仅需要从参数中读,而不改变它,一般用const引用来传递。 只有会修改左值参数的运算符,如赋值运算符,左值参数不是常量,所以用地址传递 返回值的类型设计 运算符的结果产生一个新值,就需要产生一个作为返回值的新对象 对于逻辑运算符,人们希望至少得到一个int或bool的返回值 所有的赋值运算符(如,=,+=等)均改变左值,应该能够返回一个刚刚改变了的左值的非常量引用
值返回时的优化 在返回一个对象时,通常有两种写法。如某函数返回一个Rational类的对象,它的值为两个参数的成员对应相加。它的两种写法为 return Rational( left.num + right.num, left.den + right.den); Rational tmp; tmp.num = left.num + right.num; tmp.den = left.den + right.den; return tmp;
两种写法的比较 前者的意思是“创建一个临时对象,并返回它” 。它只调用了一次构造函数。 而后者,先创建了一个对象tmp,这将调用构造函数,然后对tmp赋值,最后返回tmp。而在返回tmp时,又要创建一个临时对象,并调用拷贝构造函数用tmp对它进行初始化。在函数执行结束时,还要调用析构函数析构tmp。
几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载
输入输出运算符重载 输入输出运算符必须被重载成全局函数。 输出运算符的重载 输入运算符的重载 借助于流插入运算符(>>)和流提取运算符(<<)输入和输出用户自定义类的对象 输入输出运算符必须被重载成全局函数。 输出运算符的重载 输入运算符的重载
输出重载函数的原型 ostream & operator<<(ostream & os, const ClassType &obj) { os << 要输出的内容; return os; }
实例 为Rational类重载输出 ostream& operator<<(ostream &os, const Rational& obj) // 输出重载函数 { os << obj.num << '/' << obj.den; return os; } 如定义: Rational r(2,6); 执行cout << r;的结果是 1/3。
输入输出运算符重载 输入输出运算符必须被重载成全局函数。 输出运算符的重载 输入运算符的重载 借助于流插入运算符(>>)和流提取运算符(<<)输入和输出用户自定义类的对象 输入输出运算符必须被重载成全局函数。 输出运算符的重载 输入运算符的重载
输入重载函数的原型 istream & operator>>(istream & is, ClassType &obj) return is; }
实例 为Rational类重载输入 istream& operator>>(istream &in, Rational& obj) // 输入重载函数 { in >> obj.num >> obj.den; obj.ReductFraction(); return in; } 如定义:Rational r; 可以用cin >> r 从键盘输入r的数据。如输入为:1 3 执行cout << r;的结果是 1/3。
第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例
类型转换--系统预定义类型间的转换 隐式类型转换 ※ 赋值时 ※ 运算时 显式类型转换 ※ 强制转换法:(类型名)表达式 ※ 函数法:类型名(表达式)
自定义类型转换运算符 类类型能否和其他的类类型或内置类型互相转换? 内置类型之所以能互相转换是因为系统预先制定了转换的规则,并写好了完成转换的程序。 类类型与其它类类型或内置类型之间如何转换,编译器预先无法知道。类的设计者必须定义转换的方法。
类型转换 内置类型到类类型的转换 类类型到其它类型的转换
内置类型到类类型的转换 利用构造函数进行转换。 例如,对于Rational类的对象r,可以执行r=2。 此时,编译器隐式地调用Rational的构造函数,传给它一个参数2。构造函数将构造出一个num=2,den= 1的Rational类的对象,并将它赋给r。
explicit构造函数 任何单参数的构造函数都可以被编译器用来执行隐式转换,即把内置类型转换成对应的类类型。 在某些情况下,隐式转换是不受欢迎的。 将单参数的构造函数定义为explicit,将告诉编译器不允许执行隐式转换。 如将Ratioanal类的构造函数定义成 explicit Rational(int n1 = 0, int n2 = 1) 则对于Rational类的对象r1和r2,执行 r1 = 2 + r2;编译器就会报错
类型转换 内置类型到类类型的转换 类类型到其它类型的转换
类类型到内置类型或其他类类型的转换 可以通过类型转换函数实现 类型转换函数必须重载成成员函数 类型转换函数的格式 operator 目标类型名 ( ) const { … return (结果为目标类型的表达式); } 类型转换函数的特点 无参数,无返回值 是const函数
Rational类到double的转换 转换函数的定义: operator double () const { return (double(num)/den);} 有了这个函数,我们可以将一个Rational类的对象r赋给一个double类型的变量x。如r的值为(1,3),经过赋值x = r后,x的值为0.333333
经过运算符重载后的Rational类 class Rational { friend istream& operator>>(istream &in, Rational& obj); friend ostream& operator<<(ostream &os, const Rational& obj); friend Rational operator+(const Rational &r1, const Rational &r2); friend Rational 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;} operator double () const { return (double(num)/den);} };
Rational类的使用 输入r1: 1 3 输入r2: 2 6 #include <iostream.h> 1/3+1/3 = 2/3 1/3*1/3 = 1/9 (r1 + r2) * r3的值为2/27 5.5 - r1的值为:5.16667 1/3 #include <iostream.h> #include "Rational.h" int main() { Rational r1, r2, r3, r4; double x; cout << "输入r1: "; cin >> r1; cout << "输入r2: "; cin >> r2; r3 = r1 + r2; cout << r1 << '+' << r2 << " = " << r3 << endl; r3 = r1 * r2; cout << r1 << '*' << r2 << " = " << r3 << endl; r4 = (r1 + r2) * r3; cout << "(r1 + r2) * r3的值为:" << r4 << endl; x = 5.5 - r1; cout << "5.5 - r1的值为:" << x << endl; cout << (r1 < r2 ? r1 : r2) << endl; return 0; }
第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例
运算符重载实例 完善DoubleArray类
DoubleArray.h #ifndef _array_h #define _array_h #include <iostream.h> class DoubleArray{ friend ostream &operator<<(ostream &os, const DoubleArray &obj); friend istream &operator>>(istream &is, DoubleArray &obj); friend bool operator==(const DoubleArray &obj1, const DoubleArray &obj2); private: int low; int high; double *storage;
public: DoubleArray(int lh = 0, int rh = 0):low(lh), high(rh) { storage = new double [high - low + 1]; } DoubleArray(const DoubleArray &arr); DoubleArray &operator=(const DoubleArray &right); double & operator[](int index); const double & operator[](int index) const; DoubleArray operator()(int start, int end, int lh); ~DoubleArray() {delete [] storage; } }; #endif
DoubleArray.cpp //文件名:DoubleArray.cpp //DoubleArray类的实现 #include <cassert> #include "DoubleArray.h“ DoubleArray::DoubleArray(const DoubleArray &arr) { low = arr.low; high = arr.high; storage = new double [high - low + 1]; for (int i = 0; i < high -low + 1; ++i) storage[i] = arr.storage[i]; }
operator= DoubleArray &DoubleArray::operator= (const DoubleArray & a) { if (this == &a) return *this; delete [] storage; low = a.low; high = a.high; storage = new double[high - low + 1]; for (int i=0; i <= high - low; ++i) storage[i] = a.storage[i]; return *this; }
operator[] double & DoubleArray::operator[](int index) { assert(index >= low && index <= high); return storage[index - low]; } const double & DoubleArray::operator[] (int index) const
operator<< ostream &operator<<(ostream &os, const DoubleArray &obj) { os << "数组内容为:\n"; for (int i=obj.low; i<=obj.high; ++i) os << obj[i] << '\t'; os << endl; return os; }
operator>> istream &operator>>( istream &is, DoubleArray &obj) { cout << "请输入数组元素[" << obj.low << ", " << obj.high << "]:\n"; for (int i=obj.low; i<=obj.high ; ++i) is >> obj[i] ; return is; }
operator== bool operator==(const DoubleArray &obj1, const DoubleArray &obj2) { if (obj1.low != obj2.low || obj1.high != obj2.high) return false; for (int i = obj1.low; i<=obj1.high; ++i) if (obj1[i] != obj2[i]) return false; return true; }
operator() DoubleArray DoubleArray::operator() (int start, int end, int lh) { assert (start <= end && start >= low && end <= high ); DoubleArray tmp(lh, lh + end - start); for (int i = 0; i < end - start + 1; ++i) tmp.storage[i] = storage[start + i - low]; return tmp; }
Main函数 int main() { DoubleArray array1(20,30), array2; cin >> array1; cout << "array1 "; cout << array1; array2 = array1; cout << "执行 array2 = array1, array2 " << array2; cout << "array1 == array2 是 " << ((array1 == array2) ? "true" : "false") << endl; array2[25] = 0; cout << "执行array[25] = 0后, array1 == array2 是 " array2 = array1(22, 25, 2); cout << "执行array2 = array1(22, 25, 2)后, array2 的值为: " << array2; return 0; }
执行结果 请输入数组元素[20,30]: 1 2 3 4 5 6 7 8 9 10 11 array1的内容为: 1 2 3 4 5 6 7 8 9 10 11 执行 array2 = array1,array2的内容为: array1 == array2是true 执行array2[25] = 0后,array1 == array2是false 执行array2 = array1(22, 25, 2)后, array2 的值为:3 4 5 6
小结 运算符重载的作用 如何选择用成员函数或全局函数 如何写一个重载函数 介绍了一种区分++和—的前后缀应用的方法 通过运算符重载实现类类型和内置类型及其他类类型之间的转换
第12章 组合与继承 组合 继承 虚函数与多态性 纯虚函数与抽象类 多继承 面向对象设计范例
组合 组合就是把用户定义类的对象作为新类的数据成员 组合表示一种聚集关系,是一种部分和整体(is a part of)的关系 必须用初始化列表去初始化对象成员
组合实例 定义一个复数类,而复数的虚部和实部都用有理数表示
类定义 class Complex{ friend Complex operator+(Complex x, Complex y); friend istream& operator>>(istream &is, Complex &obj); friend ostream& operator<<(ostream &os, const Complex &obj); Rational real; //实部 Rational imag; //虚部 public: Complex(int r1 = 0, int r2 = 1, int i1= 0, int i2 = 1): real(r1, r2), imag(i1, i2) {} };
成员函数的实现 Complex operator+(Complex x, Complex y) { Complex tmp; //利用Rational类的加法重载函数完成实部和虚部的相加 tmp.real = x.real + y.real; tmp.imag = x.imag + y.imag; return tmp; }
istream& operator>>(istream &is, Complex &obj) { cout << "请输入实部:"; is >> obj.real; //利用Rational类的输入重载实现实部的输入 cout << "请输入虚部:"; is >> obj.imag; //利用Rational类的输入重载实现虚部的输入 return is; } ostream& operator<<(ostream &os, const Complex &obj) { //利用Rational类的输出重载实现实部和虚部的输出 cout << '(' << obj.real << " + " << obj.imag << "i" << ')'; return os;
复数类的使用 int main() {Complex x1,x2,x3; cout << "请输入x1:\n"; cin >> x1; cout << "请输入x2: \n"; cin >> x2; x3 = x1 + x2; cout << x1 << " + " << x2 << " = " << x3 << endl; return 0; }