本文共 13338 字,大约阅读时间需要 44 分钟。
class是一种特殊的struct,class与struct遵循相同的内存对齐原则,class中的成员函数与成员变量是分开存放的,每个对象拥有独立的成员变量,所有的对象共享类中的成员函数。
运行时,类对象退化为结构体的形式:A、所有成员变量在内存中依次排布B、由于内存对齐的存在,成员变量间可能存在内存间隙C、可以通过内存地址访问成员变量D、访问权限关键字在运行时失效#includeusing namespace std;class A{ int i; int j; char c; double d;public: void print() { cout << "i = " << i << ", " << "j = " << j << ", " << "c = " << c << ", " << "d = " << d << endl; }};struct B{ int i; int j; char c; double d;};int main(int argc, char *argv[]){ A a; //64 bit machine cout << "sizeof(A) = " << sizeof(A) << endl; // 24 cout << "sizeof(a) = " << sizeof(a) << endl; // 24 cout << "sizeof(B) = " << sizeof(B) << endl; // 24 a.print(); B* p = reinterpret_cast (&a); p->i = 1; p->j = 2; p->c = 'c'; p->d = 3.14; a.print(); return 0;}
上述代码中,class A对象与struct B对象在内存中的排布相同。
子类是由父类成员叠加子类成员得到的。
#includeusing namespace std;class Parent{protected: int m_i; int m_j;};class Child : public Parent{public: Child(int i, int j, double d) { m_i = i; m_j = j; m_d = d; } void print() { cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; cout << "m_d = "<< m_d << endl; }private: double m_d;};struct Test{ int i; int j; double d;};int main(int argc, char *argv[]){ cout << sizeof(Parent) << endl;//8 cout << sizeof(Child) << endl;//16 Child child(1,2,3.14); child.print(); Test* test = reinterpret_cast (&child); cout << "i = " << test->i << endl; cout << "j = " << test->j << endl; cout << "d = " << test->d << endl; test->i = 100; test->j = 200; test->d = 3.1415; child.print(); return 0;}
当类中声明虚函数时,C++编译器会在类中生成一个虚函数表。虚函数表是一个用于存储virtual成员函数地址的数据结构。虚函数表由编译器自动生成与维护,virtual成员函数会被编译器放入虚函数表中。存在虚函数时,每个对象中都有一个指向类的虚函数表的指针。
由于对象调用虚函数时会查询虚函数表,因此虚函数的调用效率比普通成员函数低。当创建类对象时,如果类中存在虚函数,编译器会在类对象中增加一个指向虚函数表的指针。父类对象中虚函数表存储的是父类的虚函数,子类对象中虚函数表存储的是子类对象的虚函数。虚函数表指针存储在类对象存储空间的开始的前4(8)个字节。如果一个类包含虚函数,其类包含一个虚函数表。
如果一个基类包含虚函数,基类会包含一个虚函数表,其派生类也会包含一个自己的虚函数表。虚函数表是一个函数指针数组,其数组元素是虚函数的函数指针,每个元素对应一个虚函数的函数指针。非虚成员函数的调用并不需要经过虚函数表,所以虚函数表的元素并不包括非虚成员函数的函数指针。 虚函数表中虚函数指针的赋值发生在编译器的编译阶段,即在代码编译阶段虚函数表就生成。#includeusing namespace std;class Parent{public: Parent(int i, int j) { m_i = i; m_j = j; } virtual void print() { cout << "Parent::" << __func__<< endl; cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; } virtual double sum() { cout << "Parent::" << __func__<< endl; double ret = m_i + m_j; cout < << endl; return ret; } virtual void display() { cout << "Parent::display()" << endl; }protected: int m_i; int m_j;};class Child : public Parent{public: Child(int i, int j, double d):Parent(i, j) { m_d = d; } virtual void print() { cout << "Child::" << __func__<< endl; cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; cout << "m_d = "<< m_d << endl; } virtual double sum() { cout << "Child::" << __func__<< endl; double ret = m_i + m_j + m_d; cout << ret << endl; return ret; }private: void display() { cout << "Child::display()" << endl; }private: double m_d;};struct Test{ void* vptr; int i; int j; double d;};int main(int argc, char *argv[]){ cout << sizeof(Parent) << endl;//12 cout << sizeof(Child) << endl;//24 Child child(1,2,3.14); Test* test = reinterpret_cast (&child); cout << "virtual Function Table Pointer:" << endl; cout << "vptr = " << test->vptr << endl; //虚函数表指针位于类对象的前4字节 cout << "child Object address: " << &child << endl; cout << "Member Variables Address: " << endl; cout << "&vptr = " << &test->vptr << endl; cout << "&i = " << &test->i << endl; cout << "&j = " << &test->j << endl; cout << "&d = " << &test->d << endl; //函数指针方式访问类的虚函数 cout << "Virtual Function Table: " << endl; cout << "Virtual print Function Address: " << endl; cout << (long*)(*((long *)(*((long *)&child)) + 0)) <
上述代码中,通过类对象的虚函数表指针可以访问类的虚函数表,虚函数表顺序存储了类的虚函数的函数地址,通过函数指针的方式可以调用类的虚函数,包括声明为private的虚函数。但由于使用函数指针方式访问类的虚函数时,类的虚函数在执行过程中其this指针指向的对象是不确定的,因此访问到的类对象的成员变量的值是垃圾值。
虚函数表属于类,而不是属于某个具体的类对象,一个类只需要一个虚函数表。同一个类的所有对象都使用类的唯一虚函数表。 为了指定类对象的虚函数表,类对象内部包含一个指向虚函数表的指针,指向类的虚函数表。为了让每个类对象都拥有一个虚函数表指针,编译器在类中添加了一个指针*__vptr
,用来指向虚函数表。当类对象在创建时便拥有__vptr
指针,且__vptr
指针的值会自动被设置为指向类的虚函数表。
class Parent{public: Parent(int i, int j) { m_i = i; m_j = j; } virtual void print() { cout << "Parent::" << __func__<< endl; cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; } virtual double sum() { cout << "Parent::" << __func__<< endl; double ret = m_i + m_j; cout <<< endl; return ret; } virtual void display() { cout << "Parent::display()" << endl; } int add(int value) { return m_i + m_j + value; }protected: void func() { }protected: int m_i; int m_j;};
上述代码中,类的虚函数表如下:
类Parent对象的内存布局中,虚函数表指针位于类对象存储空间的开头,其值0X409004是类Parent的虚函数表的首地址,虚函数表中的第一个数组元素是虚函数Parent::print的地址,第二个数组元素是虚函数Parent::sum,第三个数组元素是虚函数Parent::display,非虚函数不在虚函数表中。对于含有虚函数的类,虚函数表指针位于类对象内存布局的开始位置,然后依次排列类继承自父类的成员变量,最后依次排列类自身的非静态成员变量。
#includeusing namespace std;class Parent{public: Parent(int i, int j) { m_i = i; m_j = j; } virtual void print() { cout << "Parent::" << __func__<< endl; cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; } virtual double sum() { cout << "Parent::" << __func__<< endl; double ret = m_i + m_j; cout < << endl; return ret; } virtual void display() { cout << "Parent::display()" << endl; } int add(int value) { return m_i + m_j + value; }protected: void func() { }protected: int m_i; int m_j; static int m_count;};int Parent::m_count = 0;class ChildA : public Parent{public: ChildA(int i, int j, double d):Parent(i, j) { m_d = d; } virtual void print() { cout << "ChildA::" << __func__<< endl; cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; cout << "m_d = "<< m_d << endl; } virtual double sum() { cout << "ChildA::" << __func__<< endl; double ret = m_i + m_j + m_d; cout << ret << endl; return ret; }private: void display() { cout << "ChildA::display()" << endl; }private: double m_d;};class ChildB : public Parent{public: ChildB(int i, int j, double d):Parent(i, j) { m_d = d; } virtual void print() { cout << "ChildB::" << __func__<< endl; cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; cout << "m_d = "<< m_d << endl; } virtual double sum() { cout << "ChildB::" << __func__<< endl; double ret = m_i + m_j + m_d; cout << ret << endl; return ret; }private: void display() { cout << "ChildB::display()" << endl; }private: double m_d;};struct ParentTest{ void* vptr; int i; int j;};struct ChildTest{ void* vptr; int i; int j; double d;};int main(int argc, char *argv[]){ cout << sizeof(Parent) << endl;//12 cout << sizeof(ChildA) << endl;//24 cout << endl; cout << "Parent..." < (&parent); cout << "Member Variable Value:"<< endl; //虚函数表的首地址 cout << parenttest->vptr << endl;//编译时确定 cout << parenttest->i << endl;//1 cout << parenttest->j << endl;//2 cout << "Member Variable Address:" << endl; cout << &parenttest->vptr << endl; cout << &parenttest->i << endl; cout << &parenttest->j << endl; cout << endl; cout << "Child..." << endl; ChildA child(1,2,3.14); ChildTest* childtest = reinterpret_cast (&child); cout << "Member Variable Value:"<< endl; //虚函数表的首地址 cout << childtest->vptr << endl;//编译时确定 cout << childtest->i << endl;//1 cout << childtest->j << endl;//2 cout << childtest->d << endl;//3.14 cout << "Member Variable Address:" << endl; cout << &childtest->vptr << endl; cout << &childtest->i << endl; cout << &childtest->j << endl; cout << &childtest->d << endl; return 0;}
Parent、ChildA、ChildB三个类都有虚函数,C++编译器编译时会为每个类都创建一个虚函数表,即类Parent的虚函数表(Parent vtbl),类ChildA的虚函数表(ChildA vtbl),类ChildB的虚表(ChildB vtbl)。类Parent、ChildA、ChildB的对象都拥有一个虚函数表指针*vptr,用来指向自己所属类的虚函数表。
类Parent包括三个虚函数,Parent类的虚函数表包含三个指针,分别指向Parent::print()、Parent::sum()、Parent::display()三个虚函数函数。 类ChildA继承于类Parent,因此类ChildA可以调用父类Parent的函数,但类ChildA重写Parent::print()、Parent::sum()、Parent::display()三个虚函数,因此类ChildA 虚函数表的三个函数指针分别指向ChildA::print()、ChildA::sum()、ChildA::display()。 类ChildB继承于类Parent,因此类ChildB可以调用类Parent的函数,但由于类ChildB重写Parent::print()、Parent::sum()函数,类ChildB虚函数表有三个函数指针,第一个函数指针指向Parent::display()虚函数,第二个第三个依次指向ChildB::print()、ChildB::sum()虚函数。ChildA childA;Parent* p = &childA;
当定义一个ChildA类的对象childA时,childA对象包含一个虚函数表指针,指向ChildA类的虚函数表。
当定义一个Parent类的指针p指向childA对象时,p指针只能指向ChildA对象的父类Parent部分,但由于虚函数表指针位于对象存储空间的开始,因此p指针可以访问childA对象的虚函数表指针。由于childA对象的虚函数表指针指向ChildA类的虚函数表,因此p指针可以访问类ChildA的虚函数表。当使用指针调用print函数,程序在执行p->print()时,会发现p是个指针,且调用的函数是虚函数。 首先,根据虚函数表指针p->vptr来访问对象childA对应的虚函数表。然后,在虚函数表中查找所调用的虚函数对应的条目。由于虚函数表在编译阶段就生成,所以可以根据所调用的函数定位到虚函数表中的对应条目。对于 p->print()的调用,类ChildA虚函数表的第一项即是print函数指针对应的条目。 最后,根据虚函数表中找到的函数指针,调用函数ChildA::print()。Parent base;Parent* p = &base;p->print();
当base对象在创建时,base对象的虚函数表指针vptr已设置为指向Parent类的虚函数表,p->vptr指向Parent虚函数表。print在Parent虚函数表中相应的条目指向Parent::print()函数,所以 p->print()会调用Parent::print()函数。
虚函数的调用的三个步骤用表达式(*(p->vptr)[n])(p)可以概括。#includeusing namespace std;typedef void (*vfunc)();class Parent{public: vfunc print; Parent() { print = Parent::display; } static void display() { cout << "Parent::" << __func__<< endl; }};class Child : public Parent{public: Child() { print = Child::display; } static void display() { cout << "Child::" << __func__<< endl; }};int main(int argc, char *argv[]){ Parent parent; parent.print(); Child child; child.print(); Parent* p = &child; p->print(); return 0;}// output:// Parent::display// Child::display// Child::display
上述代码使用函数指针实现了多态,绕过了虚函数表,避免了虚函数表的性能损失。
由于在构造函数执行完后,类对象的虚函数表指针才被正确初始化。因此构造函数不能为虚函数。类对象中的虚函数表指针是在调用构造函数的时候完成初始化的。因此,在构造函数调用前,虚函数表指针还没有完成初始化,无法调用虚的构造函数。
在构造函数进入函数体前,进行虚函数表指针的初始化,将虚函数表指针初始化为当前类的虚函数表地址,即在基类调用构造函数的时候,会把基类的虚函数表地址赋值给虚函数表指针,而如果进执行到子类的构造函数时,把子类的虚函数表地址赋值给虚函数表指针。因此,在派生类对象的构造时,虚函数表指针指向的虚函数表地址是动态变化的。#includeusing namespace std;class Parent{public: Parent(int i, int j) { m_i = i; m_j = j; cout << "Parent(int i, int j): " << this << endl; //虚函数表指针 int* vptr = (int*)*((int*)this); cout << "vptr: " << vptr << endl; } virtual void print() { cout << "Parent::" << __func__<< endl; cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; } virtual ~Parent() { cout << "~Parent(): " << this << endl; }protected: int m_i; int m_j;};class Child : public Parent{public: Child(int i, int j, double d):Parent(i, j) { m_d = d; cout << "Child(int i, int j, double d): " << this << endl; //虚函数表指针 int* vptr = (int*)*((int*)this); cout << "vptr: " << vptr << endl; } virtual void print() { cout << "Child::" << __func__<< endl; cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; cout << "m_d = "<< m_d << endl; } ~Child() { cout << "~Child(): " << this < print(); delete p; return 0;}
析构函数可以为虚函数,可以发生多态。工程实践中,如果基类中有虚成员函数,建议将析构函数声明为虚函数,确保对象销毁时触发正确的析构函数调用,保证资源的正确回收。
#includeusing namespace std;class Parent{public: Parent(int i, int j) { m_i = i; m_j = j; cout << "Parent(int i, int j)" << endl; } virtual void print() { cout << "Parent::" << __func__<< endl; cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; } virtual ~Parent() { cout << "~Parent()" << endl; }protected: int m_i; int m_j;};class Child : public Parent{public: Child(int i, int j, double d):Parent(i, j) { m_d = d; cout << "Child(int i, int j, double d)" << endl; } virtual void print() { cout << "Child::" << __func__<< endl; cout << "m_i = "<< m_i << endl; cout << "m_j = "<< m_j << endl; cout << "m_d = "<< m_d << endl; } ~Child() { cout << "~Child()" < print(); delete p; return 0;}
在调用基类的构造函数时,其虚函数表指针指向的是基类的虚函数表,而在调用派生类的构造函数时,其虚函数表指针指向的是派生类的虚函数表。因此,构造函数内不能发生多态行为。
在调用派生类的析构函数时,其虚函数表指针指向的是派生类的虚函数表;在调用基类的析构函数时,其虚函数表指针指向的是基类的虚函数表,并且派生类的虚函数表已经被销毁。
转载于:https://blog.51cto.com/9291927/2148687