Chapter 1: Accustoming Yourself
Item 1: View C++ as a federation of languages.
-
视C++为一种联邦语言(federation ofrelated languages)。
也就是四种子语言的组合,每一个种类有它自己的特性: -
C语言:、语句、预处理程序、内置数据类型、数组、指针等都来自C.
-
面向对象C++:构造函数和析构函数、类、多态、继承等。
-
模板C++:元编程,它的约定(conventions)一般不影响其他规则。
-
STL:模板库。
Item 2: Prefer consts
, enums
, and inlines
to #defines
.
可以说是比起预处理器(preprocessor),更喜欢编译器(compiler)。
- 选择
const
定义常量而不是用#define
:
const double AspectRatio = 1.653;
常量定义通常放在头文件中,定义常量指针:
const std::string authorName("Scott Meyers");
要将常量的范围限制为类,必须使它成为成员,并且为了确保最多有一个常量的副本,您必须使它成为静态成员:
class GamePlayer {
private:
static const int NumTurns = 5; // constant declaration
int scores[NumTurns]; // use of constant
...
};
上面看到的是NumTurns
的声明,而不是定义;
c++要求为所使用的任何东西提供一个定义,但是特定于类的常量是静态且是整型的(例如,整数、char、bools)时候是一个例外。
As long as you don’t take their address, you can declare them and use them without providing a definition。
但是当去地址的时候,可以定义为:
const int GamePlayer::NumTurns; // definition of NumTurns;
将其放入实现文件中,而不是头文件中。
没有初始值,因为类常量的初值是在声明常量的地方提供的,所以在定义点不允许在设置初始值。
类内初始化只允许用于整数类型和常量。在不能使用上述语法的情况下,可以将初始值放在定义的地方:
class CostEstimate {
private:
static const double FudgeFactor; // declaration of static class
... // constant; goes in header file
};
const double // definition of static class
CostEstimate::FudgeFactor = 1.35; // constant; goes in impl. file
- 如果编译器坚持在编译期间知道数组的大小,则可以用
enum
技术:
class GamePlayer {
private:
enum { NumTurns = 5 }; // “the enum hack” — makes
// NumTurns a symbolic name for 5
int scores[NumTurns]; // fine
...
};
- 关于枚举
enum
:取const
的地址是合法的,但取enum
的地址是不合法的,通常取#define
的地址也是不合法的。如果你不想让人们得到一个指针或者引用,enum
是一个很好的方法来实现这个约束。
- 通过使用内联函数的模板,您可以获得宏的所有效率,以及常规函数的所有可预测行为和类型安全性:
template<typename T> // because we don’t
inline void callWithMax(const T& a, const T& b) // know what T is, we pass by reference-to const
{
f(a > b ? a : b);
}
- Things to Remember
✦ For simple constants, prefer const objects or enums to #defines.
✦ For function-like macros, prefer inline functions to #defines
Item 3: Use const
whenever possible.
-
如果单词
const
出现在星号的左边,所指向的是常量;如果单词const
出现在星号的右边,指针本身就是常量; -
const
放在类型前或者后是同样的声明:
void f1(const Widget *pw); // f1 takes a pointer to a
// constant Widget object
void f2(Widget const *pw); // so does f2
- STL迭代器
iterator
是基于指针建模的,因此迭代器的行为非常类似于T *
指针。声明const
迭代器类似于声明const
指针:
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin(); // iter acts like a T* const
*iter = 10; // OK, changes what iter points to
++iter; // error! iter is const
std::vector<int>::const_iterator cIter = vec.begin(); // cIter acts like a const T*
*cIter = 10; // error! *cIter is const
++cIter; // fine, changes cIter
- 函数返回一个
const
:让函数返回一个常数值通常是不合适的,但有时这样做可以在不放弃安全性或效率的情况下降低客户机错误的发生率, 除非需要修改参数或本地对象,否则请确保声明它为const
。
class Rational { ... };
const Rational operator*(const Rational& lhs, const Rational& rhs);
Rational a, b, c;
//防止出现这样的错误
if (a * b = c) ... // oops, meant to do a comparison!
-
const
Member Functions
两个原因:
1.哪些成员函数可以修改const
成员对象;
- 提高c++程序性能的基本方法之一是通过
reference-to-const
来传递对象;
- 成员函数之间只有一致性
constness
不同,可以重载这种const
性质:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // operator[] for
{ return text[position]; } // const objects
char& operator[](std::size_t position) // operator[] for
{ return text[position]; } // non-const objects
private:
std::string text;
};
TextBlock tb("Hello");
std::cout << tb[0]; // calls non-const TextBlock::operator[]
const TextBlock ctb("World");
std::cout << ctb[0]; // calls const TextBlock::operator[]
std::cout << tb[0]; // fine — reading a non-const TextBlock
tb[0] = ’x’; // fine — writing a non-const TextBlock
std::cout << ctb[0]; // fine — reading a const TextBlock
ctb[0] = ’x’; // error! — writing a const TextBlock
上面的成员函数定义后面加 const 是说明this
指针是一个pointer to const object,所以函数里不能修改non-static data member的值,注意,static member是可以被修改的。其中 tb[0] = ’x’
是传值,而且不应该修改成员函数返回类型。
- bitwise constness (also known as physical constness) and logical constness:
其中 bitwise constness表示const
函数不会修改objects内的任何bits,这也是c++对constness的定义,const
成员函数不允许修改non-static data member。但是成员函数在有的时候不是完全的bitwise constness,特别是该成员函数改变一个指针指向什么的时候。只要该指针在这个object内。
例子如下:
class CTextBlock {
public:
...
char& operator[](std::size_t position) const // inappropriate (but bitwise const) declaration of operator[]
{ return pText[position]; }
private:
char *pText;
};
为什么,因为该函数返回对对象内部数据的引用(returns a reference to the object’s internal data)。
const CTextBlock cctb("Hello"); // declare constant object
char *pc = &cctb[0]; // call the const operator[] to get a pointer to cctb’s data
*pc = ’J’; // cctb now has the value “Jello”
bitwise cosntness成员函数改变了对象成员的值,因此引出logical constness:认为const成员函数可能会修改调用它的对象中的一些位,但只是以客户机无法检测到的方式。
比如这个例子, 需要记录当前长度是否合法、计算文本块的长度,:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
std::size_t textLength; // last calculated length of textblock
bool lengthIsValid; // whether length is currently valid
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // error! can’t assign to textLength and lengthIsValid in a const
lengthIsValid = true;
} // member function
return textLength;
}
虽然textLength 和 lengthIsValid 声明不是const
,是可修改的,但是常量成员函数并不能作用在这两个成员数据上,解决的方法是声明为mutable
:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength; // these data members may
mutable bool lengthIsValid; // always be modified, even in
}; // const member functions
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // now fine
lengthIsValid = true; // also fine
}
return textLength;
}
- Avoiding Duplication in const and Non-const Member Functions
避免常量成员函数和非常量成员函数冗余,在非常量成员函数里调用常量版本:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // same as before
{
...
...
...
return text[position];
}
char& operator[](std::size_t position) // now just calls const op[]
{
return
const_cast<char&>( // cast away const on
// op[]’s return type;
static_cast<const TextBlock&>(*this) // add const to *this’s type;
[position] // call const version of op[]
);
}
...
};
如果你从一个const函数调用一个非const函数,你会冒着你承诺不修改的对象会被修改的风险。这就是为什么const成员函数调用非const成员函数是错误的:对象可以更改。这就是static_cast
在这种情况下处理*this
的原因:没有与常量相关的危险。
Things to Remember
✦ Declaring something const helps compilers detect usage errors. const can be applied to objects at any scope, to function parameters and return types, and to member functions as a whole.
✦ Compilers enforce bitwise constness, but you should program using logical constness.
✦ 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.
c++规则规定,对象的数据成员在输入构造函数体之前初始化,因此用成员初始化列表,初始化列表中的参数用作各数据成员的构造函数(复制构造、)参数:
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
: theName(name), // theName is copy-constructed from name
theAddress(address), // these are now all initializations
thePhones(phones),
numTimesConsulted(0)
{} // the ctor body is now empty
对于大多数类型,对复制构造函数的单个调用要比对默认构造函数的调用以及随后对复制赋值操作符的调用更有效——有时甚至更有效。
通常最好通过成员初始化来初始化所有内容。 类似地,即使希望默认构造数据成员,也可以使用成员初始化列表;只需要不指定任何初始化参数,因为当用户定义类型的数据成员在成员初始化列表中没有初始化器时,编译器会自动调用这些数据成员的默认构造函数:
ABEntry::ABEntry()
: theName(), // call theName’s default ctor;
theAddress(), // do the same for theAddress;
thePhones(), // and for thePhones;
numTimesConsulted(0) // but explicitly initialize
{}
-
必须初始化const或引用的数据成员;他们不能被分配。
-
初始化顺序:基类在派生类之前初始化(参见第12项),并且在类中,数据成员按照声明它们的顺序初始化。
-
静态对象:函数内部的静态对象称为局部静态对象(因为它们是函数的局部对象),其他类型的静态对象称为非局部静态对象;
翻译单元是产生单个目标文件的源代码,它基本上是一个单一的源文件,加上它所有的#include文件。我们所关心的问题至少涉及两个单独编译的源文件,每个文件至少包含一个非本地静态对象(即,一个全局对象,在名称空间范围内,或类或文件范围内的静态对象)。现在问题是:如果一个翻译单元中的非本地静态对象的初始化使用另一个翻译单元中的非本地静态对象,那么它使用的对象可以未初始化,因为在不同翻译单元中定义的非本地静态对象的初始化的相对顺序是未定义的。
class FileSystem { // from your library’s header file
public:
...
std::size_t numDisks() const; // one of many member functions
...
};
extern FileSystem tfs; // declare object for clients to use
// (“tfs” = “the file system” ); definition
// is in some .cpp file in your library
class Directory { // created by library client
public:
Directory( params );
...
};
Directory::Directory( params )
{
...
std::size_t disks = tfs.numDisks(); // use the tfs object
...
}
解决办法:单例模式(Singleton pattern):所要做的就是将每个非本地静态对象移动到它自己的函数中,在那里它被声明为静态。这些函数返回对它们包含的对象的引用。然后客户机调用函数而不是引用对象。换句话说,非本地静态对象被替换为本地静态对象。
class FileSystem { ... }; // as before
FileSystem& tfs() // this replaces the tfs object; it could be
{ // static in the FileSystem class
static FileSystem fs; // define and initialize a local static object
return fs; // return a reference to it
}
class Directory { ... }; // as before
Directory::Directory( params ) // as before, except references to tfs are
{ // now to tfs()
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir() // this replaces the tempDir object; it
{ // could be static in the Directory class
static Directory td( params ); // define/initialize local static object
return td; // return reference to it
}
Things to Remember
✦ Manually initialize objects of built-in type, because C++ only sometimes initializes them itself.
✦ In a constructor, 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.
✦ Avoid initialization order problems across translation units by replacing non-local static objects with local static objects.