前面我们提到过C中的struct结构体的大小,什么内存对齐,偏移量,然后Linux和Window下的还不一样,
搞懂了上面的这些,那就明白了C++中的了吗??
这里的结构体可是还有函数的,还有虚函数,友员函数,继承,多重继承,虚继承,等等一大堆东西,那这里面又是咋样的呢??
1,class空类
C++的空类是指这个类不带任何数据,即类中没有非静态数据成员变量,没有虚函数,也没有虚基类,也就是说:空类对象不使用任何空间,因为没有任何对象需要存储,但是,但是,但是,:C++标准规定,凡是一个对立的对象都必须具有非零大小:::::
所以,也就是说:空类或者空类的对象的大小也不为0。。。
#include <iostream>
using namespace std;
class test
{
};
int main()
{
cout<<sizeof(test)<<endl;
return 0;
}
运行结果:
所以我们可以看到空类test的大小为1
其实在C++早起的编译器中,这个值为0(也就是说空类的大小),然而当我们创建这样的对象是,它与紧接着后面的对象有相同的地址:
test tt;
int a;(这里tt和变量a有相同的地址),这样的话我们对对象tt的操作就会直接影响a,所以对与如今的编译器来说,空类的大小为1、
类的实例化就是在内存中分配一块地址,每一个实例在内存中都是独一无二的地址。所以空类实例化后也就有了自己独一无二的地址了
2,当含有虚成员函数时:
#include <iostream>
using namespace std;
class A
{
public:
virtual void fun();
};
int main()
{
cout<<sizeof(A)<<endl;
return 0;
}
运行结果(window,dev-c++):
此时,类的大小为4,因为在类中隐藏了一个指针,该指针指向虚函数表(具有虚函数表的地址),正是因为如此,使得C++能够支持多态,即在运行时绑定函数的地址。
运行结果(Linux):
所以可以看出,虚函数在linux系统里面占了8个字节
#include <iostream>
using namespace std;
class A
{
public:
virtual void aa()
{
}
private:
char k[3];
};
class B:public A
{
public:
virtual void bb()
{
}
};
int main()
{
cout<<"A is size:"<<sizeof(A)<<endl;
cout<<"B is size:"<<sizeof(B)<<endl;
return 0;
}
Linux中给出的结果是:
16,16
window中(vc,dev-c++)
8,8
说明:有虚函数的类有个virtual table(虚函数表),里面包含了类的所有虚函数,类中有个virtual table pointers,通常是:vptr指向这个virtual table,占有4个字节,成员类B public继承于A,类B的虚函数表里实际上有两个虚函数
A::aa()和B::bb()都在虚表里面,虚表的每一个表项保存着一个虚函数的入口地址。当调用虚函数时,我们是先找到虚表中的表项,找到入口地址再执行。
然而对于含有虚函数的类的大小而言,大小等于成员变量+指向虚表的指针
类B的大小等于char k【3】的大小加上一个指向虚函数表指针vptr的大小,并且还必须考虑内存对齐,故结果为:8
所以,觉得应该是在linux下:在类中涉及到虚函数的指针(指向虚表的指针+指向父类的指针)都应该占用8个字节,下面的程序都可以这样理解!!!
#include <iostream>
using namespace std;
class A
{
public:
virtual void aa()
{
}
private:
char k[3];
};
class B:public A
{
};
int main()
{
cout<<"A is size:"<<sizeof(A)<<endl;
cout<<"B is size:"<<sizeof(B)<<endl;
return 0;
}
window下(vc和dev-c++):
A is size:8
B is size:8
linux下:
A is size:16
B is size:16
类B看上去没有虚函数,实际上类B也是有的,它是通过继承A来的,所以说,类A和类B的虚函数表里面都是A::aa()
#include <iostream>
using namespace std;
class A
{
public:
virtual void aa()
{}
virtual void aa1()
{}
private:
char k[3];
};
class B:public A
{
virtual void bb()
{}
virtual void bb1()
{}
};
int main()
{
cout<<"A is size:"<<sizeof(A)<<endl;
cout<<"B is size:"<<sizeof(B)<<endl;
return 0;
}
window下(vc和dev-c++)
A is size:8
B is size:8
linux下:
A is size:16
B is size:16
可以看出:一个类中若存在多个虚函数,无论有多少个虚函数都只是存在一个指向虚表的指针,虚表的每一个表项保存着一个虚函数的入口地址。当调用虚函数时,我们是先找到虚表中它对应的入口地址,找到入口地址再执行。对于上面这种情况:
类A的虚表中存在的是:A:aa() ,A:aa1()
类B的虚表中存在的是:A:aa() ,A:aa1(),B:bb(),B:bb1()
#include <iostream>
using namespace std;
class A
{
public:
virtual void aa()
{}
virtual void aa1()
{}
private:
char k[3];
};
class B
{
virtual void bb()
{}
virtual void bb1()
{}
};
class C:public A,public B
{
public:
virtual void aa(){} //重写虚函数
virtual void cc()
{}
};
int main()
{
cout<<"A is size:"<<sizeof(A)<<endl;
cout<<"B is size:"<<sizeof(B)<<endl;
cout<<"C is size:"<<sizeof(C)<<endl;
return 0;
}
window下(vc和linux)
A is size:8
B is size:4
C is size:12
linux下:
A is size:16
B is size:8
C is size:24
类A和B的大小就不用解释了,参照上面我们所讲的,类C(有虚函数的覆盖)多重继承于A和B首先我们来看成员变量,有一个继承A的char[3].
在来看虚函数,那么类C中的虚函数是如何分布的,它可是继承了两个基类啊!!!(有两个虚函数表)
1,第一个虚函数表,继承于类A的虚函数和C自己的虚函数(C::aa(),A::aa2(),C::cc()),当然如果我们没有重写虚函数的话,那么第一个虚函数表就是:(A::aa(),A::aa2(),C::cc())
2,第二个虚函数表,继承于类B的虚函数和C自己的虚函数(B::bb(),B::bb2(),C::aa(),C::cc())嗯嗯,应该就是这样了
所以说:总的大小就是指向两张虚表的大小在加上继承过来的成员变量,加上内存对齐的因素就是12字节
#include <iostream>
using namespace std;
class A
{
public:
virtual void aa()
{}
private:
char k[3];
};
class B:virtual public A
{
};
int main()
{
cout<<"A is size:"<<sizeof(A)<<endl;
cout<<"B is size:"<<sizeof(B)<<endl;
return 0;
}
window下(vc和linux)
B is size:12
linux下:
A is size:16
B is size:24
类B里包含继承过来的char k【3】,继承的虚函数
类B的虚函数表里有A::aa(),所以有一个指向虚表的指针,又因为是虚继承,所以还有一个指向父类的指针,加上成员变量,考虑内存对齐,所以总大小为12
友元函数:
#include <iostream>
using namespace std;
class A
{
public:
friend void kk();
};
int main()
{
cout<<"A is size:"<<sizeof(A)<<endl;
return 0;
}
运行结果:
A is size :1
所以我们可以知道友元函数如同其他函数就是一样的,也是不影响类的大小,如上,类A也相当于空类!
#include <iostream>
using namespace std;
class A
{
public:
static int a;
};
int main()
{
cout<<"A is size:"<<sizeof(A)<<endl;
return 0;
}
运行结果:
A is size :1
所以此刻A类还是空类!!!
空类是指这个类不带任何数据,即类中没有非静态数据成员变量,没有虚函数,也没有虚基类,也没有函数(包括友元函数)
所以:每个对象里有虚表指针,指向虚表,虚表里面存放了虚函数的地址。虚函数是顺序存放虚函数地址的。不需要
用到链表(link list)
如下程序:
#include <iostream>
#include <memory.h>
#include <assert.h>
using namespace std;
class A
{
char k[3];
public:
virtual void aa() {};
};
class B:public virtual A //我知道了,这里的virtual是虚继承的意思
{
char j[3];
//加入一个变量是为了看清楚class中vfptr放在什嘛位置
public:
virtual void bb() {};
};
class C:public virtual B
{
char i[3];
public:
virtual void cc() {};
};
int main()
{
cout<<"sizeof(A):"<<sizeof(A)<<endl;
cout<<"sizeof(B):"<<sizeof(B)<<endl;
cout<<"sizeof(C):"<<sizeof(C)<<endl;
return 0;
}
运行结果:
关于虚拟继承:
虚拟继承是多重继承中特有的概念。虚拟继承是为了解决多重继承而出现的。
类D继承自类B和类C,而类B和类C都继承自类A,因此出现如下图所示这种情况
在类D中会再次出现A。为了节省空间。可以将B,C对A的继承定义为虚拟继承,而A就成了虚拟基类。如下图:
class A;
class B:public virtual A;
class C:public virtual A;
class D:public B, public C;
虚函数继承和虚继承是完全不同的概念。。。
基类和派生类的地址和内存布局的问题
#include <iostream>
using namespace std;
class A
{
int a;
};
class B
{
int b;
};
class C:public A, public B
{
int c;
};
int main()
{
C *pc = new C;
B *pb = dynamic_cast<B*>(pc);
cout<<"pc :"<<pc<<endl;
cout<<"pb :"<<pb<<endl;
cout<<"(c*)pb :"<<(C *)pb<<endl;
if(pc == pb)
cout<<"equal"<<endl;
else
cout<<"not equal"<<endl;
cout<<"int(pc):"<<(long)pc<<endl;
cout<<"int(pb):"<<(long)pb<<endl;
if((long)pc == (long)pb)
cout<<"equal"<<endl;
else
cout<<"not equal"<<endl;
return 0;
}
运行结果:
如上:if(pc == pb)
两端的数据类型不同,比较时需要进行隐式类型转换。(通常情况下,我们都是往高类型上转换(int char
会转成int))
(pc == pb)相当于:pc == (C *)pb;
pb实际上指向的地址是对象C中的子类B的部分,从地址上跟pc不一样,所以直接比较是不想等的。
但是:(C *)隐式转换pb后的pb指向的对象(pc指向的对象)的部分,是同一部分,也就显示相等了
而第二处:两端转换为int,指针pc和pb的值不同,转换后的int值不同。。。。
本文关于虚函数的类的介绍:
摘自(谢谢啦):http://www.cnblogs.com/luxiaoxun/archive/2012/09/01/2666395.html
关于C++虚函数,可以看《C++虚函数表解析》:http://blog.csdn.net/haoel/article/details/1948051