我是靠谱客的博主 心灵美身影,最近开发中收集的这篇文章主要介绍《C++》基础入门_15——多态性,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

以下内容为大学期间学习C++语言总结的知识:

《C++》基础入门_01——数据存储,表示形式,基本运算
《C++》基础入门_02——面向对象的总体概括
《C++》基础入门_03——程序的开发过程
《C++》基础入门_04——四大语句
《C++》基础入门_05——函数详解篇
《C++》基础入门_06——面向对象的详述
《C++》基础入门_07——数据的共享保护:const
《C++》基础入门_08——利用数组实现对批量数据的处理
《C++》基础入门_09——指针和引用的讲解
《C++》基础入门_10——用户自定义数据类型详细篇
《C++》基础入门_11——友元的讲解
《C++》基础入门_12——类模板
《C++》基础入门_13——运算符的重载
《C++》基础入门_14——继承与派生
《C++》基础入门_15——多态性
《C++》基础入门_16——输入输出流详讲
《C++》基础入门_17——对象串行化
《C++》基础入门_18——异常处理
《C++》基础入门_19——命名空间
《C++》基础入门_20——文件操作
《C++》基础入门_21——在函数中返回数组的常用方法


文章目录

    • 一、多态性
      • 1.1 概念
      • 1.2 从系统的实现分类
        • 1.2.1 静态多态性
        • 1.2.2 动态多态性
          • 1. 虚函数
          • 2. 虚函数的使用
    • 二、静态关联与动态关联
      • 2.1 静态关联
      • 2.2 动态关联
      • 2.3 虚函数动态绑定的实现原理
    • 三、虚析构函数
    • 四、纯虚函数与抽象类
      • 4.1 纯虚函数
      • 4.2 抽象类(抽象基类)
    • 五、多态类型与非多态类型
    • 六、运行时类型识别
      • 6.1 运行时类型识别的方式
        • 6.1.1 用dynamic_cast做类型转换
        • 6.1.2 用typeid直接获取类型信息


一、多态性

1.1 概念

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


1.2 从系统的实现分类

1.2.1 静态多态性

  • 特点:在程序编译过程时系统决定调用哪个函数,因此静态多态性又称为编译时的多态性。
  • 实现方式:通过函数重载实现的。包括函数重载和运算符重载。
  • 优缺点:函数调用速度快,效率高,但缺乏灵活性。
		#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;
		}	

结果:
在这里插入图片描述

1.2.2 动态多态性

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

    不使用虚函数时的输出信息:
    基类指针调用的是基类的display方法
    在这里插入图片描述

  • 使用虚函数

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

    使用虚函数后的输出信息:
    基类指针调用的是子对象的display方法
    在这里插入图片描述


二、静态关联与动态关联

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

2.1 静态关联

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

2.2 动态关联

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

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

  • 动态选择被执行的函数
    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()
    	*/
    }
    
  • 最好把所有的基类析构函数声明为虚函数。


四、纯虚函数与抽象类

4.1 纯虚函数

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

4.2 抽象类(抽象基类)

  • 定义一些类不用于生成对象,而是作为基类去建立派生类,它为一个类族提供公共接口。
  • 带有纯虚函数的类称为抽象类。
  • 因为纯虚函数不能被调用,所以抽象类不能建立对象,但是可以定义指向抽象类数据的指针变量。此指针指向抽象类的具体类。
  • 抽象类的派生类若对基类的所有纯虚函数都进行了定义,则派生类称为了具体类,不在是抽象类,可以建立对象。若没有对所有纯虚函数进行定义,则派生类任然是抽象类,不能建立对象。
  • 作用:
    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. 比虚函数动态绑定的开销更大,因此应仅对虚函数无法解决的问题使用。

6.1 运行时类型识别的方式

6.1.1 用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;
    	}
    

    结果: 在这里插入图片描述

6.1.2 用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
	*/
}

最后

以上就是心灵美身影为你收集整理的《C++》基础入门_15——多态性的全部内容,希望文章能够帮你解决《C++》基础入门_15——多态性所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部