简述
- C++有三大特性,封装,继承和多态。本文章就主要介绍下多态。
多态
- 多态性是面向对象程序设计的重要特性。利用多态性可以设计和实现一个易于扩展的系统。在C++中,多态性是指具有不同功能的函数用同一个函数名,即用同一函数名调用不同内容的函数。
- 从系统实现的角度看,多态性分为两类 : 静态多态和动态多态
- 静态多态 : 系统在编译的时候就能知道要调用的是哪个函数。也叫做编译时的多态性。
- 动态多态 : 程序在运行过程中才动态地确定操作所针对的对象。
静态多态
- 静态多态主要通过函数重载和运算符重载来实现。
- 函数重载
- 函数重载指完成不同功能的函数可以具有相同的函数名,通过函数的参数类型和参数个数来决定具体调用哪一个函数。
-
#include <iostream> #include <functional> int myAdd(int data1, int data2, int data3) { std::cout << "myAdd 123" << std::endl; return data1 + data2 + data3; } int myAdd(int data1, int data2) { std::cout << "myAdd 12" << std::endl; return data1 + data2; } int main(){ myAdd(12, 22); myAdd(12, 22, 33); system("pause"); return 0; }
- 运行结果
-
myAdd 12 myAdd 123 请按任意键继续. . .
- 上面这个例子,两个函数名是一样的,但参数个数不相同,我们就可以通过传入不同的参数个数来调用不同的函数,实现函数重载。也可以通过不同的参数类型来实现函数重载,这个就不举例了。需要注意的是,仅函数返回值不同,不能定义为重载函数。
- 运算符重载
- 格式
-
<返回类型说明符> operator <运算符符号>(<参数表>) { 函数体 }
-
- 示例
-
#include <stdio.h> #include <iostream> #include <string.h> #include <stdlib.h> using namespace std; class String { private: //私有成员属性 char *ptr; int len; public: //共有成员方法 String() //构造方法,分配空间,初始化成员属性 { ptr = new char(1); //指针初始化 *ptr = '\0'; len = 0; } String(const char *str) //构造方法 { this->len = strlen(str); this->ptr = new char[this->len + 1]; memset(this->ptr, 0, this->len + 1); strcpy(this->ptr, str); } //析构方法不能重载 ~String() //析构方法,动态内存(malloc),动态对象(new)的回收 { delete[]this->ptr; //[]ptr } void show() { cout << this->ptr << endl; } bool operator==(String &str) //运算符 == 的重载方法 { if (strcmp(this->ptr, str.ptr) == 0) { return true; } else { return false; } } bool operator>(String &str) //运算符 > 的重载方法 { if (strcmp(this->ptr, str.ptr) > 0) { return true; } else { return false; } } bool operator<(String &str) //运算符 < 的重载方法 { if (strcmp(this->ptr, str.ptr) < 0) { return true; } else { return false; } } String &operator=(const String &str) //运算符 = 的重载方法 { if (this != &str) //如果赋值运算符左右不相等 { delete[]this->ptr; this->len = str.len; this->ptr = new char[this->len + 1]; memset(this->ptr, 0, this->len + 1); strcpy(this->ptr, str.ptr); } return *this; } String &operator+(const String &str) //运算符 + 的重载方法 { String *p = new String; p->len = this->len + str.len; p->ptr = new char[p->len + 1]; memset(p->ptr, 0, p->len + 1); strcpy(p->ptr, this->ptr); strcat(p->ptr, str.ptr); return *p; } char operator[](int tmp) //运算符 [] 的重载方法 { return this->ptr[tmp]; } }; int main() { String str1("hello word"); String str2("welcome to beijing!"); String str3; str3 = str1 + str2; str1.show(); str2.show(); str3.show(); system("pause"); return 0; }
- 运行结果
-
hello word welcome to beijing! hello wordwelcome to beijing! 请按任意键继续. . .
- 上面程序实现了一个string类,对常用运算符进行了重载。
- 需要注意的是,以下运算符不能被重载
-
. * :: ?: sizeof
- 格式
动态多态
- 动态多态实现过程
- 程序编译时
- 为每一个有虚函数的类设置一个虚函数表v_table,一个指针数组,存放每个虚函数的入口地址。
- 在函数调用处插入一个隐藏的,指向虚函数表的指针v_pointer
- 程序运行时
- 根据对象的v_pointer,在相应的虚函数表中获得函数入口,来调用正确的函数。
- 程序编译时
- 虚函数
- 动态多态要通过虚函数来实现。虚函数是在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数。
- 虚函数使用
- 虚函数格式
- virtual <函数返回类型> <函数名>(<参数表>)
- 虚函数使用
- 在基类中用virtual声明成员函数为虚函数
- 在派生类中重新定义此函数
- 定义一个指向基类的指针(或引用)
- 通过对该指针(或引用)作同类族对象赋值,调用该类对象同名虚函数
- 注意
- 派生类中同名函数必须与基类虚函数完全一致(即函数名、参数个数与类型、返回类型都相同)
- 派生类必须以公用方式继承
- 当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数
- 虚函数格式
- 示例
-
#include <iostream> class Base { public: virtual void show() //虚方法(函数) { std::cout << "I am base!" << std::endl; } }; class Desive1 :public Base { public: void show() { std::cout << "I am desive1" << std::endl; } }; int main() { Base *b = new Desive1; b->show(); //通过虚方法,调用派生类方法(实现:通过虚函数表) system("pause"); return 0; }
-
- 执行结果
-
I am desive1 请按任意键继续. . .
- 从这个例子可以看出,通过虚函数,我们new了一个派生类对象,赋值给基类指针,通过基类指针就可以直接调用派生类方法。
-
- 虚析构函数
- 程序中最好把析构函数声明为虚函数!即使基类不需要析构函数,也显式地定义一个函数体为空的虚析构函数。下面通过一个例子说明以下为什么
- 示例
-
#include <iostream> class Base { public: virtual void show() //虚方法(函数) { std::cout << "I am base!" << std::endl; } ~Base() { std::cout << "~Base" << std::endl; } }; class Desive1 :public Base { public: void show() { std::cout << "I am desive1" << std::endl; } ~Desive1() { std::cout << "~Desive1" << std::endl; } }; int main() { Base *b = new Desive1; b->show(); //通过虚方法,调用派生类方法(实现:通过虚函数表) delete b; system("pause"); return 0; }
-
- 运行结果
-
I am desive1 ~Base 请按任意键继续. . .
- 有没有发现问题,我们去delete基类指针的时候,由于析构函数不是虚函数,没有实现动态多态,所以调用的还是基类的析构函数,这就会导致无法释放派生类的资源,造成内存泄漏。
-
- 我们对上面程序进行下修改
-
#include <iostream> class Base { public: virtual void show() //虚方法(函数) { std::cout << "I am base!" << std::endl; } virtual ~Base() { std::cout << "~Base" << std::endl; } }; class Desive1 :public Base { public: void show() { std::cout << "I am desive1" << std::endl; } ~Desive1() { std::cout << "~Desive1" << std::endl; } }; int main() { Base *b = new Desive1; b->show(); //通过虚方法,调用派生类方法(实现:通过虚函数表) delete b; system("pause"); return 0; }
-
- 运行结果
-
I am desive1 ~Desive1 ~Base 请按任意键继续. . .
- 只需要将基类的析构函数声明为虚函数,再去释放基类指针,基类的析构函数和派生类的析构函数都可以调用到。因此我们一般将基类的析构函数都声明为虚析构函数。
-