两种成员变量:non_static、static;三种成员函数:non_static、static、virtual。
所有非静态数据成员的大小;
由内存对齐而填补的内存大小;
为支持virtual成员产生的额外负担,即虚函数表。
每个含有虚函数的类,其产生的对象都含有4bytes的虚函数表指针,用来指向虚函数地址。
总结:对象的地址是类中第一个非静态成员变量的地址,空类也占用1bytes(编译器隐含的增加1byte的占位成员)主因是C++要求每个对象实例在内存中都要有独一无二的地址。
用类去定义对象时,系统会为每一个对象分配存储空间。
每个对象所占用的存储空间只是该对象的数据部分(成员变量(静态变量除外)、虚函数表指针(4字节)和虚基类表指针也属于数据部分)所占用的存储空间,而不包括函数代码所占用的存储空间。
普通成员函数和静态成员函数,都不占用对象的存储空间,都存储在代码区。
在编译器处理后,成员变量和成员函数是分离的,成员函数的大小不在类的对象里面,且被多个对象共享。
优点:这种成员函数和成员变量分离的设计,存取效率高。
类的非静态类成员函数都隐含了一个指向类对象的指针型参数(即this指针),因而只有类对象才能调用(此时this指针有实值)。
类内部的成员变量:
类内部的成员函数:
实例化不同对象时,只给数据分配空间,各个对象调用函数时都跳转到(内联函数例外)函数在代码区的入口执行,可以节省拷贝多份代码的空间。
内联函数(声明和定义都要加inline)也是存放在代码区:
成员变量的内存地址:成员变量拥有真正的内存地址,可通过一个指针来访问和修改成员变量的值。
成员变量指针:
&A::m_val
,可以用来打印成员变量的偏移量。
int A::*ptr_a = &A::m_val
,成员变量的指针变量ptr_a中保存的只是成员变量的偏移值。
成员变量指针、不指向任何成员变量的成员变量指针。
0x00000000
。0xcccccccc
。0xffffffff
。每一个虚函数表前都有一个指针指向type_info,负责对RTTI(runtime type interpret)的支持。
虚函数表:含有虚函数或其父类含有虚函数的类,编译器都会为其添加一个虚函数表vptr
(在类对象内存空间会有一个指针指向该虚函数表vptr),用来存放虚函数的地址。注:每个虚函数表末尾为nullptr。
虚基类表:虚继承产生虚基类表vbptr
。
第一个条目:虚基类表指针 vbptr 所在地址,相对于该类内存首地址的偏移值。
第二、第三…个条目:依次为该类的最左虚继承父类、次左虚继承父类…的vptr 或 成员变量的位置,相对于虚基类表指针的偏移值。
#include
using namespace std;
class Base
{
public:
Base(int i) : baseI(i) { cout << "Base::Base(int i)" << endl; };
virtual ~Base() { cout << "virtual ~Base()" << endl; }
virtual void print(void) { cout << "virtual void Base::print()" << endl; }
int getl() { return baseI; }
static int countl() { return (++baseS); }
private:
int baseI;
static int baseS;
};
int Base::baseS = 0;
int main()
{
Base b(1000);
long* vptrAddress = (long*)(&b);
cout << "虚函数指针(vptr)的地址是:" << vptrAddress << endl;
cout << "通过Base类对象b,找到虚函数表存放的第一个虚函数Base::~Base()的地址:" << (long *)(*(long*)(&b)) << endl;
typedef void(*Fun)(void);
Fun virfunc_2 = (Fun)(*((long*)((long*)(*(long*)(&b)) + 1)));
cout << "第二个虚函数的地址是:" << (long*)((long*)(*(long*)(&b)) + 1) << endl;
cout << "通过Base类对象b,找到虚函数表存放的虚函数Base::setl()的地址并调用:";
virfunc_2();
return 0;
}
#include
using namespace std;
class Base
{
public:
Base(int i) : baseI(i) { cout << "Base::Base(int i)" << endl; };
virtual void print() { cout << "virtual void Base::print()" << endl; }
virtual ~Base() { cout << "virtual ~Base()" << endl; }
int getl() { return baseI; }
static int countl() { return (++baseS); }
private:
int baseI;
static int baseS;
};
int Base::baseS = 0;
class Derive : public Base
{
public:
Derive(int d) : Base(1000), DeriveI(d) { cout << "Derive::Derive(int i)" << endl; };
// overwrite父类虚函数:(override用来指明,这个虚函数是对父类虚函数的重写)
virtual void print() override { cout << "virtual void Derive::print()" << endl; }
// Derive声明的新的虚函数:
virtual void Derive_print() { cout << "virtual void Derive::Derive_print()" << endl; }
virtual ~Derive() { cout << "virtual ~Derive()" << endl; }
private:
int DeriveI;
};
int main()
{
typedef void(*Fun)(void);
Derive d(2000);
// --[0]
{
cout << "[0] Base::vptr\t地址:\t" << (long*)(&d) << endl;
// -vprt[0]
cout << " [0]\t地址:\t" << *((long*)*((long*)(&d))) << "\t==>\t";
Fun fun_0 = (Fun)(*((long*)*((long*)(&d))));
fun_0();
// -vprt[1]析构函数无法通过地址调用(编译器正常调用析构函数时,会传入this指针,这里直接调用并没有传入this指针),故手动输出
cout << " [1]\t" << "virtual Derive::~Derive()" << endl;
// -vprt[2]
cout << " [2]\t地址:\t" << *((long*)*((long*)(&d)) + 2) << "\t==>\t";
Fun fun_2 = (Fun)(*((long*)*((long*)(&d)) + 2));
fun_2();
}
// --[1]
{
cout << "[1] Base::baseI\t地址:\t" << (long*)(&d) + 1 << "\t:\t" << *(long*)((long*)(&d) + 1) << endl;
}
// --[2]
{
cout << "[2] Derive::DeriveI\t地址:\t" << (long*)(&d) + 2 << "\t:\t" << *(long*)((long*)(&d) + 2) << endl;
}
return 0;
}
分析子类overwrite父类虚函数、子类定义了新的虚函数,子类对象的内存布局。
非菱形继承:
#include
using namespace std;
class Base1
{
public:
Base1(int i) :base1I(i) { cout << "Base1::Base1(int i)" << endl; }
virtual ~Base1() { cout << "virtual Base1::~Base1()" << endl; }
virtual void print(void) { cout << "virtual void Base1::print()" << endl; }
private:
int base1I;
};
class Base2
{
public:
Base2(int i) :base2I(i) { cout << "Base2::Base2(int i)" << endl; };
virtual ~Base2() { cout << "virtual Base2::~Base2()" << endl; }
virtual void print(void) { cout << "virtual void Base2::print()" << endl; }
virtual void others() { cout << "virtual void Base2::others()" << endl; }
private:
int base2I;
};
class Derive_multiBase :public Base1, public Base2
{
public:
Derive_multiBase(int d) :Base1(1000), Base2(2000), derive_multiBaseI(d)
{ cout << "Derive_multiBase::Derive_multiBase(int d)" << endl; };
virtual ~Derive_multiBase() { cout << "virtual Derive_multiBase::~Derive_multiBase()" << endl; }
virtual void print(void) override { cout << "virtual void Derive_multiBase::print" << endl; }
virtual void Derive_print() { cout << "virtual void Derive_multiBase::Derive_print" << endl; }
private:
int derive_multiBaseI;
};
int main()
{
typedef void(*Fun)(void);
Derive_multiBase d(3000);
cout << "........................................." << endl;
// --Base1::
{
cout << "[0] Base1::vptr\t地址: " << (long*)(&d) << endl;
// -vptr[0]析构函数无法通过地址调用(调用构造/析构函数时,编译器会自动插入一个this指针,用作初始化/释放),故手动输出
cout << "\t[0] " << "virtual Derive_multiBase::~Derive_multiBase()" << endl;
// -vptr[1]
cout << "\t[1] 地址: " << *((long*)(*(long*)(&d)) + 1) << " ";
Fun fun_1 = (Fun)(*((int *)*((long*)(&d)) + 1));
fun_1();
// -vptr[2]
cout << "\t[2] 地址: " << *((long*)(*((long*)(&d))) + 2) << " ";
Fun fun_2 = (Fun)*((long*)*((long*)(&d)) + 2);
fun_2();
// Base1::base1I
cout << "Base1::base1I: " << *(long*)((long*)(&d) + 1) << "\t地址: " << (long*)(&d) + 1 << endl;
}
// --Base2::
{
cout << "[2] Base2::vptr\t地址: " << (long*)(&d) + 2 << endl;
// vptr[0]析构函数无法通过地址调用,故手动输出
cout << "\t[0] " << "virtual Derive_multiBase::~Derive_multiBase()" << endl;
// vptr[1]
cout << "\t[1] 地址: " << *(long*)((long*)(*((long*)(&d) + 2)) + 1) << " ";
Fun fun_1 = (Fun)*(long*)((long*)(*((long*)(&d) + 2)) + 1);
fun_1();
// vptr[2]
cout << "\t[1] 地址: " << *(long*)((long*)(*((long*)(&d) + 2)) + 2) << " ";
Fun fun_2 = (Fun)*(long*)((long*)(*((long*)(&d) + 2)) + 2);
fun_2();
cout << "Base2::base2I: " << *(long*)((long*)(&d) + 3) << "\t地址: " << (long*)(&d) + 3 << endl;
}
// --Derive_multiBase::
{
cout << "Derive_multiBase::derive_multiBaseI: " << *(long*)((long*)(&d) + 4) << "\t地址: " << (long*)(&d) + 4 << endl;
}
cout << "........................................." << endl;
return 0;
}
菱形继承:
通过虚继承来解决,因孙子类中存在两个相同的祖先类,导致“二义性”的问题(需要通过引入“虚继承”来解决)。
虚继承而来的子类,会生成一个隐藏的虚基类指针(vbptr
)。
简单虚继承:
#include
using namespace std;
class Base
{
public:
Base(int i = 1) :iBase(i) {}
virtual void f_1() { cout << "Base::f_1()" << endl; }
virtual void Base_f() { cout << "Base::Base_f()" << endl; }
private:
int iBase;
};
class Derive : virtual public Base
{
public:
Derive(int i = 20) : Base(10), iDerive(i) {}
virtual void f_1() override { cout << "Derive::f_1()" << endl; }
virtual void f_2() { cout << "Derive::f_2()" << endl; }
virtual void Derive_f() { cout << "Derive::Derive_f()" << endl; }
private:
int iDerive;
};
int main()
{
typedef void(*Func)(void);
Derive derive;
cout << "Derive对象内存大小为:" << sizeof(derive) << endl;
// --[0]
{
// --取得Derive的虚函数表指针所在的地址:
cout << "[0] Derive::vptr的地址: " << (long*)(&derive) << endl;
// 通过虚函数表指针,调用Derive::vptr中的虚函数:
for (int i = 0; i < 2; ++i)
{
cout << "\t[" << i << "] 地址: " << *((long*)(*(long*)(&derive)) + i) << "\t";
Func fun = (Func)(*((long*)(*(long*)(&derive)) + i));
fun();
}
}
// --[1]
{
cout << "[1] vbptr的地址:" << (long*)(&derive) + 1 << endl; //虚表指针的地址
// 输出虚基类指针条目所指的内容
string implications[2] = { "the offset from current position to the first address of object", "the offset from current position to parent class's vptr" };
for (int i = 0; i < 2; i++)
{
cout << "\t[" << i << "] " << implications[i] << ": " << *(long*)((long*)(*((long*)(&derive) + 1)) + i) << endl;
}
}
// --[2]
{
cout << "[2] Derive::iDerive的地址: " << (long*)(&derive) + 2 << "\t" << *(long*)((long*)(&derive) + 2) << endl;
}
// --[3]
{
cout << "[3] 地址: " << (long*)(&derive) + 3 << "\t值=" << *(long*)((long*)(&derive) + 3) << endl;
}
// --[4]
{
cout << "[4] Base::vptr的地址:" << (long*)(&derive) + 4 << endl;
//输出Base::vptr中的虚函数
for (int i = 0; i < 2; ++i)
{
cout << "\t[" << i << "]\t地址: " << *((int *)(*((int *)(&derive) + 4)) + i) << "\t";
Func fun = (Func)(*((int *)(*((int *)(&derive) + 4)) + i));
fun();
}
}
// --[5]
{
cout << "[5] Base::iBase的地址: " << (int *)(&derive) + 5 << "\t" << *(int*)((int *)(&derive) + 5) << endl;
}
return 0;
}
菱形虚继承:
#include
using namespace std;
class B
{
public:
B(int i = 10) :ib(i) {}
virtual void f() { cout << "B::f()" << endl; }
virtual void Bf() { cout << "B::Bf()" << endl; }
private:
int ib;
};
class B1 : virtual public B
{
public:
B1(int i = 100) :ib1(i) {}
virtual void f() override { cout << "B1::f()" << endl; }
virtual void f1() { cout << "B1::f1()" << endl; }
virtual void Bf1() { cout << "B1::Bf1()" << endl; }
private:
int ib1;
};
class B2 : virtual public B
{
public:
B2(int i = 1000) :ib2(i) {}
virtual void f() override { cout << "B2::f()" << endl; }
virtual void f2() { cout << "B2::f2()" << endl; }
virtual void Bf2() { cout << "B2::Bf2()" << endl; }
public:
int ib2;
};
class D : public B1, public B2
{
public:
D(int i = 10000) :id(i) {}
virtual void f() override { cout << "D::f()" << endl; }
virtual void f1() override { cout << "D::f1()" << endl; }
virtual void f2() override { cout << "D::f2()" << endl; }
virtual void Df() { cout << "D::Df()" << endl; }
private:
int id;
};
int main()
{
typedef void(*Fun)(void);
D d;
cout << "D对象内存大小为:" << sizeof(d) << endl;
// ---B1
{
// --[0]
{
// B1的虚函数表指针的地址:
cout << "[0] B1::vptr的地址: " << (long*)(&d) << endl;
// 虚函数表指针B1::vptr指向的虚函数表中的虚函数的地址,并调用:
for (int i = 0; i < 3; ++i)
{
cout << "\t[" << i << "] 地址:\t" << *((long*)(*(long*)(&d)) + i) << "\t";
Fun fun = (Fun)(*((long*)(*(long*)(&d)) + i));
fun();
}
}
// --[1]
{
// B1虚基类表指针的地址:
cout << "[1] B1::vbptr的地址: " << (long*)(&d) + 1 << endl;
// B1虚基类指针指向的虚基类表中,条目所指的内容:
long** vftab = (long**)(&d);
for (int i = 0; i < 2; i++)
{
//等价于cout << "\t[" << i << "]\t" << *(long*)((long*)(*((long*)(&d) + 1)) + i) << endl;
cout << "\t[" << i << "]\t" << vftab[1][i] << endl;
}
}
// --[2]
{
cout << "[2] B1::ib1的地址: " << (long*)(&d) + 2 << "\t" << *(long*)((long*)(&d) + 2) << endl;
}
}
// ---B2
{
// --[3]
{
// B2的虚函数表指针的地址:
cout << "[3] B2::vptr的地址:" << (long*)(&d) + 3 << endl;
// 虚函数表指针B2::vptr指向的虚函数表中的虚函数的地址,并调用:
for (int i = 0; i < 2; ++i)
{
cout << "\t[" << i << "] 地址:\t" << *((long*)(*((long*)(&d) + 3)) + i) << "\t";
Fun fun = (Fun)*((long*)(*((long*)(&d) + 3)) + i);
fun();
}
}
// --[4]
{
// B2虚基类表指针的地址:
cout << "[4] B2::vbptr的地址:\t" << (long*)(&d) + 4 << endl;
// B2虚基类指针指向的虚基类表中,条目所指的内容:
for (int i = 0; i < 2; i++)
{
cout << "\t[" << i << "]\t" << *(long*)((long*)(*((long*)(&d) + 4)) + i) << endl;
}
}
// --[5]
{
cout << "[2] B2::ib2的地址: " << (long*)(&d) + 5 << "\t" << *(long*)((long*)(&d) + 5) << endl;
}
}
// ---D
{
// --[6]
cout << "[6] D::id的地址: " << (long*)(&d) + 6 << "\t" << *(long*)((long*)(&d) + 6) << endl;
}
// 0x00000000
{
// --[7]
cout << "[7] 地址: " << (long*)(&d) + 7 << "\t值=" << *(long*)((long*)(&d) + 7) << endl;
}
// ---B间接父类(祖先类)
{
// --[8]
{
// B的虚函数表指针的地址:
cout << "[8] B::vptr的地址: " << (long*)(&d) + 8 << endl;
// 虚函数表指针B::vptr指向的虚函数表中的虚函数的地址,并调用:
for (int i = 0; i < 2; ++i)
{
cout << "\t[" << i << "] 地址:\t" << *((long*)(*((long*)(&d) + 8)) + i) << "\t";
Fun fun = (Fun)*((long*)(*((long*)(&d) + 8)) + i);
fun();
}
}
// --[9]
{
cout << "[9] B::id地址: " << (long*)(&d) + 9 << "\t" << *(long*)((long*)(&d) + 9) << endl;
}
}
return 0;
}
参考文章1、参考文章2。
(每个含有虚函数的类均有一个虚函数表,但多继承中子类的虚函数表用的是继承顺序中首个继承的类的虚函数表,且根据继承的父类的个数含有不同的虚函数表指针) 。
(每个含有虚函数的类的类对象都有一个虚函数表指针,但指向同一个虚函数表)。
vptr
:用于指向虚函数表的首地址,且该类的每个对象都会增加 4字节或者8字节 用来存放虚函数指针(可以看作隐藏的成员变量,属于类对象)。当类对象要调用虚函数时,会通过虚函数表指针找到类的虚函数表,通过类的虚函数表就能够调用类的虚函数。
#include
using namespace std;
class Base
{
public:
virtual void func1() { cout << "Base::func1()" << endl; }
virtual void func2() { cout << "Base::func2()" << endl; }
virtual void func3() { cout << "Base::func3()" << endl; }
};
class Derive : public Base
{
public:
virtual void func3() override { cout << "Derive::func3()" << endl; }
};
int main()
{
/* 手动调用子类的虚函数表中的虚函数: */
Derive* derive = new Derive();
long* derive_first_ptr = (long*)(derive);
// 将*derive_vftable_ptr的值转换为16进制,即虚函数表指针指向的位置
long* derive_vftable_ptr = (long*)(*derive_first_ptr);
cout << "derive_first_address = " << derive << endl;
cout << "derive_first_address = " << derive_first_ptr << endl;
printf("derive_vftable_ptr = %p\n", *derive_vftable_ptr);
for (int i = 0; i < 3; ++i)
{
printf("derive_vftable_ptr[%d] = %p\n", i, derive_vftable_ptr[i]);
}
// 定义一个函数的指针类型:
typedef void(*Func)(void);
// 依次调用Derive类对象derive的虚函数表中的虚函数
for (int i = 0; i < 3; ++i)
{
Func f = (Func)(derive_vftable_ptr[i]);
f();
}
cout << "..........................\n";
/* 手动调用父类的虚函数表中的虚函数: */
Base* base = new Base();
long* base_first_ptr = (long*)(base);
long* base_vftable_ptr = (long*)(*base_first_ptr);
cout << "base_first_address = " << base << endl;
cout << "base_first_address = " << base_first_ptr << endl;
printf("base_vftable_ptr = %p\n", *base_vftable_ptr);
for (int i = 0; i < 3; ++i)
{
printf("base_vftable_ptr[%d] = %p\n", i, base_vftable_ptr[i]);
}
// 定义一个函数的指针类型:
typedef void(*Func)(void);
// 依次调用Derive类对象derive的虚函数表中的虚函数
for (int i = 0; i < 3; ++i)
{
Func f = (Func)(base_vftable_ptr[i]);
f();
}
return 0;
}
主要是为了支持面向对象程序设计时的三大特性之一:多态性。
创建过程:
虚函数表在可执行程序中的位置:
可执行程序被载入到内存中时的内存结构(从高地址到低地址),包含了栈区(栈区内存向下增长)、堆区(堆区内存向上增长)、数据段、代码段。
静态联编:编译的时候,就能确定调用哪个函数,并把调用语句和被调用函数绑定在一起。
动态联编:程序运行的时候,根据实际情况,动态的把调用语句和被调用语句绑定在一起(一般出现在多态和虚函数情况下,虚函数、多态:专门给指针和引用来使用的)。
某些情况下, 编译器会在类中增加隐形成员变量(比如有虚函数,该变量会用来存放虚函数表指针),这会导致一个类变得不单纯。
这种隐藏的成员变量的赋值时机,往往是在执行构造函数或者拷贝构造函数的函数体之前。
在栈中生成一个该类的局部对象,通过该对象能直接调用虚函数,并且能正常析构掉该对象(因为静态联编,即编译时期就已经确认了函数调用的位置)。
对象指针指向堆中new一个该类对象实例,通过该对象指针调用虚函数或者delete指针,实际上需要查找“虚函数表指针-虚函数表-虚函数”(因为动态联编,即运行时动态匹配,一般在多态或类中含有虚函数时出现)。
引用栈区的对象实例,并进行虚函数的调用,也属于动态联编,故也需要查找“虚函数表指针-虚函数表-虚函数”。
注意:构造函数 和 拷贝构造函数中,尽可能不要使用 memset()
和 memcpy()
这两个函数。
#include
#include
using namespace std;
class A
{
public:
A()
{
memset(this, 0, sizeof(A)); // 即此种操作会将this对象指向的内存空间中,虚函数表指针清空
// ,导致在“动态联编”时,无法找到虚函数表,进而也不会找到虚函数
cout << "A::A()" << endl;
}
A(const A* a)
{
memcpy(this, a, sizeof(A));
cout << "A::A(const A& a)" << endl;
}
virtual ~A()
{
cout << "virtual A::~A()" << endl;
}
virtual void virtual_ptfunc()
{
cout << "virtal A::ptfunc()" << endl;
}
};
int main()
{
// 静态联编:编译时就确定了函数的调用位置
A a;
a.virtual_ptfunc();
// 动态联编:类对象隐含的虚函数表指针在构造函数执行前以初始化,但构造函数中清空了类对象的内存空间,故指针/引用的类对象无法在运行时动态通过“虚函数表指针 --> 虚函数表 --> 虚函数”完成调用
//A* aPtr = new A();
//aPtr->virtual_ptfunc();
//delete aPtr;
//A& a_cite1 = a;
//a_cite1.virtual_ptfunc;
//A& a_cite2 = *aPtr;
//a_cite2.virtual_ptfunc;
return 0;
}
实质:找到虚函数表中虚函数的地址进行调用的。
A aObj;
(reinterpret_cast<(void*)()>(**(int**)(&aObj)))();
// 本质:找到虚函数表中虚函数的地址并调用
long* objVptr = (long*)(&aobj); // 类对象的首地址,即虚函数表指针所在的地址
long* vptr = (long*)(*objVptr); // 虚函数表的首地址
typedef void(*Func)(void); // 定义函数指针类型
Func func = (Func)(vptr[0]); func(); // 给函数指针赋值为第一个虚函数的地址,并调用该虚函数
注意:类对象的私有成员变量,都可以在类外通过访问类对象的内存直接访问,但由于不同平台/编译器对类对象内存布局可能不同(影响代码的移植问题),可能引发意料不到的问题。公开成员函数/友元函数可以实现类外对私有有成员的访问,但这极大的破坏了封装性,需要具体结合实际情况。
总结:本质,由于“父子类构造和析构的顺序(父类先构造后析构)”问题带来的,如果强行调用,则会失去虚函数的作用。
namespace _nmsp
{
class A
{
public:
int a;
public:
A()
{
cout << "A::A()的this指针的地址:" << this << endl;
}
void funcA()
{
cout << "A::funcA()的this指针的地址:" << this << endl;
}
};
class B
{
public:
int b;
public:
B()
{
cout << "B::B()的this指针的地址:" << this << endl;
}
void funcB()
{
cout << "B::funcB()的this指针的地址:" << this << endl;
}
};
class C : public A, public B
{
public:
int c;
public:
C()
{
cout << "C::C()的this指针的地址:" << this << endl;
}
void funcB() // C类中的funcB()函数,覆盖了B类的同名函数funcB()
{
cout << "C::funcB()的this指针的地址:" << this << endl;
}
void funcC()
{
cout << "C::funcC()的this指针的地址:" << this << endl;
}
};
void test()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
cout << sizeof(C) << endl;
// 构造函数的调用顺序,按照继承的顺序,先基类后派生类
C myc;
myc.funcA();
myc.funcB();
myc.B::funcB();
myc.funcC();
}
// 执行test()后,终端输出:
//4
//4
//12
//A::A()的this指针的地址:0000005CA7CFF608
//B::B()的this指针的地址:0000005CA7CFF60C
//C::C()的this指针的地址:0000005CA7CFF608
//A::funcA()的this指针的地址:0000005CA7CFF608
//C::funcB()的this指针的地址:0000005CA7CFF608
//B::funcB()的this指针的地址:0000005CA7CFF60C
//C::funcC()的this指针的地址:0000005CA7CFF608
}
默认构造函数(缺省构造函数),生成类对象时,如果类中没有默认构造函数,编译器会隐式的生成一个“合成默认构造函数”。
编译器生成“合成默认构造函数”的情况:
拷贝构造函数,在类对象拷贝赋值时,如果类中没有拷贝构造函数,编译器会隐式的生成一个“合成拷贝构造函数”(编译器内部实现)。
调用拷贝构造函数时,原有的成员变量的值不会被改变,只是值的拷贝而已。
编译器生成“合成拷贝构造函数”的情况:
vbtable
赋值,并将两个直接基类的虚基类表vbtable
赋值到派生类的对象内存空间));细节问题:
如果只是成员变量的赋值,编译器并不会生成拷贝构造函数,内部直接进行赋值。
“合成拷贝构造函数”,一般是用来做一些特殊的事情(调用父类的拷贝构造函数、给类对象虚函数表指针赋值)。
只有在满足“合成拷贝构造函数”的前提下,激发拷贝动作编译器才会合成拷贝构造函数。
程序员,如果在类中增加了拷贝构造函数后,就要对类中各个成员变量的初始化负责。因在加入自定义的拷贝构造函数后,编译器内部的bitwise
按位复制能力会失效。
深、浅拷贝的问题(类中含有的成员变量包含指针变量或者其他非内置类类型):
在自定义拷贝构造函数时,为了避免“自我拷贝”,需要进行判断。
只有一个类中,没有定义任何自己版本的拷贝构造函数、拷贝赋值运算符、析构函数,且类的每个非静态变量都是可以移动时,编译器才会合成移动语义(移动构造函数 或者 移动赋值运算符)。
变量是否是可以移动的:
程序员的角度看代码 —> 编译器的角度理解代码。
定义时,初始化一个对象。
class A
{
public:
A() { m_i = 0; }
A(const A &tmpA) { m_i = tmpA.m_i; }
public:
int m_i;
};
int main()
{
A a1;
a1.m_i = 1;
A a2 = a1;
// 等价于A a2(a1)。编译器内部会分成两步:(只会调用拷贝构造函数,并不会调用默认构造函数)
// A a2;
// a2.A::A(a1);
return 0;
}
参数的初始化。
class A
{
public:
A() { cout << "默认构造函数" <<endl; }
~ A() { cout << "析构函数" <<endl; }
A(const A& tmpA) { cout << "拷贝构造函数" <<endl; }
void func(A tmpa) { cout << "void func(A tmpa)" << endl; return; }
};
int main()
{
A a;
// 编译器在函数func()的空间中,构造了一个tmpa类对象,并在func()调用完成后析构掉
func(a);
return 0;
]
返回值初始化。
class A
{
public:
A(int val) : m_val(val) {}
// 优化前,调用了构造函数、拷贝构造函数(将a对象拷贝给临时对象)、两次析构函数
/*
该函数内部调用了构造函数得到了类对象a,然后将a对象拷贝给临时对象temp_a并析构掉类对象a。最终函数返回临时对象temp_a给m_a。
如果函数返回的临时对象temp_a没有接收者,则会在函数返回后,立即析构掉这个临时对象。
*/
A func()
{
A a;
return a;
}
// 编译器优化后,仅调用了构造函数!!!
void func(A& tmp_a)
{
A a;
tmp_a.A::A(a.m_val);
return;
}
public:
int m_val;
};
int main()
{
A m_a = func();
// 编译器视角:
// A m_a;
// func(m_a);
return 0;
}
class A
{
public:
A func(const A& a)
{
A tmp_a;
tmp_a.m_a1 = a.m_a1;
tmp_a.m_a2 = a.m_a2;
return tmp_a;
}
// 优化后:
A func_(const A& a)
{
return A(a.m_a1, a.m_a2);
}
// 编译器视角,看待优化后的代码!!!
void func_2(const A& tmp_a, const A& a)
{
tmp_a.A::A(a.m_a1, a.m_a2);
return;
}
private:
int m_a1;
int m_a2;
};
编译器在成员函数中,优先找类内定义的成员变量;全局函数中,优先找全局范围的变量。
编译器对成员函数的解析,在整个类定义完毕之后,才开始。
成员函数的参数类型:类定义语句(typedef、using … 等),根据“最近碰到的原则”(程序从上到下),故一般在类中最开始位置。
#include
using namespace std;
typedef string mytype;
class test1
{
public:
// “最近碰到的原则”,故这里的mytype==string
void func(mytype myVar_)
{
myVar = myVar_; // 报错,因myVar是int,而myVar_是string
}
private:
typedef int mytype;
mytype myVar;
};
class test2
{
private:
/* 建议将typedef、using...等信息,置于类的最前面 */
typedef int mytype;
mytype myVar;
public:
void func(mytype myVar_)
{
myVar = myVar_; // 不报错,myVar、myVar_均是int
}
};
内存中存储的数据,都是按照成员变量的定义顺序来存储的;
静态成员变量存放在数据段,一旦生成可执行文件,地址就是固定的;
非静成员态变量,根据类对象而定,且类对象所占用的内存是一块连续的内存(栈/堆区);
A a; // 类对象中的数据在栈上
A *a = new A(); // 对象指针指向的是堆区,即类对象的数据在堆区
边界调整(目的是提高程序运行的效率,编译器自动去做),会在成员变量之间填补一些字节,使类对象的sizeof是4/8的倍数(这样会导致数据存储时不是紧密排列的);
实现各个成员变量在内存空间中紧密的排列:
#pragma pack(1) // 进行“1字节对齐”
... // 需要进行“1字节对齐”的类
#pragma back() // 取消“1字节对齐”
多重继承中,由于层次结构的存在,导致在边界调整后,子类占用的内存空间变大;
#include
using namespace std;
class Base
{
public:
int m_base1;
char m_base2;
};
class Base_ : public Base
{
public:
char m_base_;
};
class Derived : public Base_
{
public:
char m_derived;
};
class ptClass
{
public:
int m_pta;
char m_ptb;
char m_ptc;
char m_ptd;
};
int main()
{
printf("sizeof(ptClass) = %d\n", sizeof(ptClass));
/* 多重继承中,存在的层次结构,扩大了类占用的空间 */
printf("sizeof(Base) = %d\n", sizeof(Base));
printf("sizeof(Base_) = %d\n", sizeof(Base_));
printf("sizeof(Derived) = %d\n", sizeof(Derived));
cout << ".......... Base的内存分布 ..........." << endl;
printf("m_base1 = %d\n", &Base::m_base1);
printf("m_base2 = %d\n", &Base::m_base2);
cout << ".......... Base_的内存分布 ..........." << endl;
printf("m_base1 = %d\n", &Base_::m_base1);
printf("m_base2 = %d\n", &Base_::m_base2);
printf("m_base_ = %d\n", &Base_::m_base_);
cout << ".......... Derived的内存分布 ..........." << endl;
printf("m_base1 = %d\n", &Derived::m_base1);
printf("m_base2 = %d\n", &Derived::m_base2);
printf("m_base_ = %d\n", &Derived::m_base_);
printf("m_derived = %d\n", &Derived::m_derived);
return 0;
}
&A::m_val
(而且要用printf来打印输出)、直接使用&m_val,输出的是成员变量的物理地址。#include
using namespace std;
// 可以尝试给类,取消/添加“#pragma pack(1) ... #pragma pack()”,来观察各个变量的地址和偏移量的变化
#pragma pack(1)
class A
{
public:
int m_a;
char m_b;
int m_c;
static int m_sa;
static char m_sb;
static int m_sc;
};
int A::m_sa = 0;
char A::m_sb = 'i';
int A::m_sc = 0;
#pragma pack()
int main()
{
A a;
/* 打印对象a中,各个变量的物理地址: */
printf("A::m_a = %p\n", &a.m_a);
printf("A::m_b = %p\n", &a.m_b);
printf("A::m_c = %p\n", &a.m_c);
printf("A::m_sa = %p\n", &a.m_sa);
printf("A::m_sb = %p\n", &a.m_sb);
printf("A::m_sc = %p\n", &a.m_sc);
/* 打印对象a中,各个变量的地址偏移量(相对于对象的首地址而言): */
printf("A::m_a = %d\n", &A::m_a);
printf("A::m_b = %d\n", &A::m_b);
printf("A::m_c = %d\n", &A::m_c);
return 0;
}
静态成员变量(不在类对象中保存,而在内存空间的数据段(可执行文件一旦形成,物理地址是固定的)):
// 编译器会自动将类名和变量名,结合成一个新的名字;可以避免不同的类中存在相同的静态变量名而出现错误
类名::变量名
对象名.变量名
对象指针名->变量名
非静态/普通成员变量(存放在对象中,故存取形式要根据类对象的定义方式变化)的存取:
类名 类对象名; 对象名.变量名;
类名 *对象指针名 = new 类名(); 对象指针名->变量名;
在类中,
this指针
(对象本身),并通过“this指针 + 成员变量的偏移值”来修改成员变量的值。一个子类中对象中包含的内容:(父类成员 + 自己的成员)
#include
using namespace std;
class Base_nonVirtualTablePtr
{
public:
Base_nonVirtualTablePtr()
{
printf("Base_nonVirtualTablePtr对象的this指针指向的地址:%p\n", this);
}
int m_a;
};
class Derived1 : public Base_nonVirtualTablePtr
{
public:
Derived1()
{
printf("Derived1对象的this指针指向的地址:%p\n", this);
}
virtual ~Derived1() {};
int m_b;
int m_c;
};
class Base_virtualTablePtr
{
public:
Base_virtualTablePtr()
{
printf("Base_noVirtualTablePtr对象的this指针指向的地址:%p\n", this);
}
int m_a;
virtual ~Base_virtualTablePtr() {}
};
class Derived2 : public Base_virtualTablePtr
{
public:
Derived2()
{
printf("Derived1对象的this指针指向的地址:%p\n", this);
}
int m_b;
int m_c;
};
int main()
{
/* Linux下g++测试结果: */
/* 单一继承父类不带虚函数表指针 */
Derived1 son1;
/*
Base_noVirtualTablePtr对象的this指针指向的地址:0x7ffeed977618
Derived1对象的this指针指向的地址:0x7ffeed977610
*/
printf("m_a = %d\n", &Base_noVirtualTablePtr::m_a); // 0
printf("m_b = %d\n", &Derived1::m_b); // 4 + 8(虚函数表指针) ==> 12
printf("m_c = %d\n", &Derived1::m_c); // 16
cout << "..............." << endl;
/* 单一继承父类带虚函数表指针 */
Derived2 son2;
/*
Base_virtualTablePtr对象的this指针指向的地址:0x7ffeed9775f0
Derived2对象的this指针指向的地址:0x7ffeed9775f0
*/
printf("m_a = %d\n", &Base_virtualTablePtr::m_a); // 0 + 8(虚函数表指针) ==> 8
printf("m_b = %d\n", &Derived2::m_b); // 12
printf("m_c = %d\n", &Derived2::m_c); // 16
// 结论:尽管单一继承下父类带/不带虚函数,会影响偏移量,但子类对象的数据在内存中的分布是相同的
return 0;
}
成员变量的定位:
单一继承虚函数的调用:只需确认“通过哪个虚函数表”来调用即可。
多重继承中,由于父类是平级,故各个父类中的首个非静态成员变量的偏移量是相同的。
子类的this指针的地址和继承的第一直接基类相同,即子类的虚函数指针和第一直接基类的虚函数指针存放在同一个虚函数表中(按照先父类后子类的原则)。
通过this指针的调整后,偏移量相同的两个直接基类的非静态成员变量也能正常访问。
两个直接基类和一个派生类的内存(栈区)分布:
通过“this+偏移值”,可访问非静态成员变量,如下:
#include
using namespace std;
class Base1
{
public:
int m_base1;
virtual void virtual_func1() { cout << "Base1::virtual_func1()" << endl; }
Base1() { printf("Base1对象this指针的地址:%p\n", this); }
};
class Base2
{
public:
int m_base2;
virtual void virtual_func2() { cout << "Base2::virtual_func2()" << endl; }
Base2() { printf("Base2对象this指针的地址:%p\n", this); }
};
class Derived : public Base1, public Base2
{
public:
int m_derived1;
int m_derived2;
virtual void virtual_func3() { cout << "Derived::virtual_func3()" << endl; }
Derived() { printf("Derived对象this指针的地址:%p\n", this); }
};
int main()
{
Derived derived;
printf("m_base1的地址偏移量:%d\n", &Derived::m_base1);
printf("m_base2的地址偏移量:%d\n", &Derived::m_base2);
printf("m_derived1的地址偏移量:%d\n", &Derived::m_derived1);
printf("m_derived2的地址偏移量:%d\n", &Derived::m_derived2);
return 0;
}
/*
Base1对象this指针的地址:00D5F87C
Base2对象this指针的地址:00D5F884
Derived对象this指针的地址:00D5F87C
m_base1的地址偏移量:4
m_base2的地址偏移量:4
m_derived1的地址偏移量:16
m_derived2的地址偏移量:20
*/
当父类指针指向子类对象时,通过this指针的调整,就可正常访问到父类的“成员变量/成员函数/被子类重写的虚函数”。
#include
using namespace std;
class Base1
{
public:
int m_base1;
virtual void virtual_func1() { cout << "Base1::virtual_func1()" << endl; }
Base1() { printf("Base1对象this指针的地址:%p\n", this); }
};
class Base2
{
public:
int m_base2;
virtual void virtual_func2() { cout << "Base2::virtual_func2()" << endl; }
Base2() { printf("Base2对象this指针的地址:%p\n", this); }
};
class Derived : public Base1, public Base2
{
public:
int m_derived1;
int m_derived2;
virtual void virtual_func3() { cout << "Derived::virtual_func3()" << endl; }
Derived() { printf("Derived对象this指针的地址:%p\n", this); }
};
int main()
{
Derived* derived = new Derived();
printf("derived2指向的地址:%p\n", derived);
Base1* base1 = derived; // 父类Base1指向子类Derived时,this指针不需要调整
printf("base1指向的地址:%p\n", base1);
Base2* base2 = derived; // 父类Base2指向子类Derived时,this指针需要向下调整
printf("base2指向的地址:%p\n", base2);
derived = (Derived*)base2; // 父类Base2给子类Derived赋值时,this指针需要向上调整
printf("derived2指向的地址:%p\n", derived);
delete derived;
return 0;
}
更复杂的继承关系:
虚基类(编译器实现极其复杂),虚继承的消耗是巨大的,非必要不要使用:
单一虚继承:
三层虚继承的内存布局(vbptr1、vbptr2是虚基类表指针;虚基类子对象被放在了最后)。
只有在对虚基类的成员变量进行处理(如赋值的时候),才会用到虚基类表 — 取其中的值用作偏移值来进行虚基类成员变量首地址的定位运算。
经过以上分析,得出结论:访问虚基类的成员变量要比访问其他成员变量更慢。
// 等价于直接调用一个普通的成员函数,不需要通过虚函数列表来查找并调用虚函数
对象名.虚函数名(参数列表);
类范围操作符::虚函数名(参数列表); // 仅限于类内调用
// 编译器调用过程:“虚函数表指针 ---> 虚函数表 ---> 虚函数的入口地址”
类对象指针->虚函数名(参数列表);
// 编译器找到虚函数的入口地址后,会插入一个this指针作为虚函数参数列表的第一个参数,之后就相当于调用一个普通函数
vcall thunk
,其调用的是真实的虚函数的地址。静态绑定: 绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期。
普通成员函数、缺省参数一般是静态绑定。
动态绑定: 绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期。
虚函数是动态绑定。
继承非虚函数(静态绑定):不应该在一个子类中,“重写”继承来的非虚成员函数,但可以“重载”。
非虚继承父类的虚函数(动态绑定):子类的同名同参的虚函数会覆盖掉继承来的父类的虚函数,并与其他的虚函数一起存放在虚函数表中(相当于该类中多了一个虚拟的成员变量)。
缺省参数是静态绑定的。
在给定参数缺省值的情况下,会同时出现:虚函数的动态绑定、缺省参数的静态绑定。
#include
using namespace std;
class Base
{
public:
Base() { }
virtual void func(int val = 1) { cout << "Base::func(), val = " << val << endl; }
};
class Derive : public Base
{
public:
Derive() { }
// 注意:尽量不要在子类中,重定义虚函数的缺省参数的值。
virtual void func(int val = 2) { cout << "Derive::func(), val = " << val << endl; }
};
int main()
{
// 普通函数是静态绑定;虚函数是动态绑定;虚函数的缺省参数是静态绑定;
Derive derive;
Base *pbase = &derive;
pbase->func(); /* 结果Derive::func(), val = 1 */
return 0;
}
存在虚函数且调用虚函数,才会涉及多态(没有虚函数,绝不可能存在多态性)。
A* a1 = new A();
a1->myVirtualFunc(); // 该调用过程即是多态
A* a3 = &a2;
a3->myVirtualFunc(); // 该调用过程即是多态
A a2;
a2.myVirtualFunc(); // 该调用过程不是多态
调用虚函数的过程:“虚函数表 --> 虚函数指针 --> 虚函数的入口地址”,则该调用过程就是多态。
通过类对象指针指向、类对象引用对象实例,调用过程为:“查虚函数表 --> 找到虚函数地址 --> 调用虚函数”,才会存在多态。
如果非指针/引用,则编译期就会确定虚函数的调用地址,则不存在多态性。
Derived derived;
Base* pbase = new Derived();
pbase->virtualfunc(); // Derived::virtualfunc()
Base& citebase = derived;
citebase.virtualfunc(); // Derived::virtualfunc()
总结:当以父类指针或引用调用子类中重写了的虚函数时(因为会动态调用子类的虚函数),就能看出多态。
Base2 *pbase2 = new Derive();
// 等价于
Derive *temp = new Derive();
Base2 *pbase2 = (Base2*)((char*)(temp) + sizeof(Base));
delete pbase2
会报错。Base2 *pbase2 = new Derive();
delete pbase2; // 报错,因pbase2指向的是其所在的内存,而不是整个被new出来的整个子类对象的内存
父类的析构函数不是虚函数,则不会触发动态绑定,结果就是只会调用父类的析构函数,从而导致内存泄漏(如果子类的析构函数中存在delete这样的代码的话,内存泄漏是必然的)。
父类的析构函数是虚函数,则子类必然是虚析构函数(c++语言的规定,与子类析构函数前是否有修饰virtual无关),则会触发动态绑定。
因new的实际是一个子类对象,所以先执行的是子类的析构函数,同时编译器还向子类的析构函数中插入了调用父类析构函数的代码(实现了先调用子类析构函数,再调用父类析构函数,故可让整个对象完美释放)。
在父类有虚函数的情况下,某个类继承了多少个(含有虚函数的)父类,就会有几个虚函数表。
指向对象的首地址,故只有对象指针才能正确地调用对象的成员函数:
RTTI中,type_info相关的信息,在编译过后就在可执行文件中。
const typeinfo& typeInfo = typeid(参数); typeInfo.name();
// 等价于:
typeid(参数).name()
注意:基类无虚函数,则不是多态,也就更谈不上RTTI!!!
虚函数导致的开销增加:
多重继承导致的开销增加:
/* 指向成员函数的指针 */
// 非静态成员函数指针:
typedef void(A::*ptrfunc_a)(int val); // 声明
ptrfunc_a = &A::m_func(); // 定义
A a; (a.(*ptrfunc_a))(10); // 类对象调用
A *ptr_a = new A(); (ptr_a->(*ptrfunc_a))(10); // 类对象指针调用
// 静态成员函数指针:
void(*static_func_ptr)(int val) = &A::m_static_func(); // 声明、定义
static_func_ptr(10); // 调用
/* 注意:使用成员函数指针来调用成员函数,必须要对象的介入(静态成员(属于整个类)除外,可不用this指针的参与)*/
/* 指向虚成员函数的指针 */
不建议,在类的构造函数(析构函数)中调用虚函数:
多层次继承时,构造函数的执行步骤:
memset(this, 0, sizeof(类))
或memcpy()
等函数,会导致虚函数表指针为空,导致后续虚函数的调用出错。对于类中没有拷贝构造函数和拷贝复制运算符的情况,编译器会有一些默认的对象复制行为(只能执行简单的复制操作,且效率高)。
// 拷贝构造函数和拷贝赋值运算符:
A& operator=(const A &temp);
A(const A &temp);
// 禁用拷贝构造函数或者赋值运算符:
// 方法一:将函数声明为private
// 方法二(c++11):
A& operator=(const A &temp) = delete; A(const A &temp) = delete
编译器合成析构函数的情况:
局部对象的构造和析构:
只要出了局部对象的作用域{ },编译器会在适当的地方插入调用对象析构函数的代码。
局部对象,尽量定义在需要用到它的代码段附近,即现用现定义(c与c++不同之处)。
*在函数开头定义对象(如传统的c语言),则需要在所有函数的出口处加析构函数。
全局对象的构造和析构:
静态局部对象的构造和析构:
全局、局部静态变量等所占用的内存大小信息,一般都是在目标文件/在可执行文件中。
静态局部对象数组,如果没有对该数组进行有用的操作时,编译器就不会给这个对象数组分配实际的物理内存。
拷贝构造函数、拷贝赋值运算符、直接运算产生的临时对象。临时对象很耗费成本,写代码的时候尽量避免!!!
在调用函数模板时,编译器会实例化一个函数,即再生成一个具体针对这些类型的函数体代码。
类模板中的静态成员变量,在初始化和调用时,类模板并没有被实例化出来,只能被当作变量使用;
template <typename T>
class A
{
public:
static T m_static;
...
};
template<class T>
T A <T>::m_static = 0;
类模板的实例化:
A<int> a(0);
A<int>& a2 = a;
const A<int>& a3 = 0; // 使用了隐式类型转换,等价于:A tmpObj(0); const A& a3 = tmppObj;
template class A<int>; // 编译器会将类模板中,所有内容都会被实例化出来
template void A<int>::func();