讨论C++多态
多态
概念
多态即完成某个行为,不同对象会产生不同的状态。
定义及实现
多态构成的必要条件:
- 基类的指针或者引用指向其派生类。
- 基类的指针或者引用调用的函数必须是虚函数,且派生类对基类的虚函数进行重写。
class Animal {
public:
virtual void eat() {
cout << "动物在吃饭" << endl;
}
};
class Dog : public Animal {
public:
virtual void eat() {
cout << "狗在啃骨头" << endl;
}
};
void func(Animal& a) {
a.eat();
}
int main() {
Animal a;
Dog g;
func(a);
func(g);
return 0;
}
- 函数参数是
Animal
对象的引用,传入的对象一个是Animal
类,一个是Dog
类,可以看到传入Dog
类对象,调用了Dog
类中重写的eat()
函数。
虚函数
虚函数是被关键字
virtual
修饰的类成员函数。普通函数不能被virtual
修饰,静态成员函数因为没有this
指针,因此不能是虚函数。
虚函数重写
派生类中有一个和基类完全相同的虚函数(函数名相同、参数列表相同、返回值类型相同),称为派生类对基类的虚函数进行了重写。
-
但虚函数重写有两个例外:
-
协变:派生类和基类的虚函数返回值类型不同(返回值类型构成继承关系)
-
class Animal { public: virtual Animal* eat() { cout << "动物在吃饭" << endl; return nullptr; } }; class Dog : public Animal { public: virtual Dog* eat() { cout << "狗在啃骨头" << endl; return nullptr; } }; void func(Animal& a) { a.eat(); } int main() { Animal a; Dog g; func(a); func(g); return 0; }
-
结果不变,依然构成多态。
-
-
析构函数重写
-
class Animal { public: ~Animal() { cout << "~Animal()" << endl; } }; class Dog : public Animal { public: ~Dog() { cout << "~Dog()" << endl; } }; int main() { Animal a; Dog g; return 0; }
-
编写上述代码,输出结果正常。
Dog
对象析构后需要调用基类析构函数。 -
但当我们使用
Animal
对象的指针指向Dog
对象,再释放,结果将不一样。 -
class Animal { public: ~Animal() { cout << "~Animal()" << endl; } }; class Dog : public Animal { public: ~Dog() { cout << "~Dog()" << endl; } }; int main() { Animal* p = new Animal; Animal* g = new Dog; delete p; delete g; return 0; }
-
根据上述结果可以看出,创建的
Dog
对象并没有调用析构函数,如果对象内存在动态开辟空间,将会造成内存泄露。 -
造成这种结果的原因是因为析构函数不是虚函数,编译器实施静态绑定,指针变量
g
的类型是Animal
,因此只会调用Animal
的析构函数。 -
要想完整调用析构函数,必须将基类的析构函数设定为虚函数。
-
class Animal { public: virtual ~Animal() { cout << "~Animal()" << endl; } }; class Dog : public Animal { public: virtual ~Dog() { cout << "~Dog()" << endl; } }; int main() { Animal* p = new Animal; Animal* g = new Dog; delete p; delete g; return 0; }
-
-
final和override
final
和override
都是C++11提供的关键字。
- 被
final
修饰的类不能被继承,修饰的函数不能被重写。
class Animal {
public:
virtual void fun() final {
cout << "fun()" << endl;
}
virtual ~Animal() {
cout << "~Animal()" << endl;
}
};
class Dog : public Animal {
public:
virtual void fun() {
cout << "Dog::fun()" << endl;
}
virtual ~Dog() {
cout << "~Dog()" << endl;
}
};
void func(Animal &a) {
a.fun();
}
int main() {
Dog g;
func(g);
return 0;
}
- 会报错
declaration of 'fun' overrides a 'final' function
,证明final
修饰的虚函数不能被重写。
class Animal final{
public:
virtual void fun() {
cout << "fun()" << endl;
}
virtual ~Animal() {
cout << "~Animal()" << endl;
}
};
class Dog : public Animal {
public:
virtual void fun() {
cout << "Dog::fun()" << endl;
}
virtual ~Dog() {
cout << "~Dog()" << endl;
}
};
void func(Animal &a) {
a.fun();
}
int main() {
Dog g;
func(g);
return 0;
}
- 同样会报错,
base 'Animal' is marked 'final'
,证明final
修饰的类不能被继承。
override
关键字是检查派生类是否重写了基类的某个虚函数,若没有重写,编译报错。
class Animal {
public:
virtual void fun() {
cout << "fun()" << endl;
}
virtual ~Animal() {
cout << "~Animal()" << endl;
}
};
class Dog : public Animal {
public:
virtual void fun(int x = 1) override {
cout << "Dog::fun()" << endl;
}
virtual ~Dog() {
cout << "~Dog()" << endl;
}
};
void func(Animal &a) {
a.fun();
}
int main() {
Dog g;
func(g);
return 0;
}
- 会报错,
'fun' marked 'override' but does not override any member functions
,证明override
会对重写进行检查。
重载、重写和重定义
抽象类
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化对象。
纯虚函数
纯虚函数就是在虚函数后面加上
=0
。
- 纯虚函数规范了派生类必须要重写。
接口继承和实现继承
- 普通函数继承是一种实现继承,派生类继承了基类的函数,可以调用其函数,继承了函数的实现。虚函数的继承是一种接口继承,派生类继承了基类虚函数的接口,目的是为了重写达成多态,继承的是接口。
多态的原理
虚函数表
- 验证虚函数表的存在。
class Base {
public:
virtual void Func1() {
cout << "Base::Func1()" << endl;
}
virtual void Func2() {
cout << "Base::Func2()" << endl;
}
void Func3() {
cout << "Base::Func3()" << endl;
}
public:
int _b = 1;
};
int main() {
Base b;
Derive d;
cout << "sizeof(b) = " << sizeof(b) << endl;
cout << &b << endl;
cout << &b._b << endl;
return 0;
}
- 成员变量地址上还有一个8字节大小的地址空间,刚好是64位下指针的大小,因此可以判断
Base
对象中存在一个指针,这个指针就是虚函数表指针。一个含有虚函数的类中都至少有一个虚函数表,因为虚函数的地址需要存放在虚函数表中。
我们通过打印虚函数表中数据,来发现多态的原理。
打印单继承虚函数表
typedef void (*VFTPTR)(); // typedef函数指针
class Base {
public:
virtual void Func1() {
cout << "Base::Func1()" << endl;
}
virtual void Func2() {
cout << "Base::Func2()" << endl;
}
void Func3() {
cout << "Base::Func3()" << endl;
}
public:
int _b = 1;
};
class Derive : public Base {
public:
virtual void Func1() {
cout << "Derive::Func1()" << endl; // 重写Func1函数
}
public:
int _d = 2;
};
void printVFT(VFTPTR *a) { // 打印函数指针
for (int i = 0; i < 2; ++i) {
printf("[%d]:%p->", i, a[i]);
VFTPTR f = a[i];
f();
}
printf("\n");
}
int main() {
Base b;
Derive d;
printVFT((VFTPTR *) (*((long long *) &b)));
printVFT((VFTPTR *) (*((long long *) &d)));
return 0;
}
- 可以看出,
Base
对象和Derive
对象使用的不是同一张虚函数表,虚函数表是在编译阶段就创建好的,Derive
对象继承Base
对象虚函数表中的函数地址,当不发生重写时,地址不发生变化。当发生重写时,派生类重写函数的地址将覆盖原有基类函数地址,形成新的指向,完成地址的覆盖。
动态绑定和静态绑定
- 静态绑定又称为前期绑定,在编译期间确定了程序的行为,也称为静态多态,如:函数重载。
- 动态绑定又称为后期绑定,在运行期间根据具体的类型确定程序行为,调用具体的函数,也称为动态多态。
多继承虚函数表
class Base1 {
public:
virtual void Func1() {
cout << "Base1::Func1()" << endl;
}
virtual void Func2() {
cout << "Base1::Func2()" << endl;
}
public:
int _b1 = 1;
};
class Base2 {
public:
virtual void Func1() {
cout << "Base2::Func1()" << endl;
}
virtual void Func2() {
cout << "Base2::Func2()" << endl;
}
public:
int _b2 = 1;
};
class Derive : public Base1, public Base2 {
public:
virtual void Func1() {
cout << "Derive::Func1()" << endl;
}
virtual void Func3() {
cout << "Derive::Func3()" << endl;
}
public:
int _d = 1;
};
Derive
类继承Base1
和Base2
,重写Func1()
函数。
typedef void (*VFTPTR)();
void printVFT(VFTPTR* a) {
for (int i = 0; i < 3; ++i) {
printf("[%d]:%p->", i, a[i]);
VFTPTR f = a[i];
f();
}
printf("\n");
}
int main() {
Derive d;
printVFT((VFTPTR *) (*((long long *) &d)));
return 0;
}
int main() {
Derive d;
VFTPTR* t2 = (VFTPTR*)(*(long long*)((char*)&d + sizeof(Base1))); // Derive对象继承,指针偏移到Base2部分。
printVFT(t2);
return 0;
}
- 由此可以看出,
Derive
对象先继承了Base1
,自己独有的虚函数Func3()
会放在第一个继承基类部分的虚函数表中。