Effective C++读书笔记 第一部分 让自己习惯C++

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

本条款提示读者,C++已经不是一门很单一的语言,而应该将之视为一个由相关语言组成的联邦。从语言形式上看,它是一个多重范型编程语言(multiparadigm programminglanguage) ,一个同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional) 、泛型形式(generic) 、元编程形式(metaprogramming )的语言,从语言种类上看,它由若干次语言组成,分别为:
(1) C。说到底C++ 仍是以C 为基础。区块(blocks) 、语句( statements) 、预处理器( preprocessor) 、内置数据类型(built-in data types) 、数组(arrays) 、指针(pointers) 等统统来自C。
(2) Object-Oriented C++。这部分也就是C with Classes 的: classes (包括构造函数和析构函数) ,封装( encapsulation) 、继承( inheritance) 、多态(polymorphism) 、virtual 函数(动态绑定) ……
(3) Template C++。这是C++ 的泛型编程(generic programming) 部分,也是大多数程序员经验最少的部分。Template 相关考虑与设计己经弥漫整个C++,实际上由于templates 威力强大,它们带来崭新的编程范型(programming paradigm) ,也就是所谓的templatemetaprogramming (TMP,模板元编程)
(4) STL。 STL 是个template 程序库,它是非常特殊的一个。它对容器(containers) 、迭代器(iterators) 、算法(algorithms) 以及函数对象(function objects) 的规约有极佳的紧密配合与协调。

请记住:

这四个次语言,当你从某个次语言切换到另一个,导致高效编程守则要求你改变策略。C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。

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

本条款讨论了C语言中的#define在C++程序设计中的带来的问题并给出了替代方案。

C语言中的宏定义#define只是进行简单的替换,对于程序调试,效率来说,会带来麻烦,在C++中,提倡使用const,enum和inline代替#define;然而,有了consts 、enums 和inlines,我们对预处理器(特别是#define) 的需求降低了,但并非完全消除。#include 仍然是必需品,而#ifdef/#ifndef 也继续扮演控制编译的重要角色。目前还不到预处理器全面引迫的时候。

这个条款或许可以改为“宁可 以编译器替换预处理器”。即尽量少用预处理。

编译过程:.c文件--预处理-->.i文件--编译-->.o文件--链接-->bin文件

预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。检查包含预处理指令的语句和宏定义,并对源代码进行相应的转换。预处理过程还会删除程序中的注释和多余的空白字符。可见预处理过程先于编译器对源代码进行处理。预处理指令是以#号开头的代码行。

 例:#define ASPECT_RATIO 1.653

记号名称ASPECT_RATIO也许从未被编译器看见,也许在编译器开始处理源代码之前它就被预处理器移走了。即编译源代码时ASPECT_RATIO已被1.653取代。ASPECT_RATIO可能并未进入记号表(symbol table)。

替换:const double AspectRatio = 1.653;

好处应该有:多了类型检查,因为#define 只是单纯的替换,而这种替换在目标码中可能出现多份1.653;改用常量绝不会出现相同情况。

常量替换#define两点注意:

1)、定义常量指针:

const char *authorName = “Shenzi”;

cosnt std::string authorName("Shenzi");

2)、类专属常量

static const int NumTurns = 5;//static 静态常量 所有的对象只有一份拷贝。

万一你编译器不允许“static整数型class常量”完成“in calss初值设定”(即在类的声明中设定静态整形的初值),我们可以通过枚举类型予以补偿:

enum { NumTurns = 5 };

*取一个const的地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。如果你不想让别人获取一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。

 例:#define CALL_WITH_MAX(a,b)    f((a) > (b)) ? (a) : (b))
     宏看起来像函数,但不会招致函数调用带来的额外开销,而是一种简单的替换。

替换:
      template<typename T>
      inline void callWithMax(cosnt T &a, cosnt T &b)
      {
            f(a > b ? a : b);
       }
      
callWithMax是个真正的函数,它遵循作用于和访问规则。

 请记住:

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

 条款03:尽可能使用const

本条款总结了Const的使用场景和使用它带来的好处。
关键字canst 多才多艺。你可以用它在classes 外部修饰global 或namespace作用域中的常量,或修饰文件、函数、或区块作用域(block scope) 中被声明为static 的对象。你也可以用它修饰classes 内部的static 和non-static 成员变量。面对指针,你也可以指出指针自身、指针所指物,或两者都(或都不)是const。你应该尽可能地使用const,这样降低程序错误,使程序易于理解。
此外,一个编程技巧是:当const 和non-const 成员函数有着实质等价的实现时,令non-const 版本调用const 版本可避免代码重复:


 const允许你告诉编译器和其他程序员某值应保持不变,只要“某值”确实是不该被改变的,那就该确实说出来。

 关键字const多才多艺:

 例:
        char greeting[] = "Hello";
        char *p = greeting;    //指针p及所指的字符串都可改变;
        const char *p = greeting;    //指针p本身可以改变,如p = &Anyother;p所指的字符串不可改变;
        char * cosnt p = greeting;    //指针p不可改变,所指对象可改变;
        const char * const p = greeting;    //指针p及所致对象都不可改变;
     说明:

  • 如果关键字const出现在星号左边,表示被指物事常量。const char *p和char const *p两种写法意义一样,都说明所致对象为常量;
  • 如果关键字const出现在星号右边,表示指针自身是常量。
STL例子:
         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

以下几点注意:

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

例:const Rational operator* (const Rational &lhs, cosnt Rational &rhs);

2)、const成员函数使class接口比较容易被理解,它们使“操作const对象”称为可能;

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

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

请记住:

  • 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体;
  • 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的车辆”(conceptual constness);
  • 当cosnt和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
  条款04:确定对象被使用前已先被初始化

本条款告诫程序员,在C++程序设计中,应该对所有对象初始化,以避免不必要的错误,同时,给出了高效初始化对象的方法和正确初始化对象的方法。

(1)赋值和初始化:初始化构造函数最好使用成员初值列(member initialization list) ,而不要在构造函数本体内使用赋值操作(assignment) 。初值列出的成员变量,其排列次序应该和它们在class 中的声明次序相同。

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,它们就一定需要初值,不能被赋值。

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

 请记住:

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




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值