面向对象中一些知识点
文章中的图片来源于侯捷老师的课件
用一句话说,构造函数由内而外,析构函数由外而内。即子类会先调用基类的default构造函数,然后才执行自己的;析构的时候,子类先析构自己的,然后才调用基类的析构函数。无论是调用基类的构造函数还是调用基类的析构函数,都是由编译器自动调用的,不需用户专门设计。
复合关系也是如此。
-
vptr和vtbl
看下面具有继承关系的三个类:
1 class A { 2 public: 3 virtual void vfunc1(); 4 virtual void vfunc2(); 5 void func1(); 6 void func2(); 7 private: 8 int m_data1, m_data2; 9 }; 10 11 class B :public A { 12 public: 13 virtual void vfunc1(); 14 void func2(); 15 private: 16 int m_data3; 17 }; 18 19 class C :public B { 20 public: 21 private: 22 int m_data1, m_data4; 23 };
一个类占用多大的内存是由其数据成员决定的,so,class A,B,C在内存中的位置如下图最左边所示,如图,A的成员是data1和data2,;B首先继承了A,所以拥有data1和data2,再加上自己的成员data3,C同理。当然,最上方的地址是因为虚函数的存在,只要class中有虚函数,不管多少,都一样,对象中就会多出一个最上方所示的指针,所以大小就是成员的大小加4(32位系统),Note:继承时继承的不止有数据,还有函数,但是,函数无法计算占内存多大,所以,函数继承的是调用权,不是继承内存的大小。所以,父类有虚函数,子类一定有。那么,普通函数和虚函数在内存中有什么不同呢?A,B,C共有8个函数,如下图最右侧,下面将左边的虚指针和右边函数关联起来:A中有虚函数,所以必然有虚指针,会指向一个虚表,两个虚指针分别指向了A的v1和A的v2,B有两个虚函数,但重写了v1,只继承A的v2,所以B的虚指针分别指向了B的v1和A的v2,C的分析同理。

如果现在有一个指针指向左侧C的整块空间,我们new C就可以得到一个指针p,通过这个指针调用vfunc1(),就是所谓的动态绑定(dynamic bindding),而我们通常调用一个普通函数的时候,编译器会调到所在地址,完成后再回来,产生的是静态绑定,而动态绑定的是通过顶端的指针找到虚指针,然后再找到虚表,再找到指向的函数。,解析形式就是上面图片中的代码部分。这个指针必须指向的是父类。举个课本的例子,就是图形的例子,父类 figure,子类可以有 circle,trangle,square....,他们都与draw()函数,在父类中,我吗就需要将draw()定义为virtual的,每个子类都会有自己的draw(),那么,动态绑定的必要条件是什么呢?1.必须通过指针调用,2.该指针向上转型,3.调用虚函数
这种虚函数的机制就是C++中的多态
-
this 指针
通过对象调用一个函数,哪个对象的地址就是this pointer,使用下面的图片解释this 的使用

图中,显示有两个class,一个是CDocument,这个class有一个函数OnFileOpen(),和子类CMyDoc,该类有一个虚函数Serialise(),在父类中调用。再看mian()函数中的使用,首先创建一个子类对象,然后调用父类的OnFileOpen()函数,执行到Serialize()的时候,就会转到子类中的,执行完后就又回去,如图上箭头所示。之所以会转到子类中的Serialize(),就是因为动态绑定这里就又一个隐藏的this pointer,对象myDoc 的地址就是这个隐藏的this pointer
Note:在C++的所有成员函数中,一定有一个隐藏的this poiter 作为参数,so,该隐藏的this pointer会传给OnFileOpen(),传进去后,就会被编译器解析为左上方的代码
- const
- const member functions
以之前的复数为例:
1 class complex 2 { 3 public: 4 complex (double r = 0, double i = 0): re (r), im (i) { } 5 complex& operator += (const complex&); 6 7 double real () const { return re; } 8 double imag () const { return im; } 9 private: 10 double re, im; 11 12 friend complex& __doapl (complex *, const complex&); 13 14 };

先用一张表给出const member functions 和non-const ,ember functions

当成员函数的const和non-const 版本同时存在时,const object 只能调用const版本,non-const object只能调用non-const 版本。const member function 就是告诉编译器,这个函数不会改变class 的data
比如
const String str("Hello,World"); str.print();
如果,设计 String::print() 时没有指明是const的,那就是const object调用non-const member function,就会编译出错
class template std::basic_string<....> //有如下两个成员函数: char T operator[](size_type pos)const {/*必须考虑COW*/} reference operator[](size_type pos) {/*不必考虑COW*/}
COW:Copy on write
上面两个重载函数是可以并存的。const是属于签名的一部分?,
- new 和delete
在复数的例子中有过应用,不多说,下面说一下new和delete的重载
1 void* myAlloc(size_t size) { return malloc(size); } 2 void myFree(void* ptr) { return free(ptr); } 3 //注意,下面的函数不可以声明于同一namespace内 4 inline void* operator new(size_t size) 5 { 6 cout << "global new() " << endl; 7 return myAlloc(size); 8 } 9 10 inline void* operator new[](size_t size) 11 { 12 cout << "global new[]() " << endl; 13 return myAlloc(size); 14 } 15 16 inline void operator delete(void* ptr) 17 { 18 cout << "global delete() " << endl; 19 return myFree(ptr); 20 } 21 22 inline void operator delete[](void* ptr) 23 { 24 cout << "global delete[]() " << endl; 25 return myFree(ptr); 26 }
我们使用new和delete时,如果我们自己重载了new和delete,编译器会调用我们重载的,如果没有重载,就调用全局的new和delete
重载member operatornew/delete
class Foo { public: void* operator new(size_t); void operator delete(void*, size_t); }; Foo* p new Foo; ....... delete p;
重载 member operator new[]/delete[]
class Foo { public: void* operator new[](size_t); void operator delete[](void*, size_t); }; Foo* p new Foo[N]; ........ delete[] p;
下面用一个相对完整的程序

浙公网安备 33010602011771号