科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件C++箴言:理解Terminology术语

C++箴言:理解Terminology术语

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

这是一个所有程序员都应该了解的小型的 C++ 词汇表。下面的条目都足够重要,值得我们对它们的含义务必取得完全一致。

来源:中国软件网 2008年4月16日

关键字: 术语 Terminology C++ C Linux

  • 评论
  • 分享微博
  • 分享邮件
这是一个所有程序员都应该了解的小型的 C++ 词汇表。下面的条目都足够重要,值得我们对它们的含义务必取得完全一致。

  声明 (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")
  注意即使是内建类型,我还是更喜欢将整数看作一个 "object",某些人将 "object" 这个名字保留给 用户定义类型,但我不是他们中的一员。再有就是注意函数 numDigits 的返回类型是 std::size_t,也就是说,namespace std 中的 size_t 类型。这个 namespace 是 C++ 标准库中每一样东西实际所在的地方。但是,因为 C 标准库(严谨地说,来自于 C89)在 C++ 中也能使用,从 C 继承来的符号(诸 如 size_t)可能存在于全局范围,std 内部,或两者都有,这依赖于哪一个头文件被 #included。在本书中,我假设 C++ 头文件被 #included,这也就是为 什么我用 std::size_t 代替 size_t 的原因。当行文中涉及到标准库组件时,我一般不再提及 std,这依赖于你认可类似 size_t,vector,以及 cout 之类的东西 都在 std 中,在示例代码中,我总是包含 std,因为真正的代码没有它将无法编译。

  顺便说一下,size_t 仅仅是某些供 C++ 对某物计数时 使用的 unsigned 类型的 typedef(例如,一个基于 char* 的 string 中字符的个数,一个 STL 容器中元素的个数,等等)。它也是 vector,deque,以及 string 的 operator[] 函数所持有的类型,这是一个在 Item 3 中定义我们自己的 operator[] 函数时将要遵守的惯例。

  每一个函数的声明都表 明了它的识别标志(signature),也就是它的参数和返回类型。一个函数的识别标志(signature)与它的类型相同。对于 numDigits 的情况,识别标志 (signature)是 std::size_t (int),也就是说,“函数持有一个 int,并返回一个 std::size_t”。官方的“识别标志(signature)”的 C++ 定义排除了函数的返回 类型,但是在本书中,将返回类型考虑为识别标志的一部分更加有用。

  定义(definition)为编译器提供在声明时被省略的细节。对于一个 对象,定义是编译器为对象留出内存的地方。对于一个函数或一个函数模板,定义提供代码本体。对于一个类或一个类模板,定义列出了类或者模板的成 员: 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)是设定一个对象的第一个值的过程。对于用户定义类 型的对象,初始化通过构造函数完成任务。缺省构造函数(default constructor)就是不需要任何引数(arguments)就可以调用的构造函数。这样的一个 构造函数既可以是没有参数(parameters),也可以是每一个参数都有缺省值: 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
};
  这里 B 和 C 的构造函数都被声明为 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.)
  构造函数被声明为 explicit(显式的)通常比 non-explicit(非显式)的更可取,因为它 们可以防止编译器执行意外的(常常是无意识的)类型转换。除非我有一个好的理由允许一个构造函数被用于隐式类型转换(implicit type conversions),否则就将它声明为 explicit(显式的)。我希望你能遵循同样的方针。

  构造函数被声明为 explicit(显式的)通常比 non-explicit(非显式)的更可取,因为它们可以防止编译器执行意外的(常常是无意识的)类型转换。除非我有一个好的理由允许一个构造函数被用于隐 式类型转换(implicit type conversions),否则就将它声明为 explicit(显式的)。我希望你能遵循同样的方针。

  请注意我是如何突出上面 的示例代码中的强制转换(cast)的。贯穿本书,我用这样的突出引导你注意那些应该注意的材料。(我也突出章节号码,但那仅仅是因为我想让它好看一 些。)

  拷贝构造函数(copy constructor)被用来以一个对象来初始化同类型的另一个对象,拷贝赋值运算符(copy assignment operator)被用来将一个对象中的值拷贝到同类型的另一个对象中: 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
  当你看到什么东西看起来像一个赋值的话,要仔细阅读,因为 "=" 在语法上还可以被用来调用拷贝构造 函数: Widget w3 = w2; // invoke copy constructor!
  幸运的是,拷贝构造函数很容易从拷贝赋值中区别出来。如 果一个新的对象被定义(就象上面那行代码中的 w3),一个构造函数必须被调用;它不可能是一个赋值。如果没有新的对象被定义(就象上面那行 "w1 = w2" 代码中),没有构造函数能被调用,所以它就是一个赋值。

  拷贝构造函数是一个特别重要的函数,因为它定义一个对象如何通过传值 的方式被传递。例如,考虑这个: bool hasAcceptableQuality(Widget w);

...
Widget aWidget;
if (hasAcceptableQuality(aWidget)) ...
  参数 w 通过传值的方式被传递给 hasAcceptableQuality,所以在上面的调用中,aWidget 被拷贝给 w。拷贝 动作通过 Widget 的拷贝构造函数被执行。通过传值方式传递意味着“调用拷贝构造函数”。(无论如何,通过传值方式传递用户定义类型通常是一个不好的 想法,传引用给 const 通常是更好的选择。)

  STL 是标准模板库(Standard Template Library),作为 C++ 的标准库的一部分,致力于容 器(containers)(例如,vector,list,set,map,等等),迭代器(iterators)(例如,vector<int>::iterator,set<string>::iterator,等等), 算法(algorithms)(例如,for_each,find,sort,等等),以及相关机能。相关机能中的很多都通过函数对象(function objects)——行为表现类似于 函数的对象——提供。这样的对象来自于重载了 operator() ——函数调用运算符——的类,如果你不熟悉 STL,在读本书的时候,你应该有一本像样的参 考手册备查,因为对于我来说 STL 太有用了,以至于不能不利用它。一但你用了一点点,你也会有同样的感觉。

  从 Java 或 C# 那样的语言 来到 C++ 的程序员可能会对未定义行为(undefined behavior)的概念感到吃惊。因为各种各样的原因,C++ 中的一些结构成分(constructs)的行为没 有确切的定义:你不能可靠地预知运行时会发生什么。这里是两个带有未定义行为的代码的例子: 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
  为了强调未定义行为的结果是不可预言而且可能是令人讨厌的,有经验的 C++ 程序员常常 说带有未定义行为的程序能(can)删除你的硬盘。这是真的:一个带有未定义行为的程序可以(could)删除你的硬盘。只不过可能性不太大。更可能的 是那个程序的表现反复无常,有时会运行正常,有时会彻底完蛋,还有时会产生错误的结果。有实力的 C++ 程序员能以最佳状态避开未定义行为。本书中 ,我会指出许多你必须要注意它的地方。

  另一个可能把从其它语言转到 C++ 的程序员搞糊涂的条目是接口(interface)。Java 和 .NET 的 语言都将接口作为一种语言要素,但是在 C++ 中没有这种事。当我使用条目“接口(interface)”时,一般情况下我说的是一个函数的识别标志,是一个类 的可访问元素(例如,一个类的 "public interface","protected interface",或 "private interface"),或者是对一个模板的类型参数来说必须合法的表达式 。也就是说,我是作为一个相当普遍的设计概念来谈论接口(interface)的。

  客户(client)是使用你写的代码(一般是接口 (interfaces))的某人或某物。例如,一个函数的客户就是它的用户:调用这个函数(或持有它的地址)的代码的片段以及写出和维护这样的代码的人。 类或者模板的客户是使用这个类或模板的软件的部件,以及写出和维护那些代码的程序员。在讨论客户的时候,我一般指向程序员,因为程序员会被困扰 和误导,或者因为不好的接口而烦恼。但他们写的代码却不会。

  你也许不习惯于为客户着想,但是我会用大量的时间试图说服你:你应该 尽你所能使他们的生活更轻松。记住,你也是一个其他人开发的软件的客户。难道你不希望那些人为你把事情弄得轻松些吗?除此之外,在某种程度上, 你几乎肯定能发现你自己处在了你自己的客户的位置上(也就是说,使用你写的代码),而这个时候,你会为你在开发你的接口时在头脑中保持了对客户 的关心而感到高兴。

  我常常掩盖函数和函数模板之间以及类和类模板之间的区别。那是因为对其中一个确定的事对另一个常常也可以确定 。如果不是这样,我会区别对待类,函数,以及由类和函数产生的模板。

  在代码注释中提到构造函数和析构函数时,我有时使用缩写形式 ctor 和 dtor。

  Naming Conventions 命名惯例

  我试图为对象,类,函数,模板等选择意味深长的名字,但是在我的某些名 字后面的含义可能不会立即显现出来。例如,我特别喜欢的两个参数名字是 lhs 和 rhs。它们分别代表 "left-hand side" 和 "right-hand side"。我经常用它们 作为实现二元运算符的函数(例如,operator== 和 operator*)的参数名。例如,如果 a 和 b 是代表有理数的对象,而且如果 Rational 对象能通过一个非 成员的 operator* 函数相乘(Item 24 中解释的很可能就是这种情况),表达式 a * b
  与函数调用 operator*(a,b)
  就是等价的。

  我也这样声明 operator*过: const Rational operator*(const Rational& lhs, const Rational& rhs);
  你可以看到,左手操作数 (left-hand operand)a 在函数内部以 lhs 的面目出现,而右手操作数(right-hand operand)b 以 rhs 的面目出现。

  对于成员函数左手参 数(left-hand argument)表现为 this 指针,所以有时候我单独使用参数名 rhs。你可能已经在第 5 页中某些 Widget 成员函数的声明(本文介绍拷贝构造 函数的那一段中的例子——译者注)中注意到了这一点。这一点提醒了我。我经常在示例中使用 Widget 类。"Widget" 并不意味着什么东西。它仅仅是在我 需要一个示例类的名字的时候不时地使用一下的名字。它和 GUI 工具包中的 widgets 没有任何关系。

  我经常遵循这个规则为指针命名:一 个指向类型 T 的对象的指针被称为 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
  我对引用使用类似的惯例:rw 可 以认为是一个引向 Widget 的引用,而 ra 是一个引向 Airplane 的引用。

  在我讨论成员函数的时候我偶尔会使用名字 mf。

  Threading Considerations 对线程的考虑

  作为一种语言,C++ 没有线程的概念——实际上,是没有任何一种并发的概念。对于 C++ 标准 库也是同样如此。就 C++ 涉及的范围而言,多线程编程并不存在。

  而且至今它们依然如此。我致力于让此书基于标准的,可移植的 C++, 但我也不能对线程安全(thread safety)已成为很多程序员所面临的一个问题的事实视而不见。我对付这个标准 C++ 和现实之间的裂痕的方法就是指出某 处的 C++ 结构成分(constructs)以我的分析很可能在多线程环境中引起问题的地方。这样不但不会使本书成为一本用 C++ 进行多线程编程的书。反而, 它更会使本书在相当程度上成为这样一本 C++ 编程的书:将自己在很大程度上限制于单线程考虑,承认多线程的存在,并试图指出有线程意识的程序员需 要特别当心评估我提供的建议的地方。

  如果你不熟悉多线程编程或者不必为此担心,你可以忽略我关于线程的讨论。如果你正在编写一个 多线程的应用或库,无论如何,请记住我的评注和并将它作为你使用 C++ 时需要致力去解决的问题的起点。

  TR1 and Boost TR1 和 Boost

  你会发现提及 TR1 和 Boost 的地方遍及整个系列。它们每一个都专门在某些细节上进行描述,但是,不幸的是,这些都在整个系列 的最后。(他们在那里是因为那样更好一些,我确实试过很多其它的地方。)如果你愿意,但是如果你更喜欢从本书的起始处而不是结尾处开始,以下摘 要会对你有所帮助:

  ·TR1 ("Technical Report 1") 是被加入 C++ 标准库的新机能的规格说明书。这些机能以新的类和函数模板的形式提供 了诸如哈希表(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 的更多,无论如何,在很多情况下,它还是值得去了解一下的。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章