C++类机制实验及结论对象的组成.docx
《C++类机制实验及结论对象的组成.docx》由会员分享,可在线阅读,更多相关《C++类机制实验及结论对象的组成.docx(16页珍藏版)》请在冰豆网上搜索。
C++类机制实验及结论对象的组成
假想:
类的实例由2部分组成:
成员变量、虚函数表指针(如果有虚函数),非虚函数不占用空间。
对非虚函数的调用由编译器内部机制实现。
1、关于虚函数:
论证方法:
通过打印sizeof(smlMammal)、sizeof(smlDog)
针对Mammal和Dog虚函数在这两个类中的不同的组成方式来查看他们的大小,来推测虚函数在类对象中的表现形式。
1、都不包含虚函数
classMammal
{
public:
Mammal(){cout<<"MammalConstructor\n"<~Mammal(){cout<<"Mammaldestructor\n"<public:
//virtualvoidSpeek(void){}
//virtualvoidSleep(void){}
};
classDog:
publicMammal
{
public:
Dog(){cout<<"Dogconstructor\n"<~Dog(){cout<<"Dogdestructor\n"<public:
//virtualvoidSpeek(void){}
//virtualvoidSleep(void){}
};
intmain(void)
{
MammalsmlMammal;
DogsmlDog;
cout<<"\nsizeof(smlMamal)is:
"<cout<<"\nsizeof(smlDog)is:
"<return0;
}
结论1:
从运行结果结合事先提出的假想得出结论:
基类:
由于基类没有虚函数也没有成员变量,所以基类对象不包含任何内容,只占用最小的存储单位。
子类:
由于基类没有虚函数和成员变量,子类也没有增加虚函数和成员变量,所以子类不包含任何内容,只占用最小的存储单位。
关于最上面提到的“小存储单位”:
在定义一个对象的时候,编译器会将它分配到某一段物理地址(具体大小又类的组成决定),这是天经地义的。
但是这里的两个对象都没有实际内容(没成员函数,也没有虚函数表指针)所以这个对象只占用被分配到的地址的一个字节,即使没有任何内容,但是这是必须要分配的。
2、基类包含虚函数,子类不包含虚函数
classMammal
{
public:
Mammal(){cout<<"MammalConstructor\n"<~Mammal(){cout<<"Mammaldestructor\n"<public:
virtualvoidSpeek(void){}
virtualvoidSleep(void){}
};
classDog:
publicMammal
{
public:
Dog(){cout<<"Dogconstructor\n"<~Dog(){cout<<"Dogdestructor\n"<public:
//virtualvoidSpeek(void){}
//virtualvoidSleep(void){}
};
intmain(void)
{
MammalsmlMammal;
DogsmlDog;
cout<<"\nsizeof(smlMamal)is:
"<cout<<"\nsizeof(smlDog)is:
"<return0;
}
结论2:
从运行结果结合事先提出的假想得出结论:
基类:
基类包含了虚函数,所以基类的实例需要一个指向基类虚函数表的指针,因此多了一个指针,而在地址总线为32位的计算机系统上指针的大小刚好4个字节。
这符合最初的假想。
因此可以推断,每个对象都有一个指向虚函数表的指针(如果有虚函数)。
子类:
子类虽然没有定义任何虚函数和变量,但是基类中有虚函数,因此必须包含一个指向子类虚函数表的指针。
因此可以推断,每个对象都有一个指向虚函数表的指针(如果有虚函数)。
关于最上面提到的“虚函数表”:
见书P103.11.5.1虚函数的工作原理。
3、基类包含虚函数,子类包含虚函数
classMammal
{
public:
Mammal(){cout<<"MammalConstructor\n"<~Mammal(){cout<<"Mammaldestructor\n"<public:
virtualvoidSpeek(void){}
virtualvoidSleep(void){}
};
classDog:
publicMammal
{
public:
Dog(){cout<<"Dogconstructor\n"<~Dog(){cout<<"Dogdestructor\n"<public:
virtualvoidSpeek(void){}
virtualvoidSleep(void){}
};
intmain(void)
{
MammalsmlMammal;
DogsmlDog;
cout<<"\nsizeof(smlMamal)is:
"<cout<<"\nsizeof(smlDog)is:
"<return0;
}
结论3:
从运行结果结合事先提出的假想得出结论:
基类:
同2。
子类:
子类定义了虚函数,基类中也有虚函数,因此必须包含一个指向子类虚函数表的指针。
因此可以推断,每个对象都有且仅有一个指向虚函数表的指针(如果有虚函数)。
4、基类不包含虚函数,子类包含虚函数
classMammal
{
public:
Mammal(){cout<<"MammalConstructor\n"<~Mammal(){cout<<"Mammaldestructor\n"<public:
//virtualvoidSpeek(void){}
//virtualvoidSleep(void){}
};
classDog:
publicMammal
{
public:
Dog(){cout<<"Dogconstructor\n"<~Dog(){cout<<"Dogdestructor\n"<public:
virtualvoidSpeek(void){}
virtualvoidSleep(void){}
};
intmain(void)
{
MammalsmlMammal;
DogsmlDog;
cout<<"\nsizeof(smlMamal)is:
"<cout<<"\nsizeof(smlDog)is:
"<return0;
}
结论4:
从运行结果结合事先提出的假想得出结论:
基类:
由于基类没有虚函数,所以基类对象不包含任何内容,只占用最小的存储单位。
子类:
基类没有定义虚函数,但是子类增加了虚函数,所以子类对象必须包含一个指向子类虚函数表的指针。
至此关于“虚函数”在类对象中的组成已经探讨完毕。
得出如下结论:
结论一:
类如果包含虚函数(可以是继承来也可以自己定义),那么编译器会生针对每个类生成一张虚函数表,类的对象会包含一个指向这个虚函数表的指针。
2、关于成员变量:
论证方法:
通过打印sizeof(smlMammal)、sizeof(smlDog)
针对Mammal和Dog成员变量在这两个类中的不同的组成方式来查看他们的大小,来推测虚函数在类对象中的表现形式。
1、都不包含成员变量
classMammal
{
public:
Mammal(){cout<<"MammalConstructor\n"<~Mammal(){cout<<"Mammaldestructor\n"<public:
//voidSpeek(void){}
//voidSleep(void){}
private:
//unsignedintitAge;
};
classDog:
publicMammal
{
public:
Dog(){cout<<"Dogconstructor\n"<~Dog(){cout<<"Dogdestructor\n"<public:
//virtualvoidSpeek(void){}
//virtualvoidSleep(void){}
private:
//unsignedintcolor;
};
intmain(void)
{
MammalsmlMammal;
DogsmlDog;
cout<<"\nsizeof(smlMamal)is:
"<cout<<"\nsizeof(smlDog)is:
"<return0;
}
结论2_1:
从运行结果结合事先提出的假想得出结论:
基类:
基类没有成员变量,所以基类对象不包含任何内容,只占用最小的存储单位。
子类:
基类没有成员变量,而且子类也没定义成员变量,所以子类对象不包含任何内容,只占用最小的存储单位。
2、基类包含成员变量,子类不包含成员变量
classMammal
{
public:
Mammal(){cout<<"MammalConstructor\n"<~Mammal(){cout<<"Mammaldestructor\n"<public:
//voidSpeek(void){}
//voidSleep(void){}
private:
unsignedintitAge;
};
classDog:
publicMammal
{
public:
Dog(){cout<<"Dogconstructor\n"<~Dog(){cout<<"Dogdestructor\n"<public:
//virtualvoidSpeek(void){}
//virtualvoidSleep(void){}
private:
//unsignedintcolor;
};
intmain(void)
{
MammalsmlMammal;
DogsmlDog;
cout<<"\nsizeof(smlMamal)is:
"<cout<<"\nsizeof(smlDog)is:
"<return0;
}
结论2_2:
从运行结果结合事先提出的假想得出结论:
基类:
基类含有成员变量,所以基类占用4个字节(int在本计算机系统中的大小)大小的空间。
子类:
基类含有成员变量,子类没定义成员变量。
所以子类包含一个继承自基类的成员变量占用4个字节大小的空间。
3、基类不包含成员变量,子类包含成员变量
classMammal
{
public:
Mammal(){cout<<"MammalConstructor\n"<~Mammal(){cout<<"Mammaldestructor\n"<public:
//voidSpeek(void){}
//voidSleep(void){}
private:
//unsignedintitAge;
};
classDog:
publicMammal
{
public:
Dog(){cout<<"Dogconstructor\n"<~Dog(){cout<<"Dogdestructor\n"<public:
//virtualvoidSpeek(void){}
//virtualvoidSleep(void){}
private:
unsignedintcolor;
};
intmain(void)
{
MammalsmlMammal;
DogsmlDog;
cout<<"\nsizeof(smlMamal)is:
"<cout<<"\nsizeof(smlDog)is:
"<return0;
}
结论2_3:
从运行结果结合事先提出的假想得出结论:
基类:
基类不含成员变量,所以基类占用1个字节。
子类:
基类不含成员变量,子类定义了一个成员变量。
所以子类自身包含一个成员变量占用4个字节大小的空间。
4、基类包含成员变量,子类包含成员变量
classMammal
{
public:
Mammal(){cout<<"MammalConstructor\n"<~Mammal(){cout<<"Mammaldestructor\n"<public:
//voidSpeek(void){}
//voidSleep(void){}
private:
unsignedintitAge;
};
classDog:
publicMammal
{
public:
Dog(){cout<<"Dogconstructor\n"<~Dog(){cout<<"Dogdestructor\n"<public:
//virtualvoidSpeek(void){}
//virtualvoidSleep(void){}
private:
unsignedintcolor;
};
intmain(void)
{
MammalsmlMammal;
DogsmlDog;
cout<<"\nsizeof(smlMamal)is:
"<cout<<"\nsizeof(smlDog)is:
"<return0;
}
结论2_4:
从运行结果结合事先提出的假想得出结论:
基类:
基类包含成员变量,所以基类对象占用4个字节。
子类:
基类包含成员变量,子类定义了一个成员变量。
所以子类自身包含一个成员变量同时有一个继承基类的成员变量占用4*2=8个字节大小的空间。
至此关于“成员变量”在类对象中的组成已经探讨完毕。
得出如下结论:
结论二:
成员变量线性改变类实例的大小。
3、同时包含多个虚函数和成员变量
classMammal
{
public:
Mammal(){cout<<"MammalConstructor\n"<~Mammal(){cout<<"Mammaldestructor\n"<public:
virtualvoidSpeek(void){}
virtualvoidSleep(void){}
private:
unsignedintitsAge;
unsignedintitsWeight;
};
classDog:
publicMammal
{
public:
Dog(){cout<<"Dogconstructor\n"<~Dog(){cout<<"Dogdestructor\n"<public:
virtualvoidSpeek(void){}
virtualvoidSleep(void){}
private:
unsignedintitsColor;
unsignedintitsSize;
};
intmain(void)
{
MammalsmlMammal;
DogsmlDog;
cout<<"\nsizeof(smlMamal)is:
"<cout<<"\nsizeof(smlDog)is:
"<return0;
}
结论2_4:
从运行结果结合事先提出的假想得出结论:
基类:
基类包含2个虚函数和2个成员变量。
所有虚函数在实例中只用一个指向虚函数表的指针,因此虚函数在实例中只占用4个字节的大小;两个int类型的成员变量分别占用4个字节。
因此基类实例共占用12个字节大小的空间。
子类:
子类中定义了2个虚函数和2个成员变量,同时从基类中继承了2个成员变量。
虽然在基类和子类中均有虚函数,但是所有虚函数只表现为一个指向虚函数表的指针,同时包含4个成员变量。
所以子类实例一共占用20个字节大小的空间。
结论三:
虚函数在实例中的表现仅为一个指向相应对象的虚函数表的指针;成员变量对实例大小的影响是线性改变的。
4、关于成员函数:
将基类中的Speek()成员函数前的virtual去掉,同时在子类中覆盖这个成员函数并定义一个新的成员函数Move()。
classMammal
{
public:
Mammal(){cout<<"MammalConstructor\n"<~Mammal(){cout<<"Mammaldestructor\n"<public:
voidSpeak(void){}
virtualvoidSleep(void){}
private:
unsignedintitsAge;
unsignedintitsWeight;
};
classDog:
publicMammal
{
public:
Dog(){cout<<"Dogconstructor\n"<~Dog(){cout<<"Dogdestructor\n"<public:
voidSpeak(void){}
voidMove(void){}
virtualvoidSleep(void){}
private:
unsignedintitsColor;
unsignedintitsSize;
};
intmain(void)
{
MammalsmlMammal;
DogsmlDog;
cout<<"\nsizeof(smlMamal)is:
"<cout<<"\nsizeof(smlDog)is:
"<return0;
}
结论四:
非虚函数在类的实例中不占用空间,对这些函数的调用是编译器内部机制实现的。
总结论:
假想:
类的实例由2部分组成:
成员变量、虚函数表指针(如果有虚函数),非虚函数不占用空间。
对非虚函数的调用由编译器内部机制实现。
成立
写在最后:
一、本文存在的问题:
1、由于没有事先准备,本文语言组织欠佳。
2、主函数中打印的内容和运行结果有一点不符,这是因为考虑到排版的问题,在文档中将打印内容作出了修改,而在编译环境中没有修改。
3、类定义中speek()应为speak()后来才发现写错,但是如果修改的话有大量工作需要做,因此没有修改。
但是这并不影响实验。
4、关于本文的中使用术语的准确性。
限于对C++和别的面向对象语言的了解,暂时不追求本文所提到的相关术语的准确性。
二、写本文的初衷:
非C++初学者可能会对本文嗤之以鼻,因为这些是一些基本常识。
但是由于本人水平有限,只是在此做了个小实验而已,供自己了解C++类的组成。