1.对象模型(objiect model):关于vptr和vtbl以及关于Dynamic Binding
继承时除了继承成员变量外,还继承成员函数;继承的成员函数是继承它的调用权;
动态绑定:即通过指向对象的指针找到相应的虚函数虚表,找到其中的第n个,把它当成函数指针,去调用这个函数;
(*(p->vptr)[n])(p)或(*p->vptr[n](p));
n就是这个虚函数在虚函数表格中的第几个;
第一,通过指针;
第二,向上转型;
第三,调用虚函数;
只要符合这三个条件,编译器就会走上面那条路线;
2.对象模型(objiect model):关于Dynamic Binding
假设:A是父类,B继承A,C继承B;
B b;
A a = (A)b;
a.vfun1(); //通过对象调用,不是指针,所以是静态绑定; //vfun1()是虚函数
A* pa = new B;
pa->vfun1(); //pa是基类指针,但是指向派生类对象,由于动态绑定的存在,它会找到派生类重写的虚函数,调用它,调用路线如上述1;
3.对象模型(objiect model):关于this指针
示例代码:
CDocument::OnFileOpen() //父类的OnFileOpen()函数
{
...
serialize();
...
}
virtual serialize();
class CMyDoc:
public CDocument
{
virtual serialize(){...};
}
int main()
{
CMyDoc myDoc;
...
myDoc.OnFileOpen();
}
在main()函数里,先定义了一个CMyDoc类型的对象myDoc,然后myDoc调用类的成员函数OnFileOpen();这个OnFileOpen不是CMyDoc写的,而是CMyDoc的父类CDocument写的,在父类CDocument里,它做完了OnFileOpen所有基础的工作,剩下一个serialize;这个函数是一个虚函数,CMyDoc在继承的时候重写了它;所以,结果上面那些操作,语句myDoc.OnFileOpen()会先执行父类已经做好的工作,等到调用serialize函数的时候,编译器会找到CMyDoc重写的serialize的内容,执行它;
所以OnFlieOpen()既有父类已经做好的内容,也有子类自己的部分;
其中,编译器在调用 myDoc.OnFileOpen()时,用C的写法,可以写成这样:CDocument::OnFileOpen(&myDoc);因为调用OnFileOpen的是MyDoc,所以this指针实际上是指向myDoc的。
在C++里,所有的成员函数一定都有一个隐藏的this指针作为参数;
4.谈谈const
const member function(常量成员函数)
const要放只能放在成员函数后头,一般的全局函数是不可以在函数描述后加const的;
放在那个位置的意思就是告诉编译器设计者本人不打算改变成员变量?
const算不算函数签名的一部分;算
变量是共享的,就必须Copy On Write
例:
charT
operator[](size_type pos)const
{..../*不必考虑COW*/}
reference
operator[](size_type pos)
{.../*一定要考虑COW*/}
C++语法规定,当成员函数的const和non-const同事存在时,const objiect只能调用const版本;
non-const objiect只能调用non-const版本;
5.关于new和delete的重载
new底层实际调用malloc,delete底层调用free
//下面是这四个重载函数的接口
//inline void* operator new(size_t size) //重载这四个操作符一点要有参数
{cout << " " ; return myAlloc(size);}
//inline void* operator new[](size_t size)
{cout << " " ; return myAlloc(size);}
//inline void* operator delete(void* ptr)
{cout << " " ; return myFree(ptr);}
//inline void* operator delete[](void* ptr)
{cout << " " ; return myFree(ptr);}
void* myAlloc(size_t size)
{return malloc(szie);}
void myFree(void* ptr)
{return free(ptr);}
上面四个重载函数只是提供一个接口,实际上new delete new[] delete[] 底层仍然调用malloc和free,在这些接口函数里,我们可以自己进行一些其他的操作,防止内存泄漏。
它们一旦被重载,影响深远,慎重慎重。
class Foo
{
public:
void* operator new(size_t size);
void* operator delete(void*,size_t);//size_t可要可不要
};
try
{
void* mem = operator new(sizeof(Foo));
p = static_cast<Foo*>(mem);
p->Foo::Foo();
}
p->~Foo();
operator delete(p);
Foo* p = new Foo;
...
delete p;
示例接口
Foo* pf = new Foo; //若没有重载,就使用全局的new和delete,如果重载就调用设计者设计的;
delete pf;
语法上提供,有下面一种用法
Foo* pf = ::new Foo;
::delete pf;
这样使用者会跳过设计者重载的这样使用者会跳过设计者重载的delete和new函数而是用全局的;
下面我们看看重载了new和delete new[]和delete[]的对象的大小;
对于下面这种情况,被new出来的Foo对象的大小是12;
Foo* p = new Foo(7);
delete p;
对于这种情况,被new[]和delete[]出来的对象大小是64(12*5+4),除了5个Foo对象之外,还有一个count计数器,记着使用者new了多少个Foo对象;
Foo* pArray = new Foo[5];
delete[] pArry;
重载new()和delete()
我们可以重载class member operator new(),写出多个版本,前提是每一个版本都必须有独特的参数类,第一参数必须是size_;
我们也可以重载class member operator delete(),写出多个版本。但它们绝对不会被到delete调用,只有当new所调用的构造函数。。。
即使operator delete()没有一一对应operator new(),编译器也不会报错;