• C++ 多态性


    8.1多态概述

    多态是指

    当同样的类的成员函数被不同类型的对象调用时,会产生不同的行为,及调用了不同功能的函数

    例如运算符,+ 号 函数,当不同类型的数相加时,不同类型的变量采用不同的方式进行加法运算。

    8.1.1多态的类型

    重载多态,强制多态,包含多态:虚函数,参数多态

    8.1.2多态的实现

    绑定:静态绑定与动态绑定

    8.2运算符重载

    由于运算符的操作对象只能是基本数据类型,而当如果要实现两个对象之间的直接相加运算,则需要自己定义运算符’+’ 。

    如:定一个复数类,对象为a and b ,则要直接计算a + b ,那么需要自己对运算符进行重载,

    运算符重载实质就是函数重载,自己定义运算符函数

    8.2.1运算符重载规则

    一般形式有两种:

    (1)重载为类的成员函数的一般语法形式:

    返回类型 类名::operator 运算符(形参表){

    }

    (2)运算符重载为非成员函数的一般语法形式:

    返回类型 operator 运算符(形参表){

    }

    这里的"运算符"指:‘+’ ,‘-’…

    8.2.2运算符重载为成员函数

    实质就是函数重载,

    按运算符分为两种情况:

    1.对于双目运算符B,即a + b 型

    如果重载为类的成员函数,使之实现 c1 B c2 (这里c1为A类的对象,c2为C类的对象,B为运算符),

    则应当把B重载为A类的成员函数,该函数只有一个形参,形参的类型是对象c2所属的类型(即C类),

    经过重载后,表达式c1 B c2就相当于函数调用c1.operator B(c2)

    2.对于前置运算符U ,即 -a型

    如果重载为类的成员函数,使之实现 U c1 (这里c1为A类的对象)

    则U应当重载为A类的成员函数,函数没有形参,

    #include <iostream>
    using namespace std; 
    
    class Complex{
    	public:
    		Complex(double r = 0.0 , double i = 0.0): real(r),imag(i){ }
    		Complex operator+(const Complex &c2) const;  //运算符+重载成员函数
    		Complex operator-(const Complex &c2) const;  //注意参数表中c2的类型,一般为同c1类型,
    		void display() const;
    	
    	private:
    		double real;
    		double imag;
    };
    Complex Complex::operator+(const Complex &c2) const{
    	return Complex(real + c2.real , imag + c2.imag); //创建一个临时无名对象作为返回值
    }
    Complex Complex::operator-(const Complex &c2) const{
    	return Complex(real - c2.real , imag - c2.imag);
    }
    
    void Complex::display() const{
    	cout << "("<<real << "," << imag << ")" << endl;
    }
    
    int main()
    {
    	Complex c1(5 , 4) , c2(2 , 10), c3;
    	cout << "c1= ";
    	c1.display();
    	cout << "c2= ";
    	c2.display();
    	c3 = c1 - c2;
    	cout << "c3 = c1 - c2 = ";
    	c3.display();
    	c3 = c1 + c2;
    	cout << "c3 = c1 + c2 = " ;
    	c3.display();
     system("pause");
    	return 0;
    }  
    c1= (5,4)
    c2= (2,10)
    c3 = c1 - c2 = (3,-6)
    c3 = c1 + c2 = (7,14)
    
    • 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

    8.2.3运算符重载为非成员函数

    运算所需要的操作数都需要通过函数的形参表来传递,在形参表中形参从左到右的顺序就是运算符操作数的顺序。

    如果要在函数中访问类的私有变量,则需将函数声明为类的友元函数,

    按运算符分为两种情况:

    1.对于双目运算符B,即a + b 型

    如果重载为非成员函数,使之实现 c1 B c2 (这里c1为A类的对象,c2为C类的对象,B为运算符),

    函数形参为c1 and c2 ,经过重载后,表达式c1 B c2就相当于函数调用operator B(c1,c2)

    2.对于前置运算符U ,即 -a型

    如果重载为非成员函数,使之实现 U c1 (这里c1为A类的对象)

    则U应当重载为非成员函数,函数的形参为c1;

    经过重载后,表达式 B c1就相当于函数调用operator B(c1)

    #include <iostream>
    using namespace std; 
    
    class Complex{
    	public:
    		Complex(double r = 0.0 , double i = 0.0): real(r),imag(i){ }
    		friend Complex operator+(const Complex &c1 , const Complex &c2);  //运算符+重载成员函数
    		friend Complex operator-(const Complex &c1 ,const Complex &c2);  //注意参数表中c2的类型,一般为同c1类型,
    		friend ostream & operator<<(ostream &out , const Complex &c);//运算符<<重载
    	
    	private:
    		double real;
    		double imag;
    };
    Complex operator+(const Complex &c1 ,const Complex &c2){ //因为是友元函数因而不用说明作用域
    	return Complex(c1.real + c2.real , c1.imag + c2.imag); 
    }
    Complex operator-(const Complex &c1 ,const Complex &c2){
    	return Complex(c1.real - c2.real , c1.imag - c2.imag);
    }
    ostream & operator<<(ostream &out , const Complex &c){//对左移运算符进行重载,
    	out<<"("<<c.real<<", " << c.imag <<")"; //当指向的是一个Complex类时,就调用该函数,按照函数体进行输出
    	return out;
    } 
    
    int main()
    {
    	Complex c1(5 , 4) , c2(2 , 10), c3;
    	cout << "c1= ";
    	cout<< c1 << endl; //重载后的左移运算符,直接可以按照类类型进行输出,
    	cout << "c2= ";
    	cout << c2 << endl;
    	c3 = c1 - c2;
    	cout << "c3 = c1 - c2 = ";
    	cout << c3 << endl;
    	c3 = c1 + c2;
    	cout << "c3 = c1 + c2 = " ;
    	cout << c3 << endl;
     system("pause");
    	return 0;
    }  
    c1= (5, 4)
    c2= (2, 10)
    c3 = c1 - c2 = (3, -6)
    c3 = c1 + c2 = (7, 14)
    
    • 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

    8.3虚函数

    虚函数必须是非静态的成员函数,

    8.3.1一般虚函数成员

    声明语法:

    virtual 函数类型 函数名(形参表);

    如果需要通过基类的指针指向派生类的对象,并访问某个与基类同名的成员,(都知道,由类型兼容性规则知,通过基类的指针指向派生类的对象,当访问某个与基类同名的成员时,只能够访问到基类同名的该成员),

    那么首先在基类中将这个同名函数说明为虚函数,

    然后,即使通过基类类型的指针,也可以访问到由基类指针所指的不同的派生类的不同对象的那个同名函数,而不是基类的同名函数。

    #include <iostream>
    using namespace std;
    
    //1.基类Base1 
    class Base1 { 
    	public:
    		virtual void display() const {cout << "Base1::display()" << endl;} 
    };
    //2.公有派生类Base2
    class Base2 : public Base1 {
    	public:
    		void display() const {cout << "Base2::display()" << endl;} 
    }; 
    //3.公有派生类Base3
    class Base3 : public Base2 {
    	public:
    		void display() const {cout << "Base3::display()" << endl;} 
    };
    
    //4.fun函数,参数为指向基类对象的指针
    void fun(Base1 * ptr){
    	ptr->display(); //对象指针->成员名 
    } 
    int main()
    {
    	Base1 base1;
    	Base2 base2;
    	Base3 base3;
    	
    	fun(&base1);
    	fun(&base2);
    	fun(&base3);
    	system("pause");
    	return 0;
    }
    //没定义为虚函数结果:  //结果是根据用什么类型的指针来接收改变
    // Base1::display()
    // Base1::display()
    // Base1::display()
     
    //定义为虚函数后:    //结果是根据传入地址的类型改变
    // Base1::display()
    // Base2::display()
    // Base3::display()
    
    • 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

    final 和override说明符

    使用override 关键字来标记说明派生类中的虚函数,如果使用override关键字标记了某个派生类的成员函数,但该函数并没有覆盖已存在的虚函数,则编译器会报错。

    class B {
    public :
    	void f(int) const;
    }
    class A : public B{
    public:
    	void f(int)const override; //正确
    	void f() override;//错误,无参
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当把某个函数指定为final,意味着该函数不能被覆盖,任何试图覆盖该函数的操作都会报错。

    class B {
    public :
    	void f(int) const final;
    }
    class A : public B{
    public:
    	void f(int)const ; //错
    	void f();//对,不是函数的覆盖,因为参数不同
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    8.3.2虚析构函数

    不能声明虚构造函数,但可声明虚析构函数,声明语法:

    virtual ~类名( );

    若不定义虚析构函数,则当使用基类指针删除派生类对象时调用的是基类的构造函数,那么派生类的析构函数没有执行,因此派生类对象的动态内存没有释放,因而造成危险。

    如果一个基类的析构函数是虚析构函数,则它的所有派生类的析构函数也是虚函数。

    class  Base{
    public:
    	virtual ~Base();
    }
    
    • 1
    • 2
    • 3
    • 4

    8.4纯虚函数与抽象类

    8.4.1纯虚函数

    纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本

    声明形式:

    virtual 函数类型 函数名( 参数表 ) = 0;

    声明为纯虚函数后,基类中就可以不再给出函数的实现部分。函数体由派生类具体给出。

    8.4.2抽象类

    带有纯虚函数的类是抽象类

    主要作用是通过它为一个类族建立一个公共的接口,使它们能够更有效的发挥多态性,

    接口的完整实现,即就是纯虚函数的函数体,要由派生类自己定义。

    注意:

    (1)抽象类派生出新的类之后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以定义自己的对象,因而不再是抽象类;

    (2)反之,如果没有全部给出纯虚函数的函数实现,则这是的派生类仍然是一个抽象类。

    抽象类不能实例化即不能定义一个抽象类的对象

    但可以定义抽象类的指针和引用,指向并访问派生类对象,进而访问派生类的成员。

    #include <iostream>
    using namespace std;
    
    //1.基类Base1 
    class Base1 { 
    	public:
    		virtual void display() const = 0; //纯虚函数 
    };
    //2.公有派生类Base2
    class Base2 : public Base1 {
    	public:
    		void display() const; //覆盖基类的虚函数
    }; 
    void Base2::display()const {
    	cout << "Base2::display() "<< endl;
    }
    //3.公有派生类Base3
    class Base3 : public Base2 {
    	public:
    		void display() const; //覆盖基类的虚函数
    };
    
    void Base3::display() const {
    	cout << "Base3::display()" << endl;
    }
    //4.fun函数,参数为指向基类对象的指针
    void fun(Base1 * ptr){
    	ptr->display(); //对象指针->成员名 
    } 
    int main()
    {
    	Base2 base2;
    	Base3 base3;
    
    	fun(&base2);
    	fun(&base3);
    	system("pause");
    	return 0;
    }
    Base2::display() 
    Base3::display()
    
    • 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

    假如,不实现Base2的display()

    //2.公有派生类Base2
    class Base2 : public Base1 {
    	public:
    		//void display() const; //覆盖基类的虚函数
    }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    即子类没有给出纯虚函数的具体实现,因此该子类是抽象类,不能定义对象。

    Base2 base2;
    //错:cannot declare variable 'base2' to be of abstract type 'Base2
    
    • 1
    • 2
  • 相关阅读:
    multisim仿真电路图红绿灯
    SpringMvc拦截器
    基于图搜索的规划算法之A*家族(七):Field D*算法
    【kubernetes】关于k8s集群的pod控制器
    java中的比较器
    <MySQL> 如何合理的设计数据库中的表?数据表设计的三种关系
    树莓派——舵机
    【可观测性系列】 OpenTelemetry Collector的部署模式分析
    炼丹系列1: 分层学习率&梯度累积
    Python数组基本操作
  • 原文地址:https://blog.csdn.net/QHBfuture/article/details/125510273