美文网首页
《Effective c++》读书笔记1

《Effective c++》读书笔记1

作者: 动如参商_06f8 | 来源:发表于2018-12-12 21:17 被阅读0次

    本章一共有四个条款:

    • 视c++为一个语言联邦
    • 尽量以constenuminline替换#define
    • 尽可能使用const
    • 确定对象被使用前已被初始化

    视c++为一个语言联邦

      这个条款介绍c++总共由四个次语言部分组成,分别为:

    • C part of c++:这一点表示c++是以c为基础的,其中的区块(blocks)、语句(statements)、数组(arrays)和指针(pointers)都来自c。注意,c语言部分和专属于c++部分的初始化方式不一样。

    • Object-oriented c++: 这一部分即面向对象部分,包括封装、继承、多态和虚函数等等。

    • Template C++ :这是c++的泛型编程部分。

    • STL

    尽量以const, enum, inline替换#define

      本条款也可称之为“宁以编译器替换预处理器”。#define与前三者的差别在于,它不被视为语言的一部分,在编译器处理源码之前就由预处理器来处理了,其所定义的名称不记入记号表。

    • 对于单纯常量,最好以const对象或enums替换#define
    • 对于形似函数的宏,最好改用inline函数替换#define

    尽可能使用const

      对于关键字const,考虑其对指针的修饰:

    const char* p=greeting;      //(1)
    char const* p=greeting;      //(2)
    char* const p=greeting;      //(3)
    

    如果const出现在星号左边表示*p所指的字符串是常量,即不能对*p重新赋值,如(1)(2)式所示,且(1)(2)式的意义完全一样。如果const出现在星号右边表示指针p是常指针,即不能对p重新赋值使其指向其他的字符串。值得注意的是,在STL中迭代器是以指针为根据塑模出来的,所以迭代器的作用就像一个指针,声明迭代器为const与声明指针为const的含义一样,如果希望迭代器所指的东西为const则需要定义const_iterator,这里非常容易混淆!

      关于const关键词有一个很重要的概念,即const成员函数,并引出另外两个流行概念:bitwise constlogical constness.,其中bitwise const流派主张const成员函数不可以更改对象内任何non-static成员变量,考虑下述代码:

    class CTextBlock
    {
    public:
        std::size_t length() const;             //(1)式
    
    private:
        char* pText;                            //(2)式
        std::size_t textLength;                 //(3)式
        bool lengthIsValid;                     //(4)式
    };
    
    std::size_t CTextBlock::length() const
    {
        if (!lengthIsValid)
        {
            textLength = std::strlen(pText);
            lengthIsValid = true;
        }
        char* temp= "Hello";
        pText =temp;                            //(5)式
        *pText = *temp;                         //(6)式
        return textLength;
    }
    

    其中(1)式的length()被声明为const成员函数,但是在函数的定义中对象的成员变量都被进行了重新赋值,所以上述代码无法通过编译。值得注意的是,(6)式改变了对象的某些bits,因为修改了指针指向的字符串,但是不幸的是它却能通过bitwise const的测试,事实上这就是所谓的logical constness。在有些时候,即使我们声明了const成员函数,我们也希望某些变量可以被重新赋值,可以通过关键字mutable释放掉bitwise constness约束。

    class CTextBlock
    {
    public:
        std::size_t length() const;                     //(1)式
    
    private:
        char* pText;                                    //(2)式
        mutable std::size_t textLength;                 //(3)式
        mutable bool lengthIsValid;                     //(4)式
    };
    
    std::size_t CTextBlock::length() const
    {
        if (!lengthIsValid)
        {
            textLength = std::strlen(pText);
            lengthIsValid = true;
        }
        return textLength;
    }
    

    上述代码就可以正常通过编译。

    在某些时候,我们可能在定义了一个const成员函数的同时也需要定义一个对应的non-const的成员函数,两者的功能代码可能会有大部分是重复的。为了避免重复代码,可以通过转型动作使得non-const成员函数调用const成员函数,注意不能反过来操作,因为会破坏const成员函数的bitwise constness约束。

    class CTextBlock
    {
    public:
        const char& operator[](std::size_t position) const
        {
            ...
            ...
            ...
            return text[position];
        }
    
        char& operator[](std::size_t position)
        {
            return
                const_cast<char&>(static_cast<const CTextBlock&>(*this))[position];   //(1)式
        }
    private:
        char* text;
    };
    
    

    其中(1)式经过了两次强制转型动作。

    确定对象被使用前已先被初始化

      前面我们说过在c++的c语言部分和非c语言部分的初始化规则并不是相同的,在C part of C++中,如果初始化可能招致运行期成本那么就不保证发生初始化,一旦进入non-C parts of C++,规则就有些变化。这就是为什么array不保证其内容被初始化,而vector确有此保证。针对这种情况,一个保险的情况是:永远在使用对象之前先将它初始化。对于内置类型之外的任何其他东西,初始化责任落在构造函数身上,这里需要注意的是别混淆赋值初始化的区别。

    class ABEntry
    {
    public:
        ABEntry(const std::string& name);
    
    private:
        std::string theName;
    };
    
    ABEntry::ABEntry(const std::string& name) :theName(name)   //(成员初值列,初始化)
    {
        //theName = name;                                      //(赋值操作)
    }
    

    上述代码展示了赋值操作与初始化的区别,c++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前,所以在本例中,如果成员变量theName是以赋值的方式进行“初始化”,那么他实际上进行的是:先调用theName自身的默认构造函数为其设初值,再用name给它赋予新值。而如果直接使用成员初值列的方式对其进行初始化执行的操作是:利用nametheName进行拷贝构造。后一种方法比前一种方法高效很多,所以建议尽可能使用成员初值列的方式进行初始化,值得注意的是:成员变量的初始化顺序只与其声明顺序有关,而与其成员初值列的顺序无关

    所谓编译单元是指产出单一目标文件的那些源码,基本上它是单一源码文件加上其所含入的头文件。如果涉及至少两个源码文件,每一个内含至少一个non-local static对象,且某个non-local static对象的初始化使用了另一个编译单元的某个non-local static对象,它所用的这个对象可能尚未被初始化,因为c++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。

    /*第一个源码文件*/
    class FileSystem
    {
    public:
        std::size_t numDisks() const;
    };
    extern FileSystem tfs;
    /*第二个源码文件*/
    class Directory
    {
    public:
        Directory();
    };
    Directory::Directory()
    {
        std::size_t disks = tfs.numDisks();
    }
    /*创建一个Directory对象*/
    Directory tempDir();           //(1)式
    

    上述代码中的(1)式,除非tfstempDir之前先被初始化,否则tempDir的构造函数会用到尚未初始化的tfs,但实际上这是无法保证的。解决这个问题的办法是:将每个non-local static对象 搬到自己的专属函数内,这些函数返回一个reference指向它所含的对象,然后用户调用这些函数。

    /*第一个源码文件*/
    class FileSystem
    {
    public:
        std::size_t numDisks() const;
    };
    FileSystem& tfs()
    {
        static FileSystem fs;
        return fs;
    }
    /*第二个源码文件*/
    class Directory
    {
    public:
        Directory();
    };
    Directory::Directory()
    {
        std::size_t disks = tfs().numDisks();
    }
    Directory& tempDir()
    {
        static Directory td;
        return td;
    }
    

    如上进行修改,调用的方式由直接使用tfstempDir改为tfs()tempDir()

    相关文章

      网友评论

          本文标题:《Effective c++》读书笔记1

          本文链接:https://www.haomeiwen.com/subject/feuuhqtx.html