Linux程序设计实践

来源:互联网 发布:淘宝网上哪家玉好 编辑:程序博客网 时间:2024/05/13 20:57

第一章 范式初见面

结构化的程序设计思想

加法器:在这个加法器中,已经保存了被加数,现在需要传递加数给这个加法器,以让其返回加法计算结果。

struct SLAugend {    int iAugend;};int Add(struct SLAugend *pSLAugend, int iAddend){    return pSLAugend->iAugend + iAddend;}

结构体SLAugend:保存加法器的被加数;具体被加数则由iAugend字段保存
加法函数Add:让函数接受两个参数,一是SLAugend结构体的指针,二是加数iAddend

扩展–带权重的加法器

struct SLWeightingAugend{    int iAugend;    int iWeight;};int WeightingAdd(struct SLWeightingAugend *pSLWeightingAugend, int iAddend){    return pSLWeightingAugend->iWeight * pSLWeightingAugend->iAugend + iAddend;}

在上述代码中,SLWeightingAugend保存了被加数和它的权重,而WeightingAdd则是带权重的加法函数。编者实际为我的导师,他告知我编码时能不写注释就不写注释,尽量以代码自注释的方式来码,23333实际感觉这样会使得变量名过于,在没有自动补全的vim上确实有点恼火。

基于对象的程序设计思想

以面向对象语言思想来描述加法器即是:编写一个加法器的类,用一个数据成员保存被加数,然后再写一个public的加法方法。

class CLAdder{    public:    explicit CLAdder(int iAugend){        m_iAugend = iAugend;    }    int Add(int iAddend){        return m_iAugend + iAddend;    }    private    int m_iAugend;};

扩展带权重的加法器

class CLWeightingAdder{    public:    CLWeightingAdder(int iAugend, int iWeight){        m_iAugend = iAugend;        m_iWeight = iWeight;    }    int Add(int iAddend){        return m_iAugend * m_iWeight + iAddend;    }    private:    int m_iAugend;    int m_iWeight;}

基于对象方法和结构化方法的差异

  • 封装:在面向对象编程中,我们将数据和操作这些数据的方法封装到了CLAdder这个类当中。这里即是iAugend、iWeight和Add方法被封装到了CLAdder类或CLWeightingAdder类当中。

改进code1_1的结构化加法器

一个实际项目,往往都会有很多的.h文件和.c文件以有效组织代码。按照这个思路,通常我们会把结构体SLAugend的定义放在一个.h文件当中,而Add函数的定义和声明,则分别放在一个.h文件和一个.c文件当中。

这样会当导致加法器的使用者每次都要查看SLAugend的头文件和Add函数的头文件,因为他需要使用结构体的成员,调用Add函数;而实现者也需要不断地查看SLAugend的头文件。

干脆把SLAugend结构体和Add函数放在一起,管理起来也比较方便。

struct SLAdder;typedef int (*FUNC_ADD)(struct SLAdder *, int);struct SLAdder{    int iAugend;    FUNC_ADD pFuncAdd;};int Add(struct SLAdder *pSLAdder, int iAddend){    return pSLAdder->iAugend + iAddend;}int main(void){    struct SLAdder adder;    adder.pFuncAdd = Add;    adder.iAugend = 3;    cout << adder.pFuncAdd(&adder,5) << endl;    return 0;    }

这里一个加法器结构体包含两个成员变量:被加数和加法函数。
而加法函数的参数为加法器结构体和被加数。
因此通过adder.pFuncAdd = Add;来指定加法器的加法函数;通过adder.iAugend = 3;来指定加法运算中加数。
最后通过adder.pFuncAdd(&adder,5)来调用这个加法器,其中直接传递了5为加法函数的参数。
注意:pFuncAdd调用的第一个参数,adder的地址。因为Add函数要访问adder的被加数字段(即是adder中的一个成员变量),所以必须把adder的地址传给该函数。在加法函数中的第一个参数实际上起到的是this指针的作用,即是this.pFuncAdd(&adder,5)。
但如果以这种方式组织结构体和操作结构体的函数,每次函数调用都需要程序员手工地把结构体实例的地址传递给该函数,会带来一些冗余操作。

面向对象的程序设计思想

简单来说,面向对象多了继承和虚函数。

class CLAdder{    public:    explicit CLAdder(int iAugend){        m_iAugend = iAugend;    }    virtual ~CLAdder()    {    }    virtual int Add(int iAddend){        return m_iAugend + iAugend;    }    protected:    int m_iAugend;}

虚函数:基类希望其派生类进行覆盖的函数。
- Add函数变成了虚函数–》就是希望派生类重写它
- m_iAugend数据成员访问权限变成了protected,保证其派生类能够访问到它
- 增加了一个虚析构函数:如果CLAdder没有虚析构函数,当delete一个类型为CLAdder的对象指针,但该指针指向的是其派生类对象时,则派生类的析构函数将不会被调用。–》使得派生类能够调用基类的虚析构函数。

派生的带权重加法器

class CLWeightingAdder: public CLAdder{    public:    CLWeightingAdder(int iAugend, int iWeight):CLAdder(iAugend)    {        m_iWeight = iWeight;    }    virtual ~CLWeightingAdder(){    }    virtual int Add(int iAddend){        return m_iAugend * m_iWeight + iAddend;    }    protected:        int m_iWeight;};
  • 重写了虚函数Add
  • 引入了权重数据成员

    和基于对象的方法相同,面向对象的方法也能实现数据和操作这些数据的方法的封装。关键在于派生类可以重写基类的成员函数,这里就是可以重写使其实现带权重加法,也可以补充些使其保持原来的加法。也就是说,面向对象的方法能封装这一个变化点。

基于接口的程序设计思想

更改需求:普通加法器的被加数必须是非负的整数,而带权重的加法器的被加数,可以是非负的整数,也可以是负的整数。
思路:给出一个基类,基类中实现一个虚的Add函数,然后CLAdder和CLWeightingAdder类都从这个基类派生,并重写Add虚函数。

class ILAdder{    public:    ILAdder(){    }    virtual ~ILAdder(){    }    virtual int Add(int iAddend)=0;};class CLAdder:public ILAdder{    public:    explicit CLAdder(unsigned int iAugend){        m_iAugend = iAugend;    }    virtual ~CLAdder(){    }    virtual int Add(int iAddend)    {        return m_iAugend + iAddend;    }    private:        unsigned int m_iAugend;};

面向对象的方法和基于接口的程序设计都能封装数据和操作数据的函数;其次,两类方法都能封装既有普通加法器,又有带权重加法器这一变化点。

  • 但是面向对象的方法无法封装CLAdder类中被加数是int类型这一情况。因为在面向对象的方法中,CLWeightingAdder类从CLAdder类继承而来,而继承关系是一种强耦合关系,这表现在派生类即耦合于基类的接口,又耦合于基类的实现。CLweightingAdder既要符合Add函数的原型要求,又要受限于CLAdder类中被加数是int类型这一情况。

  • 基于接口的设计方法中,CLAdder类和CLWeightingAdder类不再是父子关系,而变成了兄弟关系,均从接口类ILAdder继承。此继承与面向对象中的继承有所不同。CLAdder和CLWeightingAdder类都继承于ILAdder类,但是ILAdder类实质只有一个纯虚函数Add,并没有任何实现细节,连数据成员都没有。显然,CLAdder类和CLWeightingAdder类只会耦合与ILAdder类的纯虚函数Add。

基于接口的程序设计思想的模板实现

template<typename T>class ILAdder{    public:    ILAdder(){    }    virtual ~ILAdder(){    }    int Add(int iAddend){        T *pThis = (T*)(this);        return pThis->AddImpl(iAddend);    }};class CLAdder:public ILAdder<CLAdder>{    public:    explicit CLAdder(unsigned int iAugend){        m_iAugend = iAugend;    }    virtual ~CLAdder(){    }    int AddImpl(int iAddend){        return m_iAugend + iAddend;    }    private:        unsigned int m_iAugend;};class CLWeightingAdder:public ILAdder<CLWeightingAdder>{    public:    CLWeightingAdder(int iAugend, int iWeight){        m_iWeight = iWeight;        m_iAugend = iAugend;    }    virtual ~CLWeightingAdder(){    }    int AddImpl(int iAddend){        return m_iAugend * m_iWeight + iAddend;    }    private:        int m_iWeight;        int m_iAugend;};int main(void){    CLAdder adder(2);    //add.CLAdder(-3);    cout << adder.AddImpl(5) << endl;}

第二章 日志的实现

文件的基本操作

open函数的头文件是fcntl.h,f指的是file,cntl是control的缩写。open函数原型:

int open(const char *path, int flag, .../mode_t);

open函数有三个参数,C库里面最常用的printf函数也是这样的典型的参数个数不确定的函数。

在有的cpu上,编译器会用寄存器传递参数,函数使用的堆栈由被调函数分配和释放。这种调用约定在行为上和__cdecl有一个共同点:实参和形参数目不符不会导致堆栈错误。

错误码的捕捉与解析

#include<iostream>#include<errno.h>#include"fcntl.h"#include<string.h>using namespace std;int main(){    int fd = open("a.txt", O_RDONLY);    if(-1 == fd)    {        cout<<"open error"<<endl;        cout<<"errno is "<<errno<<endl;        cout<<"errno is "<<strerror(errno)<<endl;    }    return 0;}运行结果:[root@localhost 2chapter]# ./code2_1 open errorerrno is 2errno is No such file or directory

执行体程序库的出错处理

创建一个对象CLStatus,这个对象中包含返回值和出错码。

#include<iostream>using namespace std;class CLStatus{    public:   /*    * lReturnCode >= 0 means success,else fail    */    CLStatus(long lReturnCode, long lErrorCode);    virtual ~CLStatus();    public:    bool IsSuccess();    long GetErrorCode();    private:    long m_lReturnCode;    long m_lErrorCode;};CLStatus::CLStatus(long lReturnCode, long lErrorCode){//构造函数    m_lReturnCode = lReturnCode;    m_lErrorCode = lErrorCode;}CLStatus::~CLStatus(){}//析构函数bool CLStatus::IsSuccess(){    if(m_lReturnCode >= 0)        return true;    else        return false;}long CLStatus::GetErrorCode(){    return m_lErrorCode;}CLStatus f(){    CLStatus s(-1, 2);    return s;}int main(){    CLStatus s = f();    if(!s.IsSuccess()){        cout<<"f error"<<endl;        cout<<"error code"<<s.GetErrorCode()<<endl;    }    return 0;}

现在如果想要获得出错码,就必须调用GetErrorCode函数。因为在类定义中,GetErrorCode为Public类型,可以通过该该函数访问到类中private变量。如果m_lReturnCode为private时,是不能通过实例对象.属性名来访问的。这种间接访问的方式不是很方便。

因此构造出这样一种数据成员:在类的内部可以读可以写,但是在类的外部,只能读不能写。

原创粉丝点击