在类成员方法的声明 (不是定义) 语句前加 “virtual”,如 virtual void func()
class ISpeaker {
public:
virtual void func();
};
在虚函数后加 “=0”,如 virtual void func()=0
class ISpeaker {
public:
virtual void func() = 0;
};
1、分清重写、重载和隐藏
重载 (overload) ,它通常是指在同一个类中有两个或者两个以上函数,它们的函数名相同,但是函数形参不同
重写 (override) 的意思更接近覆盖,在C++中是指派生类覆盖了基类的虚函数
隐藏 (overwrite) 是指基类成员函数,无论它是否为虚函数,当派生类出现同名函数时,如果派生类函数形参基类函数,则基类函数会被隐藏。如果派生类函数形参与基类函数相同,则需要确定基类函数是否为虚函数,如果是虚函数这里的概念就是重写,否则基类函数也会被隐藏。
2、为什么要用override
重写会引发下列问题。下面的代码可以编译成功,但是派生类的四个函数都没有触发重写操作,如果测试人员没有经过仔细的测试,在软件发布前很难意识到这些问题
class Base {
public:
virtual void some_func() { }
virtual void foo(int x) { }
virtual void bar() const { }
void baz() { }
};
class Derived : public Base {
public:
virtual void sone_func() { } // (1)
virtual void foo(int &x) { } // (2)
virtual void bar() { } // (3)
virtual void baz() { } // (4)
};
(1) 派生类虚函数的函数名与基函数的虚函数不同,派生类的函数不是重写
(2) 派生类虚函数的形参列表与基类虚函数的不同,派生类的函数不是重写
(3) 派生类虚函数相对于基类的虚函数少了常量属性,派生类的函数不是重写
(4) 基类成员函数根本不是虚函数,派生类的函数不是重写
让编译器去检查继承中是否发生了重写是非常简单的一件事,只需要告诉编译器,我要重写哪些函数就行了,C++11 标准引入了 override 这个说明符,它就是用来做这件事的
class Derived : public Base {
public:
virtual void sone_func() override { }
virtual void foo(int &x) override { }
virtual void bar() override { }
virtual void baz() override { }
};
这样一修改,四个函数都引发了编译失败,比人工检查要方便很多
3、final说明符
C++11 引入 final 关键字,用来阻止派生类去继承基类的虚函数。它告诉编译器,该虚函数不允许被重写
class Base {
public:
virtual void foo(int x) { }
};
class Derived : public Base {
public:
void foo(int x) final { } // (1)
};
class Derived2 : public Derived {
public:
void foo(int x) { }
};
(1) 中间层的基函数 derived 的虚函数 foo 声明位 final,所以派生类 Derived2 重写 foo 函数时,编译器会给出错误提示
处理用于声明虚函数,final 还可以声明 类,在类名的后面声明 final,可以阻止该类被继承。如下代码所示,最终会编译失败
class Base final {
public:
virtual void foo(int x) { }
};
class Derived : public Base {
public:
void foo(int x) { }
};
虚函数 | 纯虚函数 |
---|---|
子类可以 (也可以不) 重新定义基类的虚函数,该行为称之为复写Override | 子类必须提供纯虚函数的个性化实现 |
子类如果不提供虚函数的实现,将会自动调用基类的缺省虚函数实现,作为备选方案 | 子类如果不提供纯虚函数的实现,编译将会失败。尽管在基类中可以给出纯虚函数的实现,但无法通过指向子类对象的基类类型指针来调用该纯虚函数,也即不能作为子类相应纯虚函数的备选方案 |
两个不同的子类强转为基类,调用这个基类的相同方法,两个不同的子类执行各自的函数
#include
class ISpeaker
{
protected:
size_t b;
public:
ISpeaker(size_t _v) : b(_v) { }
virtual void speak() = 0;
};
class Dog : public ISpeaker {
public:
Dog() : ISpeaker(0) { }
virtual void speak() override {
printf("woof! %llu\n", b);
}
};
class Human : public ISpeaker {
private:
size_t c;
public:
Human() : ISpeaker(1), c(2) { }
virtual void speak() override {
printf("hello! %llu\n", c);
}
};
int main(int argc, char** _argv) {
Human* pHuman = new Human();
Dog* pDog = new Dog();
ISpeaker* speaker1 = (ISpeaker*)pHuman;
ISpeaker* speaker2 = (ISpeaker*)pDog;
speaker1->speak();
speaker2->speak();
}
运行结果:
C:\Users\huan\Desktop>.\a.exe
hello! 2
woof! 0
如果一个类实现了一个虚函数,编译器在处理它的时候会生成一个虚函数表
虚函数表的定义如下:
struct SpeakerTable {
void(*speak)(void* _ptr);
};
子类的内存模型如下,狗和人类的虚函数表指针指向的结构体不同
相当于原来的子类结构体强转为父类的结构体,子类和父类第一位都是虚函数表指针,然后调用父类虚函数表中的函数,对应着调用子类虚函数表中的函数
#include
struct SpeakerTable {
void(*speak)(void* _ptr);
};
void __dog_speak(void* _ptr) {
uint8_t* p = (uint8_t*)_ptr;
p += sizeof(SpeakerTable*);
size_t b = *((size_t*)p);
printf("woof! %11u\n", b);
}
void __human_speak(void* _ptr) {
uint8_t* p = (uint8_t*)_ptr;
p += sizeof(SpeakerTable*);
p += sizeof(size_t);
size_t b = *((size_t*)p);
printf("hello! %11u\n", b);
}
const static SpeakerTable __dogSpeakTable = {
__dog_speak
};
const static SpeakerTable __humanSpeakTable = {
__human_speak
};
struct __ispeaker {
const SpeakerTable* vt;
size_t b;
};
struct __dog {
const SpeakerTable* vt;
size_t b;
};
struct __human {
const SpeakerTable* vt;
size_t b;
size_t c;
};
__dog* createDog() {
__dog* ptr = (__dog* )malloc(sizeof(__dog));
ptr->vt = &__dogSpeakTable;
ptr->b = 0;
return ptr;
}
__human* createHuman() {
__human* ptr = (__human*)malloc(sizeof(__human));
ptr->vt = &__humanSpeakTable;
ptr->b = 1;
ptr->c = 2;
return ptr;
}
int main(int _argc, char** _argv)
{
__dog* dog = createDog();
__human* human = createHuman();
__ispeaker* speaker1 = (__ispeaker*)dog;
__ispeaker* speaker2 = (__ispeaker*)human;
speaker1->vt->speak(speaker1);
speaker2->vt->speak(speaker2);
return 0;
}