标签 程序员 下的文章

CVS Primer

CVS Repository?
 –> checkout(co)
 –> commit(ci)
 –> update(up)

Repository vs Modules?
   –Repository is the modules’s container
   –Module is often a project

Repository has four main parts :
   –main trunk(called "head" in Eclipse)
   –versions
   –branchs
   –date

CVS Version?
a) Version vs Revision?
The internal revision number(修订号) that CVS keeps for each file is unrelated to the version number of the software product of which the files are part.

The CVS revision numbers are invisible to your customers (unless you give them repository access); the only publicly visible number is the "3" in Version 3.

b) Where do versions come in?
Behind the scenes, a version control system's repository is a fairly clever beast. It doesn't just store the current copy of  each of the file in its care. Instead it stores every version that has ever been checked in. If you check out a file, edit it, version then check it back in, the repository will hold both the original version and the version that contains your changes.this system of storing revisions is remarkably powerful. Using it, the version control system can do things such as:

– retrieve a special revision of a file.
– check out all of the source code of a system as it appeared two months ago.
– tell you what changed in a particular file between versions 1.3 and 1.5.
– you can't use the individual file version numbers to keep track of things such as project releases

repeat: The individual revision numbers that CVS assigns to files should not be used as external version numbers. Instead, version control systems provide you with tags (or their equivalent).

CVS tag?
Tags to the rescue. Version control systems let you assign names to a group of files (or modules, or an entire project) at a particular point in time. If you assigned the tag .Pre-Release2. to this group of three files, you could subsequently check them out using that same tag. You'd get revision 1.11 of File1.java, 1.7 of File2.java, and 1.10 of File3.java.

usage:
    cvs tag release_1_0
    cvs update –r release_1_0  //get the project which version is release_1_0

注意:Tag 的使用并不局限于产品版本号,也不局限于整个模块,你可以随意的给任何一个文件加Tag,不过滥用Tag 就会使Tag 失去它应有的作用。

CVS Branch?

– Main trunk(主线,Eclipse称为Head)
– Branch(分支)

                        main trunk
                         tagged                          tagged                   tagged
————-〉release_1_0 ———–>release_2_0———–>release_3_0
                                      |
                                      |
                                      |  branch(使用分支)
                                    ln_release_1_0
                                    (tag和branch有相似性)

主线与分支相对应,在一个模块的初始是没有分支的,而在以后的开发过程中可以从主线上引出分支,上面图中分支ln_release_1_0就是基于主线的tag release_1_0的。在主线上引出一个分支来专门对付变更后的需求,而主线则保持原来的开发进程。

C++ Advanced Training(二)

今天侯老师花了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的用途。
CShape
  /|\
   |
CRect
  /|\
   |
CSquare

大家从上面的图中也会有所了解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该函数接口)。
 

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats