大雄的纯虚函数和抽象类学习笔记

写在前面,
这是我读其他人的文章写下的笔记,第一版和原文几乎相同。侵联删,
后续我也会加入我在其他地方学到的相关的东西,也就是说,这是我自己的一个学习笔记而已。
当然了,既然是学习笔记,除了链接原文外肯定还有其他知识点
原文链接:https://github.com/Light-City/CPlusPlusThings/tree/master/basic_content/abstract

1、虚函数

(1)定义
在C++中,基类必须将它的两种成员函数区分开来:
一种是基类希望其派生类进行覆盖的函数;另一种是基类希望派生类直接继承而不要改变的函数。
对于前者,基类通过在函数之前加上virtual关键字将其定义为虚函数(virtual)。

class Base{ // 基类 
public: 
  virtual int func(int n) const; 
}; 
  
class Derive_Class : public Base{ 
public: 
  int func(int n) const; // 默认也为虚函数 
}; 

当我们在派生类中覆盖某个函数时,可以在函数前加virtual关键字。然而这不是必须的,因为一旦某个函数被声明成虚函数,则所有派生类中它都是虚函数。任何构造函数之外的非静态函数都可以是虚函数。派生类经常(但不总是)覆盖它继承的虚函数,如果派生类没有覆盖其基类中某个虚函数,则该虚函数的行为类似于其他的普通成员,派生类会直接继承其在基类中的版本。

(2)动态绑定
当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定(dynamic binding)。因为我们直到运行时才能知道到底调用了哪个版本的虚函数,可能是基类中的版本也可能是派生类中的版本,判断的依据是引用(或指针)所绑定的对象的真实类型。与非虚函数在编译时绑定不同,虚函数是在运行时选择函数的版本,所以动态绑定也叫运行时绑定(run-time binding)。

(3)静态类型与动态类型
静态类型指的是变量声明时的类型或表达式生成的类型,它在编译时总是已知的;动态类型指的是变量或表达式表示的内存中的对象的类型,它直到运行时才可知。当且仅当通过基类的指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同。如果表达式既不是引用也不是指针,则它的动态类型永远与静态类型一致。

(4)final与override
派生类中如果定义了一个函数与基类中虚函数同名但形参列表不同,编译器会认为这是派生类新定义的函数。如果我们的意图本是覆盖虚函数,则这种错误很难发现。通过在派生类中的虚函数最后加override关键字使得意图更加清晰。如果我们使用override标记了某个函数,但该函数并没有覆盖已存在的虚函数,编译器将报错。

class Base{ // 基类 
public: 
  virtual int func(int a, int b) const; 
}; 
  
class Derive_Class : public Base{ 
public: 
  int func(int a) const override; // 报错,没有覆盖虚函数 
}; 

如果我们定义一个类,并不希望它被继承。或者希望某个函数不被覆盖,则可以把类或者函数指定为final,则之后任何尝试继承该类或覆盖该函数的操作将引发错误。

class Base final { /*  */ };   // 基类不能被继承 
class Derive_Class : public Base { /* */ };   // 报错 
  
void func(int) const final;  // 不允许后续的其他类覆盖func(int)

(5)回避虚函数的机制
在某些情况下,我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本。可以使用作用域运算符实现这一目的。

// 强行调用基类中定义的函数版本而不管baseP的动态类型是什么 
int a = baseP->Base::func(42); 

如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域运算符,则在运行时该调用将被解析为对派生类版本自身的调用,从而导致无限递归。

2、 纯虚函数与抽象类、

(1)定义与声明
*
C++中的纯虚函数(或抽象函数)是我们没有实现的虚函数!我们只需声明它! 通过声明中赋值0来声明纯虚函数

// 抽象类
Class A {
public: 
    virtual void show() = 0; // 纯虚函数
    /* Other members */
}; 

纯虚函数:没有函数体的虚函数
抽象类:包含纯虚函数的类
**抽象类只能作为基类来派生新类使用,不能创建抽象类的对象,**抽象类的指针和引用->由抽象类派生出来的类的对象!

#include<iostream>

using namespace std;
class A
{
private:
    int a;
public:
    virtual void show()=0; // 纯虚函数
};


int main()
{
    /*
     * 1. 抽象类只能作为基类来派生新类使用
     * 2. 抽象类的指针和引用->由抽象类派生出来的类的对象!
     */
    A a; // error 抽象类,不能创建对象

    A *a1; // ok 可以定义抽象类的指针

    A *a2 = new A(); // error, A是抽象类,不能创建对象
}

(2)抽象类的实现
*
抽象类中:在成员函数内可以调用纯虚函数,在构造函数/析构函数内部不能使用纯虚函数。
如果一个类从抽象类派生而来,它必须实现了基类中的所有纯虚函数,才能成为非抽象类。

#include<iostream>
using namespace std;

class A {
public:
    virtual void f() = 0;  // 纯虚函数
    void g(){ this->f(); }
    A(){}
};
class B:public A{
public:
    void f(){ cout<<"B:f()"<<endl;}
};
int main(){
    B b;
    b.g();
    return 0;
}

**

3、注意

**

  • (1)纯虚函数使一个类变成抽象类
#include<iostream> 
using namespace std; 

/**
 * @brief 抽象类至少包含一个纯虚函数
 */
class Test 
{ 
    int x; 
public: 
    virtual void show() = 0; 
    int getX() { return x; } 
}; 

int main(void) 
{ 
    Test t;  //error! 不能创建抽象类的对象
    return 0; 
} 
  • (2)抽象类类型的指针和引用
#include<iostream> 
using namespace std; 


/**
 * @brief 抽象类至少包含一个纯虚函数
 */
class Base
{ 
    int x; 
public: 
    virtual void show() = 0; 
    int getX() { return x; } 
    
}; 
class Derived: public Base 
{ 
public: 
    void show() { cout << "In Derived \n"; } 
    Derived(){}
}; 
int main(void) 
{ 
    //Base b;  //error! 不能创建抽象类的对象
    //Base *b = new Base(); error!
    Base *bp = new Derived(); // 抽象类的指针和引用 -> 由抽象类派生出来的类的对象
    bp->show();
    return 0; 
} 
  • (3)如果我们不在派生类中覆盖纯虚函数,那么派生类也会变成抽象类
#include<iostream> 
using namespace std; 

class Base
{ 
    int x; 
public: 
    virtual void show() = 0; 
    int getX() { return x; } 
}; 
class Derived: public Base 
{ 
public: 
//    void show() { } 
}; 
int main(void) 
{ 
    Derived d;  //error! 派生类没有实现纯虚函数,那么派生类也会变为抽象类,不能创建抽象类的对象
    return 0; 
} 
  • (4)抽象类可以有构造函数
#include<iostream> 
using namespace std; 

// An abstract class with constructor 
class Base 
{ 
    protected: 
        int x; 
    public: 
        virtual void fun() = 0; 
        Base(int i) { x = i; } 
}; 

class Derived: public Base 
{ 
    int y; 
    public: 
    Derived(int i, int j):Base(i) { y = j; } 
    void fun() { cout << "x = " << x << ", y = " << y; } 
}; 

int main(void) 
{ 
    Derived d(4, 5); 
    d.fun(); 
    return 0; 
} 
  • (5)构造函数不能是虚函数,而析构函数可以是虚析构函数

尽管虚函数表vtable是在编译阶段就已经建立的,但指向虚函数表的指针vptr是在运行阶段实例化对象时才产生的。 如果类含有虚函数,编译器会在构造函数中添加代码来创建vptr。 问题来了,如果构造函数是虚的,那么它需要vptr来访问vtable,可这个时候vptr还没产生。 因此,构造函数不可以为虚函数。
我们之所以使用虚函数,是因为需要在信息不全的情况下进行多态运行。而构造函数是用来初始化实例的,实例的类型必须是明确的。 因此,构造函数没有必要被声明为虚函数。构造函数不可以声明为虚函数。同时除了inline|explicit之外,构造函数不允许使用其它任何关键字。

#include <iostream> 
using namespace std; 

 LIBRARY SRART 
class Base 
{ 
    public: 
        Base() { } 

        virtual // Ensures to invoke actual object destructor 
            ~Base() { } 

        virtual void ChangeAttributes() = 0; 

        // The "Virtual Constructor" 
        static Base *Create(int id); 

        // The "Virtual Copy Constructor" 
        virtual Base *Clone() = 0; 
}; 

class Derived1 : public Base 
{ 
    public: 
        Derived1() 
        { 
            cout << "Derived1 created" << endl; 
        } 

        Derived1(const Derived1& rhs) 
        { 
            cout << "Derived1 created by deep copy" << endl; 
        } 

        ~Derived1() 
        { 
            cout << "~Derived1 destroyed" << endl; 
        } 

        void ChangeAttributes() 
        { 
            cout << "Derived1 Attributes Changed" << endl; 
        } 

        Base *Clone() 
        { 
            return new Derived1(*this); 
        } 
}; 

class Derived2 : public Base 
{ 
    public: 
        Derived2() 
        { 
            cout << "Derived2 created" << endl; 
        } 

        Derived2(const Derived2& rhs) 
        { 
            cout << "Derived2 created by deep copy" << endl; 
        } 

        ~Derived2() 
        { 
            cout << "~Derived2 destroyed" << endl; 
        } 

        void ChangeAttributes() 
        { 
            cout << "Derived2 Attributes Changed" << endl; 
        } 

        Base *Clone() 
        { 
            return new Derived2(*this); 
        } 
}; 

class Derived3 : public Base 
{ 
    public: 
        Derived3() 
        { 
            cout << "Derived3 created" << endl; 
        } 

        Derived3(const Derived3& rhs) 
        { 
            cout << "Derived3 created by deep copy" << endl; 
        } 

        ~Derived3() 
        { 
            cout << "~Derived3 destroyed" << endl; 
        } 

        void ChangeAttributes() 
        { 
            cout << "Derived3 Attributes Changed" << endl; 
        } 

        Base *Clone() 
        { 
            return new Derived3(*this); 
        } 
}; 

// We can also declare "Create" outside Base. 
// But is more relevant to limit it's scope to Base 
Base *Base::Create(int id) 
{ 
    // Just expand the if-else ladder, if new Derived class is created 
    // User need not be recompiled to create newly added class objects 

    if( id == 1 ) 
    { 
        return new Derived1; 
    } 
    else if( id == 2 ) 
    { 
        return new Derived2; 
    } 
    else
    { 
        return new Derived3; 
    } 
} 
 LIBRARY END 

 UTILITY SRART 
class User 
{ 
    public: 
        User() : pBase(0) 
    { 
        // Creates any object of Base heirarchey at runtime 

        int input; 

        cout << "Enter ID (1, 2 or 3): "; 
        cin >> input; 

        while( (input != 1) && (input != 2) && (input != 3) ) 
        { 
            cout << "Enter ID (1, 2 or 3 only): "; 
            cin >> input; 
        } 

        // Create objects via the "Virtual Constructor" 
        pBase = Base::Create(input); 
    } 

        ~User() 
        { 
            if( pBase ) 
            { 
                delete pBase; 
                pBase = 0; 
            } 
        } 

        void Action() 
        { 
            // Duplicate current object 
            Base *pNewBase = pBase->Clone(); 

            // Change its attributes 
            pNewBase->ChangeAttributes(); 

            // Dispose the created object 
            delete pNewBase; 
        } 

    private: 
        Base *pBase; 
}; 

 UTILITY END 

 Consumer of User (UTILITY) class 
int main() 
{ 
    User *user = new User(); 

    user->Action(); 

    delete user; 
} 

析构函数可以声明为虚函数。如果我们需要删除一个指向派生类的基类指针时,应该把析构函数声明为虚函数。 事实上,只要一个类有可能会被其它类所继承, 就应该声明虚析构函数(哪怕该析构函数不执行任何操作)。

#include<iostream> 

using namespace std; 

class base { 
    public: 
        base()      
        { cout<<"Constructing base \n"; } 
        ~base() 
        { cout<<"Destructing base \n"; }      
}; 

class derived: public base { 
    public: 
        derived()      
        { cout<<"Constructing derived \n"; } 
        ~derived() 
        { cout<<"Destructing derived \n"; } 
}; 

int main(void) 
{ 
    derived *d = new derived();   
    base *b = d; 
    delete b; 
    return 0; 
} 
  • 当基类指针指向派生类对象并删除对象时,我们可能希望调用适当的析构函数。
  • (6)当基类指针指向派生类对象并删除对象时,我们可能希望调用适当的析构函数。 如果析构函数不是虚拟的,则只能调用基类析构函数。
#include<iostream>
using namespace std;

class Base  {
    public:
        Base()    { cout << "Constructor: Base" << endl; }
        virtual ~Base()   { cout << "Destructor : Base" << endl; }
};

class Derived: public Base {
    public:
        Derived()   { cout << "Constructor: Derived" << endl; }
        ~Derived()   { cout << "Destructor : Derived" << endl; }
};

int main()  {
    Base *Var = new Derived();
    delete Var;
    return 0;
}

(7)虚函数与私有函数

  • 基类指针指向继承类对象,则调用继承类对象的函数;
  • int main()必须声明为Base类的友元,否则编译失败。 编译器报错: ptr无法访问私有函数。 当然,把基类声明为public, 继承类为private,该问题就不存在了。
#include<iostream> 
using namespace std; 

class Derived; 

class Base { 
    private: 
        virtual void fun() { cout << "Base Fun"; } 
        friend int main(); 
}; 

class Derived: public Base { 
    public: 
        void fun() { cout << "Derived Fun"; } 
}; 

int main() 
{ 
    Base *ptr = new Derived; 
    ptr->fun(); 
    return 0; 
}

(8)虚函数与内联

通常类成员函数都会被编译器考虑是否进行内联。 但通过基类指针或者引用调用的虚函数必定不能被内联。 当然,实体对象调用虚函数或者静态调用时可以被内联,虚析构函数的静态调用也一定会被内联展开。

虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联
内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。

示例:抽象类由派生类继承实现

#include<iostream> 
using namespace std; 

class Base 
{ 
    int x; 
    public: 
    virtual void fun() = 0; 
    int getX() { return x; } 
}; 

class Derived: public Base 
{ 
    int y; 
    public: 
    void fun() { cout << "fun() called"; }  // 实现了fun()函数
}; 

int main(void) 
{ 
    Derived d; 
    d.fun(); 
    return 0; 
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值