《C++新经典对象模型》之第4章 数据语义学

《C++新经典对象模型》之第4章 数据语义学

4.1 数据成员绑定时机

4.1.1 成员函数函数体的解析时机(整个类定义完毕后)

int myvar = 5; // 全局量
int myfunc()
{
	// return ::myvar;
	return myvar;
}
class A0
{
public:
	int myfunc()
	{
		// return ::myvar;// 全局量
		return myvar;//类成员变量
	}

private:
	int myvar{3};
};

4.1.2 成员函数参数类型的确定时机(最近碰到原则)


typedef string mytype;
class A
{
	typedef int mytype;

public:
	void myfunc(mytype tmpvalue) // int
	{
		m_value = tmpvalue;
	}

private:
	mytype m_value; // int
};

void myfunc(mytype tmpvalue) // string类型
{
	mytype mvalue = tmpvalue;
}
04.01.cpp
#include <iostream>
using namespace std;

int myvar = 5; // 全局量
int myfunc()
{
	// return ::myvar;
	return myvar;
}
class A0
{
public:
	int myfunc()
	{
		// return ::myvar;
		return myvar;
	}

private:
	int myvar{3};
};

typedef string mytype;
class A
{
	typedef int mytype;

public:
	void myfunc(mytype tmpvalue) // int
	{
		m_value = tmpvalue;
	}

private:
	mytype m_value; // int
};

void myfunc(mytype tmpvalue) // string类型
{
	mytype mvalue = tmpvalue;
}

int main()
{
	cout << myfunc() << endl;
	A0 a0;
	cout << a0.myfunc() << endl;

	A a;
	a.myfunc(4);
	myfunc("dd");

	cout << "Over!\n";
	return 0;
}

4.2 进程内存空间布局

最高内存地址
栈区(向下增长)
堆区(向上增长)
数据段(含BSS段)
代码段
最低内存地址

进程虚拟地址空间(内存)划分:
(1)栈(堆栈/栈区):局部变量等。
(2)堆(堆区):new、malloc等申请内存空间。
(3)BSS段:未初始化全局变量,初始化为0的全局变量。
(4)数据段:已初始化的全局变量,包含BSS段。
(5)代码段:存放程序执行代码。

Linux上列出可执行文件中全局变量等存放位置的nm命令。

全局量(全局变量、全局函数、类静态成员变量等)的地址值在生成可执行文件时已经确定,存放在bss段、数据段等,加载(映射)到内存时不会改变。

04.02.cpp
#include <cstdio>
#include <iostream>
using namespace std;

int *ptest = new int(120);
int g1;
int g2;

int g3 = 12;
int g4 = 32;
int g5;
int g6 = 0;
static int g7;
static int g8 = 0;
static int g9 = 10;
void mygfunc()
{
	return;
}
// 定义一个类
class MYACLS
{
public:
	int m_i;
	static int m_si; // 声明
	int m_j;
	static int m_sj;
	int m_k;
	static int m_sk;
};
int MYACLS::m_sj = 12; // 定义

int main()
{
	{
		int e_mytest = 9; // 程序运行时临时放在栈中
	}

	{
		printf("&ptest=%p\n", &ptest);
		printf("&g1=%p\n", &g1);
		printf("&g2=%p\n", &g2);
		printf("&g3=%p\n", &g3);
		printf("&g4=%p\n", &g4);
		printf("&g5=%p\n", &g5);
		printf("&g6=%p\n", &g6);
		printf("&g7=%p\n", &g7);
		printf("&g8=%p\n", &g8);
		printf("&g9=%p\n", &g9);
		printf("&MYACLS::m_sj=%p\n", &(MYACLS::m_sj));
		printf("&mygfunc()=%p\n", mygfunc);
		printf("&main()=%p\n", main);

		int i = 7;
		printf("&i=%p\n", &i);
	}

	cout << "Over!\n";
	return 0;
}

4.3 数据成员布局

4.3.1 观察成员变量地址规律

静态成员变量不占类对象的内存空间,存储在整个程序的数据段中。
普通成员变量按照类中定义从上到下布局。

4.3.2 边界调整与字节对齐

成员变量之间边界会调整,就是填充一些字节,提高程序执行效率。

//紧密排列
#pragma pack(1)

//类定义代码

//恢复默认对齐
#pragma pack()

4.3.3 成员变量偏移量的打印

&MYACLS::m_i
#define GET(A, m) (long long)(&((A *)nullptr)->m)
04.03.cpp
#include <stdio.h>
#include <iostream>
using namespace std;

// A代表类名,m代表成员名,类成员偏移地址
#define GET(A, m) (long long)(&((A *)nullptr)->m)

#pragma pack(1) // 1字节对齐
class MYACLS
{
public:
	int m_i;
	static int m_si; // 声明
	int m_j;
	static int m_sj;
	int m_k;
	static int m_sk;

public:
	char m_c; // 1字节
	int m_n;  // 4字节

private:
	int m_pria;
	int m_prib;

public:
	void printMemPoint() // 因为要打印私有的成员变量偏移值,只能用成员函数打印
	{
		cout << "Print member variable offset values---------------" << endl;
		printf("MYACLS::m_i = %d\n", &MYACLS::m_i); // 类名不省略,打印偏移值,否则打印成员变量的物理地址
		printf("MYACLS::m_j = %d\n", &MYACLS::m_j);
		printf("MYACLS::m_k = %d\n", &MYACLS::m_k);
		printf("MYACLS::m_c = %d\n", &MYACLS::m_c);
		printf("MYACLS::m_n = %d\n", &MYACLS::m_n);

		printf("MYACLS::m_pria = %d\n", &MYACLS::m_pria);
		printf("MYACLS::m_prib = %d\n", &MYACLS::m_prib);
		cout << "------------------------" << endl;
		cout << "MYACLS::m_prib = " << GET(MYACLS, m_prib) << endl;
	}

public:
	virtual void mfv() {}
};
#pragma pack()		  // 恢复缺省对齐
int MYACLS::m_sj = 0; // 定义

int main()
{
	{
		cout << sizeof(MYACLS) << endl; // 4*6+1+8

		MYACLS myobj;
		myobj.m_k = 8;
		myobj.m_i = 2;
		myobj.m_j = 5;

		printf("&myobj = %p\n", &myobj);
		printf("&myobj.m_i = %p\n", &myobj.m_i);
		printf("&myobj.m_j = %p\n", &myobj.m_j);
		printf("&myobj.m_k = %p\n", &myobj.m_k);
		printf("&myobj.m_c = %p\n", &myobj.m_c);
		printf("&myobj.m_n = %p\n", &myobj.m_n);
	}

	{
		MYACLS *pmyobj = new MYACLS();
		printf("pmyobj = %p\n", pmyobj);
		printf("&pmyobj->m_i = %p\n", &pmyobj->m_i);
		printf("&pmyobj->m_j = %p\n", &pmyobj->m_j);
		printf("&pmyobj->m_k = %p\n", &pmyobj->m_k);
		printf("&pmyobj->m_c = %p\n", &pmyobj->m_c);
		printf("&pmyobj->m_n = %p\n", &pmyobj->m_n);
		pmyobj->printMemPoint();
	}

	int MYACLS::*mypoint = &MYACLS::m_n;
	printf("pmyobj->m_n offset address = %d\n", mypoint);

	cout << "Over!\n";
	return 0;
}

4.4 数据成员的存取

4.4.1 静态成员变量的存取

类名、对象名或者对象指针存取静态成员变量。

4.4.2 非静态成员变量的存取

非静态成员变量只能通过类对象来存取。
对象名访问成员变量的方式,编译期间就计算出成员变量的偏移值。
对象指针(虚)访问成员变量,执行期间,间接访问。

04.04.cpp
#include <cstdio>
#include <iostream>
using namespace std;

struct FAC
{
	int m_fai;
	int m_faj;
};

struct MYACLS : FAC
{
	int m_i;
	int m_j;
	static int m_si; // 声明

	void myfunc()
	{
		m_i = 5;
		m_j = 6;
	}
};
int MYACLS::m_si = 0; // 定义

int main()
{
	cout << MYACLS::m_si << endl;

	MYACLS myobj;
	cout << myobj.m_si << endl;

	MYACLS *pmyobj = new MYACLS();
	cout << pmyobj->m_si << endl;

	MYACLS::m_si = 1;
	myobj.m_si = 2;
	pmyobj->m_si = 3;

	printf("&myobj.m_i = %p\n", &myobj.m_i);
	printf("&pmyobj->m_i = %p\n", &pmyobj->m_i);
	printf("&MYACLS::m_si = %p\n", &MYACLS::m_si);
	printf("&MYACLS::m_si = %p\n", &myobj.m_si);
	printf("&MYACLS::m_si = %p\n", &pmyobj->m_si);

	pmyobj->myfunc();
	printf("MYACLS::m_i = %d\n", &MYACLS::m_i); // 8
	printf("MYACLS::m_j = %d\n", &MYACLS::m_j); // 12
	pmyobj->m_faj = 7;							// 给父类的成员变量赋值

	cout << "Over!\n";
	return 0;
}

4.5 单一继承下的数据成员布局

struct FAC // 父类
{
	int m_fai;//4
	int m_faj;//4
};
struct MYACLS : FAC // 子类
{
	//4
	//4
	int m_i;//4
	int m_j;//4
};
struct Base
{
	int m_i1;//4
	char m_c1;//1
	char m_c2;//1
	char m_c3;//1
};
struct Base1
{
	int m_i1;//4
	char m_c1;//1(4)
};
struct Base2 : Base1
{
	char m_c2;//1(4)
};
struct Base3 : Base2
{
	char m_c3;1(4)
};
//Linux下Base3布局
// int m_i1;//4
// char m_c1;//1(3)
// char m_c2;//1
// char m_c3;//1(2)

04.05.cpp
#include <cstdio>
#include <iostream>
using namespace std;

struct FAC // 父类
{
	int m_fai;
	int m_faj;
};
struct MYACLS : FAC // 子类
{
	int m_i;
	int m_j;
};

struct Base
{
	int m_i1;
	char m_c1;
	char m_c2;
	char m_c3;
};

struct Base1
{
	int m_i1;
	char m_c1;
};
struct Base2 : Base1
{
	char m_c2;
};
struct Base3 : Base2
{
	char m_c3;
};

int main()
{
	{
		printf("&FAC::m_fai = %d\n", &FAC::m_fai);
		printf("&FAC::m_faj = %d\n", &FAC::m_faj);
		printf("&MYACLS::m_fai = %d\n", &MYACLS::m_fai);
		printf("&MYACLS::m_faj = %d\n", &MYACLS::m_faj);
		printf("&MYACLS::m_i = %d\n", &MYACLS::m_i);
		printf("&MYACLS::m_j = %d\n", &MYACLS::m_j);

		FAC facobj;
		MYACLS myaclobj;
	}

	{
		cout << sizeof(Base) << endl;

		printf("&Base::m_i1 = %d\n", &Base::m_i1);
		printf("&Base::m_c1 = %d\n", &Base::m_c1);
		printf("&Base::m_c2 = %d\n", &Base::m_c2);
		printf("&Base::m_c3 = %d\n", &Base::m_c3);
	}

	{
		cout << sizeof(Base1) << endl; // 8
		cout << sizeof(Base2) << endl; // 12
		cout << sizeof(Base3) << endl; // 12
	}

	{
		printf("&Base3::m_i1 = %d\n", &Base3::m_i1);
		printf("&Base3::m_c1 = %d\n", &Base3::m_c1);
		printf("&Base3::m_c2 = %d\n", &Base3::m_c2);
		printf("&Base3::m_c3 = %d\n", &Base3::m_c3);
	}
	
	cout << "Over!\n";
	return 0;
}

4.6 单类单继承虚函数下的数据成员布局

4.6.1 单个类带虚函数的数据成员布局


struct MYACLS
{
	int m_i;					// 4
	int m_j;					// 4
	virtual void myvirfunc() {} // 8
	// 8
	// 4, 4
};

4.6.2 单一继承父类带虚函数的数据成员布局

struct Base0
{
	int m_bi;					 // 4
	virtual void mybvirfunc() {} // 8
};
struct MYACLS0 : Base0
{
	int m_i;					// 4
	int m_j;					// 4
	virtual void myvirfunc() {} // 8

	// 8
	// 4,4
	// 4
};

4.6.3 单一继承父类不带虚函数的数据成员布局

04.06.cpp
#include <iostream>
#include <cstdio>
using namespace std;

struct MYACLS
{
	int m_i;					// 4
	int m_j;					// 4
	virtual void myvirfunc() {} // 8

	// 8
	// 4, 4
};

struct Base0
{
	int m_bi;					 // 4
	virtual void mybvirfunc() {} // 8
};
struct MYACLS0 : Base0
{
	int m_i;					// 4
	int m_j;					// 4
	virtual void myvirfunc() {} // 8

	// 8
	// 4,4
	// 4
};

struct Base1
{
	int m_bi; // 4
};
struct MYACLS1 : Base1
{
	int m_i;					// 4
	int m_j;					// 4
	virtual void myvirfunc() {} // 8

	// 8
	// 4,4
	// 4
};

int main()
{
	{
		cout << sizeof(MYACLS) << endl;
		printf("&MYACLS::m_i = %d\n", &MYACLS::m_i);
		printf("&MYACLS::m_j = %d\n", &MYACLS::m_j);
	}
	{
		cout << sizeof(MYACLS0) << endl;
		printf("&MYACLS0::m_bi = %d\n", &MYACLS0::m_bi);
		printf("&MYACLS0::m_i = %d\n", &MYACLS0::m_i);
		printf("&MYACLS0::m_j = %d\n", &MYACLS0::m_j);
	}
	{
		cout << sizeof(MYACLS1) << endl;
		printf("&MYACLS1::m_bi = %d\n", &MYACLS1::m_bi); // 0,站在父类角度去计算,父类无虚函数
		printf("&MYACLS1::m_i = %d\n", &MYACLS1::m_i);
		printf("&MYACLS1::m_j = %d\n", &MYACLS1::m_j);
	}

	cout << "Over!\n";
	return 0;
}

4.7 多重继承数据布局与this调整深谈

4.7.1 单一继承数据成员布局this指针偏移知识补充

struct Base
{
	int m_bi; // 4
	Base(){
		printf("%p\n", this);
	}
};
struct MYACLS : Base//访问Base类成员时,this指针调整,跳过虚函数表指针
{
	int m_i;					// 4
	int m_j;					// 4
	virtual void myvirfunc() {} // 8

	MYACLS(){
		printf("%p\n", this);
	}
	// 8
	// 4,4
	// 4
};

4.7.2 多重继承且父类都带虚函数的数据成员布局

struct Base
{
	virtual void mybvirfunc() {}
	int m_bi;
	Base()
	{
		printf("Base::Base(), this:%p!\n", this);
	}
	// 8
	// 4(m_bi)
};

struct Base2
{
	virtual void mybvirfunc2() {}
	int m_b2i;
	// 8
	// 4(m_b2i)

	Base2()
	{
		printf("Base2::Base2(), this:%p!\n", this);
	}
};

struct MYACLS : Base, Base2
{
	int m_i;
	int m_j;
	// 8
	// 4(m_bi)
	// 8
	// 4, 4(m_b2i, m_i)
	// 4(m_j)

	virtual void myvirfunc() {}
	MYACLS()
	{
		printf("MYACLS::MYACLS(), this:%p!\n", this);
	}
};
04.07.cpp
#include <iostream>
#include <cstdio>
using namespace std;

struct Base
{
	virtual void mybvirfunc() {}
	int m_bi;
	Base()
	{
		printf("Base::Base(), this:%p!\n", this);
	}
	// 8
	// 4(m_bi)
};

struct Base2
{
	virtual void mybvirfunc2() {}
	int m_b2i;
	// 8
	// 4(m_b2i)

	Base2()
	{
		printf("Base2::Base2(), this:%p!\n", this);
	}
};

struct MYACLS : Base, Base2
{
	int m_i;
	int m_j;
	// 8
	// 4(m_bi)
	// 8
	// 4, 4(m_b2i, m_i)
	// 4(m_j)

	virtual void myvirfunc() {}
	MYACLS()
	{
		printf("MYACLS::MYACLS(), this:%p!\n", this);
	}
};

int main()
{
	{
		cout << sizeof(MYACLS) << endl;
		printf("MYACLS::m_bi = %d\n", &MYACLS::m_bi);	// 8,相对父类Base偏移量
		printf("MYACLS::m_b2i = %d\n", &MYACLS::m_b2i); // 8,相对父类Base2偏移量
		printf("MYACLS::m_i = %d\n", &MYACLS::m_i);
		printf("MYACLS::m_j = %d\n", &MYACLS::m_j);
	}
	cout << "***********************\n";
	{
		MYACLS myobj;
		myobj.m_i = 3;
		myobj.m_j = 6;
		myobj.m_bi = 9;
		myobj.m_b2i = 12;

		Base *pbase = &myobj; // Base类的首地址和MYACLS类的首地址相同, this不需要调整

		Base2 *pbase2 = &myobj; // this指针自动调整
		// Base2* pbase2 = (Base2*)(((char*)&myobj) + sizeof(Base));//编译器可能调整方式

		printf("&myobj = %p\n", &myobj);
		printf("pbase = %p\n", pbase);
		printf("pbase2 = %p\n", pbase2);
		printf("pbase2 = %p\n", ((char *)&myobj) + sizeof(Base));
		cout << "***********************\n";

		Base2 *pbase3 = new MYACLS(); // this指针自动调整
		// delete pbase3;//error

		MYACLS *psubobj = (MYACLS *)pbase3; // this指针自动调整
		printf("pbase3 = %p\n", pbase3);
		printf("psubobj = %p\n", psubobj);

		delete psubobj;//真正的new分配的内存首地址
	}

	cout << "Over!\n";
	return 0;
}

4.8 虚基类问题的提出和初探

4.8.1 虚基类问(虚继承/虚派生)题的提出

struct Grand // 爷爷类
{
	int m_grand;
};

struct A0_1 : Grand
{
};
struct A0_2 : Grand
{
};
struct C0 : A0_1, A0_2
{
};
	{
		cout << sizeof(Grand) << endl;
		cout << sizeof(A0_1) << endl;
		cout << sizeof(A0_2) << endl;
		cout << sizeof(C0) << endl;
	}

	{

		C0 c0;
		c0.A0_1::m_grand = 5;
		c0.A0_2::m_grand = 8;
		// c0.m_grand = 12; //error,访问不明确
	}

虚基类,三层结构,一个爷爷类,两个父亲类,然后孩子类。
虚基类代表孙子类对象中,只包含一份独立的爷爷类对象,无论父类中继承多少次。

虚基类必须在孙子类构造函数的初始化列表中初始化爷爷类。

struct Grand // 爷爷类
{
	int m_grand;
};

struct A1 : virtual Grand // 注意这里用了virtual
{
	int m_a1;
	// 8
	// 4(m_a1)
};
struct A2 : virtual Grand // 注意这里用了virtual
{
	int m_a2;
	// 8
	// 4(m_a2)
};
struct C1 : A1, A2 // 这里不需要virtual
{
	int m_c1;
	// 8
	// 4(m_a1)
	// 8
	// 4, 4(m_a2, m_c1)
};
{
		A1 a1;
		A2 a2;

		C1 c1;
		c1.m_grand = 12; // ok
	}

4.8.2 虚基类初探


struct Grand // 爷爷类
{
	int m_grand;//4
};

struct A1 : virtual Grand // 注意这里用了virtual
{
	int m_a1;
	// 8(虚基类表指针vbptr, virtual base table pointer)
	// 4(m_a1),4(m_grand)
};
struct A2 : virtual Grand // 注意这里用了virtual
{
	int m_a2;
	// 8(虚基类表指针)
	// 4(m_a2),4(m_grand)
};
struct C1 : A1, A2 // 这里不需要virtual
{
	int m_c1;
	// 8(vbptr1,继承自A1)
	// 4(m_a1),4
	// 8(vbptr2,继承自A2)
	// 4(m_a2), 4(m_c1)
	// 4(m_grand), 4
};
04.08.cpp
#include <cstdio>
#include <iostream>
using namespace std;

struct Grand // 爷爷类
{
	int m_grand;
};

struct A0_1 : Grand
{
};
struct A0_2 : Grand
{
};
struct C0 : A0_1, A0_2
{
};

struct A1 : virtual Grand // 注意这里用了virtual
{
	int m_a1;
	// 8
	// 4(m_a1)
};
struct A2 : virtual Grand // 注意这里用了virtual
{
	int m_a2;
	// 8
	// 4(m_a2)
};
struct C1 : A1, A2 // 这里不需要virtual
{
	int m_c1;
	// 8
	// 4(m_a1)
	// 8
	// 4, 4(m_a2, m_c1)
};

int main()
{
	{
		cout << sizeof(Grand) << endl;
		cout << sizeof(A0_1) << endl;
		cout << sizeof(A0_2) << endl;
		cout << sizeof(C0) << endl;
	}

	{

		C0 c0;
		c0.A0_1::m_grand = 5;
		c0.A0_2::m_grand = 8;
		// c0.m_grand = 12; //error,访问不明确
	}

	{
		A1 a1;
		A2 a2;

		C1 c1;
		c1.m_grand = 12; // ok
	}
	cout << "************\n";
	{
		cout << sizeof(Grand) << endl; // 4
		cout << sizeof(A1) << endl;	   // 16
		cout << sizeof(A2) << endl;	   // 16
		cout << sizeof(C1) << endl;	   // 40
	}
	cout << "************\n";

	printf("&C1::m_a1 = %d\n", &C1::m_a1);
	printf("&C1::m_a2 = %d\n", &C1::m_a2);
	printf("&C1::m_c1 = %d\n", &C1::m_c1);
	printf("&C1::m_grand = %d\n", &C1::m_grand);

	cout << "Over!\n";
	return 0;
}

4.9 两层结构时虚基类表内容分析

虚基类表,编译时,编译器已经生成,跟着类走,依附于类而存在。
虚基类表8字节长,多一个虚基类,增加4字节。
class A1:virtual public Grand1, virtual public Grand2 {}

4.9.1 虚基类表内容之5~8字节内容分析

虚基类表中的5~8字节存储着虚基类表指针的首地址和虚基类对象首地址之间的偏移量。

4.9.2 各种形式的继承

class Grand // 爷爷类
{
public:
	int m_grand; // 4
};

class Grand2
{
public:
	int m_grand2; // 4
};

class A1_0 : virtual public Grand, public Grand2
{
public:
	int m_a1;
	// 4(m_grand2) + 4
	// 8(vbptr1)
	// 4(m_a1)+4(m_grand)
};
class A1_1 : public Grand, virtual public Grand2
{
public:
	int m_a1;
	// 4(m_grand) + 4
	// 8(vbptr1)
	// 4(m_a1)+4(m_grand2)
};
class A1_2 : virtual public Grand, virtual public Grand2
{
public:
	int m_a1;
	// 8(vbptr1)
	// 4(m_a1) + 4(m_grand)
	// 4(m_grand2)+4
};

4.9.3 虚基类表内容之1~4字节内容分析

虚基类表内容的前4个字节应该是对象首地址与虚基类表指针首地址的偏移量。
只有对虚基类的成员变量进行赋值等处理时,才会使用虚基类——取其中偏移值定位虚基类成员变量首地址。

04.09.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

class Grand // 爷爷类
{
public:
	int m_grand; // 4
};

class Grand2
{
public:
	int m_grand2; // 4
};

class A1_0 : virtual public Grand, public Grand2
{
public:
	int m_a1;
	// 4(m_grand2) + 4
	// 8(vbptr1)
	// 4(m_a1)+4(m_grand)
};
class A1_1 : public Grand, virtual public Grand2
{
public:
	int m_a1;
	// 4(m_grand) + 4
	// 8(vbptr1)
	// 4(m_a1)+4(m_grand2)
};
class A1_2 : virtual public Grand, virtual public Grand2
{
public:
	int m_a1;
	// 8(vbptr1)
	// 4(m_a1) + 4(m_grand)
	// 4(m_grand2)+4
};

// class A1 : virtual public  Grand //注意这里用了virtual
// class A1 : virtual public  Grand, public Grand2
// class A1 : public  Grand, virtual public Grand2
class A1 : virtual public Grand, virtual public Grand2
{
public:
	int m_a1;
};
class A2 : virtual public Grand // 注意这里用了virtual
{
public:
	int m_a2;
};
class C1 : public A1, public A2 // 这里不需要virtual
{
public:
	int m_c1;
};

int main()
{
	{
		cout << sizeof(Grand) << endl;
		cout << sizeof(Grand2) << endl;

		cout << sizeof(A1_0) << endl;
		printf("&A1_0::m_grand2 = %d\n", &A1_0::m_grand2);
		printf("&A1_0::m_a1 = %d\n", &A1_0::m_a1);
		printf("&A1_0::m_grand = %d\n", &A1_0::m_grand);

		cout << sizeof(A1_1) << endl;
		printf("&A1_1::m_grand2 = %d\n", &A1_1::m_grand2);
		printf("&A1_1::m_a1 = %d\n", &A1_1::m_a1);
		printf("&A1_1::m_grand = %d\n", &A1_1::m_grand);

		cout << sizeof(A1_2) << endl;
		printf("&A1_2::m_grand2 = %d\n", &A1_2::m_grand2);
		printf("&A1_2::m_a1 = %d\n", &A1_2::m_a1);
		printf("&A1_2::m_grand = %d\n", &A1_2::m_grand);
	}

	{
		cout << sizeof(Grand) << endl;
		cout << sizeof(A1) << endl;
		cout << sizeof(A2) << endl;
		cout << sizeof(C1) << endl;
	}

	//{
	//	C1 c1;
	//	//c1.m_grand = 12; //这句编译时会报错
	//	c1.A1::m_grand = 5;
	//	c1.A2::m_grand = 8;
	//}

	{
		C1 c1;
		c1.m_grand = 12; // 这句编译时不会再报错
	}

	{
		A1 a1obj;
		a1obj.m_grand = 2;
		a1obj.m_a1 = 5;
	}

	{
		A1 a1obj; // a1obj的地址是0x00EFFD48,该地址的第5~8字节内容为0x00179b30,因此虚基类表地址是0x00179b30
		a1obj.m_grand = 2;
		a1obj.m_grand2 = 6;
		a1obj.m_a1 = 5;
	}

	cout << "Over!\n";
	return 0;
}

4.10 三层结构时虚基类表内容分析与虚基类设计原由

4.10.1 三层结构时虚基类表内容分析

class Grand // 爷爷类
{
public:
	int m_grand; // 4
};

class A1 : virtual public Grand // 注意这里用了virtual
{
public:
	int m_a1;
	// 8(vbptr)
	// 4(m_a1)+4(m_grand)
};
class A2 : virtual public Grand // 注意这里用了virtual
{
public:
	int m_a2;
	// 8(vbptr)
	// 4(m_a2)+4(m_grand)
};
class C1 : public A1, public A2 // 这里不需要virtual
{
public:
	int m_c1;
	// 8(vbptr A1)
	// 4(m_a1)+4
	// 8(vbptr A2)
	// 4(m_a2)+4(m_c1)
	// 4(m_grand)+4
};
	{
		cout << sizeof(Grand) << endl;
		cout << sizeof(A1) << endl;
		cout << sizeof(A2) << endl;
		cout << sizeof(C1) << endl;

		printf("&C1::m_a1=%d\n", &C1::m_a1);
		printf("&C1::m_a2=%d\n", &C1::m_a2);
		printf("&C1::m_c1=%d\n", &C1::m_c1);
		printf("&C1::m_grand=%d\n", &C1::m_grand);
	}

访问虚基类的成员变量比访问其他成员变量更慢。

4.10.2 虚基类为什么这样设计

C++标准规定框架,编译器内部具体实现。
凡是访问虚基类的成员变量,都借助虚基类表来查询偏移量,确定虚基类成员变量的内存地址,比访问普通成员变量多几步,访问速度更慢。

04.10.cpp
#include <cstdio>
#include <iostream>
using namespace std;

class Grand // 爷爷类
{
public:
	int m_grand; // 4
};

class A1 : virtual public Grand // 注意这里用了virtual
{
public:
	int m_a1;
	// 8(vbptr)
	// 4(m_a1)+4(m_grand)
};
class A2 : virtual public Grand // 注意这里用了virtual
{
public:
	int m_a2;
	// 8(vbptr)
	// 4(m_a2)+4(m_grand)
};
class C1 : public A1, public A2 // 这里不需要virtual
{
public:
	int m_c1;
	// 8(vbptr A1)
	// 4(m_a1)+4
	// 8(vbptr A2)
	// 4(m_a2)+4(m_c1)
	// 4(m_grand)+4
};

int main()
{
	{
		cout << sizeof(Grand) << endl;
		cout << sizeof(A1) << endl;
		cout << sizeof(A2) << endl;
		cout << sizeof(C1) << endl;

		printf("&C1::m_a1=%d\n", &C1::m_a1);
		printf("&C1::m_a2=%d\n", &C1::m_a2);
		printf("&C1::m_c1=%d\n", &C1::m_c1);
		printf("&C1::m_grand=%d\n", &C1::m_grand);
	}

	{
		C1 c1obj;
		c1obj.m_grand = 2;
		c1obj.m_a1 = 5;
		c1obj.m_a2 = 6;
		c1obj.m_c1 = 8;

		A2 &pa2 = c1obj;
		pa2.m_grand = 8;
		pa2.m_a2 = 9;
		printf("m_grand=%d\n", c1obj.m_grand);
		printf("m_a2=%d\n", c1obj.m_a2);

		A2 *pa3 = &c1obj;
		pa3->m_grand = 80;
		pa3->m_a2 = 90;
		printf("m_grand=%d\n", c1obj.m_grand);
		printf("m_a2=%d\n", c1obj.m_a2);
	}

	{
		A1 a1obj;
		a1obj.m_grand = 2;
	}
	{
		C1 c2obj;
	}

	cout << "Over!\n";
	return 0;
}

4.11 成员变量地址、偏移与指针等重申

4.11.1 对象成员变量内存地址及其指针

对象的成员变量就像独立的变量。

struct MYACLS
{
	int m_i;
	int m_j;
	int m_k;
};
	MYACLS myobj;
	myobj.m_i = myobj.m_j = myobj.m_k = 0;
	printf("&myobj.m_i = %p\n", &myobj.m_i);

	MYACLS *pmyobj = new MYACLS();
	printf("&pmyobj->m_i = %p\n", &pmyobj->m_i);
	printf("&pmyobj->m_j = %p\n", &pmyobj->m_j);

	int *p1 = &myobj.m_i; // 对象成员变量指针
	int *p2 = &pmyobj->m_j;
	*p1 = 15;
	*p2 = 30;
	printf("&p1=%p, *p1=%d\n", p1, myobj.m_i);
	printf("&p2=%p, *p2=%d\n", p2, pmyobj->m_j);

4.11.2 成员变量的偏移量及其指针

成员变量指针保存偏移值,非真正的内存地址。

	// 成员变量指针
	int MYACLS::*mypoint = &MYACLS::m_j;
	printf("MYACLS::m_j offset value = %d\n", mypoint);

	mypoint = &MYACLS::m_i;
	printf("MYACLS::m_i offset value = %d\n", mypoint);

4.11.3 成员变量指针和不指向任何成员变量的成员变量指针

  • 通过成员变量指针访问成员变量
// 成员变量指针
	int MYACLS::*mypoint = &MYACLS::m_j;
	printf("MYACLS::m_j offset value = %d\n", mypoint);
	
	MYACLS myobj;
	myobj.*mypoint = 22;   // 注意写法
	MYACLS *pmyobj = new MYACLS();
	pmyobj->*mypoint = 19; // 注意写法
  • 成员变量指针作为形参
void myfunc(int MYACLS::*mempoint, MYACLS &obj)
{
	obj.*mempoint = 260; // 注意写法
}
	int MYACLS::*mypoint = &MYACLS::m_j;
	printf("MYACLS::m_j offset value = %d\n", mypoint);
	
	MYACLS myobj;
	myfunc(mypoint, myobj);				
	
	MYACLS *pmyobj = new MYACLS();	
	myfunc(mypoint, *pmyobj);	
  • 空成员变量指针
		int MYACLS::*mypoint2;//未初始化,编译器赋默认值0xcccccccc
		mypoint2 = 0;//空,编译器赋值0xffffffff(-1)
		mypoint2 = NULL;//空,编译器赋值0xffffffff(-1)

		// mypoint2 += 1; //语法错误,普通指针可以,但成员变量指针不可以
		// mypoint2++; //语法错误
		// mypoint2 = ( (&MYACLS::m_i) + 1);//语法错误
04.11.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;

struct MYACLS
{
	int m_i;
	int m_j;
	int m_k;
};

void myfunc(int MYACLS::*mempoint, MYACLS &obj)
{
	obj.*mempoint = 260; // 注意写法
}

int main()
{
	MYACLS myobj;
	myobj.m_i = myobj.m_j = myobj.m_k = 0;
	printf("&myobj.m_i = %p\n", &myobj.m_i);

	MYACLS *pmyobj = new MYACLS();
	printf("&pmyobj->m_i = %p\n", &pmyobj->m_i);
	printf("&pmyobj->m_j = %p\n", &pmyobj->m_j);

	int *p1 = &myobj.m_i; // 对象成员变量指针
	int *p2 = &pmyobj->m_j;
	*p1 = 15;
	*p2 = 30;
	printf("&p1=%p, *p1=%d\n", p1, myobj.m_i);
	printf("&p2=%p, *p2=%d\n", p2, pmyobj->m_j);

	printf("&MYACLS::m_i = %d\n", &MYACLS::m_i);
	printf("&MYACLS::m_j = %d\n", &MYACLS::m_j);

	// 成员变量指针
	int MYACLS::*mypoint = &MYACLS::m_j;
	printf("MYACLS::m_j offset value = %d\n", mypoint);

	mypoint = &MYACLS::m_i;
	printf("MYACLS::m_i offset value = %d\n", mypoint);

	myobj.*mypoint = 22;   // 注意写法
	pmyobj->*mypoint = 19; // 注意写法

	myfunc(mypoint, myobj);									 // 注意调用方法
	myfunc(mypoint, *pmyobj);								 // 注意调用方法
	cout << "sizeof(mypoint) = " << sizeof(mypoint) << endl; // 8字节

	/*
	{
		int MYACLS::*mypoint2;
		mypoint2 = 0;
		mypoint2 = NULL;

		// mypoint2 += 1; //语法错误,普通指针可以,但成员变量指针不可以
		// mypoint2++; //语法错误
		// mypoint2 = ( (&MYACLS::m_i) + 1);//语法错误

		printf("mypoint2 = %d\n", mypoint2);

		int MYACLS::*mypoint10 = &MYACLS::m_i;
		if (mypoint == mypoint10) // 本条件是成立的
		{
			cout << "条件成立" << endl;
		}
	}
	*/

	cout << "Over!\n";
	return 0;
}
  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值