class A {
};
// sizeof(A) = 1
空类的大小之所以为1,因为标准规定完整对象的大小>0,否则两个不同对象可能拥有相同的地址,故编译器会生成1B占位符。
那么两个对象为什么不能地址相同呢?
There would be no way to distinguish between these two objects when referencing them with pointers.
空类中到底都有什么呢?
class A {
public:
A(); // 默认构造函数
A(const A&); // 拷贝构造函数
~A(); // 析构函数
A& operator=(const A&); // 赋值运算符
A* operator&(); // 取址运算符(非const)
const A* operator&() const; // 取址运算符(const)
};
仅仅声明一个类,不会创建这些函数。只有当定义类的对象时,才会产生。
定义虚函数f
,是为了用基类的引用或指针调用派生类的f
,最终调用哪个f
取决于传入的实参,即在运行时选择函数的版本,也就是所谓的动态绑定。
class Base {
public:
virtual void f() {
cout << "Base";
}
virtual void g() {}
private:
int i;
};
class Derived : public Base {
public:
virtual void f() { // 覆盖Base::f
cout << "Derived";
}
virtual void h() {}
private:
int j;
};
int main() {
Base* p = new Derived();
p->f(); // 调用派生类的f()
delete p;
return 0;
}
基类指针p
调用虚函数f
,f
作用的可能是基类对象,也可能是派生类对象,这就是多态(同样消息作用于不同类型对象产生不同的行为)的一种方式,即动态多态。
正因为编译器无法确定使用哪个虚函数,所以所有的虚函数必须定义,否则编译器会报错。
析构函数是虚函数,因为要确保执行相应对象的析构函数。如果基类指针指向派生类对象,会调用派生类的析构函数,然后调用基类的析构函数。
与虚函数必须定义相反,纯虚函数无须定义(要定义必须在类的外部),含有纯虚函数的类是抽象基类。
抽象基类定义好接口,继承该类的其他类可以覆盖这个接口。
virtual void f() = 0; // 声明纯虚函数
之所以要引入纯虚函数,是因为很多时候基类产生对象是没有意义的。比如动物类可以派生出狗、猪等子类,但动物类生成对象毫无意义。
因此,不能创建抽象基类的对象,派生类必须覆盖(override)以定义自己的f
,否则派生类仍然是抽象基类。
struct B {
virtual void f1(int) const;
virtual void f2();
void f3();
};
struct D1 :B {
void f1(int) const override; // 正确:f1与基类中的f1匹配
void f2(int) override; // 错误:B没有形如f2(int)的函数
void f3() override; // 错误:f3不是虚函数
void f4() override; // 错误:B没有名为f4的函数
};
原文:https://www.cnblogs.com/EIMadrigal/p/12384883.html