多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# Terminology(术语) 作者:Scott Meyers 译者:fatalerror99 (iTePub's Nirvana) 发布:http://blog.csdn.net/fatalerror99/ 这是一个所有程序员都应该了解的小型的 C++ vocabulary(词汇表)。下面的术语都足够重要,对它们的含义取得完全一致对于我们来说是完全必要的。 declaration(声明)告诉编译器关于某物的 name(名字)和 type(类型),但它省略了某些细节。以下这些都是 declaration(声明): extern int x; // object declaration std::size_t numDigits(int number); // function declaration class Widget; // class declaration template<typename T> // template declaration class GraphNode; // (see Item 42 for info on // the use of "typename") 注意即使是 built-in type(内建类型),我还是更喜欢将整数 x 看作一个 "object",某些人将 "object" 这个名字保留给 user-defined type(用户定义类型),但我不是他们中的一员。再有就是注意函数 numDigits 的返回类型是 std::size_t,也就是说,namespace(命名空间)std 中的 size_t 类型。这个 namespace(命名空间)是 C++ 标准库中每一样东西实际所在的地方。但是,因为 C 标准库(严谨地说,来自于 C89)在 C++ 中也能使用,从 C 继承来的符号(诸如 size_t)可能存在于全局范围,或 std 内部,或两者都有,这依赖于哪一个头文件被 #include。在本书中,我假设 C++ 头文件被 #include,这也就是为什么我用 std::size_t 代替 size_t 的原因。当文字讨论中涉及到标准库组件时,我一般不再提及 std,这依赖于你认可类似 size_t,vector,以及 cout 之类的东西都在 std 中,在示例代码中,我总是包含 std,因为真正的代码没有它将无法编译。 顺便说一下,size_t 仅仅是供 C++ 对某物计数时使用的某些 unsigned 类型的 typedef(例如,一个 char\*-based string(基于 char\* 的 string)中字符的个数,一个 STL container(容器)中元素的个数,等等)。它也是 vector,deque,和 string 的 operator[] 函数所持有的类型,这是一个在 Item 3 中定义我们自己的 operator[] 函数时将要遵守的惯例。 每一个函数的 declaration(声明)都表明了它的 signature(识别特征),也就是它的参数和返回类型。一个函数的 signature(识别特征)与它的类型相同。对于 numDigits 的情况,signature(识别特征)是 std::size_t (int),也就是说,“函数取得一个 int,并返回一个 std::size_t”。官方的 "signature"(识别特征)的 C++ 定义排除了函数的返回类型,但是在本书中,将返回类型考虑为 signature(识别特征)的一部分更加有用。 definition(定义)为编译器提供在 declaration(声明)时被省略的细节。对于一个 object(对象),definition(定义)是编译器为 object(对象)留出内存的地方。对于一个 function(函数)或一个 function template(函数模板),definition(定义)提供代码本体。对于一个 class(类)或一个 class template(类模板),definition(定义)列出了 class(类)或者 template(模板)的 members(成员): int x; // object definition std::size_t numDigits(int number) // function definition. { // (This function returns std::size_t digitsSoFar = 1; // the number of digits // in its parameter.) while ((number /= 10) != 0) ++digitsSoFar; return digitsSoFar; } class Widget { // class definition public: Widget(); ~Widget(); ... }; template<typename T> // template definition class GraphNode { public: GraphNode(); ~GraphNode(); ... }; initialization(初始化)是设定一个 object(对象)的第一个值的过程。对于 user-defined types(用户定义类型)的 objects(对象),initialization(初始化)通过 constructors(构造函数)完成。default constructor(缺省构造函数)就是不需要任何 arguments(引数)就可以调用的那一个。这样的一个 constructor(构造函数)既可以是没有 parameters(参数),也可以是每一个 parameter(参数)都有缺省值: class A { public: A(); // default constructor }; class B { public: explicit B(int x = 0, bool b = true); // default constructor; see below }; // for info on "explicit" class C { public: explicit C(int x); // not a default constructor }; 这里 classes B 和 C 的 constructors(构造函数)都被声明为 explicit(显式)。这是为了防止它们被用来执行 implicit type conversions(隐式类型转换),虽然他们还可以被用于 explicit type conversions(显示类型转换): ``` void doSomething(B bObject); // a function taking an object of // type B B bObj1; // an object of type B doSomething(bObj1); // fine, passes a B to doSomething B bObj2(28); // fine, creates a B from the int 28 // (the bool defaults to true) doSomething(28); // error! doSomething takes a B, // not an int, and there is no // implicit conversion from int to B doSomething(B(28)); // fine, uses the B constructor to // explicitly convert (i.e., cast) the // int to a B for this call. (See // Item 27 for info on casting.) ``` constructors(构造函数)被声明为 explicit(显式)通常比 non-explicit(非显式)更可取,因为它们可以防止编译器执行意外的(常常是无意识的)type conversions(类型转换)。除非我有一个好的理由允许一个 constructor(构造函数)被用于 implicit type conversions(隐式类型转换),否则我就将它声明为 explicit(显式)。我希望你能遵循同样的方针。 请注意我是如何突出上面的示例代码中的 cast(强制转换)的。贯穿本书,我用这样的突出引导你注意那些应该注意的材料。(我也突出章节号码,但那仅仅是因为我想让它好看一些。) copy constructor(拷贝构造函数)被用来以一个 object(对象)来初始化同类型的另一个 object(对象),copy assignment operator(拷贝赋值运算符)被用来将一个 object(对象)中的值拷贝到同类型的另一个 object(对象)中: ``` class Widget { public: Widget(); // default constructor Widget(const Widget& rhs); // copy constructor Widget& operator=(const Widget& rhs); // copy assignment operator ... }; Widget w1; // invoke default constructor Widget w2(w1); // invoke copy constructor w1 = w2; // invoke copy // assignment operator ``` 当你看到什么东西看起来像一个 assignment(赋值)的话,要仔细阅读,因为 "=" 在语法上还可以被用来调用 copy constructor(拷贝构造函数): ``` Widget w3 = w2; // invoke copy constructor! ``` 幸运的是,copy constructor(拷贝构造函数)很容易从 copy assignment(拷贝赋值)中区别出来。如果一个新的 object(对象)被定义(就象上面那行代码中的 w3),一个 constructor(构造函数)必须被调用;它不可能是一个 assignment(赋值)。如果没有新的 object(对象)被定义(就象上面那行 "w1 = w2" 代码中),没有 constructor(构造函数)能被调用,所以它就是一个 assignment(赋值)。 copy constructor(拷贝构造函数)是一个特别重要的函数,因为它定义一个 object(对象)如何 passed by value(通过传值的方式被传递)。例如,考虑这个: ``` bool hasAcceptableQuality(Widget w); ... Widget aWidget; if (hasAcceptableQuality(aWidget)) ... ``` 参数 w 通过传值的方式被传递给 hasAcceptableQuality,所以在上面的调用中,aWidget 被拷贝给 w。拷贝动作通过 Widget 的 copy constructor(拷贝构造函数)被执行。pass-by-value(通过传值方式传递)意味着 "call the copy constructor"(调用拷贝构造函数)。(然而,通过传值方式传递 user-defined types(用户定义类型)通常是一个不好的想法,pass-by-reference-to-const(传引用给 const)通常是更好的选择。关于细节,参见 Item 20。) STL 是 Standard Template Library(标准模板库),作为 C++ 标准库的一部分,致力于 containers(容器)(例如,vector,list,set,map,等等),iterators(迭代器)(例如,vector<int>::iterator,set<string>::iterator,等等),algorithms(算法)(例如,for_each,find,sort,等等),以及相关机能。相关机能中的很多都通过 function objects(函数对象)——行为表现类似于函数的 objects(对象)——提供。这样的 objects(对象)来自于重载了 operator() ——函数调用运算符——的 class(类),如果你不熟悉 STL,在读本书的时候,你应该有一本像样的参考手册备查,因为对于我来说 STL 太有用了,以至于不能不利用它。一但你用了一点点,你也会有同样的感觉。 从 Java 或 C# 那样的语言来到 C++ 的程序员可能会对 undefined behavior(未定义行为)的概念感到吃惊。因为各种各样的原因,C++ 中的一些 constructs(结构成分)的行为没有确切的定义:你不能可靠地预知运行时会发生什么。这里是两个带有 undefined behavior(未定义行为)的代码的例子: ``` int *p = 0; // p is a null pointer std::cout << *p; // dereferencing a null pointer // yields undefined behavior char name[] = "Darla"; // name is an array of size 6 (don't // forget the trailing null!) char c = name[10]; // referring to an invalid array index // yields undefined behavior ``` 为了强调 undefined behavior(未定义行为)的结果是不可预言而且可能是令人讨厌的,有经验的 C++ 程序员常常说带有 undefined behavior(未定义行为)的程序 can(能)毁掉你的辛苦工作的成果。这是真的:一个带有 undefined behavior(未定义行为)的程序 could(可以)毁掉你的心血。只不过可能性不太大。更可能的是那个程序的表现反复无常,有时会运行正常,有时会彻底完蛋,还有时会产生错误的结果。有实力的 C++ 程序员能以最佳状态避开 undefined behavior(未定义行为)。本书中,我会指出许多你必须要注意它的地方。 另一个可能把从其它语言转到 C++ 的程序员搞糊涂的术语是 interface(接口)。Java 和 .NET 的语言都将 Interfaces(接口)作为一种语言要素,但是在 C++ 中没有这种事,但是在 Item 31 讨论了如何模拟它。当我使用术语 "interface"(接口)时,一般情况下我说的是一个函数的 signature(识别特征),是一个 class(类)的可访问元素(例如,一个 class(类)的 "public interface","protected interface",或 "private interface"),或者是对一个 template(模板)的 type parameter(类型参数)来说必须合法的 expressions(表达式)(参见 Item 41)。也就是说,我是作为一个相当普遍的设计概念来谈论 interface(接口)的。 client(客户)是使用你写的代码(一般是 interfaces(接口))的某人或某物。例如,一个函数的 clients(客户)就是它的使用者:调用这个函数(或持有它的地址)的代码的片段以及写出和维护这样的代码的人。class(类)或者 template(模板)的 clients(客户)是使用这个 class(类)或 template(模板)的软件的部件,以及写出和维护那些代码的程序员。在讨论 clients(客户)的时候,我一般指向程序员,因为程序员会被困扰和误导,或者因为不好的 interfaces(接口)而烦恼。但他们写的代码却不会。 你也许不习惯于为 clients(客户)着想,但是我会用大量的时间试图说服你:你应该尽你所能使他们的生活更轻松。记住,你也是一个其他人开发的软件的 client(客户)。难道你不希望那些人为你把事情弄得轻松些吗?除此之外,你几乎肯定会在某个时候发现你自己处在了你自己的 client(客户)的位置上(也就是说,使用你写的代码),而这个时候,你会为你在开发你的 interfaces(接口)时在头脑中保持了对 client(客户)的关心而感到高兴。 在本书中,我常常掩盖 functions(函数)和 function templates(函数模板)之间以及 classes(类)和 class templates(类模板)之间的区别。那是因为对其中一个确定的事对另一个常常也可以确定。如果不是这样,我会区别对待 classes(类),functions(函数),以及由 classes(类)和 functions(函数)产生的 templates(模板)。 在代码注释中提到 constructor(构造函数)和 destructors(析构函数)时,我有时使用缩写形式 ctor 和 dtor。 Naming Conventions(命名惯例) 我试图为 objects(对象),classes(类),functions(函数),templates(模板)等选择意味深长的名字,但是在我的某些名字后面的含义可能不会立即显现出来。例如,我特别喜欢的两个 parameter names(参数名字)是 lhs 和 rhs。它们分别代表 "left-hand side" 和 "right-hand side"。我经常用它们作为实现 binary operators(二元运算符)的函数(例如,operator== 和 operator\*)的 parameter names(参数名字)。例如,如果 a 和 b 是代表有理数的 objects(对象),而且如果 Rational objects(对象)能通过一个 non-member(非成员)的 operator\* 函数相乘(Item 24 中解释的很可能就是这种情况),表达式 ``` a * b ``` 与函数调用 ``` operator*(a,b) ``` 就是等价的。 在 Item 24 中,我这样声明 operator\*: ``` const Rational operator*(const Rational& lhs, const Rational& rhs); ``` 你可以看到,left-hand operand(左手操作数)a 在函数内部以 lhs 的面目出现,而 right-hand operand(右手操作数)b 以 rhs 的面目出现。 对于 member functions(成员函数),left-hand argument(左手参数)表现为 this pointer(this 指针),所以有时候我单独使用 parameter name(参数名字) rhs。你可能已经在第 5 页中某些 Widget 的 member functions(成员函数)的 declarations(声明)(本文介绍 copy constructor(拷贝构造函数)的那一段中的例子——译者注)中注意到了这一点。这一点提醒了我。我经常在示例中使用 Widget class(类)。"Widget" 并不意味着什么东西。它仅仅是在我需要一个示例类的名字的时候不时地使用一下的名字。它和 GUI 工具包中的 widgets 没有任何关系。 我经常遵循这个规则为 pointers(指针)命名:一个指向 type(类型)T 的 object(对象)的 pointer(指针)被称为 pt,"pointer to T"。以下是例子: ``` Widget *pw; // pw = ptr to Widget class Airplane; Airplane *pa; // pa = ptr to Airplane class GameCharacter; GameCharacter *pgc; // pgc = ptr to GameCharacter ``` 我对 references(引用)使用类似的惯例:rw 可以认为是一个 reference to a Widget(引向一个 Widget 的引用),而 ra 是一个 reference to an Airplane(引向一个 Airplane 的引用)。 在讨论 member functions(成员函数)的时候我偶尔会使用名字 mf。 Threading Considerations(对线程的考虑) 作为一种语言,C++ 没有 threads(线程)的概念——实际上,是没有任何一种 concurrency(并发)的概念。对于 C++ 标准库也是同样如此。就 C++ 涉及的范围而言,multithreaded programs(多线程编程)并不存在。 而且至今它们依然如此。我致力于让此书基于标准的,可移植的 C++,但我也不能对 thread safety(线程安全)已成为很多程序员所面临的一个问题的事实视而不见。我对付这个标准 C++ 和现实之间的裂痕的方法就是指出某个 C++ constructs(结构成分)以我的分析很可能在 threaded environment(线程环境)中引起问题的地方。这样不但不会使本书成为一本 multithreaded programming with C++(用 C++ 进行多线程编程)的书。反而,它更会使本书在相当程度上成为这样一本 C++ 编程的书:将自己在很大程度上限制于 single-threaded(单线程)思路,承认 multithreading(多线程)的存在,并试图指出有线程意识的程序员需要特别留心评估我提供的建议的地方。 如果你不熟悉 multithreading(多线程)或者不必为此担心,你可以忽略我关于线程的讨论。如果你正在编写一个多线程的应用或库,无论如何,请记住我的评注和并将它作为你使用 C++ 时需要致力去解决的问题的起点。 TR1 和 Boost 你会发现提及 TR1 和 Boost 的地方遍及全书。它们每一个都有一个专门的 Item 在某些细节上进行描述(Item 54 是 TR1,Item 55 是 Boost),但是,不幸的是,这些 Item 在全书的最后。(他们在那里是因为那样更好一些,我确实试过很多其它的地方。)如果你愿意,你现在就可以翻开并阅读那些 Item,但是如果你更喜欢从本书的起始处而不是结尾处开始,以下摘要会对你有所帮助: * TR1 ("Technical Report 1") 是被加入 C++ 标准库的新机能的 specification(规格说明书)。这些机能以新的 class(类)和 function templates(函数模板)的形式提供了诸如 hash tables(哈希表),reference-counting smart pointers(引用计数智能指针),regular expressions(正则表达式),等等。所有的 TR1 组件都位于嵌套在 namespace std 内部的 namespace tr1 内。 * Boost 是一个组织和一个网站 (http://boost.org) 提供的可移植的,经过同行评审的,开源的 C++ 库。大多数 TR1 机能都基于 Boost 的工作,而且直到编译器厂商在他们的 C++ 库发行版中包含 TR1 之前,Boost 网站很可能会保持开发者寻找 TR1 实现的第一站的地位。Boost 提供的东西比用于 TR1 的更多,无论如何,在很多情况下,它还是值得去了解一下的。