多态
|总字数:3.9k|阅读时长:15分钟|浏览量:|
摘要
介绍了C++中的多态性,包括编译时多态(函数重载、运算符重载)和运行时多态(虚函数、动态绑定),并详细讲解了纯虚函数和抽象类的概念及其应用。
目录
[TOC]
程序的多态性
多态的含义
在程序中同一符号或名字在不同情况下具有不同解释
两种最基本的形式
编译时的多态
指在程序编译阶段即可确定下来的多态性
由重载机制实现:
运行时的多态
指必须等到程序动态运行时才可确定的多态性
由动态绑定实现:
继承 + 虚函数
重载
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。
下面的实例中,同名函数 print() 被用于输出不同的数据类型:
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <iostream> using namespace std; class printData { public: void print(int i) { cout << "整数为: " << i << endl; } void print(double f) { cout << "浮点数为: " << f << endl; } void print(char c[]) { cout << "字符串为: " << c << endl; } }; int main(void) { printData pd; pd.print(5); pd.print(500.263); char c[] = "Hello C++"; pd.print(c); return 0; }
|
当上面的代码被编译和执行时,它会产生下列结果:
1 2 3
| 整数为: 5 浮点数为: 500.263 字符串为: Hello C++
|
函数重载的二义性
函数调用时参数的隐式类型转换
编译程序选择重载函数的原则:
- 若实参类型与某一个重载函数的形参类型完全匹配,则选择调用该重载函数;
- 若找不到与实参类型完全匹配的函数原型,但若将实参类型转换为更高级类型后能找到完全匹配,编译程序将选择该调用重载函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> using nameapace std; float abs (float x) { return (x >= 0 ? x : -x); } double abs (double x) { return (x >= 0 ? x : -x); } int main() { cout << abs(3.14) << "\n"; cout << abs(-5) << “\n”; return 0; }
|
在重载函数中使用缺省参数造成二义性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <iostream> using namespace std; int func(int i) { return i; } int func(int i, int j = 10) { return i * j; } int main() { cout << func(3, 4) << "\n"; cout << func(5) << "\n"; return 0; }
void print(int x) { cout<<"Calling print(int) with "<<x<<".\n"; } void print(int x, int y) { cout<<"Calling print(int, int) with " <<x<<", "<<y<< ".\n"; } void print(double x) { cout<<"Calling print(double) with "<<x<<".\n"; }
int main() { print(800); print(3.14159); print(3, 4); float f=1.5; print(f); return 0; }
|
构造函数重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <iostream> using namespace std; class COMPLEX { public: COMPLEX(double r = 0, double i = 0); COMPLEX(const COMPLEX& other); void print(); COMPLEX add(const COMPLEX& other); COMPLEX subtract(const COMPLEX& other); protected: double real, image; }; COMPLEX::COMPLEX(double r, double i) { real = r; image = i; } COMPLEX::COMPLEX(const COMPLEX& other) { real = other.real; image = other.image; } void COMPLEX::print() { cout << real; if (image > 0) cout << "+" << image << "i"; else if (image < 0) cout << image << "i"; cout << "\n"; } COMPLEX COMPLEX::add(const COMPLEX& other) { real = real + other.real; image = image + other.image; return *this; COMPLEX COMPLEX::subtract(const COMPLEX& other) { real = real - other.real; image = image - other.image; return *this; } int main() { COMPLEX c1(1, 2); COMPLEX c2(2); COMPLEX c3(c1); c3.print(); c2.add(c1); c3.subtract(c2); c3.print(); return 0; }
|
运算符重载
概念
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
1
| Box operator+(const Box&);
|
声明加法运算符用于把两个 Box 对象相加,返回最终的 Box 对象。大多数的重载运算符可被定义为普通的非成员函数或者被定义为类成员函数。如果我们定义上面的函数为类的非成员函数,那么我们需要为每次操作传递两个参数,如下所示:
1
| Box operator+(const Box&, const Box&);
|
下面的实例使用成员函数演示了运算符重载的概念。在这里,对象作为参数进行传递,对象的属性使用 this 运算符进行访问,如下所示:
例1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| #include <iostream> using namespace std; class Box { public: double getVolume(void) { return length * breadth * height; } void setLength( double len ) { length = len; } void setBreadth( double bre ) { breadth = bre; } void setHeight( double hei ) { height = hei; } Box operator+(const Box& b) { Box box; box.length = this->length + b.length; box.breadth = this->breadth + b.breadth; box.height = this->height + b.height; return box; } private: double length; double breadth; double height; };
int main( ) { Box Box1; Box Box2; Box Box3; double volume = 0.0; Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0); Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0); volume = Box1.getVolume(); cout << "Volume of Box1 : " << volume <<endl; volume = Box2.getVolume(); cout << "Volume of Box2 : " << volume <<endl; Box3 = Box1 + Box2; volume = Box3.getVolume(); cout << "Volume of Box3 : " << volume <<endl; return 0; }
|
当上面的代码被编译和执行时,它会产生下列结果:
1 2 3
| Volume of Box1 : 210 Volume of Box2 : 1560 Volume of Box3 : 5400
|
例2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> using namespace std;
int abs(int x) { cout << "Using integer version of abs().\n"; return (x >= 0 ? x : -x); }
double abs(double x) { cout << "Using floating-point version of abs().\n"; return (x >= 0.0 ? x : -x); }
long abs(long x) { cout << "Using long integer version of abs().\n"; return (x >= 0 ? x : -x); }
int main() { cout << abs(-5) << "\n"; cout << abs(-1234) << "\n"; cout << abs(3.14) << "\n"; return 0; }
|
其他规则
- 重载运算符无法改变任何运算符的优先级与结合性质;
- 重载运算符不可改变运算符的操作数个数;
- 重载运算符不可使用缺省参数;
- 除了赋值运算外,重载运算符可由派生类继承下去;
运算符的可重载性
下面是可重载的运算符列表:
双目算术运算符 |
+ (加),-(减),*(乘),/(除),% (取模) |
关系运算符 |
==(等于),!= (不等于),< (小于),> (大于),<=(小于等于),>=(大于等于) |
逻辑运算符 |
\ |
\ |
(逻辑或),&&(逻辑与),!(逻辑非) |
单目运算符 |
+ (正),-(负),*(指针),&(取地址) |
自增自减运算符 |
++(自增),—(自减) |
位运算符 |
\ |
(按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移) |
赋值运算符 |
=, +=, -=, *=, /= , % = , &=, \ |
=, ^=, <<=, >>= |
空间申请与释放 |
new, delete, new[ ] , delete[] |
其他运算符 |
()(函数调用),->(成员访问),,(逗号),[](下标) |
下面是不可重载的运算符列表:
- .:成员访问运算符
- .*, ->*:成员指针访问运算符
- :::域运算符
- sizeof:长度运算符
- ?::条件运算符
- #: 预处理符号
各种运算符重载的实例,帮助更好地理解重载的概念。
友元运算符重载
规则
- 一元运算符必须显式声明一个参数
- 二元运算符必须显式声明二个参数
- 不能做友元重载的运算符的运算符
= ( ) [ ] ->
- 友元函数不是该类的成员,因此在友元函数中不能使用this指针
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <iostream> using namespace std; class INTEGER { public: INTEGER(int i = 0) { value = i; return; } INTEGER(const INTEGER& other) { value = other.value; return; } friend INTEGER operator +(INTEGER left, INTEGER right) { INTEGER temp; temp.value = left.value + right.value; return temp; } friend void display(INTEGER &t); private: int value; }; void display(INTEGER &t) { cout<<t.value<<endl; } void main() { INTEGER x(10); INTEGER y = x; INTEGER z; y = x + 2; z = 30 + y; }
|
虚函数
虚函数是类中被冠以virtual的成员函数
1 2 3 4
| virtual 返回值类型 类名::函数名(参数表) { …… }
|
虚函数在公有继承结构中在一个或多个派生类中被重定义,在被调用过程中,通过指向基类的指针实现动态绑定
使用虚函数
使用前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <iostream> class BASE { public: void who( ) { cout<<“BASE\n”;} }; class FIRST_D:public BASE { public: void who( ) { cout<<“First Derivation\n”;} }; class SECOND_D:public BASE { public: void who( ) { cout<<“Second Derivation\n”;} }; int main( ) { BASE b_obj; FIRST_D f_obj; SECOND_D s_obj; BASE *p; p = &b_obj; p->who(); p = &f_obj; p->who(); p = &s_obj; p->who(); return 0; }
|
输出结果
不管p指向什么对象,通过p三次调用的都是基类的who函数
原因:普通成员函数的调用采用的是静态绑定,通过指针引起的普通成员函数调用,仅仅与指针(或引用)的基类型有关,而与此刻该指针正在指向什么对象无关
使用后
p->who() 依赖于运行时p所指向的对象:
1 2 3
| class BASE { public: virtual void who( ) { cout<<“BASE\n”;} };
|
输出结果
1 2 3
| BASE First Derivation Second Derivation
|
运行时的多态性:
基类的虚函数定义了一种接口,在派生类中为此接口定义不同的实现版本,由于虚函数的解释机制,实现“单界面、多实现版本”的思想,在运行时刻才将函数与不同实现版本进行匹配
虚函数说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> using namespace std; class BASE { public: virtual void f1( ) { …… } virtual void f2( ) { …… } virtual void f3( ) { …… } void f ( ) { …… } }; class DERIVED:public BASE { public: void f1( ) { …… } void f2( int a ) { …… } char f3( ) { …… } void f ( ) { …… } }; void main( ) { DERIVED d; BASE *p = &d ; p->f1( ); p->f2( ); p->f ( ); ((DERIVED *)p)->f2(100); }
|
虚函数的限制
- 虚函数必须是类的成员函数
- 不能将友元说明为虚函数
- 一旦一个函数被说明成虚函数,不管经历了多少派生类层次,都将保持其虚特性
例
1 2 3 4 5 6 7
| class THREE_D:public FIRST_D { public: void who( ) { cout<<“Three Derivation\n”;} };
|
纯虚函数和抽象类
纯虚函数
是一个在基类中声明的虚函数,但在基类中没有定义函数体,要求任何派生类都必须定义自己的版本
一般形式virtual 函数类型 函数名(形参表)=0;
纯虚函数的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include<iostream> using namespace std; class Circle { public: void setr(int x){ r=x; } virtual void show()=0; protected: int r; }; class Area: public Circle{ public: void show(){ cout<<"Area is "<<3.14*r*r<<endl;} }; class Perimeter: public Circle{ public: void show(){cout<<"Perimeter is "<<2*3.14*r<<endl;} }; void main() { Circle *ptr; Area ob1; Perimeter ob2; ob1.setr(10); ob2.setr(10); ptr=&ob1; ptr->show(); ptr=&ob2; ptr->show(); }
|
抽象类
如果一个类中至少有一个纯虚函数,则该类为抽象类
抽象类的特性
- 只能用作其他类的基类
- 不能用于直接创建对象实例
- 不能做函数的形参类型、返回类型或显式类型转换。
- 可声明抽象类的指针和引用