概念

  • 多态性是面向对象程序设计的一个重要特征。若一种语言只支持类,而不支持多态,则不能称为面向对象语言的,只能说是基于对象的。
    多态,即一种事物对种形态。
  • 面向对象方法中对多态的表示:向不同对象发送同一个消息,不同的对象在接受时产生的行为不同。即每个对象有自己的方法去响应共同的消息。
  • C++中多态性的表现形式之一:具有不同功能的函数可以用同一个函数名,这样就可以实现用一个函数名调用不同内容的函数。
  • 多态性是一个接口,多种方法。


从系统的实现分类:

静态多态性

  • 特点:在程序编译过程时系统决定调用哪个函数,因此静态多态性又称为编译时的多态性。
  • 实现方式:通过函数重载实现的。包括函数重载和运算符重载。
  • 优缺点:函数调用速度快,效率高,但缺乏灵活性。
		#include <iostream>
		#include <string>
		using namespace std;
		//声明类point
		class Point {
		public:
			Point(int a=0,int b=0):x(a),y(b) {}
			void setPoint(int,int);
			int getX() const { return x; }//常成员函数:为了保证不修改数据成员的值,只能引用
			int getY() const { return y; }
			friend ostream &operator << (ostream &, const Point &);
		protected:
			int x, y;
		};
		void Point::setPoint(int a,int b) {
			x = a;
			y = b;
		}
		//重载流运算符
		ostream & operator <<(ostream & output, const Point & p) {
			output << "[" << p.x << "," << p.y << "]" << endl;
			return output;
		}
		//声明point类的公有派生类Circle类:表示对一个圆的抽象
		class Circle :public Point {
		public:
			Circle(int a = 0, int b = 0, int c = 0):Point(a,b) {//給基类传参只能用初始化器的形式
				r = c;
			}
			void setR(int);
			int getR() const { return r; }
			double area() const { return 3.14*r*r; }   //获得圆的表面积
			friend ostream & operator <<(ostream &,const Circle &);
		protected:
			int r;
		};
		void Circle::setR(int a) {
			r= a;
		}
		ostream &operator <<(ostream &output, const Circle &c) {
			output << "圆心:[" << c.x << "," << c.y << "],半径:r=" << c.r << endl;
			return output;
		}

		//声明Circle的公有派生类cylinder类,代表一个圆柱的抽象
		class Cylinder :public Circle {
		public :
			Cylinder(int a = 0, int b = 0, int c = 0, int h = 0):Circle(a,b,c) {
				height = h;
			}
			void setH(int h) { height = h; };
			int getH() { return height; }
			double area()const  {   //获得圆柱的表面积
				return  2 * Circle::area()+2*3.14*r*height;
			}
			double volume()  const { //获得圆柱的体积
				return Circle::area()*height;
			}
			friend ostream & operator <<(ostream &, const Cylinder &);
		private:
			int height;
		};
		ostream & operator <<(ostream &output, const Cylinder &c) {
			output << "圆心:[" << c.x << "," << c.y << "],半径:r=" << c.r << ",高度: h=" << c.height << endl;
			return output;
		}
		void main() {
			Circle c(2, 2, 1);
			cout << "Circle  message:" << endl;
			cout << c;
			cout << "圆面积: s=" << c.area() << endl;

			c.setPoint(1,1);
			c.setR(2);
			cout << endl;
			cout << "Circle  message:" << endl;
			cout << c;
			cout << "圆面积: s=" << c.area() << endl;

			Point &p = c;//派生类对象可以代替基类对象向基类对象的引用初始化或赋值。p不能认为是c的别名,它只是c中基类一部分的别名,得到了c的起始地址,与c中基类部分共享同一段存储单元。
			cout << endl;
			cout<< "Point  message:" << endl;
			cout << p;

			Cylinder cy(3, 3, 3, 1);
			cout << endl;
			cout << "Cylinder  message:" << endl;
			cout << cy;
			cout << "圆柱表面积:" << cy.area() << endl;
			cout << "圆柱的体积:" << cy.volume() << endl;

			cy.setPoint(4, 4);
			cy.setR(4);
			cy.setH(2);
			Point &p1 = cy;
			cout << endl;
			cout << "Point message:" << endl;
			cout << p1 << endl;

			Circle &p2 = cy;
			cout << "Circle message:" << endl;
			cout << p2<<endl;
		}

动态多态性

  • 特点:不在编译时确定调用哪个函数,而是程序运行过程中才动态确定操作所针对的对象。它又称为运行时的多态。
  • 实现方式:虚函数实现
虚函数:
  • 人们提出能否用一个调用形式来调用基类和派生类同名函数。在程序中不是通过不同对象的名字去访问不同派生类中的同名函数,而是通过指针调用他们,只需要在调用前临时给指针变量赋予不同的值即可。
  • 虚函数就是在在基类声明函数是虚拟的,并不是实际存在的函数,然后在派生类中才正式定义此函数。
  • 作用:允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
  • 虚函数是动态绑定的基础。
  • 是非静态的成员函数。
  • 在类的声明中,在函数原型之前写virtual。
  • virtual 只用来说明类声明中的原型,不能用在函数实现时。
  • 具有继承性,基类中声明了虚函数,派生类中无论是否说明,同原型函数都自动为虚函数。
  • 本质:不是重载声明而是覆盖。
  • 调用方式:通过基类指针或引用,执行时会根据指针指向的对象的类,决定调用哪个函数
虚函数的使用
  1. 在基类中用virtual声明成员函数为虚函数。
  2. 在派生类中重新定义此函数,函数名,函数类型,函数参数个数和类型都必须和基类的虚函数相同。
  3. 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
  4. 通过指针调用虚函数,此时调用的是指针变量指向对象的同名函数。
  • 不使用虚函数:

    #include <iostream>
    #include <string>
    using namespace std;
    //声明类point
    class Student {
    public:
    	Student(int, string, int);
    	void display();
    protected:
    	int sno;
    	string name;
    	int age;
    };
    Student::Student(int s, string n, int a) {
    	sno = s;
    	name = n;
    	age = a;
    }
    void Student::display() {
    	cout << "student :: massage:" << endl;
    	cout << "sno: " <<sno<< endl;
    	cout << "sname: " << name << endl;
    	cout << "age: " << age << endl;
    	cout << endl;
    }
    class Student1 :public Student {
    public:
    	Student1(int, string, int, char);
    	void display();
    private:
    	char sex;
    };
    Student1::Student1(int s, string n, int a, char se):Student(s,n,a) {
    	sex = se;
    }
    void Student1::display() {
    	cout << "student1 :: massage:" << endl;
    	cout << "sno: " << sno << endl;
    	cout << "sname: " << name << endl;
    	cout << "age: " << age << endl;
    	cout << "sex: " << sex << endl;
    	cout << endl;
    }
    int main() {
    	Student s1(111, "Tom", 18);
    	Student1 s2(222, "Rain", 21,'f');
    	Student *p = &s1;
    	p->display();
    	p = &s2;
    	p->display();
    
    	return 0;
    }
    
  • 使用虚函数

    #include <iostream>
    #include <string>
    using namespace std;
    //声明类point
    class Student {
    public:
    	Student(int, string, int);
    	virtual void display();
    protected:
    	int sno;
    	string name;
    	int age;
    };
    Student::Student(int s, string n, int a) {
    	sno = s;
    	name = n;
    	age = a;
    }
    void Student::display() {
    	cout << "student :: massage:" << endl;
    	cout << "sno: " <<sno<< endl;
    	cout << "sname: " << name << endl;
    	cout << "age: " << age << endl;
    	cout << endl;
    }
    class Student1 :public Student {
    public:
    	Student1(int, string, int, char);
    	void display();
    private:
    	char sex;
    };
    Student1::Student1(int s, string n, int a, char se):Student(s,n,a) {
    	sex = se;
    }
    void Student1::display() {
    	cout << "student1 :: massage:" << endl;
    	cout << "sno: " << sno << endl;
    	cout << "sname: " << name << endl;
    	cout << "age: " << age << endl;
    	cout << "sex: " << sex << endl;
    	cout << endl;
    }
    int main() {
    	Student s1(111, "Tom", 18);
    	Student1 s2(222, "Rain", 21,'f');
    	Student *p = &s1;
    	p->display();
    	p = &s2;
    	p->display();
    
    	return 0;
    }
    



静态关联与动态关联

确定调用的具体对象的过程称为关联。
关联是把一个标识符和一个存储地址联系起来。

静态关联

  • 函数重载和通过对象名调用的虚函数,在程序编译过程时就可以确定调用的虚函数属于哪个类,此过程称为静态关联。
  • 运行前进行关联的,又称早期关联。

动态关联

  • 不能在编译时确定调用哪个函数,而是程序运行过程中才把类和虚函数绑定在一起的,它称为动态关联。
  • 在编译以后的运行阶段进行的,又称为滞后关联。

虚函数动态绑定的实现原理

  • 动态选择被执行的函数
    1. 函数的调用,需要通过函数代码的入口地址
    2. 把函数入口地址作为变量,在不同情况下赋予不同的值,
      通过该变量调用函数,就可动态选择被执行的函数
  • 虚表
    1. 每个多态类有一个虚表(virtual table)
    2. 虚表中有当前类的各个虚函数的入口地址
    3. 每个对象有一个指向当前类的虚表的指针(虚指针vptr)
  • 动态绑定的实现
    1. 构造函数中为对象的虚指针赋值
    2. 通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址
    3. 通过该入口地址调用虚函数


虚析构函数

  • 当派生类的对象从内存中撤销时一般先调用派生类的析构函数,在调用基类的析构函数。

  • 构造函数不能是虚函数,析构函数可以是虚函数

  • 如果用new运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量。在程序用带指针参数的delete运算符撤销对象时,会发生一种情况:系统只会执行基类的析构函数,而不会执行派生类的析构函数。

    #include <iostream>
    #include <string>
    using namespace std;
    //声明类point
    class Student {
    public:
    	Student(int, string, int);
    	~Student() {
    		cout << " ~Student()"<<endl;
    	}
    	virtual void display();
    protected:
    	int sno;
    	string name;
    	int age;
    };
    Student::Student(int s, string n, int a) {
    	sno = s;
    	name = n;
    	age = a;
    }
    void Student::display() {
    	cout << "student :: massage:" << endl;
    	cout << "sno: " <<sno<< endl;
    	cout << "sname: " << name << endl;
    	cout << "age: " << age << endl;
    	cout << endl;
    }
    class Student1 :public Student {
    public:
    	Student1(int, string, int, char);
    	~Student1() {
    		cout << " ~Student1()" << endl;
    	}
    	void display();
    private:
    	char sex;
    };
    Student1::Student1(int s, string n, int a, char se):Student(s,n,a) {
    	sex = se;
    }
    void Student1::display() {
    	cout << "student1 :: massage:" << endl;
    	cout << "sno: " << sno << endl;
    	cout << "sname: " << name << endl;
    	cout << "age: " << age << endl;
    	cout << "sex: " << sex << endl;
    	cout << endl;
    }
    int main() {
    	Student *p = new Student1(111,"yj",18,'f');
    	delete p;
    	return 0;
    	/*
    	执行结果:
    	       ~Student()
    	*/
    }
    
  • 为什么需要虚析构函数?

    1. 可能通过基类指针删除派生类对象;
    2. 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。
  • 使用方法:把析构函数声明为虚析构函数

    #include <iostream>
    #include <string>
    using namespace std;
    //声明类point
    class Student {
    public:
    	Student(int, string, int);
    	virtual ~Student() {
    		cout << " ~Student()"<<endl;
    	}
    	virtual void display();
    protected:
    	int sno;
    	string name;
    	int age;
    };
    Student::Student(int s, string n, int a) {
    	sno = s;
    	name = n;
    	age = a;
    }
    void Student::display() {
    	cout << "student :: massage:" << endl;
    	cout << "sno: " <<sno<< endl;
    	cout << "sname: " << name << endl;
    	cout << "age: " << age << endl;
    	cout << endl;
    }
    class Student1 :public Student {
    public:
    	Student1(int, string, int, char);
    	~Student1() {
    		cout << " ~Student1()" << endl;
    	}
    	void display();
    private:
    	char sex;
    };
    Student1::Student1(int s, string n, int a, char se):Student(s,n,a) {
    	sex = se;
    }
    void Student1::display() {
    	cout << "student1 :: massage:" << endl;
    	cout << "sno: " << sno << endl;
    	cout << "sname: " << name << endl;
    	cout << "age: " << age << endl;
    	cout << "sex: " << sex << endl;
    	cout << endl;
    }
    int main() {
    	Student *p = new Student1(111,"yj",18,'f');
    	delete p;
    	return 0;
    	/*
    	执行结果:
    	       ~Student1()
    	       ~Student()
    	*/
    }
    
  • 最好把所有的基类析构函数声明为虚函数。


纯虚函数与抽象类

纯虚函数

  • 纯虚函数是在声明虚函数时被初始化为“0”的函数。
  • 纯虚函数的一般形式:
    virtual 函数类型 函数名 (参数表 )=0 ;
    1. 纯虚函数无函数体;
    2. 最后面的0并不代表返回值为0,他只是形式上的作用,告诉编译系统这是纯虚函数。
    3. 这是一个声明语句,要有“;”分号。
  • 纯虚函数只是函数名而不具备函数功能,不能被调用。
  • 纯虚函数作用:在基类中为其派生类保留一个函数名,以便派生类需要时进行定义。

抽象类(抽象基类)

  • 定义一些类不用于生成对象,而是作为基类去建立派生类,它为一个类族提供公共接口。
  • 带有纯虚函数的类称为抽象类。
  • 因为纯虚函数不能被调用,所以抽象类不能建立对象,但是可以定义指向抽象类数据的指针变量。此指针指向抽象类的具体类。
  • 抽象类的派生类若对基类的所有纯虚函数都进行了定义,则派生类称为了具体类,不在是抽象类,可以建立对象。若没有对所有纯虚函数进行定义,则派生类任然是抽象类,不能建立对象。
  • 作用:
    1. 抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。
    2. 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。
#include <iostream>
#include <string>
using namespace std;
class Base1 { // 基类 Base1定义
public:
	virtual void display() const = 0; // 纯虚函数
 };
class Base2:public Base1 { // 公有派生类 Base2 定义
public:
	void display() const { // 覆盖基类的虚函数
			cout << "Base2::display()" << endl;
	}
};
class Derived : public Base2 { // 公有派生类 Derived 定义
public:
	void display() const {// 覆盖基类的虚函数
			cout << "Derived :: display()" << endl;
	}
 };

void fun(Base1  * ptr) {
		ptr  -> display() ; // 对象指针-> 成员名
}
int main() {
		Base2 b2;  // 定义Base2类对象
		Derived d; // 定义Derived 类对象
		fun(&b2 ); // 用Base2对象的指针调用fun函数
		fun(&d);   // 用 Derived 对象的指针调用 fun 函数
		return 0;
		/*
		Base2::display()
        Derived :: display()
		*/
}



多态类型与非多态类型

  • 有虚函数的类类型称为多态类型,其它类型皆为非多态类型。
  • 二者的差异
    1. 语言层面的差异
      多态类型支持运行时类型识别
      多态类型对象占用额外的空间
    2. 设计原则上的差异
      • 多态类型
      多态类型的析构函数一般应为虚函数
      • 非多态类型
      1. 非多态类型不宜作为公共基类
        • 由于没有利用动态多态性,一般可以用组合,而无需用共有继承;
        • 如果继承,则由于析构函数不是虚函数,删除对象时所执行操作与指针类型有关,易引起混乱。
      2. 把不需被继承的类型设定为非多态类型
        • 由于成员函数都是静态绑定,调用速度较快;
        • 对象占用空间较小。



运行时类型识别

  • 运行时类型识别
    1. 允许在运行时通过基类指针(或引用)辨别对象所属的具体派生类;
    2. 只对多态类型适用;
    3. 比虚函数动态绑定的开销更大,因此应仅对虚函数无法解决的问题使用。

运行时类型识别的方式

  • 用dynamic_cast做类型转换的尝试;

    dynamic_cast的使用

    1. 语法形式
      dynamic_cast<目的类型>(表达式)
    2. 功能
      • 将基类指针转换为派生类指针,将基类引用转换为派生类引用;
      • 转换是有条件的
        • 如果指针(或引用)所指对象的实际类型与转换的目的类型兼容,则转换成功进行;
        • 否则如执行的是指针类型的转换,则得到空指针;
          如果执行的是引用类型的转换,则抛出异常。
    ```
    #include <iostream>
    #include <string>
    using namespace std;
    class Base{
    public:
    	virtual void fun1(){ cout << "Base::fun1()" << endl; }
    	virtual ~Base() { }
    };
    class Derived1 : public Base {
    public:
    	virtual void fun1() { cout << "Derived1::fun1()" << endl; }
    	virtual void fun2() { cout << "Derived1::fun2()" << endl; }
    };
    class Derived2 : public Derived1 {
    public:
    	virtual void fun1() { cout << "Derived2::fun1()" << endl; }
    	virtual void fun2() { cout << "Derived2::fun2()" << endl; }
    };
    void fun(Base *b) {
    	b->fun1();
    	//尝试将b转换为Derived1指针
    	Derived1 *d = dynamic_cast<Derived1 *>(b);
    	//判断转换是否成功
    	if (d != 0) {
    		cout << "将Base转换为Derived1指针成功。"<<endl;
    		d->fun2();
    	}
    	else {
    		cout << "将Base转换为Derived1指针失败!" << endl;
    	}
    }
    int main() {
    	Base b;
    	cout << "传入Base的对象:" << endl;
    	fun(&b);
    	cout << endl;
    	Derived1 d1;
    	cout << "传入Derived1的对象:" << endl;
    	fun(&d1);
    	cout<< endl;
    	Derived2 d2;
    	cout << "传入Derived2的对象:" << endl;
    	fun(&d2);
    	return 0;
    }
    ```
    
  • 用typeid直接获取类型信息。

    1. 语法形式
      typeid ( 表达式 )
      typeid ( 类型说明符 )
    2. 功能
      1. 获得表达式或类型说明符的类型信息
        • 表达式有多态类型时,会被求值,并得到动态类型信息;
        • 否则,表达式不被求值,只能得到静态的类型信息。
      2. 类型信息用type_info对象表示
        1. type_info是typeinfo头文件中声明的类;
        2. typeid的结果是type_info类型的常引用;
        3. 可以用type_info的重载的“==”、“!=”操作符比较两类型的异同;
        4. type_info的name成员函数返回类型名称,类型为const char *。
    #include <iostream>
    #include <string>
    using namespace std;
    class Base{
    public:
    	virtual ~Base() { }
    };
    class Derived : public Base {
    };
    void fun(Base *b) {
    	//得到表示b和*b类型信息的对象
    	const type_info &info1 = typeid(b);
    	const type_info &info2 = typeid(*b);
    	cout << "typeid(b): " << info1.name() << endl;
    	cout << "typeid(*b): " << info2.name() << endl;
    	if (info2 == typeid(Base)) //判断*b是否为Base类型
    		cout << "A base class!" << endl;
    }
    int main() {
    	Base b;
    	fun(&b);
    	Derived d;
    	fun(&d);
    	return 0;
    	/*
    	    typeid(b): class Base *
    		typeid(*b): class Base
    		A base class!
    		typeid(b): class Base *
    		typeid(*b): class Derived
    	*/
    }
    
10-06 14:07