zoukankan      html  css  js  c++  java
  • 让自己习惯C++

      所谓声明式是告诉编译器某个东西的名称和类型,但是略去细节。

      每个函数的声明式揭示其签名式,也就是参数和返回类型,一个函数的签名等同于该函数的类型。

      定义式的任务是提供声明式所遗漏的一些细节,对对象而言,定义式是编译器为此对象拨发内存地点,对function或function template而言,定义式提供了代码本体,对class或class template而言,定义式列出了他们的成员。

      初始化是“给予对象初值的过程”,对用户自定义类型的对象而言,初始化由构造函数执行。

      default构造函数是一个可被调用而不带任何实参者,这样的函数要不没有参数,要不每个参数都有缺省值。copy构造函数被用来“以同型对象初始化自我对象”,copy assignment操作符是被用来从一个同型对象中拷贝值到自我对象,区别在于有没有个新对象被定义。

    条款01:视C++为一个语言联邦

    1. C:说到底C++仍是以C为基础。区块,语句,预处理器,内置数据类型,数组,指针统统来自C。
    2. Object-Oreinted C++:这一部分是面向对象设计之古典守则在C++上的最直接实施。类,封装,继承,多态,virtual函数等等...
    3. Template C++:这是C++泛型编程部分。
    4. STL。STL是个template程序库:容器(containers),迭代器(iterators),算法(algorithms)以及函数对象(function objects)...

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

      用#define可能会导致你是用的名称未进入符号表;若#define AA=6,,可能导致目标代码中出现多份6,解决此问题的办法是用const替换#define。

    1. const的好处:
      1. define直接常量替换,出现编译错误不易定位(不知道常量是哪个变量)。
      2. define没有作用域,一旦被#define,则在其后的编译过程中都有效,除非在某处#undef,const有作用域提供了封装性
    2. enum的好处:
      1. 提供了封装性
      2. 编译器肯定不会分配额外内存空间(其实const也不会)
    3. inline的好处:
      1. define宏函数容易造成误用
    //define误用举例
    
    #define MAX(a, b) a > b ? a : b
    
    int a = 5, b = 0;
    MAX(++a, b) //a++调用2次
    MAX(++a, b+10) //a++调用一次

      对const和enum的解释

    class GamePlayer
    {
    private:
        static const int num=6;//声明该常量——这是一个声明式。in class初始值设定
        int arr[num];//使用该常量
    };
    //但是c++要求对使用的任何东西提供一个定义式,但是如果是class的专属常量又是static类型,则需特殊
    //处理。只要不取他们的地址,可以声明使用他们而无需提供定义式,但是如果取某个class专属常量的地址
    //或你不取其地址编译器坚持要看到定义式,则你需要提供一个定义式。
    const int GamePlayer::num;//要把此式子放进实现文件而不是头文件;在声明时已经提供初始值,
                                //现在不提供初始值
    
    class GamePlayer
    {
    private:
        enum{num=6};//编译期间需要一个class常量,但是编译器不允许编译期间完成in class初值设定,用enumerate代替
        int arr[num];//使用该常量
    };

    宏实现工厂模式

    1. 需要一个全局的map用于存储类的信息以及创建实例的函数
    2. 需要调用全局对象的构造函数用于注册
    using namespace std;
    
    typedef void *(*register_fun)();
    
    class CCFactory{
    public:
      static void *NewInstance(string class_name){
        auto it = map_.find(class_name);
        if(it == map_.end()){
          return NULL;
        }else
          return it->second();
      }
      static void Register(string class_name, register_fun func){
        map_[class_name] = func;
      }
    private:
      static map<string, register_fun> map_; 
    };
    
    map<string, register_fun> CCFactory::map_;
    
    class Register{
    public:
      Register(string class_name, register_fun func){
        CCFactory::Register(class_name, func);
      }
    };
    
    #define REGISTER_CLASS(class_name); 
      const Register class_name_register(#class_name, []()->void *{return new class_name;});
    1. 对于单纯常量,最好以const对象或enum替换#define。
    2. 对于形似函数宏,最好改用inline函数替换#define。

    条款03:尽可能使用const

    • 如果关键字const出现在星号左边,表示被指物事常量。const char *p和char const *p两种写法意义一样,都说明所致对象为常量;
    • 如果关键字const出现在星号右边,表示指针自身是常量。   
    const std::vector<int>::interator iter = vec.begin();//作用像T *const, ++iter 错误:iter是const
    std::vector<int>::const_iterator cIter = vec.begin();//作用像const T*,*cIter = 10 错误:*cIter是const

      令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而不至于放弃安全性和高效性。

    const Rational operator* (const Rational &lhs, cosnt Rational &rhs);
    
    Rational a,b,c;
    if((a*b)=c)
        ...//在a*b的成果上调用operator=,实际上是想一个比较动作

      声明为const的成员函数,不可改变non-static成员变量,在成员变量声明之前添加mutable可让其在const成员函数中可被改变。

      不要在const内调用non-const成员函数,因为对象有可能被改动。

      可以在non-const内调用const,因为non-const内本来就可能改动对象,可以调用const。

    const_cast<char &>(static_cast<const TextBlock &>(*this))[position];
    //static_cast 将TextBlock &转为const TextBlock &;
    //const_cast将返回值去掉const约束;

      operator[]返回值类型是引用,因为

    char& operator[](size_t pos)
    {
      return arr[pos];  
    }
    a[0]='x';//如果返回值是内置类型,则赋值就不合法,因为C++by value返回对象这一事实意味着改动的其实是a.arr[0]的一个副本!
    1. 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
    2. 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量”(conceptual constness)。
    3. 当cosnt和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

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

      永远在使用对象之前先将它初始化。对于无任何成员的内置类型,你必须手工完成此事。至于内置类型以外的任何其它东西,初始化责任落在构造函数身上,确保每一个构造函数都将对象的每一个成员初始化。

      C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。所以应将成员变量的初始化置于构造函数的初始化列表中。

    ABEntry::ABEntry(const std::string& name, const std::string& address,
                                         const std::list<PhoneNumber>& phones)
         {
                theName = name; //这些都是赋值,而非初始化
                theAddress = address; //这些成员变量在进入函数体之前已调用默认构造函数,接着又调用赋值函数,
                thePhones = phones;//即要经过两次的函数调用。            
                numTimesConsulted = 0;
        }
    
    ABEntry::ABEntry(const std::string& name, const std::string& address,const std::list<PhoneNumber>& phones)
            : theName(name), //这些才是初始化
            theAddress(address),//这些成员变量只用相应的值进行拷贝构造函数,所以通常效率更高。
            thePhones(phones),
            numTimesConsulted(0)
            {    }

      所以,对于非内置类型变量的初始化应在初始化列表中完成,以提高效率。而对于内置类型对象,如numTimesConsulted(int),其初始化和赋值的成本相同,但为了一致性最好也通过成员初始化表来初始化,如果我们忘记了对他初始化,那么他就没有初始值。

      如果成员变量时const或reference,它们就一定需要初值,不能被赋值。

      base class更早于derived class被初始化,而class的成员变量总是以其声明顺序被初始化。

      C++有着十分固定的“成员初始化次序”。基类总是在派生类之前被初始化,而类的成员变量总是以其说明次序被初始化。所以:当在成员初始化列表中列各成员时,最好总是以其声明次序为次序。

      static对象:global对象,定义于namespace作用域内的对象,在classes内,函数内,以及在file作用域被声明为static对象,函数内的static对象被称为local static对象,其他对象被称为non-local static对象。

      non-local static对象(global,namespace内,class内,或file作用域被声明为static)的初始化使用了另一个编译单元内某个non-local static对象,他可能未被初始化,因为不同编译单元内的non-local static象的初始化次序无明确的定义。

      解决办法:将每个non-local static对象搬到自己专属函数内,该对象再此函数内被声明为static,这些函数返回个引用指向它所含的对象,然后用户调用这些函数而不直接指涉这些对象。也即是non-local static对象被local static对象替换了。这就是单例模式的一个实现手法。

      函数内的local static对象在函数被调用期间,首次遇上该函数对象之定义式时被初始化。

      任何一种non-const static对象,不论他是local或non-local,在多线程环境下都会有麻烦,办法是:程序单线程启动阶段手工调用所有reference-returning函数,这可消除与初始化有关的竞速问题。

      避免在对象被初始化之前过早使用他们,需要做:

    1. 手工初始化内置型non-member对象。
    2. 使用成员初始值列(member-initialization lists)对付对象的所有成分。
    3. 加强设计关于non-local static对象初始化次序问题。

    请记住:

    1. 为内置对象进行手工初始化,因为C++不保证初始化它们;
    2. 构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在类中的声明次序相同;
    3. 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。   
  • 相关阅读:
    软件设计的哲学:第十七章 一致性
    软件设计的哲学:第十六章 修改现有代码
    软件设计的哲学: 第十五章 先写注释
    软件设计的哲学:第十四章 选个好名字
    软件设计的哲学:第十三章 注释应该描述代码中隐藏的内容
    软件设计的哲学:第二十章 为什么要写注释
    软件设计的哲学:第十一章 两次设计
    软件设计的哲学: 第十章 定义不存在错误
    软件设计的哲学: 第九章 合并还是分解
    软件设计的哲学:第八章 降低复杂性
  • 原文地址:https://www.cnblogs.com/tianzeng/p/12296779.html
Copyright © 2011-2022 走看看