类详解:
一,类在内存中的存放方式
<1>不含成员变量的类
class A
{
//sizeof(A) == 1
//空类具有1个字节的唯一标识,用于区别不同的空类
};
class B
{
public:
//sizeof(B) == 1,其他同上
//成员函数不占用任何对象内存空间
B(){}
~B(){}
};
class C
{
public:
//sizeof(C) == 4
//拥有虚函数的类则会创建一个虚表指针
C(){}
virtual ~C(){}
};
<2>静态变量&常量。
class cA
{
public:
int a;
int b;
static int z;
//const int e = 0;这样初始化时错误的,类常量只能使用构造函数
const int e;
public:
cA(int pe):e(pe)
{
}
static void function1()
{
}
};
内存结构如下
由此可以知道:
(1)类常量只能在构造函数中赋值。
(2)静态函数和静态变量不占用类内存空间。
<3>含成员变量的类。
(1)无虚函数类
class A
{
public:
int a;
int b;
public:
void function1(){}
};
内存排列输出如下:
由此可以知道,可以看见类的成员函数并不会占内存空间,而类的成员变量则会按顺序储存。
(PS,假如类的成员变量涉及多种类型,则需要进行字节对齐)。
(2)含虚函数类
class A
{
public:
int a;
int b;
public:
void function1(){}
virtual void fuction2(){}
};
内存排列输出如下:
由此可以知道:
(1),当存在虚函数时,在类内存的开始处(偏移值为0时),会保存一个虚表指针,然后再按顺序存放成员变量。
(2),接下来是虚表,它记录了一张虚表,该虚表表示对应的虚指针在内存中的分布,左边的0表示第一个虚函数的位置。
编译器是在构造函数创建此虚表指针以及虚表的。
问:那么编译器是如何利用虚表指针和虚表实现多态的呢?
答:当创建一个含有虚函数的父类的对象时,编译器在对象构造时将虚表指针指向父类的虚函数;同样,当创建子类的对象时,编译器在构造函数里将虚表指针(子类只有一个虚表指针,它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)。
(3)继承后的类
class A
{
public:
int a;
int b;
public:
void function1(){}
virtual void fuction2(){}
};
class B:public A
{
public:
int c;
public:
virtual void function2(){}//重写
void function3(){} //属于派生类的成员函数
}
内存排列输出如下:
由此可以知道:
(1)内存排列继续沿用cA的内存结构,然后在末尾增加新的成员变量内存空间。
(2)虚函数表使用的是cB自己的虚表。
(4)带有自己虚函数及没重载父类虚函数的的派生类。
class B:public A
{
public:
int c;
public:
virtual void function2(){}//重写
virtual void function3(){} //属于派生类的成员函数
}
内存排列输出如下:
由此可知道:
(1)没有重载父类虚函数,则虚函数表使用的是父类的虚函数。
(2)新增的派生类虚函数,会加进派生类的虚表。
(5)多继承派生类的内存结构。
class C:public A
{
public:
int c;
public:
virtual void function2(){}//重写
virtual void function3(){} //属于派生类的成员函数
}
class D:public C,public B
{
}
类D的内存结构如下:
由此可知道:
(1)多重继承会按照继承的顺序将虚表指针及成员变量按顺序排列。
(2)将会记录所有直接父类的虚表,因此我们调用function2和function3时必须使用对应的父类标记调用。
cD *tmp = new cD();
tmp->function2();//这样编辑器会提示函数不明确,不能编译
tmp->B::function2();//这样写能正确调用
假如我修改类D重载B,C的function3,将会将屏蔽B,C的虚函数:
class D:public C,public B
{
//属于派生类的成员函数
virtual void function3()
{
}
}
则在虚表部分会有相应的修改
(6)虚继承。
class D:virtual public C,virtual public B
{
//属于派生类的成员函数
virtual void function3()
{
}
}
内存结构如下:
二,类引申出来的问题
(1)指向类的指针在不实例化的情况下,调用类的成员函数有什么结果,虚函数呢?
答:由类的内存结构我们可以知道,类的成员函数(非虚函数,也包含构造函数与析构函数)是储存在一个固定的位置,和类的实例化无关(类的实例化只申请类成员变量空间和绑定虚函数表)。
因此类没实例化之前,类的成员变量和虚函数表没有初始化,但是类的成员函数因为与类的实例化无关,所以可以正常调用。但是此时请注意,在类的成员函数中假如使用到类的成员变量,程序将会运行时出错,同理,调用虚函数时也会出错。
(2)在类的构造函数中能调用delete this吗,会出现什么结果?
答:此问题主要考察类调用函数的原理,由问题(1)我们可以知道,类不实例化时还是能调用自己的成员函数的(但函数不能操作成员变量),其实类在调用成员函数时就是将自己的this指针作为参数传进成员函数,因此成员函数能随时使用this中指向的已被创建出来的成员变量,因此,由于构造函数也是成员函数,所以在构造函数是可以使用delete this的,但是delete后该变量的实例化将会被消除,后续就不能使用了。
参考文档:
c++ 类成员函数内删除this指针
http://blog.csdn.net/fridayzhu/article/details/32396205
C++类内存分布
http://www.cnblogs.com/jerry19880126/p/3616999.html
声明:
该文仅作学习与记录之用,欢迎技术纠错和讨论;
非技术性言论皆为一家之谈,如有不同意见请坚持己见;
如有雷同可能为学习汝之所得,请各位巨人的肩膀还请继续空出位置。