《Effective C++》学习笔记(1)

记一下要点,算是复习。
水平有限,有些地方直接用原文了……


Chapter 1 Accustoming Yourself to C++(让自己习惯C++)

Item 1: View C++ as a federation of languages(把C++看作语言的联邦国)

书中说,今日C++已经是一种多范式编程语言(multiparadigm programming language)。它是以下几种具体编程范式的组合:过程式(procedural)、OO(object-oriented)、函数式(functional)、泛型(generic)和元编程(metaprogramming)。其中,根据《冒号课堂》所述,过程式编程属于命令式编程(imperative programming)范式,而函数式编程属于声明式编程(declarative programming)范式。
我们可以将C++看成以下四种子语言(sublanguage):
1)C;(呃,自己基本上当C使了,惭愧惭愧)
2)Object-Oriented C++;(这个易懂。毕竟一般来说,我们都当C++是一种OOP语言)
3)Template C++;C++中的泛型编程,属于模板元编程(TMP,template metaprogramming)范式。
4)STL:常用的模板库,很好很强大。

ToR(Thing to Remember)——Rules for effective C++ programming vary, depending on the part of C++ you are using.(高效的C++编程的规则不一,其决定于你所用的C++的部分)


Item 2: Prefer consts, enums, and inlines to #defines
(比较#defines,推荐使用const, enum和inline取代之)

基于思想——prefer the compiler to the precessor,使用#define声明的“常量”不以符号名的形式记录于符号表(symbol table)。
比如
#indefine Pi 1.14
对于compiler,它只看到1.14,而不会知道常量Pi的存在;如果调试阶段这部分有错,便难以找到错误。
用const代替后,即
const double Pi = 1.14
这样常量Pi便记录在符号表内,方便之后的调试。

用const代替#define,有两点需要注意:
1)定义常量指针(constant pointer)。要记得用const定义pointer,比如下例:
const char * const authorName = "Scott Meyers";
要注意的是,上例只是定义了字符指针,authorName只指向字符串"Scott Meyers"的第一个字符。所以,用以下声明更好些:
const std:: string authorName("Scott Meyers");
2)关于类里具体的常量(class-specific constant),即作用域问题。
比如:
class GamePlayer{
private:
    static const int NumTurns = 5;        // constant declaration
    int scores[NumTurns];                            // use of constant
    ...
};
#define是不可能创造类具体的常量,因为它不能限定作用域。站在面向对象的角度看,const定义的常量能被封装,而#define不行。
对于上例,有的编译器对为静态类成员(static class member)初始赋值视为非法。一般地,类内初始化(in-class initialization)只允许适用于数值类型(integral types)和常量(constants)。
如果上述语法不适用,可以将初始值放在定义点处:
class GamePlayer{
private:
    static const double FudgeFactor;        // declaration of static class const
    ...                                                                 //  goes in header file
};
// definition of static class constant;  goes in impl.file
const double ConstEstimate::FudgeFactor = 1.35;    

对于在类进行编译时便需要初始化常量的值的情况下(比如上述数组GamePlayer::score的声明),我们可以用enum处理,此法称为the enum hack。此法利用了这样一个特性: the value of an enumerated type can be used where ints are experted。如下:
class GamePlayer{
private:
    enum { NumTurns = 5 };        // "the enum hack"——makes Numturns a symbolic name for 5
    int scores[NumTurns];                 // fine
    ...
};
几点说明:
1)此处the enum hack的行为相比const,更类似#define。比如,你获取一个const的地址是合法的,但你试图去获取一个enum的地址便是不合法的,同时获取#define对象的地址也是非法的。如果你想禁止用户获取你的数值常量的地址,用enum定义是一种方法。
2)使用the enum hack是purely pragmatic的。事实上,the enum hack是模板元编程(template metaprogramming)的基础技术。


回归主题,另一个关于#define的误用出现在利用它去实现一些宏(to implement macros that look like funtion but that don't incur the overhead of a funtion call)
比如下面的宏声明:
// call f with the maximum of a and b
#define CALL_WITH_MAX(a, b)  f( (a) > (b) ? (a) : (b) )
在执行下述代码:
int a = 5, b = 10;
CALL_WITH_MAX(++a, b);               // a is incremented twice
CALL_WITH_MAX(++a, b + 10);       // a is incremented once
明显地,当(++a)大于b时,转到代码++a,a再自增;而(++a)小于b时,转到b+10,b再增加10。
这一般不是设计者最初希望出现的情况。
使用一个inline function的模板即可处理此问题:
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
    tof(a > b ? a : b);
}

ToR
a)For simple constants, prefer const objects or enums to #defines.
b)For function-like macros, prefer inline functions to #defines.


Item 3: Use const whenever possible. (任何可能的情况下都要使用const)

之前说过用const的好处了~
这部分提到用const的一些注意事项:
在声明变量时,尤其是对于指针(pointer),看以下代码:

char greeting[] = "Hello";
char *p = greeting;                  //non-const pointer 
                                             //non-const data
const char *p  = greeting;       //non-const pointer
                                            //const data
char * const p = greeting;       //const pointer
                                           //non-const pointer
const char * const p = greeting; //const pointer
                                                  //const data

在星号*左边,指针指向的对象是const的;在星号*右边,表示指针本身是const的。
比如:
void f1(const Widget *pw)

void f2(Widget const *pw)
是一样的。指针pw指向的Widget对象是const的。

声明STL iterator时也要注意,要声明对象是常量要用const_iterator:
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin(); // 如T* const
*iter = 10; //没问题
++iter;     //出

错,iter是const的
...
std::vector<int>:: const_iterator cIter = vec.begin();//如 const T*
*cIter = 10; //出错, 对象是const的
++cIter;      //OK,没事。

用const还能避免等式判断(==)误写成赋值号(=)的错误。
比如:
class Rational {...}
const Rational operator*(const Rational& lhs, const rational& rhs);

Rational a, b, c;
...
//如果==误写成=
if (a * b  =  c) ...  //出错,a*b是const的,不能被赋值。

这样有助于在编译过程找出错误。

再说说const Member Functions的重要性
1)使类的interface更易懂。
2)使之有可能与const对象工作(make it possible to work wtih const objects);在传递对象(pass objects)上有助于实现referece-to-const。

 what does it mean for a member fuction to be const?
Two prevailing notions:
1)biwise constness(also know as physical constness); 主要说的是其不可修改性
2)logical constness;提及了mutable的用法,使数据成员(data member)可被修改。


Avoiding Duplication in const and Non-const Member Functions(避免常量和非常量成员函数的复制)

涉及code duplication的问题。
可以用非常量成员函数去调用常量成员函数的方法去避免。(It has the desired effect of avoiding code duplication by implementing the non-const version in terms of the const version)。

(这节其实我看得不甚解)

ToR
1)Declaring something const helps conpilers detect usage error. const can be applied to objects at any scope, to function parameters and return types , and to member functions as a whole.
2)Compilers enforce bitwise constness, but you should program using conceptual constness(logical constness).
3)When const and non-const member functions have essentially identical implementations, code duplication can be avoided by having the non-const version call the const version.


Item 4: Make sure that objects are initialized before they're used.
(确认对象被使用之前已被初始化)

读取非初始化的域有可能导致不可预知的行为。(Reading uninitialized values yields undefined behavior.)
最好的方法是总在使用之前就初始化对象(The best way to deal with this seemingly indeterninate state of affairs is to alsays initialize your objects before you use them)

对于类,初始化的重任落在constructor上。在构造时,确认对象所有值都被初始化了。

常见的方法有assignment,比如类PhoneNumber:

class PhoneNumber {...};
class ABEntry {
public:
    ABEntry(const std::string& name, const std::string&  address, const std:list<PhoneNumer>& 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<PhoneNumer>& phones);   //assignment的方法
{
    theName = name;
    theAddress = address;
    thePhones = phones;
    numTimesConsulted = 0;
}

但其实他们并未被真正的初始化,只是被赋值了;要使用值对应的对象的构造器进行赋值。
一般推荐用initialization list的方法,而不是assignments。
ABEntry::ABEntry(const std::string& name, const std::string&  address,
                             const std:list<PhoneNumer>& phones);   //initialization list
:theName(name);
    theAddress(address);
    thePhones(phones);
    numTimesConsulted(0);
{}

注意,要按声明顺序初始化。

接着讨论——在不同translation units定义的非局部静态对象的初始化顺序问题。

先说说静态对象(static object)
A static object  is one that exists from the time it's constructed until the end of the program.
所以栈和堆都不属于静态对象。
静态对象一般有全局对象(global objects)、在命名域定义的对象(objects defined at namespace scope)、在类内声明为 static的对象( objects declared static inside classes)、在函数内声明为static的对象(objects declared static inside functions)和在文件域定义为static的对象(objects declared static at file scope)。
在函数内的静态对象也称为局部静态对象(local static objects,因为他们只驻于此函数内),,而其他静态对象被称为非局部静态对象(non-local static objects)。
静态对象在程序退出时自动析构,比如类的destructor。

而A translation unit is the source code giving rise to a single objects file.
一般来说,它是一个单独的源文件,加上它的所有#include的头文件。

此问题涉及至少两个不同已编译的源文件,每个文件包含至少一个非局部静态对象的情况上。
具体描述如下:
如果在一个translation unit上的非局部对象的初始化使用另一个translation unit的非局部对象,它使用的对象可能未被初始化。原因是,在不同translation unit上定义的非局部对象的初始化的相对顺序是未被定义的。
在此情况上我们可以用一个局部静态对象对着替换非局部静态对象。(详见书p31-p32)


总之,避免使用未初始化的对象要注意三点:
第一,手动初始化内置类型的非成员对象(manually initialize non-member objects of built-in types)。
第二,使用initialization list去初始化一个对象的所有部分。
第三,design around the initialization order uncertainty that afflicts non-local static objects defined in separate translation units.

ToR:
1)Manually initialize non-member objects of built-in types, because C++ only sometimes initializes them itselt.(手动初始化内置类型的非成员对象,因为C++只偶尔会初始化它们)。
2)In a construct, prefer use of the member initialization list to assignment inside the body of the constructor. List data members in the initialization list in the same order they're declared in the class.(在一个构架器内,相对于assignment 方法,建议用initialization list的方法进行初始化。同时要注意初始化数据成员时要按声明时的顺序)
3)Avoid initialization order problems across translation units by replacing non-local static objects with local static objects.(通过用局部静态对象代替非局部静态对象来避免不同解释单元内的初始化问题)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值