• 第一章、让自己习惯C++


    条款02:尽量以const,enum,inline替换 #define

    无法利用#define 创建一个class 专属常量,因为#defines并不重视作用域。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。这意味着#defines不仅不能够用来定义class专属常量,也不能够提供任何封装性,也就是说没有任何private #define 这样的东西。而当然const成员变量是可以被封装的

    #define ASPECT_RATIO 1.653
    以下面的方式替代上面:
    const double AspectRatio =1.653;
    
    class CostEstimate{
    private:
        static const double FudgeFactor;//static class 常量声明位于头文件内
        ...
        
    };
    const CostEstimate::FudgeFactor =1.35;//static class 常量定义位于实现文件内
    

    假如你的编译器不允许“static 整数型class常量”完成“in class初值设定”,可以改用“the enum hack”补偿做法。其理论基础是:“一个属于枚举类型的数值可权充ints被使用”,
    于是GamePlayer可被定义如下:

    
    class GamePlayer{
    private:
        enum{NumTurns=5};//"the enum hack " 令NumTurns成为5的一个记号名称
        int scores[NumTurns];
        ...
    };
    

    当利用#define定义宏函数时:

    //以a和b的比较值调用 f
    #define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
    

    替换成,inline函数

    template<typename T>//由于我们不知道
    inline void callWithMax(const T& a,const T& b ){//T是什么,所以采用pass by reference to const
        f( a > b ? a : b );
    }
    

    有了const,enum和inline,我们对预处理器(特别是#define)的需求降低了,但并非完全消除。#include仍然是必需品,而#ifdef/#ifndef也继续扮演控制编译的重要角色。

    1、对于单纯常量,最好以const对象或enum替换#define。
    2、对于形似函数的宏,最好改用inline函数替换#define。

    条款03:尽可能使用const

    如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。

    1、被指物是常量

    const Widget *pw;//这是一个指针,指向一个常量的(不变的)Widget对象。
    Widget const *pw;//这个也是
    

    2、重载操作符时

    class Rational{...};
    const Rational operator *(const Rational &lhs,const Rational &rhs);
    

    禁止如下的操作

    Rational a,b,c;
    (a*b) =c;//在a*b的结果调用operator ,禁止乘积的结果被赋值
    if(a*b=c);//这里其实想做一个比较的动作 把 == 写错成 = 了。
    

    3、const成员函数

    假设你需要定义const成员函数,但是也想要修改一些数据成员,可能写成如下的代码

    class CTextBlock{
    public:
       
        std::size_t length() const;
    
    private:
        char *pText;
        std::size_t textLength;//最近一次计算的文本区块长度
        bool lengthIsValid;//目前长度是否有效    
    };
    
    std::size_t CTextBlock::length() const
    {
        if(!lengthIsValid)
        {
            textLength=std::strlen(pText);//错误!在const 成员函数内
            lengthIsValid=true;       //不能赋值给 textLength和lengthIsValid
        }
        return textLength;
    }
    

    如上代码是会产生编译错误。因此修改成以下:

    class CTextBlock{
    public:
       
        std::size_t length() const;
    
    private:
        char *pText;
        mutable std::size_t textLength;//这些成员变量可能总是会被更改,即使在const成员函数内
        mutable bool lengthIsValid;//    
    };
    
    std::size_t CTextBlock::length() const
    {
        if(!lengthIsValid)
        {
            textLength=std::strlen(pText);//现在可以这样
            lengthIsValid=true;           //也可以这样                     //不能赋值给 textLength和lengthIsValid
        }
        return textLength;
    }
    

    bitwise constness实际上就是const成员函数不可以更改对象内任何non-static成员变量。但是mutable可以释放这种约束。

    在const和non-const成员函数中避免重复

    在non-const operator[ ]调用其const兄弟,实施转型操作,将*this从其原始类型TextBlock&转型为const TextBlock& ,即使转型操作为其加上const,所以这里共有两次转型操作。第一次:为 * this加上const,第二次:从 const operator[ ]的返回值中移除const。

    const成员函数承诺绝不更改其对象的逻辑状态,non-const 成员函数却没有这种承诺,

    class TextBlock{
    public:
        ...
        ...
        const char& operator[](std::size_t position) const//一如既往
        {
            ...
            ...
            return  text[position];
                    
        }
        char& operator[](std::size_t position) //现在只调用const op[]
        {
            
            return  const_cast<char&>(
                        static_cast<const TextBlock&>(*this)[position]);
            //将op[]返回值的const转除为*this加上const调用const op[]
                    
        }
    private:
        std::string text;    
    };
    

    请记住
    1、将某些东西声明为const可以帮助编译器侦测出错误用法。const可以被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数个体。
    2、编译器强制实施bitwise constness ,但你编写程序时应该使用“概念上的常量性(conceptual constness)”
    3、当const 和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以避免代码重复。

    条款04 确定对象被使用前已被初始化

    下面用一个表现通讯录的class,其构造函数如下:

    
    class PhoneNumber{...};
    class ABEntry{  //ABEntry =Address Book Entry
    public:
        ABEntry(const std::string &name,const std::string & address,
                const std::list<PhoneNumber>& phones);
    private:
        std::string theName;
        std::string theAddress;
        std::list<PhoneNumber>& thePhones;
        int numTimesConsulted;
    };
    ABEntry::ABEntry(const std::string &name,const std::string & address,
                     const std::list<PhoneNumber>& phones){
        theName=name;//这些都是赋值,并非初始化
        theAddress=address;
        thePhones=phones;
        numTimesConsulted=0;
    }
    

    C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。
    下面使用成员初始化列表的方法替换赋值动作:

    ABEntry::ABEntry(const std::string &name,const std::string & address,
                     const std::list<PhoneNumber>& phones)
        :theName(name),//现在这些都是初始化
        theAddress(address),
        thePhones(phones),
        numTimesConsulted(0){
        }//现在构造函数本体不必有任何动作
    

    这个构造函数和上一个的最终结果相同,但通常效率较高。
    对于大多数类型而言,比起先调用默认构造函数然后调用拷贝赋值操作符,单调用一次复制构造函数的比较高效率的。

    static对象,其寿命从被构造出来直到程序结束为止。
    所谓编译单元是指产生单一目标文件的那些源码。

    class FileSystem{
    public:
        std::size_t numDisk() const;//众多成员函数之一
                    
                    
    };
    extern FileSystem tfs;//预备给客户使用的对象 non-local对象
    
    
    class Directory{
    public:
        Directory(params);
    };
    Directory::Directory(params){
        std::size_t disks=tfs.numDisk();
    }
    Directory tempDir(params);//为临时文件而做出的目录
    
    
    

    由上面的non-local static类型修改为如下的local static类型的对象。

    class FileSystem{
    public:
        std::size_t numDisk() const;//众多成员函数之一
                    
                    
    };
    
    FileSystem &tfs(){//这个函数用来替换tfs对象;它在FileSystem class中可能是个static。
        //定义并初始化一个local static对象,返回一个reference对象。
        static FileSystem fs;
        return  fs;
    }
    
    class Directory{
    public:
        Directory(params);
    };
    Directory::Directory(params){
        std::size_t disks=tfs.numDisk();
    }
    
    
    Directory &tempDir(){//这个函数用来替换 tempDir对象
        static Directory td;
        return td;
    }
    

    请记住
    1、为内置型对象进行手工初始化,因为C++不保证初始它们。
    2、构造函数最好使用成员初始化列表进行初始化,而不要在构造函数本体内使用赋值操作。成员初始化列表的成员变量,其排列次序应该和它们在class中的声明次序相同。
    3、为了免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象(其实就是单例)

    资料来源:Effective C++
    仅供学习 侵删

  • 相关阅读:
    element ui框架(vuex模块化)
    前端Vue页面中如何展示本地图片
    VS Code实用插件推荐
    计算机视觉40例之案例12手写数字识别
    应该继续学习编程,还是学数控?
    竞赛选题 深度学习疲劳检测 驾驶行为检测 - python opencv cnn
    【多传感器融合定位】【学习汇总】
    Linux-基础知识(黑马学习笔记)
    Scala基础【常用方法补充、模式匹配】
    305_C++_定义了一个定时器池 TimerPool 类和相关的枚举类型和结构体
  • 原文地址:https://blog.csdn.net/qq_30457077/article/details/127035379