class的成员变量分为static 和nonstatic两类
class的成员方法分为static nonstatic 和virtual三类。
class实例化的过程中,实际上实例化的是类的nonstatic成员变量,这也决定了类的内存消耗。
继承分为单继承 多继承和虚拟继承。
对子类的实例化,会先对父类进行实例化,子类是如何引用父类的,是用指针还是直接包含类的实现,因为每一个子类的父类实际上不是共享的,因此子类的父类应该是包含在子类的内存域中。
多继承和虚拟继承的差别:公共父类是否会被实例化多次。虚拟继承使用了base class table的间接引用的模型。
每一个table,就意味着一次查找。
三种设计模式:
1 面向过程
2 抽象数据类型,将数据使用接口封装,因此就抽象了
3 面向对象,对象的接口是虚表,因此接口的特性随着类的具体实例化而
类的继承之内存排布:
1 首先是虚表指针,
2 父类的非静态成员变量
3 子类的非静态成员变量
如下面的代码:
#include <iostream>
using namespace std;
class B{
public:
virtual void foo(){
cout<< "B foo";
};
int i;
int j[100];
};
class A:public B{
public:
virtual void foo(){
cout << "A foo";
}
int i;
int j[200];
};
int main(void){
A a;
cout<<&a;
a.foo();
B * pa=&a;
pa->foo();
}
a的内存的大小包括了父类的i,j和自己的i,j,a的地址起始处是虚表指针。
pa这个函数指针在调用的过程中,编译器不会制定具体掉的哪个函数,只会续表指向的地址,放入到一个寄存器中,再call。
反汇编的结果如下:
(gdb) disassemble main
Dump of assembler code for function main():
0x0000000000400940 <+0>: push %rbp
0x0000000000400941 <+1>: mov %rsp,%rbp
0x0000000000400944 <+4>: sub $0x4d0,%rsp
0x000000000040094b <+11>: lea -0x4d0(%rbp),%rax
0x0000000000400952 <+18>: mov %rax,%rdi
0x0000000000400955 <+21>: callq 0x400a50 <A::A()>
0x000000000040095a <+26>: lea -0x4d0(%rbp),%rax
0x0000000000400961 <+33>: mov %rax,%rsi
0x0000000000400964 <+36>: mov $0x6012e0,%edi
0x0000000000400969 <+41>: callq 0x400800 <_ZNSolsEPKv@plt>
0x000000000040096e <+46>: lea -0x4d0(%rbp),%rax
0x0000000000400975 <+53>: mov %rax,%rdi
0x0000000000400978 <+56>: callq 0x400a1c <A::foo()>
0x000000000040097d <+61>: lea -0x4d0(%rbp),%rax
0x0000000000400984 <+68>: mov %rax,-0x8(%rbp)
=> 0x0000000000400988 <+72>: mov -0x8(%rbp),%rax
0x000000000040098c <+76>: mov (%rax),%rax
0x000000000040098f <+79>: mov (%rax),%rax
0x0000000000400992 <+82>: mov -0x8(%rbp),%rdx
0x0000000000400996 <+86>: mov %rdx,%rdi
0x0000000000400999 <+89>: callq *%rax /这就是多态
0x000000000040099b <+91>: mov $0x0,%eax
0x00000000004009a0 <+96>: jmp 0x4009aa <main()+106>
0x00000000004009a2 <+98>: mov %rax,%rdi
0x00000000004009a5 <+101>: callq 0x400820 <_Unwind_Resume@plt>
0x00000000004009aa <+106>: leaveq
0x00000000004009ab <+107>: retq
End of assembler dump.
在汇编看来,多态,就是一次间接寻址而已。
类的虚表示独立于类的实例的,因此一个类的拷贝构造过程中,虚表指针实际上是通过目标类确定的。
比如讲a赋值到B的一个实例b,b还是B的虚表。
多继承:
源码如下:
class X{
int i;
virtual void foo(){
cout<< "X foo";
}
};
class C: public X{
int i;
virtual void foo(){
cout<< "C foo";
}
};
class B : public X{
public:
virtual void foo(){
cout<< "B foo";
};
int i;
};
class A:public C, public B{
public:
void foo(){
cout << "A foo";
}
int i;
};
(gdb) p a
$1 = {<C> = {<X> = {_vptr.X = 0x400cf0 <vtable for A+16>, i = 4197360}, i = 0}, <B> = {<X> = {_vptr.X = 0x400d08 <vtable for A+40>, i = 4196576}, i = 0}, i = -6976}
虚拟继承:
源码:
using namespace std;
class X{
int i;
virtual void foo(){
cout<< "X foo";
}
};
class C: public X{
int i;
virtual void foo(){
cout<< "C foo";
}
};
class B : public X{
public:
virtual void foo(){
cout<< "B foo";
};
int i;
};
class A:virtual public C, virtual public B{
public:
void foo(){
cout << "A foo";
}
int i;
};
int main(void){
A a;
cout<<&a;
a.foo();
X x;
B b;
C c;
}
(gdb) p a
$1 = {<C> = {<X> = {_vptr.X = 0x400d60 <vtable for A+64>, i = 65535}, i = 1}, <B> = {<X> = {_vptr.X = 0x400d80 <vtable for A+96>, i = 4197080}, i = 0},
_vptr.A = 0x400d40 <vtable for A+32>, i = 4197059}
(gdb) p x
$2 = {_vptr.X = 0x400e00 <vtable for X+16>, i = 4196576}
(gdb) p b
$3 = {<X> = {_vptr.X = 0x400dc0 <vtable for B+16>, i = 4197424}, i = 0}
(gdb) p c
$4 = {<X> = {_vptr.X = 0x400de0 <vtable for C+16>, i = 4197517}, i = 0}