• 1. C++语言程序设计杨国兴 张东玲 彭涛中国水利水电出版社
  • 2. 第6章 多态性6.1 运算符重载 6.2 运算符重载为类的成员函数 6.3 运算符重载为类的友元函数 6.4 虚函数
  • 3. 6.1 运算符重载6.1.1 问题的提出 例4.3的复数类 #include "iostream.h" class CComplex { private: double real; double imag; public: CComplex(double r, double i); void Print(); CComplex Add(CComplex c); CComplex Sub(CComplex c); }; 第6章 多态性CComplex CComplex::Add(CComplex c) { CComplex temp; temp.real = real + c.real; temp.imag = imag + c.imag; return temp; } CComplex CComplex::Sub(CComplex c) { CComplex temp; temp.real = real - c.real; temp.imag = imag - c.imag; return temp; }
  • 4. 6.1 运算符重载6.1.1 问题的提出(续一) void main(void) { CComplex a(1, 2), b(3.0, 4.0), c,d; c = a.Add(b); d = a.Sub(b); cout << "c = "; c.Print(); cout << "d = "; d.Print(); } 第6章 多态性复数加减法只能调用成员函数实现,不能使用符号“+”和“-”,可以通过重载“+”、“-”运算符,实现如c=a+b这样的调用方式运算符重载:运算符重载的实质就是对已有的运算符赋予多重含义,使同一个运算符 作用于不同类型的数据时,产生不同的行为。运算符重载的实质就是函数重载。
  • 5. 例6.1 用运算符实现复数的加减运算#include "iostream" using namespace std; class CComplex { private: double real; double imag; public: CComplex(double r=0, double i=0); void Print(); CComplex operator +(CComplex c); CComplex operator -(CComplex c); }; CComplex::CComplex (double r, double i) { real = r; imag = i; } 第6章 多态性
  • 6. 例6.1 (续一)void CComplex::Print() { cout << "(" << real << "," << imag << ")" << endl; } CComplex CComplex::operator +(CComplex c) { CComplex temp; temp.real = real + c.real; temp.imag = imag + c.imag; return temp; } CComplex CComplex::operator -(CComplex c) { CComplex temp; temp.real = real - c.real; temp.imag = imag - c.imag; return temp; } 第6章 多态性
  • 7. 例6.1 (续二)void main(void) { CComplex a(1, 2), b(3.0, 4.0), c,d; c = a+b; d = a-b; cout << "c = "; c.Print(); cout << "d = "; d.Print(); } 第6章 多态性该语句相当于对函数operator +(CComplex c)的调用:“c=a.operator +(b)”,实现两个复数的加法运算。 程序运行结果为: c = (4, 6) d = (-2, -2)
  • 8. 6.1 运算符重载6.1.2 运算符重载的格式与规则 1. 运算符重载的格式 运算符重载为类的成员函数 运算符重载为类的友元函数 运算符重载的为类的成员函数,在类中声明的格式为: 函数类型 operator 运算符(参数表); 定义该函数的格式: 函数类型 类名::operator 运算符(参数表) {        函数体; } 也可以将重载运算符函数的定义直接写在类中。第6章 多态性
  • 9. 6.1 运算符重载6.1.2 运算符重载的格式与规则(续) 2. 运算符重载的规则 (1)除“.”、“*”、“::”、“?:”和“sizeof”等几个运算符不能重载外,C++中几乎所有的运算符都可以重载。 (2)运算符被重载后,其优先级和结合性不会改变。 (3)不能改变运算符操作对象的个数。第6章 多态性 返 回
  • 10. 6.2 运算符重载为类的成员函数6.2.1 双目运算符重载 双目运算符,如果重载为类的成员函数,其参数为一个,即比运算对象少一个。 例6.2 复数的乘法运算,在上例的基础上添加乘法运算符重载函数。复数类乘法运算的定义如下: (a+bi)*(x+yi)= a*x-b*y + (a*y + b*x)i 第6章 多态性
  • 11. 例6.2 复数乘法运算源程序#include "iostream" using namespace std; class CComplex { private: double real; double imag; public: CComplex(double r=0, double i=0); void Print(); CComplex operator +(CComplex c); CComplex operator -(CComplex c); CComplex operator *(CComplex c); }; CComplex::CComplex (double r, double i) { real = r; imag = i; } 第6章 多态性
  • 12. 例6.2 (续一)void CComplex::Print() { cout << "(" << real << "," << imag << ")" << endl; } CComplex CComplex::operator +(CComplex c) { CComplex temp; temp.real = real + c.real; temp.imag = imag + c.imag; return temp; } CComplex CComplex::operator -(CComplex c) { CComplex temp; temp.real = real - c.real; temp.imag = imag - c.imag; return temp; } 第6章 多态性
  • 13. 例6.2 (续二)CComplex CComplex::operator *(CComplex c) { CComplex temp; temp.real = real * c.real - imag * c.imag; temp.imag = real * c.imag + imag * c.real; return temp; } void main(void) { CComplex a(1, 2), b(3.0, 4.0), c, d, e, f; c = a+b; d = a-b; e = a*b; f = a+1; cout << "c = "; c.Print(); cout << "d = "; d.Print(); cout << "e = "; e.Print(); cout << "f = "; f.Print(); }第6章 多态性程序运行结果为: c = (4, 6) d = (-2, -2) e = (-5,10) f = (2, 2)总结:设有双目运算符 B,如果要重载 B 为类的成员函数,使之能够实现表达式 oprd1 B oprd2,其中 oprd1 为A 类对象,则 B 应被重载为 A 类的成员函数,形参类型应该是 oprd2 所属的类型。 经重载后,表达式oprd1 B oprd2 相当于 oprd1.operator B(oprd2),注意重载双目运算符只需要一个参数。
  • 14. 6.2 运算符重载为类的成员函数6.2.2 单目运算符重载 单目运算符,如果重载为类的成员函数,不需要参数。 为区分前置和后置运算符,C++规定: 对于前置单目运算符,重载函数没有参数 对于后置单目运算符,重载函数有一个整型参数,这个整型参数没有其他用途,只是用于区分前置运算与后置运算。第6章 多态性
  • 15. 例6.3 定义一个CInt类,类中只有一个数据成员i,两个运算符“++”的重载函数,一个没有参数,实现的是前置运算符重载,另一个有一个整型参数,实现后置运算符重载。#include "iostream" using namespace std; class CInt { private: int i; public: CInt(int a=0); void Print(); CInt operator ++(); CInt operator ++(int); }; CInt::CInt (int a) { i = a; } 第6章 多态性void CInt::Print() { cout << "i=" << i << endl; } CInt CInt::operator ++() { CInt temp; temp.i = ++i; return temp; } CInt CInt::operator ++(int) { CInt temp; temp.i = i++; return temp; }
  • 16. 例6.3 (续)void main(void) { CInt a(5), b(5), c, d; c = a++; d = ++b; cout << "a: "; a.Print(); cout << "b: "; b.Print(); cout << "c: "; c.Print(); cout << "d: "; d.Print(); } 第6章 多态性程序运行结果为: a: i=6 b: i=6 c: i=5 d: i=6
  • 17. 6.2 运算符重载为类的成员函数6.2.3 赋值运算符重载 如果类中只包含简单数据类型的数据成员,则使用C++提供的赋值运算符“=”就可以实现将一个对象赋给另一个对象。如前面复数类的对象,就可以将一个对象直接赋给另一个对象。但如果类的数据成员比较复杂(如含有指针),这样直接赋值就会产生问题,我们必须重载赋值运算符“=”才能正确使用“=”。第6章 多态性
  • 18. 例6.4 类A只有一个数据成员str,是一个字符指针,在构造函数中为str申请存储空间并赋值,在析构函数中释放内存。#include "iostream" #include "string" using namespace std; class A { private: char *str; public: A(char *s="no data"); ~A(); void print(); }; A::A (char *s) { int len = strlen(s); str = new char[len+1]; strcpy(str,s); } 第6章 多态性
  • 19. 例6.4 (续)A::~A () { if(str) delete []str; } void A::print() { cout << str << endl; } void main(void) { A *p = new A("AAAA"); A a1; a1=*p; a1.print(); delete p; a1.print(); } 第6章 多态性strpstra1AAAA该语句只是将p所指向的对象数据成员str赋给对象a1的数据成员str,即两个对象的str指向了同一个单元 调用析构函数,同时将str所指向的单元释放了,再执行a1.print()时,就会出现错误。
  • 20. 例6.5 带有重载赋值运算符的A类#include "iostream" #include "string" using namespace std; class A { private: char *str; public: A(char *s="no data"); ~A(); A &operator =(A &a); void print(); }; A::A (char *s) { int len = strlen(s); str = new char[len+1]; strcpy(str,s); } 第6章 多态性
  • 21. 例6.5 (续一)A::~A () { if(str) delete []str; } A &A::operator =(A &a) { int len = strlen(a.str); if(str) delete []str; //先释放,再根据实际需要重新申请 str = new char[len+1]; strcpy(str, a.str); return *this; } void A::print() { cout << str << endl; } 第6章 多态性
  • 22. 例6.5 (续二)void main(void) { A *p = new A("AAAA"); A a1; a1=*p; a1.print(); delete p; a1.print(); } 第6章 多态性strpstra1AAAAAAAA程序运行结果为: AAAA AAAA 返 回
  • 23. 6.3 运算符重载为类的友元函数6.3.1 问题的提出 (1)复数与复数相加(c1,c2,c3是复数类的对象)。 c3=c1+c2; (2)一个复数与一个实数的加法运算 c3=c1+10.8; (3)一个实数与一个复数相加就会出现错误。 c3= 10.8 + c1; 因为加号左边的运算对象是实数,C++试图将加号右边的运算对象解释为实数,但C++无法将一个复数转换为一个实数,从而产生错误。 将运算符重载为友元函数可以解决这个问题。第6章 多态性
  • 24. 6.3 运算符重载为类的友元函数6.3.2 运算符重载为友元函数 类中的声明: friend  函数类型 operator 运算符(参数表); 运算符重载函数的定义形式: 函数类型 operator 运算符(参数表) { 函数体; }第6章 多态性
  • 25. 例6.6 用友元函数实现复数类加减运算符的重载#include "iostream" using namespace std; class CComplex { private: double real; double imag; public: CComplex(double r=0, double i=0); void Print(); friend CComplex operator +(CComplex c1,CComplex c2); friend CComplex operator -(CComplex c1,CComplex c2); }; CComplex::CComplex (double r, double i) { real = r; imag = i; } 第6章 多态性
  • 26. 例6.6 (续一)void CComplex::Print() { cout << "(" << real << "," << imag << ")" << endl; } CComplex operator +(CComplex c1, CComplex c2) { CComplex temp; temp.real = c1.real + c2.real; temp.imag = c1.imag + c2.imag; return temp; } CComplex operator -(CComplex c1, CComplex c2) { CComplex temp; temp.real = c1.real - c2.real; temp.imag = c1.imag - c2.imag; return temp; } 第6章 多态性
  • 27. 例6.6 (续二)void main(void) { CComplex a(1, 2), b(3.0, 4.0), c, d, e; c = a+b; d = b-10; e = 20+a; cout << "c = "; c.Print(); cout << "d = "; d.Print(); cout << "e = "; e.Print(); } 第6章 多态性相当于函数调用“c=operator+(a, b)” 程序运行结果为: c = (4, 6) d = (-7, 4) e = (21, 2)总结:设有双目运算符 B,如果要重载 B 为类的友元函数,则该友元函数也有两个参数,分别为运算符的两个运算对象。重载后,表达式oprd1 B oprd2 等同于调用函数operator B(oprd1,oprd2 )。 单目运算符也可以重载为类的友元函数,该友元函数有一个参数。 返 回
  • 28. 6.4 虚函数6.4.1 用虚函数实现动态多态 回顾例5.6 void main() { CShape *ps[3]; CShape s("Red"); CPoint p1(10,10), p2(100,100),p3(50,50); CLine l(p1,p2,"Green"); CCircle c(p3, 20, "Black"); ps[0] = &s; ps[1] = &l; ps[2] = &c; for(int i=0; i<3; i++) ps[i]->Draw(); } 第6章 多态性程序运行结果为: Draw a Shape. The color is Red Draw a Shape. The color is Green Draw a Shape. The color is Red Black 虽然父类的指针可以指向子类的对象,但调用的函数Draw()都是父类CShape的成员函数 为了能通过基类的指针调用派生类的成员函数,可以使用虚函数的方法,即把成员函数Draw()声明为虚函数。
  • 29. 例6.7 用虚函数实现动态多态#include #include using namespace std; class CPoint { private: int X; int Y; public: CPoint(int x=0, int y=0) { X=x; Y=y; } CPoint(CPoint &p) { X=p.X; Y=p.Y; } int GetX() { return X; } int GetY() { return Y; } };第6章 多态性
  • 30. 例6.7 (续一)class CShape { private: char Color[10]; public: CShape(char *c) { strcpy(Color,c); } virtual void Draw() { cout << "Draw a Shape. The color is " << Color << endl; } void PrintColor() { cout << Color << endl; } };第6章 多态性
  • 31. 例6.7 (续二)class CLine:public CShape { private: CPoint Start; CPoint End; public: CLine(CPoint s, CPoint e, char *c):CShape(c),Start(s),End(e) {} virtual void Draw() { cout << "Draw a Line from (" << Start.GetX() << "," << Start.GetY(); cout << ") to ("<< End.GetX() << "," << End.GetY() << "), with color "; PrintColor(); } }; 第6章 多态性
  • 32. 例6.7 (续三)class CCircle:public CShape { private: CPoint Center; int Radius; public: CCircle(CPoint ctr, int r, char *c):CShape(c),Center(ctr) { Radius = r; } virtual void Draw() { cout << "Draw a Circle at center (" << Center.GetX() << "," ; cout << Center.GetY()<< ") with radius " << Radius << " and color "; PrintColor(); } }; 第6章 多态性
  • 33. 例6.7 (续四)void main() { CShape *ps[3]; CShape s("Red"); CPoint p1(10,10), p2(100,100),p3(50,50); CLine l(p1,p2,"Green"); CCircle c(p3, 20, "Black"); ps[0] = &s; ps[1] = &l; ps[2] = &c; for(int i=0; i<3; i++) ps[i]->Draw(); } 第6章 多态性程序运行结果为: Draw a Shape. The color is Red Draw a Line from (10, 10) to (100, 100) , with color Green Draw a Circle at center (50, 50) with radius 20 and color Black
  • 34. 6.4 虚函数6.4.1 用虚函数实现动态多态(续) 总结: (1)将成员函数声明为虚函数,在函数原型前加关键字virtual,如果成员函数的定义直接写在类中,也在前面加关键字virtual。 (2)将成员函数声明为虚函数后,再将基类指针指向派生类对象,在程序运行时,就会根据指针指向的具体对象来调用各自的虚函数,称之为动态多态。 (3)如果基类的成员函数是虚函数,在其派生类中,原型相同的函数自动成为虚函数。第6章 多态性
  • 35. 6.4 虚函数6.4.2 用虚函数实现动态多态的机制 为了实现动态多态,编译器对每个包含虚函数的类创建一个虚函数地址表,并设置一个虚函数地址指针指向这个对象的虚函数地址表。使用基类指针对虚函数调用时,通过这个虚函数地址指针,在虚函数地址表中查找函数地址。 由于包含虚函数的类,有一个虚函数地址指针,与没有虚函数的类相比,含有虚函数类的对象所占用的存储空间要多一个指针所占用的内存。 第6章 多态性
  • 36. 例6.8 含有虚函数类的对象所占用的存储空间。 #include using namespace std; class A { private: int a; public: virtual void func(){} }; class B : public A { private: int b; public: virtual void func(){} virtual void func1(){} }; void main(void) { cout << "sizeof(A)=" << sizeof(A) << endl; cout << "sizeof(B)=" << sizeof(B) << endl; } 第6章 多态性程序运行结果为: sizeof(A)=8 sizeof(B)=12
  • 37. 6.4 虚函数6.4.2 用虚函数实现动态多态的机制(续) 每个含有虚函数类的对象都有一个虚函数地址指针,指向该类虚函数表,如果用基类的指针调用虚函数,在编译时,并不能确定这个指针的具体指向,而是在运行时,根据指针所指向的具体对象(基类的对象或其派生类的对象),虚函数地址指针才有一个确定的值,即相应类的这个虚函数的地址。从而实现动态多态。 第6章 多态性
  • 38. 6.4 虚函数6.4.3 虚析构函数 析构函数也可以定义为虚函数,如果基类的析构函数定义为虚析构函数,则派生类的析构函数就会自动成为虚析构函数。 如果基类的指针指向派生类对象,当用delete删除这个对象时,若析构函数不是虚函数,就要调用基类的析构函数,而不会调用派生类的析构函数。如果为基类和派生类的对象分配了动态内存,或者为派生类的对象成员分配了动态内存,这时释放的只是基类中动态分配的内存,而派生类中动态分配的内存未被释放,因此一般应将析构函数定义为虚析构函数。 第6章 多态性
  • 39. 例6.9 定义职员类CEmployee,数据成员有姓名(字符指针型数据)和年龄,由CEmployee类派生出教师类CTeacher,增加数据成员教学简历(字符指针型数据)。 #include #include using namespace std; class CEmployee { private: char *name; int age; public: CEmployee(char *n, int a); virtual ~CEmployee(); }; CEmployee::CEmployee(char *n, int a) { name=new char[strlen(n)+ 1 ]; strcpy(name, n); age = a; } 第6章 多态性
  • 40. 例6.9 (续一) CEmployee::~CEmployee() { cout << "Destruct CEmployee" << endl; if(name) { delete []name; } } class CTeacher : public CEmployee { private: char *mainCourse; public: CTeacher(char *n, char *course, int a); virtual ~CTeacher(); //由于基类已定义虚析构函数,此处也可不加virtual }; CTeacher::CTeacher(char *n, char * course, int a) : CEmployee(n,a) { mainCourse = new char[strlen(course)+1]; strcpy(mainCourse, course); } 第6章 多态性
  • 41. 例6.9 (续二) CTeacher::~CTeacher() { cout << "Destruct CTeacher" << endl; if(mainCourse) delete []mainCourse; } void main(void) { CEmployee *p[3]; p[0] = new CEmployee("Name1", 20); p[1] = new CTeacher("Name2","C for 2 years,C++ 3 years",26); p[2] = new CTeacher("Name3","Data structure for 2 years,C++ 3 years",30); for(int i=0; i<3; i++) delete p[i]; } 第6章 多态性程序运行结果为: Destruct CEmployee Destruct CTeacher Destruct CEmployee Destruct CTeacher Destruct CEmployee若不定义为虚析构函数,程序运行结果为: Destruct CEmployee Destruct CEmployee Destruct CEmployee由于未调用CTeacher类析构函数,导致成员mainCourse空间未被释放
  • 42. 6.4 虚函数6.4.4 纯虚函数与抽象类 纯虚函数是不必定义函数体的特殊虚函数,纯虚函数的声明格式为: virtual 函数类型 函数名(参数表)= 0; 含有纯虚函数的类称为抽象类。抽象类常常用作派生类的基类。 如果派生类继承了抽象类的纯虚函数,却没有重新定义原型相同且带函数体的虚函数,或者派生类定义了基类所没有定义的纯虚函数,则派生类仍然是抽象类,在多层派生的过程中,如果到某个派生类为止,所有纯虚函数都已全部重新定义,则该派生类就成为非抽象类。 不能定义抽象类的对象,但可以声明抽象类的指针和引用。第6章 多态性
  • 43. 例6.10 将例题6.7中CShape类的函数Draw()声明为纯虚函数,其它类保持不变。class CShape { private: char Color[10]; public: CShape(char *c) { strcpy(Color,c); } virtual void Draw()=0; void PrintColor() { cout << Color << endl; } }; 第6章 多态性void main() { CShape *ps[3]; CPoint p1(10,10), p2(100,100),p3(50,50); CLine l(p1,p2,"Green"); CCircle c(p3, 20, "Black"); ps[1] = &l; ps[2] = &c; for(int i=1; i<3; i++) ps[i]->Draw(); }程序运行结果为: Draw a Line from (10, 10) to (100, 100) , with color Green Draw a Circle at center (50, 50) with radius 20 and color Black 不能定义CShape类的对象, 但可以定义CShape类的指针 返 回
  • 44. 6.5 程序实例例6.11 定义整数栈类IntStack、顺序栈类SeqStack和链接栈类LinkStack,其中整数栈类IntStack是一个抽象类,作为其他两个栈的基类。 分析: 对于顺序栈类,进栈操作的方法是先将栈顶元素下标加1,然后将进栈元素赋给栈顶。出栈操作的方法是,先取得栈顶元素值,再将栈顶元素下标减1(注意:为简化程序,本实例未考虑栈满情况的处理)。 对于连接栈类,进栈操作的方法是先创建一个节点,将进栈元素赋给新创建节点的数据域,再将新创建节点的next指针指向原来的栈顶节点,然后将栈顶指针指向新创建的节点。出栈操作的方法是,先取得栈顶元素值,再将栈顶指针指向原来栈顶的下一个节点。 (具体代码参见程序)第6章 多态性
  • 45. 6.5 程序实例例6.12设计一个集合类Set,包括添加元素、减少元素、判断元素是否在集合中、输出集合,以及实现集合的并集、集合的差集、集合的交集等重载运算符。另外还有一个拷贝构造函数,并编写主函数进行测试。 分析: Set类包括私有数据成员data(存放集合元素)、count(当前元素个数)和maxlen(元素最大个数);函数包括构造函数和拷贝构造函数,另有成员函数find ()(判断元素是否在集合中)、full ()(判断集合是否为满)、empty ()(判断集合是否为空)、create ()(创建集合)、display ()(输出集合)、getlen ()(获取集合实际元素的个数)、increase ()(增加集合元素的最大个数);还重载了运算符+(添加元素和集合的并集)、运算符-(减少元素和集合的差集)、运算符*(集合的交集)。 (具体代码参见程序)第6章 多态性
  • 46. 谢 谢!