Presentation is loading. Please wait.

Presentation is loading. Please wait.

第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例.

Similar presentations


Presentation on theme: "第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例."— Presentation transcript:

1 第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例

2 什么是运算符重载 使系统内置的运算符可以用于类类型 例如:+ 运算符能够实现2个对象间的加。例如:类A的对象a1、a2、a3,希望:

3 问题的提出 把某些事交给系统去做,用户只要知道相加就可 扩充运算符的功能 增强了C++ 语言的可扩充性 使用户定义的类更像系统的内置类型

4 运算符重载的限制 不是所有的运算符都能重载 重载不能改变运算符的优先级和结合性 重载不能改变运算符的操作数个数 不能创建新的运算符

5 可以重载的运算符 +         -         *         /         %         ^         &        |     ~        !         =         <        >        +=        -=        *=     /=        %=        ^=        &=        |=        <<        >>        >>=     <<=       ==        !=        <=        >=        &&        ||        ++     --        ->*        ,        ->       []        ()        new       delete     new[]     delete[]

6 不能重载的运算符   .        .*        ::        ?:        sizeof

7 第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例

8 运算符重载的方法 运算符重载就是写一个函数解释某个运算符在某个类中的含义
要使得系统能自动找到重载的这个函数,函数名必须要体现出和某个被重载的运算符的联系。 C++中规定,重载函数名为

9 函数原型 运算符的重载不能改变运算符的运算对象数。因此,重载函数的形式参数个数(包括成员函数的隐式指针this)与运算符的运算对象数相同
运算符重载可以重载成成员函数也可以重载成全局函数实现。重载成全局函数时,最好把此函数设为友员函数 如果作为类的成员函数,它的形式参数个数比运算符的运算对象数少1。这是因为成员函数有一个隐含的参数this。在C++中,把隐含参数this作为运算符的第一个参数。 当把一个一元运算符重载成成员函数时,该函数没有形式参数。 把一个二元运算符重载成成员函数时,该函数只有一个形式参数,就是右操作数,当前对象是左操作数。

10 重载实例 为rational类增加“+”和“*”以及比较的重载函数,用以替换现有的add和multi函数

11 方案一:重载成成员函数 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; } }

12 函数实现 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;

13 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);}

14 方案二:重载成友员函数 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;} };

15 函数的实现 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; 其他函数实现略

16 重载后有理数类的使用 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; }

17 全局函数 vs成员函数 大多数运算符都可以重载成成员函数或全局函数。
赋值(=)、下标([])函数调用(())和成员访问(->)必须重载成成员函数。 具有赋值意义的运算符,如复合的赋值运算符以及++和--,不一定非要定义为成员函数,但最好定义为成员函数。 具有两个运算对象的运算符最好重载为全局函数,这样可以使得应用更加灵活。如果把加运算定义成全局函数,r是有理数类的对象,则2+r是一个合法的表达式。

18 第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例

19 几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载

20 赋值运算符 对任一类,如果用户没有自定义赋值运算符函数,那么系统为其生成一个缺省的赋值运算符函数,在对应的数据成员间赋值。
一般情况下,这个缺省的赋值运算符重载函数能满足用户的需求。但是,当类含有类型为指针的数据成员时,可能会带来一些麻烦。

21 对DoubleArray类对象执行 array1 = array2的问题
会引起内存泄漏 使这两个数组的元素存放于同一块空间中 当这两个对象析构时,先析构的对象会释放存储数组元素的空间。而当后一个对象析构时,无法释放存放数组元素的空间

22 赋值运算符“=”的原型 赋值运算符只能重载成成员函数 函数原型: X &X::operator=(const X &source) {
// 赋值过程 } 一旦创建了对象x1, x2, 可以用 x1 = x2赋值。

23 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; }

24 赋值运算符重载要点 一般来讲,需要自定义拷贝构造函数的类也需要自定义赋值运算符重载函数。
在赋值运算符重载函数中,已经将参数的值赋值给了当前对象,那为什么还需要返回值呢?记住,在C++中,赋值是一个运算,它可以形成一个表达式,而该表达式的结果值就是赋给左边的对象的值。因此,赋值运算符重载函数必须返回赋给左边的对象值。

25 赋值运算符重载和拷贝构造函数 一般来讲,需要拷贝构造函数的类也需要重载赋值运算符 定义对象时给对象赋初值调用的是拷贝构造函数
程序的语句部分中的赋值语句调用的是赋值运算符重载函数

26 几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载

27 下标运算符重载 能否象普通的数组那样通过下标运算操作DoubleArray类的对象,这样可以使DoubleArray类更像一个功能内置的数组。 可以通过重载下标运算符([])来实现 下标运算符是二元运算符,第一个运算数是数组名,第二个运算数是下标值 下标运算符必须重载成成员函数

28 DoubleArray类的[ ]重载 double & DoubleArray::operator[](int index)
{ if (index < low || index > high) {cout << "下标越界"; exit(-1); } return storage[index - low]; }

29 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';

30 几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载

31 函数调用运算符 函数调用运算符()是一个二元运算符。它的第一个运算对象是函数名,第二个参数是形式参数表。运算的结果是函数的返回值。
一个类重载了函数调用运算符,就可以把这个类的对象当做函数来使用

32 函数调用运算符重载 函数调用运算符必须重载成成员函数 函数调用运算符重载函数的原型为 函数的返回值 operator() (形式参数表);

33 函数调用运算符重载实例 在DoubleArray类增加一个功能:取数组中的一部分元素形成一个新的数组
例如,在一个下标范围为10到20的数组arr中取出下标为第12到15的元素,形成一个下标范围为2到5的数组存放在数组arr1中,可以调用 arr1 = arr(12, 15, 2)。

34 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; }

35 几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载

36 “++”和“--”重载 ++、- -:是一元操作符
这两个操作符可以是前缀,也可以是后缀。而且前缀和后缀的含义是有区别的。所以,必须有两个重载函数。 问题:两个重载函数有相同的原型 区分方法: 前缀:一元操作符。 后缀:二元操作符。

37 “++”和“--”重载 cont. 成员函数重载 ++ob重载为:ob.operator++()
ob-- 重载为:ob.operator--(int) 友元函数重载 ++ob重载为:operator++(X &ob) ob--重载为:operator--(X &ob, int) 调用时,参数int一般传递给值0。

38 ++、--重载实例 设计一个会报警的计数器类。该计数器从0开始计数,当到达预先设定好的报警值时,计数器会发出报警消息,计数器的值不再增加。

39 类定义 class Counter { int value; //计数器的值 int alarm; //报警值 public:
Counter(int a) {value = 0; alarm = a;} Counter & operator++(); //前缀的++重载 Counter operator++(int); //后缀的++重载 void print() {cout << value << endl; } };

40 类实现 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; //返回修改前的状态

41 类的使用 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; }

42 几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载

43 重载函数的原型设计考虑 参数设计 返回值的类型设计 对于任何函数的参数,如果仅需要从参数中读,而不改变它,一般用const引用来传递。
只有会修改左值参数的运算符,如赋值运算符,左值参数不是常量,所以用地址传递 返回值的类型设计 运算符的结果产生一个新值,就需要产生一个作为返回值的新对象 对于逻辑运算符,人们希望至少得到一个int或bool的返回值 所有的赋值运算符(如,=,+=等)均改变左值,应该能够返回一个刚刚改变了的左值的非常量引用

44 值返回时的优化 在返回一个对象时,通常有两种写法。如某函数返回一个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;

45 两种写法的比较 前者的意思是“创建一个临时对象,并返回它” 。它只调用了一次构造函数。
而后者,先创建了一个对象tmp,这将调用构造函数,然后对tmp赋值,最后返回tmp。而在返回tmp时,又要创建一个临时对象,并调用拷贝构造函数用tmp对它进行初始化。在函数执行结束时,还要调用析构函数析构tmp。

46 几个特殊的运算符的重载 赋值运算符 下标运算符 函数调用运算符 ++和—运算符的重载 重载函数的原型设计考虑 输入输出运算符重载

47 输入输出运算符重载 输入输出运算符必须被重载成全局函数。 输出运算符的重载 输入运算符的重载
借助于流插入运算符(>>)和流提取运算符(<<)输入和输出用户自定义类的对象 输入输出运算符必须被重载成全局函数。 输出运算符的重载 输入运算符的重载

48 输出重载函数的原型 ostream & operator<<(ostream & os,
const ClassType &obj) { os << 要输出的内容; return os; }

49 实例 为Rational类重载输出 ostream& operator<<(ostream &os,
const Rational& obj) // 输出重载函数 { os << obj.num << '/' << obj.den; return os; } 如定义: Rational r(2,6); 执行cout << r;的结果是 1/3。

50 输入输出运算符重载 输入输出运算符必须被重载成全局函数。 输出运算符的重载 输入运算符的重载
借助于流插入运算符(>>)和流提取运算符(<<)输入和输出用户自定义类的对象 输入输出运算符必须被重载成全局函数。 输出运算符的重载 输入运算符的重载

51 输入重载函数的原型 istream & operator>>(istream & is, ClassType &obj)
return is; }

52 实例 为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。

53 第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例

54 类型转换--系统预定义类型间的转换 隐式类型转换 ※ 赋值时 ※ 运算时 显式类型转换 ※ 强制转换法:(类型名)表达式
※ 函数法:类型名(表达式)

55 自定义类型转换运算符 类类型能否和其他的类类型或内置类型互相转换?
内置类型之所以能互相转换是因为系统预先制定了转换的规则,并写好了完成转换的程序。 类类型与其它类类型或内置类型之间如何转换,编译器预先无法知道。类的设计者必须定义转换的方法。

56 类型转换 内置类型到类类型的转换 类类型到其它类型的转换

57 内置类型到类类型的转换 利用构造函数进行转换。 例如,对于Rational类的对象r,可以执行r=2。
此时,编译器隐式地调用Rational的构造函数,传给它一个参数2。构造函数将构造出一个num=2,den= 1的Rational类的对象,并将它赋给r。

58 explicit构造函数 任何单参数的构造函数都可以被编译器用来执行隐式转换,即把内置类型转换成对应的类类型。
在某些情况下,隐式转换是不受欢迎的。 将单参数的构造函数定义为explicit,将告诉编译器不允许执行隐式转换。 如将Ratioanal类的构造函数定义成 explicit Rational(int n1 = 0, int n2 = 1) 则对于Rational类的对象r1和r2,执行 r1 = 2 + r2;编译器就会报错

59 类型转换 内置类型到类类型的转换 类类型到其它类型的转换

60 类类型到内置类型或其他类类型的转换 可以通过类型转换函数实现 类型转换函数必须重载成成员函数 类型转换函数的格式
operator 目标类型名 ( ) const { … return (结果为目标类型的表达式); } 类型转换函数的特点 无参数,无返回值 是const函数

61 Rational类到double的转换 转换函数的定义: operator double () const
{ return (double(num)/den);} 有了这个函数,我们可以将一个Rational类的对象r赋给一个double类型的变量x。如r的值为(1,3),经过赋值x = r后,x的值为

62 经过运算符重载后的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);} };

63 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的值为: 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 = r1; cout << "5.5 - r1的值为:" << x << endl; cout << (r1 < r2 ? r1 : r2) << endl; return 0; }

64 第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例

65 运算符重载实例 完善DoubleArray类

66 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;

67 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

68 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]; }

69 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; }

70 operator[] double & DoubleArray::operator[](int index)
{ assert(index >= low && index <= high); return storage[index - low]; } const double & DoubleArray::operator[] (int index) const

71 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; }

72 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; }

73 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; }

74 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; }

75 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; }

76 执行结果 请输入数组元素[20,30]: 1 2 3 4 5 6 7 8 9 10 11 array1的内容为:
执行 array2 = array1,array2的内容为: array1 == array2是true 执行array2[25] = 0后,array1 == array2是false 执行array2 = array1(22, 25, 2)后, array2 的值为:

77 小结 运算符重载的作用 如何选择用成员函数或全局函数 如何写一个重载函数 介绍了一种区分++和—的前后缀应用的方法
通过运算符重载实现类类型和内置类型及其他类类型之间的转换

78 第12章 组合与继承 组合 继承 虚函数与多态性 纯虚函数与抽象类 多继承 面向对象设计范例

79 组合 组合就是把用户定义类的对象作为新类的数据成员 组合表示一种聚集关系,是一种部分和整体(is a part of)的关系
必须用初始化列表去初始化对象成员

80 组合实例 定义一个复数类,而复数的虚部和实部都用有理数表示

81 类定义 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) {} };

82 成员函数的实现 Complex operator+(Complex x, Complex y) { Complex tmp;
//利用Rational类的加法重载函数完成实部和虚部的相加 tmp.real = x.real + y.real; tmp.imag = x.imag + y.imag; return tmp; }

83 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;

84 复数类的使用 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; }


Download ppt "第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例."

Similar presentations


Ads by Google