面向对象中一些知识点

 文章中的图片来源于侯捷老师的课件

用一句话说,构造函数由内而外,析构函数由外而内。即子类会先调用基类的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;

下面用一个相对完整的程序

 

posted @ 2018-03-24 18:39  Holly_U  阅读(193)  评论(0)    收藏  举报