6.15C++:虚函数、构造函数不可以为虚函数、虚析构函数、虚函数实现多态、虚函数中默认参数、基类虚函数不能是私有函数、虚函数对象调用可内联,指针/引用调用不能内联

1 虚函数

  1. 虚函数可以实现运行时的多态,在运行时觉得调用哪个函数体;
  2. 虚函数必须是非静态的成员函数,虚函数是属于对象的,不是属于整个类的,需要在运行时通过指针定位到指向的对象,然后决定调用哪个函数体
  3. 虚函数经过派生之后,就可以实现运行过程中的多态;
  4. 构造函数不能是虚函数,析构函数可以;
  5. 虚函数virtual只能出现在类定义的函数原型声明中,而不能在成员函数实现的时候;
  6. 虚函数不能是内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的
#include <iostream>
using namespace std;
class Base1 {
public:
    virtual void display() const;
}; //虚函数一般不声明为内联函数;
void Base1::display() const { //实现时不能写virtual

    cout << "111" << endl;
}
class Base2 : public Base1{
    virtual void display() const;
};
void Base2::display() const{
    cout << "222" << endl;
}
class Derived : public Base2 {
    virtual void display() const;
};
void Derived::display() const {
    cout << "333" << endl;
}
void haha(Base1* ptr) { 
//指针类型是指向初始基类的指针
    ptr->display();
}
int main()
{
    Base1 base1;
    Base2 base2;
    Derived derived;
    Base1* ptr1 = &base1;
    haha(ptr1);
    haha(&base1);
    haha(&base2);
    haha(&derived);
}
输出:
111
111
222
333

2 构造函数不可以为虚函数

尽管虚函数表vtable是在编译阶段就已经建立的,但指向虚函数表的指针vptr是在运行阶段实例化对象时才产生的。 如果类含有虚函数,编译器会在构造函数中添加代码来创建vptr。 问题来了,如果构造函数是虚的,那么它需要vptr来访问vtable,可这个时候vptr还没产生。 因此,构造函数不可以为虚函数。

我们之所以使用虚函数,是因为需要在信息不全的情况下进行多态运行。而构造函数是用来初始化实例的,实例的类型必须是明确的。 因此,构造函数没有必要被声明为虚函数。

构造函数不可以声明为虚函数。同时除了inline之外,构造函数不允许使用其它任何关键字。

3 虚析构函数

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

  1. 很多情况下要写虚析构函数
  2. 仔细看注释
  3. 当类的对象离开了它的作用域或者delete表达式应用到一个类对象的指针上时,析构函数会自动调用。

如果不写virtual,del函数在静态编译时,总是调用Base1的析构函数;
如果加上virtual,编译器会在运行时根据指针ptr所指向的实际对象决定该调用哪个析构函数;

#include <iostream>
using namespace std;
class Base1 {
public:
    Base1() {
        cout << "Base1构造" << endl;
    }
    virtual ~Base1();
};
Base1::~Base1() {
    cout << "Base1析构" << endl;
}
class Derived : public Base1 {
public:
    Derived() {
        cout << "Derived构造" << endl;
        p = new int(0);
    }
    virtual ~Derived();
private:
    int* p;
};
Derived::~Derived() {
    cout << "Derived析构" << endl;
    delete p;
}
void del(Base1* ptr) {
//指针类型是指向初始基类的指针
    delete ptr;
}
int main()
{
    Base1* ptr = new Derived();
    //指针类型是指向初始基类的指针
    del(ptr);
    return 0;
}
输出:
Base1构造
Derived构造
Derived析构
Base1析构

4 虚函数的作用:与指向初始基类的指针结合,实现多态、统一性

上两个例子中,指针ptr的类型是指向初始基类的指针,这是因为所有派生类的对象都是初始基类的对象,指向所有派生类的指针都可以调用初始基类的成员(*this->Base1::fun),所以可以用指向基类的指针来作为下列指针
①指向所有动态构造的派生类对象的指针,即便有无数级派生;
②统一进行内存释放(delete)的函数的参数,无论要删除哪一级派生类的对象占用内存;()
③统一display函数等;

通过使用虚函数与指向基类的指针,可以精简程序结构,统一函数,实现多态性。
PS:②通过虚析构函数实现,基类析构函数必须是虚函数、③通过普通虚函数实现

5 虚函数中默认参数

虚函数是动态绑定的,默认参数是静态绑定的。默认参数的使用需要看指针或者应用本身的类型,而不是对象的类型。
静态编译时将x=10。

#include <iostream> 
using namespace std;
class Base
{
public:
    virtual void fun(int x = 10)
    {
        cout << "Base::fun(), x = " << x << endl;
    }
};
class Derived : public Base
{
public:
    virtual void fun(int x = 20)
    {
        cout << "Derived::fun(), x = " << x << endl;
    }
};
int main()
{
    Derived d1;
    Base* bp = &d1;
    bp->fun();  // 10
    return 0;
}
输出:Derived::fun(), x = 10

6 基类虚函数不能是私有函数,派生类虚函数可以是私有函数

参考文章

  1. 基类中的虚函数必须是公有的(或将调用函数设为友元),因为一般用基类的指针(Base* prt = new derived;)实现多态,基类虚函数为私有成员时,静态编译时语法检查不能通过;
  2. 派生类的虚函数的访问权限不影响虚函数的动态联编,也就是多态与成员函数的访问权限并没有什么关系,基类定义了虚函数,并且是 public ,那么派生类只要 override 虚函数无论放在什么样的访问权限下,都以基类的访问权限为主。
#include<iostream>
using namespace std;
class base
{
private:
    virtual void func() { cout << "base : func()" << endl; }
};
class derived :public base
{
public:
    virtual void func() { cout << "derived :func()" << endl; }
};
int main()
{
    derived d;
    base* pbase = &d;
    pbase->func(); // 编译错误,需要将基类虚函数设为公有
    return 0;
}

7 虚函数什么时候可以被内联?

①什么是内联
关键字inline必须与函数定义放在一起才能使函数成为内联,仅仅将inline放在函数声明前是不起任何作用的。
ps:函数的声明与函数的定义形式上十分相似,但是二者有着本质上的不同。声明是不开辟内存的,仅仅告诉编译器,要声明的部分存在,要预留一点空间。定义则需要开辟内存。
声明就是告诉使用者函数名是什么,返回值是什么,参数列表又是什么。

②inline说明对编译器来说只是一种建议, 编译器可以忽略这个建议的,比如,你将一个长达100多行的函数指定为inline,编译器就会自动忽略这个inline,将这个函数还原成普通函数。

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

  1. 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
  2. 内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
  3. inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
#include <iostream>  
using namespace std;
class Base
{
    public:
        inline virtual void who()
        {
            cout << "I am Base\n";
        }
        virtual ~Base() {}
};
class Derived : public Base
{
    public:
        inline void who()  // 不写inline时隐式内联
        {
            cout << "I am Derived\n";
        }
};

int main()
{
    // 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。 
    Base b;
    b.who();

    // 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。  
    Base *ptr = new Derived();
    ptr->who();

    // 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。
    delete ptr;

    return 0;
} 
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值