zoukankan      html  css  js  c++  java
  • C++学习笔记-多态

    多态作为面向对象的重要概念,在如何一门面向对象编程语言中都有着举足轻重的作用,学习多态,有助于更好地理多态的行为
    多态性(Polymorphism)是指一个名字,多种语义;或界面相同,多种实现。
    重载函数是多态性的一种简单形式。
    虚函数允许函数调用与函数体的联系在运行时才进行,称为动态联编

    静态联编

    联编是指一个程序模块、代码之间互相关联的过程
    静态联编,是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编
    动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编。switch语句和if语句是动态联编的例子
    普通成员函数重载可表达为两种形式:

    • 在一个类说明中重载
    Show(int , char);
    Show(char * , float);
    
    • 基类的成员函数在派生类重载。有 3 种编译区分方法:
      • 根据参数的特征加以区分
      Show(int , char);
      Show(char * , float);
      
      • 使用::加以区分
      A :: Show();
      B :: Show();
      
      • 根据类对象加以区分
      //这其实是通过this指针区分
      Aobj.Show();//调用	A :: Show()
      Bobj.Show();//调用	B :: Show()
      

    类指针的关系

    基类指针和派生类指针与基类对象和派生类对象4种可能匹配:

    • 直接用基类指针引用基类对象
    • 直接用派生类指针引用派生类对象
    • 用基类指针引用一个派生类对象
    • 用派生类指针引用一个基类对象

    基类指针引用派生类对象

    class B : public A

    A    * p ;// 指向类型 A 的对象的指针
    A    A_obj ;// 类型 A 的对象
    B    B_obj ;// 类型 B 的对象
    p = &A_obj ;// p 指向类型 A 的对象
    p = &B_obj ;// p 指向类型 B 的对象,它是 A 的派生类
    

    利用 p,可以通过 B_obj 访问所有从 A 类继承的元素,但不能用 p访问 B 类自定义的元素(除非用了显式类型转换)

    派生类指针引用基类对象

    派生类指针只有经过强制类型转换之后,才能引用基类对象
    class DateTime : public Data

    ((Date *)this) -> Print();
    

    虚函数和动态联编

    冠以关键字 virtual 的成员函数称为虚函数
    实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本

    虚函数和基类指针

    基类指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员

    #include <iostream>
    using namespace std;
    
    class Base
    {
    public:
    	Base(char xx)
    	{
    		x = xx;
    	}
    	void who()
    	{
    		cout << "Base class: " << x << "
    ";
    	}
    protected:
    	char x;
    };
    
    class First_d : public Base
    {
    public:
    	First_d(char xx, char yy) : Base(xx)
    	{
    		y = yy;
    	}
    	void who()
    	{
    		cout << "First derived class: " << x << ", " << y << "
    ";
    	}
    protected:
    	char y;
    };
    
    class Second_d : public First_d
    {
    public:
    	Second_d(char xx, char yy, char zz) : First_d(xx, yy)
    	{
    		z = zz;
    	}
    	void who()
    	{
    		cout << "Second derived class: " << x << ", " << y << ", " << z << "
    ";
    	}
    protected:
    	char z;
    };
    
    void main()
    {
    	Base B_obj('A');
    	First_d F_obj('T', 'O');
    	Second_d S_obj('E', 'N', 'D');
    	Base * p;
    
    	p = &B_obj;
    	p->who();
    
    	p = &F_obj;
    	p->who();
    
    	p = &S_obj;
    	p->who();
    
    	F_obj.who();
    
    	((Second_d *)p)->who();
    
    	system("pause");
    }
    

    输出结果为

    Base class: A
    Base class: T
    Base class: E
    First derived class: T, O
    Second derived class: E, N, D
    请按任意键继续. . .

    修改程序,定义虚函数

    #include <iostream>
    using namespace std;
    
    class Base
    {
    public:
    	Base(char xx)
    	{
    		x = xx;
    	}
    	virtual void who()
    	{
    		cout << "Base class: " << x << "
    ";
    	}
    protected:
    	char x;
    };
    
    class First_d : public Base
    {
    public:
    	First_d(char xx, char yy) : Base(xx)
    	{
    		y = yy;
    	}
    	void who()
    	{
    		cout << "First derived class: " << x << ", " << y << "
    ";
    	}
    protected:
    	char y;
    };
    
    class Second_d : public First_d
    {
    public:
    	Second_d(char xx, char yy, char zz) : First_d(xx, yy)
    	{
    		z = zz;
    	}
    	void who()
    	{
    		cout << "Second derived class: " << x << ", " << y << ", " << z << "
    ";
    	}
    protected:
    	char z;
    };
    
    void main()
    {
    	Base B_obj('A');
    	First_d F_obj('T', 'O');
    	Second_d S_obj('E', 'N', 'D');
    	Base * p;
    
    	p = &B_obj;
    	p->who();
    
    	p = &F_obj;
    	p->who();
    
    	p = &S_obj;
    	p->who();
    
    	system("pause");
    }
    

    输出结果为:

    Base class: A
    First derived class: T, O
    Second derived class: E, N, D
    请按任意键继续. . .

    结论:
    由于who()的虚特性随着p指向不同对象,this指针作类型转换执行不同实现版本

    注意事项:

    • 一个虚函数,在派生类层界面相同的重载函数都保持虚特性
    • 虚函数必须是类的成员函数
    • 不能将友员说明为虚函数,但虚函数可以是另一个类的友员
    • 析构函数可以是虚函数,但构造函数不能是虚函数

    虚函数的重载特性

    • 在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同
    • 如果仅仅返回类型不同,C++认为是错误重载
    • 如果函数原型不同,仅函数名相同,丢失虚特性
    class base
    {
    public:
    	virtual void vf1();
    	virtual void vf2();
    	virtual void vf3();
    	void f();
    };
    
    class derived : public base
    {
    public:
    	void vf1();// 虚函数
    	void vf2(int);// 重载,参数不同,虚特性丢失
    	//char vf3();// error,仅返回类型不同
    	void f();// 非虚函数重载
    };
    
    void  g()
    {
    	derived d;
    	base *bp = &d;// 基类指针指向派生类对象
    	bp->vf1();// 调用 deriver :: vf1()
    	bp->vf2();// 调用 base :: vf2()
    	bp->f();// 调用 base::f()
    };
    

    虚析构函数

    • 构造函数不能是虚函数。建立一个派生类对象时,必须从类 层次的根开始,沿着继承路径逐个调用基类的构造函数
    • 析构函数可以是虚的。虚析构函数用于指引 delete 运算符正 确析构动态对象
      普通析构函数在删除动态派生类对象的调用情况:
    #include <iostream>
    using namespace std;
    
    class A
    {
    public:
    	~A()
    	{
    		cout << "A::~A() is called.
    ";
    	}
    };
    
    class B : public A
    {
    public:
    	~B()
    	{
    		cout << "B::~B() is called.
    ";
    	}
    };
    
    void main()
    {
    	A *Ap = new B;
    	B *Bp2 = new B;
    	cout << "delete first object:
    ";
    	delete Ap;
    	cout << "delete second object:
    ";
    	delete Bp2;
    	system("pause");
    }
    

    输出结果:

    delete first object:
    A::~A() is called.
    delete second object:
    B::~B() is called.
    A::~A() is called.
    请按任意键继续. . .

    虚析构函数在删除动态派生类对象的调用情况:

    #include <iostream>
    using namespace std;
    
    class A
    {
    public:
    	~A()
    	{
    		cout << "A::~A() is called.
    ";
    	}
    };
    
    class B : public A
    {
    public:
    	~B()
    	{
    		cout << "B::~B() is called.
    ";
    	}
    };
    
    void main()
    {
    	A *Ap = new B;
    	B *Bp2 = new B;
    	cout << "delete first object:
    ";
    	delete Ap;
    	cout << "delete second object:
    ";
    	delete Bp2;
    	system("pause");
    }
    

    输出结果:

    delete first object:
    B::~B() is called.
    A::~A() is called.
    delete second object:
    B::~B() is called.
    A::~A() is called.
    请按任意键继续. . .

    • 定义了基类虚析构函数,基类指针指向的派生类动态对象也可以正确地用delete析构
    • 设计类层次结构时,提供一个虚析构函数,能够使派生类对象在不同状态下正确调用析构函数

    纯虚函数和抽象类

    • 纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本
    • 纯虚函数为各派生类提供一个公共界面
    • 纯虚函数说明形式:virtual 类型 函数名(参数表)= 0 ;
    • 一个具有纯虚函数的基类称为抽象类
    class point
    {
        /*……*/
    };
    
    class shape;// 抽象类
    {
        point center;
        ···
    public:
      point where()
    {
        return center;
    }
      void move(point p )
    {
        enter = p;
        draw ();
    }
      virtual void rotate(int) = 0;// 纯虚函数
      virtual void draw () = 0;// 纯虚函数
    };
    ···
    shape x;// error,抽象类不能建立对象
    shape *p;// ok,可以声明抽象类的指针
    shape f();// error, 抽象类不能作为返回类型
    void g(shape);// error, 抽象类不能作为参数类型
    shape & h(shape &);// ok,可以声明抽象类的引用
    

    简单图形类例子:

    #include <iostream>
    using namespace std;
    
    class figure
    {
    protected:
    	double x, y;
    public:
    	void set_dim(double i, double j = 0)
    	{
    		x = i;
    		y = j;
    	}
    	virtual void show_area() = 0;
    };
    
    class triangle : public figure
    {
    public:
    	void show_area()
    	{
    		cout << "Triangle with high " << x << " and base " << y << " has an area of " << x * 0.5 * y << "
    ";
    	}
    };
    
    class square : public figure
    {
    public:
    	void show_area()
    	{
    		cout << "Square with dimension " << x << "*" << y << " has an area of " << x * y << "
    ";
    	}
    };
    
    class circle : public figure
    {
    public:
    	void show_area()
    	{
    		cout << "Circle with radius " << x;
    		cout << " has an area of " << 3.14 * x * x << "
    ";
    	}
    };
    
    void main()
    {
    	triangle t;	//派生类对象
    	square s;
    	circle c;
    	t.set_dim(10.0, 5.0);
    	t.show_area();
    	s.set_dim(10.0, 5.0);
    	s.show_area();
    	c.set_dim(9.0);
    	c.show_area();
    	system("pause");
    }
    

    输出结果:

    Triangle with high 10 and base 5 has an area of 25
    Square with dimension 10*5 has an area of 50
    Circle with radius 9 has an area of 254.34
    请按任意键继续. . .

    void main()
    {
    	figure *p;	// 声明抽象类指针
    	triangle t;
    	square s;
    	circle c;
    	p = &t;
    	p->set_dim(10.0, 5.0); 	// triangle::set_dim()
    	p->show_area();
    	p = &s;
    	p->set_dim(10.0, 5.0);	// square::set_dim()
    	p->show_area();
    	p = &c;
    	p->set_dim(9.0);		// circle::set_dim()
    	p->show_area();
    	system("pause");
    }
    

    输出结果为:

    Triangle with high 10 and base 5 has an area of 25
    Square with dimension 10*5 has an area of 50
    Circle with radius 9 has an area of 254.34
    请按任意键继续. . .

    使用抽象类引用:

    #include <iostream>
    using namespace std;
    
    class Number
    {
    public:
    	Number(int i)
    	{
    		val = i;
    	}
    	virtual void Show() = 0;
    protected:
    	int val;
    };
    class Hex_type : public Number
    {
    public:
    	Hex_type(int i) : Number(i)
    	{
    	}
    	void Show()
    	{
    		cout << "Hexadecimal:" << hex << val << endl;
    	}
    };
    class Dec_type : public Number
    {
    public:
    	Dec_type(int i) : Number(i)
    	{
    	}
    	void Show()
    	{
    		cout << "Decimal: " << dec << val << endl;
    	}
    };
    class Oct_type : public Number
    {
    public:
    	Oct_type(int i) : Number(i)
    	{
    	}
    	void Show()
    	{
    		cout << "Octal: " << oct << val << endl;
    	}
    };
    void fun(Number & n)// 抽象类的引用参数
    {
    	n.Show();
    }
    void main()
    {
    	Dec_type n1(50);
    	fun(n1);	// Dec_type::Show()
    	Hex_type n2(50);
    	fun(n2);	// Hex_type::Show()
    	Oct_type n3(50);
    	fun(n3);	// Oct_type::Show()
    	system("pause");
    }
    

    输出结果为:

    Decimal: 50
    Hexadecimal:32
    Octal: 62
    请按任意键继续. . .

    虚函数与多态的应用

    • 虚函数和多态性使成员函数根据调用对象的类型产生不同的动作
    • 多态性特别适合于实现分层结构的软件系统,便于对问题抽象时定义共性,实现时定义区别

    一个实例

    计算雇员工资
    Employee:

    class Employee
    {
    public:
    	Employee(const long, const char*);
    	virtual ~Employee();//虚析构函数
    	const char * getName() const;
    	const long getNumber() const;
    	virtual double earnings() const = 0;//纯虚函数,计算月薪
    	virtual void print() const;//虚函数,输出编号、姓名
    protected:
    	long number;//编号
    	char * name;//姓名
    };
    

    Manager:

    class Manager : public Employee
    {
    public:
    	Manager(const long, const char *, double = 0.0);
    	~Manager() { }
    	void setMonthlySalary(double);//置月薪
    	virtual double earnings() const;//计算管理人员月薪
    	virtual void print() const;//输出管理人员信息
    private:
    	double monthlySalary;//私有数据,月薪
    };
    

    HourlyWorker:

    class HourlyWorker : public Employee
    {
    public:
    	HourlyWorker(const long, const char *, double = 0.0, int = 0);
    	~HourlyWorker(){}
    	void setWage(double);//置时薪
    	void setHours(int);//置工时
    	virtual double earnings() const;//计算计时工月薪
    	virtual void print() const;//输出计时工月薪
    private:
    	double wage;//时薪
    	double hours;//工时
    };
    

    PieceWorker:

    class PieceWorker : public Employee
    {
    public:
    	PieceWorker(const long, const char *, double = 0.0, int = 0);
    	~PieceWorker() { }
    	void setWage(double);//置每件工件薪金
    	void setQuantity(int);//置工件数
    	virtual double earnings() const;//计算计件薪金
    	virtual void print() const;//输出计件薪金
    private:
    	double wagePerPiece;//每件工件薪金
    	int quantity;//工件数
    };
    

    测试用例:

    void test()
    {
    	cout << setiosflags(ios::fixed | ios::showpoint) << setprecision(2);
    	Manager m1(10135, "Cheng ShaoHua", 1200);
    	Manager m2(10201, "Yan HaiFeng");
    	m2.setMonthlySalary(5300);
    	HourlyWorker hw1(30712, "Zhao XiaoMing", 5, 8 * 20);
    	HourlyWorker hw2(30649, "Gao DongSheng");
    	hw2.setWage(4.5);
    	hw2.setHours(10 * 30);
    	PieceWorker pw1(20382, "Xiu LiWei", 0.5, 2850);
    	PieceWorker pw2(20496, "Huang DongLin");
    	pw2.setWage(0.75);
    	pw2.setQuantity(1850);
    	// 使用抽象类指针,调用派生类版本的函数
    	Employee *basePtr;
    
    	basePtr = &m1;
    	basePtr->print();
    
    	basePtr = &m2;
    	basePtr->print();
    
    	basePtr = &hw1;
    	basePtr->print();
    
    	basePtr = &hw2;
    	basePtr->print();
    
    	basePtr = &pw1;
    	basePtr->print();
    
    	basePtr = &pw2;
    	basePtr->print();
    
    	system("pause");
    }
    

    异质链表

    程序中,用基类类型指针,可以生成一个连接不同派生类对象的动态链表,即每个结点指针可以指向类层次中不同的派生类对象。这种结点类型不相同链表称为异质链表
    e.g.

    class Employee
    {
    public:
    	Employee(const long, const char*);
    	virtual ~Employee();
    	const char * getName() const;
    	const long getNumber() const;
    	virtual double earnings() const = 0;
    	virtual void print() const;
    	Employee *next;// 增加一个指针成员
    protected:
    	long number;
    	char * name;
    };
    
    void AddFront(Employee * &h, Employee * &t)//在表头插入结点
    {
    	t->next = h;
    	h = t;
    }
    void test()//测试函数
    {
    	Employee * empHead = NULL, *ptr;
    	ptr = new Manager(10135, "Cheng ShaoHua", 1200);//建立第一个结点
    	AddFront(empHead, ptr);//插入表头
    	ptr = new HourlyWorker(30712, "Zhao XiaoMing", 5, 8 * 20);//建立第二个结点
    	AddFront(empHead, ptr);//插入表头
    	ptr = new PieceWorker(20382, "Xiu LiWei", 0.5, 2850);//建立第三个结点
    	AddFront(empHead, ptr);//插入表头
    	ptr = empHead;
    	while (ptr)
    	{
    		ptr->print();
    		ptr = ptr->next;
    	}//遍历链表,输出全部信息
    	ptr = empHead;
    	while (ptr)//遍历链表,输出姓名和工资
    	{
    		cout << ptr->getName() << "  " << ptr->earnings() << endl;
    		ptr = ptr->next;
    	}
    }
    

    多态

    多态的理论基础

    • 联编是指一个程序模块、代码之间互相关联的过程。
    • 静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编。
    • 动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。switch 语句和 if 语句是动态联编的例子。
    • 理论联系实际
    1. C++与C相同,是静态编译型语言
    2. 在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认为父类指针指向的是父类对象。
    3. 由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象
      从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。这种特性就是静态联编。

    多态的c++实现

    virtual关键字,告诉编译器这个函数要支持多态;不要根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用。冠以virtual关键字的函数叫虚函数;虚函数分为两类:一般虚函数、纯虚函数。

    多态的实现效果

    多态:同样的调用语句有多种不同的表现形态

    多态实现的三个条件

    有继承、有重写、有父类指针(引用)指向子类对象

    重写与重载

    函数重载

    • 必须在同一个类中进行
    • 子类无法重载父类的函数,父类同名函数将被名称覆盖
    • 重载是在编译期间根据参数类型和个数决定函数调用

    函数重写

    • 必须发生于父类与子类之间
    • 并且父类与子类中的函数必须有完全相同的原型
    • 使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)
    • 多态是在运行期间根据具体对象的类型决定函数调用

    编译器是如何实现多态的

    1. 当类中声明虚函数时,编译器会在类中生成一个虚函数表
    2. 虚函数表是一个存储类成员函数指针的数据结构
    3. 虚函数表是由编译器自动生成与维护的
    4. virtual成员函数会被编译器放入虚函数表中
    5. 存在虚函数时,每个对象中都有一个指向虚函数表的指针
    6. VPTR一般作为类对象的第一个成员
    7. 虚函数表指针是在构造函数执行之前被赋值,还是在构造函数被赋值之后被赋

    总结

    • 虚函数和多态性使软件设计易于扩充。
    • 派生类重载基类接口相同的虚函数其虚特性不变。
    • 如果代码关联在编译时确定,称为静态联编。代码在运行时关联称为动态联编。
    • 基类指针可以指向派生类对象、基类中拥有虚函数,是支持多态性的前提。
    • 虚析构函数可以正确释放动态派生类对象的资源。
    • 纯虚函数由派生类定义实现版本。
    • 具有纯虚函数的类称为抽象类。抽象类只能作为基类,不能建立对象。抽象类指针使得派生的具体类对象具有多态操作能力。
  • 相关阅读:
    Meten Special Activities II
    Meten Special Activities II
    Meten Special Activities II
    Meten Special Activities II
    Meten Special Activities
    Meten Special Activities
    Meten Special Activities
    Meten Special Activities
    Meten Special Activities
    冒泡排序和选择排序
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664723.html
Copyright © 2011-2022 走看看