C++ Advanced Training(二)

类别:编程语言 点击:0 评论:0 推荐:

C++ Advanced Training(二)

-------C++&OOP

 

今天侯老师花了2个小节的时间把昨天的“尾巴”讲完,然后就进入今天的正题OOP,注意是OOP,not OOD。

 

听了侯老师的两天课,感觉他的讲课风格是

²        关注细节

²        以讲”故事”的方式来讲解抽象的技术。   

 

我将继续接上一节的内容谈C++。

1、Increment operator(++)

++ operator分为 ++A 和A++两种,实际在实现中A++调用了++A。我们举个例子

class Fraction

{

       Fraction& operator++();

       Fraction& operator++(int);

}

inline Fraction& operator++()

{

       m_numerator += m_denominator;

       return *this;

}

 

inline Fraction& operator++(int)

{

       Fraction oldValue = *this;

       ++(*this);      // call the prefix increment

       return oldValue;      //why?

}

 

从以上的代码段中我们可以得到两个结论:

1)从代码可以看出在使用++ operator时,特别是对自定义类型的++时,尽量选用++A型,因为A++在实现中实际上是调用A++,所以A++型要比++A型执行速度慢。

2)我们在设计数值型class时,最好以int为参照物。这也是为什么Fraction& operator++(int)返回oldValue的原因。我们举例说明在使用primitive type int时,++的用法:

int a = 5;

int b = a++;

cout << a << endl;  // a = 6

cout << b << endl; // b = 5

 

可见A++型,是先返回A的值,再做++操作。所以我们在自定义数值型class的时候也要模拟这种方式,使++ operator的使用方式保持一致,无论对primitive type 还是user-defined type。

 

2、scope and lifetime

 

这里总结以下各种object的lifetime:

²        global object                               program始 ,program终

²        local(auto) object                         scope始 , scope终

²        heap(dynamic allocated ) object    new始 , delete终

²        static local object                         scope始 ,program终

 

说明:

1)      global object的建构是在main之前所以利用global object的ctor可以帮助你做一些有用的事,MFC就利用了这点完成了许多有用的操作。

2)      在program终止之前(即在main函数执行结束之前),有global object , static local object at somewhere 和local object in main等的dtor会被调用。但是次序不定(视编译器实作方式而定),下面代码列出VC++7.1的做法:

#include <iostream>

#include <string>

using namespace std;

 

class Test2

{

       public:

              Test2(const string& str) : m_name(str)

              {

                     cout << "constructor called for " << m_name << endl;

              }

              ~Test2()

              {

                     cout << "destructor called for " << m_name << endl;

              }

       private:

              string m_name;

};

 

void g_func()

{

       static Test2 l_TestObj1("StaticLocalObjInGlobalFunc");

}

 

Test2 g_TestObj("GlobalObj");

 

int main(int argc, char *argv[])

{

       Test2 l_TestObj2("LocalObjInMain");

       g_func();

       return 0;

}

 

Output:

constructor called for GlobalObj

constructor called for LocalObjInMain

constructor called for StaticLocalObjInGlobalFunc

destructor called for LocalObjInMain

destructor called for StaticLocalObjInGlobalFunc

destructor called for GlobalObj

 

3、static member

 

1)static data members

²        独立于objects之外,众多objects共享一份static data members,也就是说每个class只有一份;

²        static data members可被继承(其access level)。

 

2)static member function 特点

²        没有this pointer ,因此就像non-member function一样;

²        必定不为virtual;

²        可以不通过object而直接访问(通过类的全名,如Accout::setRate())。

 

3)static member function 用途

²        用于处理static data member;

²        用于callback function。

static member function用于处理static data member无可厚非,我们也不必细讲,关键是为什么使用static member function来用于callback,为什么不直接是用non-static member function?

 

首先我们要知道什么是callback function?callback function是如何运行的?

callback中文译为“回调”,台湾译为“回呼”,我们拿一个实际的例子来解释什么是callback , callback function是如何工作的?

在Window平台上开发GUI应用程序时,我们会常常用到一个Win32 API,其原型如下:

BOOL LineDDA(

  int nXStart,             // x-coordinate of line's starting point

  int nYStart,             // y-coordinate of line's starting point

  int nXEnd,               // x-coordinate of line's ending point

  int nYEnd,               // y-coordinate of line's ending point

  LINEDDAPROC lpLineFunc,  // pointer to callback function

  LPARAM lpData            // pointer to application-defined data

);

这个函数的用途在msdn中被描述为 “The LineDDA function determines which pixels should be highlighted for a line defined by the specified starting and ending points. ”这个函数是做什么的我们不关心,我们关心的是它的第5个参数,这是一个LINEDDAPROC类型的函数指针,也就是说我们要使用LineDDA这个函数就必须传入一个函数地址,这是因为LineDDA在执行过程中有些动作不能确定,需要我们来告诉它怎么做,我们如何告诉它呢,就通过传入这个有着固定signature的函数的地址,而这个被LineDDA所使用的函数就叫做callback function。下面用一个图形表示这个过程:

 

通过上面的图示我们可以看出:

callback function的signature是事先定义好的,包括参数的类型和个数等。

下面我们我们就利用这个来解释为什么non-static member function不能作为callback function了。我们都知道一个class的non-static member function在被调用时,编译器会将this这一隐藏的指针加入到该funtion的参数列表中去,导致参数的个数增加而不符合callback预先定义好的signature。而static member function不含有this这一隐藏指针,所以完全胜任callback function这一角色。

 

4)static member function、non-static member function 、static data member和non-static data member之间的关系

 

告诉大家一个总的原则,理解上述几个member关系的关键在于this指针。

 

具体地说:

non-static member function既可以调用static member function,也可以处理static data member;

static member function则既不能调用non-static member function,也不能处理non-static data member。

 

 

4、new expression(new operator)&operator new

 

new operator和operator new这两个东西让一些初学者感到不能理解,甚至包括一些用过很长时间C++的老手都很可能被迷惑,这两个到底有什么区别?各自代表什么意思呢?

 

我们举个例子大家就清楚了。

Complex* pc = new Complex(1,2);      //这句代码里的new就是new operator,它是C++ 的一个关键字,当这条语句执行时,编译器会执行一系列动作。依次为:

²        调用::operator new分配内存空间;

²        casting(转型)

²        invoke Complex的constuctor

其中第一步调用::operator new分配内存空间中的::operator new就是我们所说的后者,它是真正分配内存的执行者,相当于C中的malloc函数,与malloc不同的是::operator new可以被重新定义,你可以定义你自己class专用的operator new函数。为什么我们要这么做呢?因为使用默认的::operator new分配每一块内存的同时也会分配一块叫cookie的内存块用来存放一些帮助内存管理的信息,如分配的内存的大小,供delete使用。在一些embeded system中,memory是limited的。我们要尽量减少cookie的分配,所以我们要定义自己的operator new。比如我们可以事先分配一大块内存,以后再需要动态分配内存时,就在这个大块内存中再分配出来既可。

 

operator new 在对象产生之前被调用,所以必须是static的。(同理,operator delete在对象被销毁后被调用,也应该是static的),一般即使你不explicit的声明为static的,编译器也会自动默认为static的。

 

5、delete expression(delete operator)&operator delete

 

有了4中的new operator&operator new的基础,这节的东西就很好理解了。

delete pc;

编译器会执行一系列动作,依次是:

²        invoke Complex的destructor;

²        调用::operator delete释放内存空间。

 

::operator delete 等价于C的free函数。

::operator delete和::operator new类似也可以被重新定义你自己的版本。

下面举个例子(包含operator new 和operator delete)

class Base

{

       public:

              static void* operator new(size_t size);

              static void operator delete(void* rawMemory , size_t size);

};

 

void* Base::operator new(size_t size)

{

       if(size != sizeof(Base)) //大小错误,可能是被子类调用

              return ::operator new(size);//交给默认处理函数处理

       else

              //your code to alloc the memory

}

 

void Base::operator deletevoid* rawMemory , size_t size)

{

       if(rawMemory == 0) return;

       if(size != sizeof(Base)) //大小错误,可能是被子类调用

       {

              ::operator delete(rawMemory);//交给默认处理函数处理

              return;

       }

       else

       {

              //your code to free the memory

       }

}

 

main()

{

       Base *p = new Base();          //call the operator new which you defined

             

       delete p;  // call the operator delete which you defined

}

 

main代码中当编译器扫描到new时会看Base类中是否重新定义了operator new,如果是则调用Base专用的operator new。delete也是同理。

 

注:关于operator new &operator delete的一个原则就是:如果你写了一个operator new,就应该写一个对应的operator delete

开始OOP

侯老师认为学好OOP就要学好两方面:polymorphism和template method。

 

我的一个同事一直和侯老师争论下面的这两个概念的理解,这里我把我的理解写下来:

framework &application framework

framework----  it is always a library which is large ,complex and have many classes and many associations among these classes. such as c++ library , Microsoft .net class library,Win32 API

application framework---- it have helped you define the skeleton of the application ,what you should do is only to override some virtual functions or add some business logic code , that is all。such as MFC ,VCL等。

6、SubObject and virtual destructor

我们看一个例子来说明subobject的概念和virtual destructor的用途。

 

大家从上面的图中也会有所了解subobject的概念。在CSquare object中,既有CRect的suboject又有CShape的subobject。它们的构造顺序是:由内向外,而析构顺序为:由外向内。

如果有下面代码:

CRect* p = new CSquare();

delete p;

这时如果CRect的dtor为non-virtual的,上述的代码就相当于企图用一个拥有non-virtual dtor的base class的指针来删除一个derived class oject, 其结果是未定义的。最可能的是执行期未调用derived object的dtor, 因为compiler看到基类拥有的是non-virtual dtor,所以根据p的静态类型将dtor编死,而不经过虚拟机制的route。所以告诫如下:“总是让base class拥有virtual dtor”。这样通过虚拟机制route的编译会将derived类的dtor编进去,我们就能够通过基类指针销毁derived object了。

 

7、Template method

 

其实这是design pattern的内容,由于这个pattern比较好理解,所以侯老师把它拿到前面来了。我们还是画图理解比较容易,

 

侯老师说理解这个关键在于理解library code(你用money买的) 和application code(你自己写的),心中在这两个code之间划一条线(见图中那条虚线),库代码都是固定的,不会因为你的业务逻辑而改变的。在库代码中一般都存在这样的函数,它的动作流程很规律,比如Windows应用程序的打开文件操作,流程不过是“打开文件对话框”、“选择文件类型和文件名”、“读入文件内容”等,无论事打开什么文件这个流程都不会改变,这类函数被称为template method。还是以打开文件这一动作为例,在该template method中我们要有一个函数负责读取文件的内容,而文件的类型多种多样,内容的格式也不相同,那我们如何在代码执行到这个读取文件函数(primitiveFunc)时能根据不同的文件类型执行不同的动作呢?我们利用polyphorism机制,见上面的图形,当main中的代码执行到a.TemplateMethod中的primitiveFunc的时候,代码将调用不同的子类override的那个primitiveFunc而不是库代码中实现的那个primitiveFunc。

 

 

8、Polymorphism vs static type &dynamic type

 

我个人认为学好polymorphism的关键在于:

1)  看call through object 还是 call through pointer

2)  static type or dynamic type

 

至于什么是多态,我这里就不多说了,任何一本C++教材都会有详细的讲解。

static type ---- 变量声明时的type;

dynamic type ---- 变量实际的type;

举例说明:

CShape* p ;

p = new CRect();

上述代码中指针p的static type为CShape* , 而dynamic type为CRect* 。

 

再看看下面代码:

class CShape

{

       public:

       virtual void draw()

       {

              cout << "Draw for CShape" << endl;

       }

};

class CRect : public CShape

{

       public:

       virtual void draw()

       {

              cout << "Draw for CRect" << endl;

       }

};

class CSquare : public CRect

{

       public:

       virtual void draw()

       {

              cout << "Draw for CSquare" << endl;

       }

};

int main(int argc, char *argv[])

{

       CShape* p;

 

       CShape s;

       s.draw(); //invoke CShape::draw()

      

       CRect rc1;

       rc1.draw();     //invoke CRect::draw()

 

       p = new CRect();

       p->draw();     //invoke CRect::draw()

 

       delete p;

       p = new CSquare();

       p->draw();     //invoke CSquare::draw()

 

       delete p;

       return 0;

}

Output:

Draw for CShape

Draw for CRect

Draw for CRect

Draw for CSquare

 

通过pointer去call function时,编译器会去查看该pointer的动态类型来决定到底调用哪个函数。如上述代码中的指针p,第一次被赋予一个CRect* 类型,通过p call draw时,compiler得知p的dynamic type为CRect* ,而不是CShape*,所以调用CRect::draw;同理第二次调用的是动态类型CSquare的draw。

 

通过obj调用function时比较简单,obj是什么类型的就调用哪个类型的draw即可。

 

9、Inside the object model

这里涉及到virtual pointer、virtual table等而且要画大量的图才能理解的更好,我倒觉得不如看看inside the c++ object model这本书,所以这里就不详细描述了(^_^其实我比较懒)。

 

10、virtual func vs non-virtual func vs pure virtual func

 

pure virtual func --- 为了让derived class只继承其接口。

virtual virtual func --- 为了让derived class继承该函数的接口和预设行为。

non-virtual func --- 为了让derived class继承该函数的接口和实现(继承实现的前提是derived class没有hide该函数接口)。

 

 

 

本文地址:http://com.8s8s.com/it/it25511.htm