class A
{
private:
int a;
public:
void func1() {};
void func2() {};
}
cout << sizeof(A) << endl;
上述计算结果是多少呢? 很显然, 计算结果的大小是4, 在类中类的大小由成员变量决定的, 成员函数不占用类大小. 成员变量与成员函数是分开存储的.
class A
{
private:
int a;
public:
virtual void func1() {};
virtual void func2() {};
}
cout << sizeof(A) << endl;
现在的结果是多少呢? 编译器结果输出是16, 其中int 占4字节.为什么会多出12字节呢? 答案很简单,当一个类中加入虚函数时,此时的成员变量会增加一个名字叫vptr的指针.由于系统是64位,所以指针大小是8字节,考虑内存对齐的原因,所以结果是16字节.
在编译期间,如果类中的虚函数>=1的时候,编译器在编译阶段会为类生成一个虚函数表vtbl.生成虚函数表的原因是为了实现动态绑定或运行时多态性.
在编译期间, 编译器会向类的构造函数中插入一条为虚函数表指针赋值的语句,将虚函数表的地址赋值给vptr.
C++中的多态是指通过基类的指针或引用调用派生类的成员函数时,根据实际对象的类型来确定调用的是哪个类的成员函数。即方法的行为取决于调用该方法的对象.
第一点: 虚函数的调用需要用到虚函数表指针,在上面的介绍中,我们知道了虚函数表指针生成和赋值的时机,在创建对象时,编译器会为对象分配内存,之后调用构造函数进行初始化工作.如果构造函数是虚函数,那么调用构造函数的时候需要用到虚表指针指向的虚函数表,由于构造函数是虚函数,所以现在的虚函数表指针的指向是不可知的,就无法调用构造函数.所以构造函数不能是虚函数.
第二点:静态联编在编译期间能够知道使用的函数,由于虚函数的出现,使得静态联编变得困难,就是说在编译期间无法确定要使用的函数,所以需要动态联编.而对于构造函数,编译器能明确知道调用的对象,所以将构造函数声明为虚函数完全没有意义.
使用虚析构函数能够让正确的析构函数序列被调用
友元函数不能为虚函数, 因为友元函数不是类成员,而只有类成员才能是虚函数
如果派生类函数没有重新定义函数, 则派生类将使用原来函数的基类版本.
class A
{
public:
virtual void func1(int x) const;
}
class B : public A
{
public:
virtual void func1() const;
}
B b;
b.func1() // ok
b.func11(6) // no
新的函数定义将隐藏基类的的同名函数.所以这引出了两条经验规则
第一: 如果重新定义继承的方法, 应该确保与基类的原型完全相同.但如果返回类型是基类引用或者指针, 则可以修改为指向派生类的引用或指针.
第二:如果基类声明被重载了, 则应在派生类中重新定义所有的基类版本
class A
{
public:
virtual void func1(int x) const;
virtual void func1(double x) const;
virtual void func(() const;
}
class B : public A
{
public:
virtual void func1() const;
virtual void func1(double x) const;
virtual void func(() const;
}
如果只定义一个版本,其他的两个版本将被隐藏.派生类对象将无法使用他们.