跳转到内容

C++ 编程

来自 Wikibooks,开放世界中的开放书籍

编码风格约定

[编辑 | 编辑源代码]

使用指南或一套约定为程序员提供了一套代码规范化或编码风格规则,这些规则规定了如何格式化代码、命名变量、放置注释或任何其他与语言无关的代码结构决策。这非常重要,因为您与他人共享项目。通过建立对一组通用的编码标准和建议达成一致,可以节省时间和精力,通过提高代码库的可理解性和透明度,为未记录的结构提供共同基础,从而简化调试并提高代码的可维护性。这些规则也可以称为源代码风格代码约定编码标准或其变体。

许多组织发布了 C++ 风格指南。可以在C++ 编码约定参考部分找到不同方法的列表。C++ 编程中最常用的风格是 ANSI 或 Allman 风格,而许多 C 编程仍然使用 Kernighan 和 Ritchie (K&R) 风格。需要注意的是,这应该是您在项目中做出的第一个决定之一,在民主环境中,达成共识可能非常困难。

Bjarne Stroustrup 关于风格约定的演示视频(不仅仅是 C++14)https://github.com/isocpp/CppCoreGuidelines以及指南链接:https://github.com/isocpp/CppCoreGuidelines%7CCppCoreGuidelines Herb Sutter 关于风格约定的演示视频(不仅仅是 C++14)https://www.youtube.com/watch?v=hEx5DNLWGgA GSL(指南支持库)链接 https://github.com/Microsoft/GSL


程序员倾向于坚持一种编码风格,他们将其自动化,并且任何偏差都很难适应,如果您没有喜欢的风格,请尝试对常用风格进行尽可能小的修改,或者尽可能广泛地了解,以便轻松适应变化或为您的方法辩护。有一些软件可以帮助格式化或美化代码,但自动化也存在缺点。如前所述,编译器完全忽略了缩进和空格或制表符的使用。编码风格应根据标准化的最低共同需求而有所不同。

另一个因素,即使程度很小,选择编码风格约定的因素是 IDE(或代码编辑器)及其功能,这可能例如会影响确定代码的冗长程度、行的最大长度等。一些编辑器现在具有非常有用的功能,例如单词补全、重构功能等,这些功能可以使某些规范变得不必要或过时。这将使编码风格的采用也取决于目标代码用户可用的软件。

受代码风格选择影响的领域有

  • 可重用性
    • 自文档代码
    • 国际化
    • 可维护性
    • 可移植性
  • 优化
  • 构建过程
  • 避免错误
  • 安全性
标准化很重要

无论您选择哪种特定的编码风格,一旦选择,都应在同一个项目中保持一致。阅读遵循不同风格的代码可能会变得非常困难。在接下来的部分中,我们将尝试解释为什么某些选项是常见做法,而不会强迫您采用特定的风格。

注意
使用糟糕的编码风格比根本没有编码风格更糟糕,因为您会将不良实践扩展到所有代码库中。

25 行 80 列

[编辑 | 编辑源代码]

这条规则是普遍推荐的,但经常有人反驳说这条规则已经过时。该规则起源于基于文本的计算机终端和点阵打印机通常最多只能显示 80 列文本的时代。因此,超过 80 列的文本要么会不方便地换行,要么更糟的是根本无法显示。

除了设备的物理限制外,这条规则通常仍然建议在以下论点下提出:如果您编写的代码将超过 80 列或 25 行,那么就该考虑将代码拆分为函数了。较小的封装代码块有助于审查代码,因为它可以一次全部看到,而无需向上或向下滚动。这使程序员对项目的思维表示模块化,从而简化了思维表示。当您必须返回一个您已停止工作 6 个月的项目时,此做法将为您节省宝贵的时间。

例如,您可能希望将长的输出语句拆分为多行

    fprintf(stdout,"The quick brown fox jumps over the lazy dog. "
                   "The quick brown fox jumps over the lazy dog.\n"
                   "The quick brown fox jumps over the lazy dog - %d", 2);


此推荐做法也与本书0 表示成功函数约定相关,我们将在本书的函数部分介绍。

空白和缩进

[编辑 | 编辑源代码]

注意
空格、制表符和换行符(换行)称为空白。空白用于分隔相邻的单词和数字;在其他任何地方都被忽略,除了在引号和预处理器指令中。

使用空白来提高代码可读性的约定称为缩进风格。每个代码块和每个定义都应遵循一致的缩进风格。这通常意味着 {} 中的所有内容。但是,单行代码块也一样。

使用固定数量的空格进行缩进。建议各不相同;2、3、4、8 都是常见的数字。如果您使用制表符进行缩进,则必须注意编辑器和打印机可能会以不同的方式处理和扩展制表符。K&R 标准建议缩进大小为 4 个空格。

制表符的使用是有争议的,其基本前提是它降低了源代码的可移植性,因为加载到具有不同设置的不同编辑器中的相同源代码看起来不会相同。这是某些程序员更喜欢使用空格的一致性(或配置编辑器以用必要的空格数替换制表键的使用)的主要原因之一。

例如,程序也可以用以下方式编写

// Using an indentation size of 2
if ( a > 5 )  { b=a; a++; }

但是,使用正确的缩进可以使相同的代码更具可读性

// Using an indentation size of 2
if ( a > 5 )  {
  b = a;
  a++;
}

// Using an indentation size of 4
if ( a > 5 )
{
    b = a;
    a++;
}

大括号(花括号)的位置

[编辑 | 编辑源代码]

正如我们在语句部分早期看到的那样,复合语句在 C++ 中非常重要,它们也受不同编码风格的影响,这些风格建议采用不同的开始和结束大括号({})放置方式。有些建议将开始大括号放在语句所在行的末尾(K&R)。其他一些建议将它们放在单独的一行,但不缩进(ANSI C++)。GNU 建议将大括号放在单独的一行,并将其缩进一半。我们建议选择一种大括号放置风格并坚持使用它。

示例

if (a > 5) {
  // This is K&R style
}

if (a > 5) 
{
  // This is ANSI C++ style
}

if (a > 5) 
  {
    // This is GNU style
  }

注释是编译器忽略的代码部分,允许用户在源代码的相关区域进行简单的注释。注释可以是块的形式,也可以是单行的形式。

  • 单行注释(非正式地,C++ 样式),以//开头,一直持续到行尾。如果注释行中的最后一个字符是\,则注释将继续到下一行。
  • 多行注释(非正式地,C 样式),以/*开头,以*/结束。

注意
自从 1999 年修订版以来,C 也允许使用C++ 风格的注释,因此非正式名称在很大程度上具有历史意义,用于区分这两种注释方法。

我们现在将描述如何在源代码中添加注释,但不会描述在哪里、如何以及何时注释;我们将在稍后详细介绍。

C 风格注释

[编辑 | 编辑源代码]

如果您使用 C 风格注释,请尝试像这样使用它

注释单行

/*void EventLoop(); /**/

注释多行

/*
void EventLoop();
void EventLoop();
/**/

这允许您轻松取消注释。例如

取消注释单行

void EventLoop(); /**/

取消注释多行

void EventLoop();
void EventLoop();
/**/

注意
某些编译器可能会生成错误/警告。
尽量避免在函数内部使用 C 风格注释,因为 C 风格注释不支持嵌套(大多数编辑器现在都具有一定的颜色功能来防止此类错误,但很容易错过,并且不应该假设代码是如何读取的)。

... 通过仅删除注释的开头并激活下一个注释,您确实重新激活了注释的代码,因为如果您以这种方式开始注释,它将一直有效,直到找到注释的结束符*/

注意
请记住,C 风格注释/* like this */ 不“嵌套”,即您不能编写

int function() /* This is a comment */
{              
 return 0;  
}              and this is the same comment */
               so this isn't in the comment, and will give an error*/

因为文本所以这不在注释中 */在行尾,不在注释内部;注释在第一个*/找到的序列结束,忽略任何中间/*序列,这在人类读者看来可能像是嵌套注释的开始。

C++ 风格注释

[编辑 | 编辑源代码]

示例

// This is a single one line comment

或者

if (expression) // This needs a comment
{
  statements;   
}
else
{
  statements;
}

反斜杠是续行字符,并将注释续到下一行

// This comment will also comment the following line \
std::cout << "This line will not print" << std::endl;
使用注释临时忽略代码

注释有时也用于包含我们希望编译器临时忽略的代码。这在查找程序中的错误时非常有用。如果程序没有产生预期的结果,则可以通过注释掉代码来跟踪哪个特定语句包含错误。

使用C 风格注释的示例
/* This is a single line comment */

或者

/*
   This is a multiple line comment
*/
CC++ 风格

组合多行注释 (/* */) 和 c++ 注释 (//) 以注释掉多行代码

注释掉代码

/*
void EventLoop();
void EventLoop();
void EventLoop();
void EventLoop();
void EventLoop();
//*/

取消注释代码块

//*
void EventLoop();
void EventLoop();
void EventLoop();
void EventLoop();
void EventLoop();
//*/

这是因为//* 仍然是 c++ 注释。而 //*/ 充当 c++ 注释和多行注释终止符。但是,如果任何多行注释用于函数描述,则此方法无效。

关于使用预处理语句的说明

另一种方法(被认为是不好的做法)是选择性地启用或禁用代码部分

#if(0)   // Change this to 1 to uncomments.
void EventLoop();
#endif

这被认为是不好的做法,因为当混合使用多个 #if 时,代码通常变得难以阅读,如果使用它们,请不要忘记在 #endif 中添加一条注释,说明它对应哪个 #if

#if (FEATURE_1 == 1)
do_something;
#endif //FEATURE_1 == 1

您可以通过使用内联函数(通常被认为比宏更易读,且没有性能损失)来防止难以阅读,这些函数在 #if #else #endif 中仅包含两个部分

inline do_test()
  {
    #if (Feature_1 == 1)
      do_something
    #endif  //FEATURE_1 == 1
  }

并调用

do_test();

在程序中

注意
应避免使用单行 C 风格注释,因为它们被认为已过时。混合使用 C 和 C++ 风格的单行注释被认为是不好的做法。一个常见的例外是,为了测试/调试目的,禁用单行语句中间的特定代码部分,在发布代码中,应删除此类操作的任何需求。

命名标识符

[编辑 | 编辑源代码]

C++ 对标识符关键字的名称的限制已在代码部分中介绍。它们在命名方面提供了很大的自由,可以使用特定的前缀或后缀,以大写或小写字母开头名称,将所有字母都保留为单一大小写,或者对于复合词,使用像 "_" 这样的单词分隔符或翻转每个组成词的首字母的大小写。

注意
还需要注意避免与操作系统的 API(取决于可移植性要求)或其他标准发生冲突。例如,POSIX 的关键字以 "_t" 结尾。

匈牙利命名法
[编辑 | 编辑源代码]

匈牙利命名法,现在也称为 Apps 匈牙利命名法,是由 Charles Simonyi(大约 1972 年至 1981 年在施乐帕洛阿尔托研究中心工作的程序员,后来成为微软的首席架构师)发明的;并且直到最近,它一直是大多数微软代码中使用的首要命名约定。它使用前缀(例如“m_”表示成员变量,“p”表示指针),而标识符的其余部分通常使用某种混合大小写形式写出。我们提到此约定是因为您很可能会发现它正在使用,如果您在 Windows 中进行任何编程,则更有可能,如果您有兴趣了解更多信息,可以查看维基百科关于此表示法的条目

此表示法被认为已过时,因为它极易出错,并且需要付出一些努力才能在当今的 IDE 中进行维护,而没有带来任何实际好处。如今,重构是一项日常任务,IDE 已发展为提供标识符弹出窗口和颜色方案使用的帮助。所有这些信息辅助工具减少了对这种表示法的需求。

前导下划线
[编辑 | 编辑源代码]

在大多数情况下,最好避免使用前导下划线。它们是为编译器或库的内部变量保留的,并且可能使您的代码的可移植性降低,并且更难以维护。这些变量也可以从库中剥离(即变量不再可访问,它对外部世界隐藏),因此除非您想覆盖库的内部变量,否则不要这样做。

重用现有名称
[编辑 | 编辑源代码]

不要将标准库函数和对象的名称用于您的标识符,因为这些名称被视为保留字,并且当以意外的方式使用时,程序可能变得难以理解。

合理的名称
[编辑 | 编辑源代码]

始终使用良好、未缩写、拼写正确且有意义的名称。

首选英语(因为 C++ 和大多数库已经使用英语),并避免使用简短的隐晦名称。这将使阅读和键入名称变得更容易,而不必查找它。

注意
忽略此规则以用于循环变量和在小范围内 (~20 行) 使用的变量是可以接受的,如果该变量的目的足够明显,可以为它们指定简短的名称以节省空间。从历史上看,在这种情况下最常用的变量名称是“i”。

“i”可能源自“增量”或“索引”一词。“i”在for循环中非常常见,这非常符合此类变量名称的使用规范。

在早期的 Fortran 编译器(直到 F77)中,以字母 i 到 n 开头的变量隐式表示整数 - 并且按照惯例,前几个 (i、j、k) 通常用作循环计数器。

名称指示用途
[编辑 | 编辑源代码]

标识符应指示其表示的变量/函数等的函数,例如,foobar可能不是存储人员年龄的变量的良好名称。

标识符名称也应该具有描述性。n可能不是表示员工人数的全局变量的良好名称。但是,必须找到长名称和大量键入之间的良好平衡。因此,对于在小范围或上下文中使用的变量,可以放宽此规则。许多程序员更喜欢短变量(例如 i)作为循环迭代器。

按照惯例,变量名称以小写字符开头。在包含多个自然语言单词的标识符中,使用下划线或大写来分隔单词,例如num_chars(K&R 风格)或numChars(Java 风格)。建议您选择一种表示法,并且不要在一个项目中混合使用它们。

在命名 #defines、常量变量、enum 常量和宏时,使用全大写字母并以 '_' 分隔符连接;这使得很清楚该值不可更改,并且在宏的情况下,明确表示您正在使用需要谨慎的结构。

注意
有一种广泛的观点认为,仅应将 LIKE_THIS 样式的名称用于宏,以便宏使用的命名空间(不尊重 C++ 范围)不会与用于其他标识符的命名空间重叠。与 C++ 命名约定中通常一样,没有一个普遍接受的标准。通常,最重要的是保持一致性。

函数和成员函数
[编辑 | 编辑源代码]

赋予函数和成员函数的名称应具有描述性,并明确其功能。由于函数和成员函数通常执行操作,因此最佳名称选择通常包含动词和名词的混合,例如 CheckForErrors() 而不是 ErrorCheck(),以及 dump_data_to_file() 而不是 data_file()。函数和成员函数的清晰和描述性名称有时可以更容易地正确猜测函数和成员函数的功能,有助于使代码更具自文档性。通过遵循此命名约定和其他命名约定,程序可以更自然地阅读。

在使用包含缩写的名称时,人们似乎有非常不同的直觉。最好确定一种策略,使名称完全可预测。例如,NetworkABCKey。请注意,来自 ABC 的 C 和来自 key 的 K 如何混淆。有些人并不介意这一点,而另一些人则非常讨厌,因此您会在不同的代码中找到不同的策略,因此您永远不知道该叫什么。

前缀和后缀有时很有用

  • Min - 表示某物可以具有的最小值。
  • Max - 表示某物可以具有的最大值。
  • Cnt - 某物的当前计数。
  • Count - 某物的当前计数。
  • Num - 某物的当前数量。
  • Key - 键值。
  • Hash - 哈希值。
  • Size - 某物的当前大小。
  • Len - 某物的当前长度。
  • Pos - 某物的当前位置。
  • Limit - 某物的当前限制。
  • Is - 查询某事物是否为真。
  • Not - 查询某事物是否不为真。
  • Has - 查询某事物是否具有特定值、属性或特性。
  • Can - 查询某事物是否可以完成。
  • Get - 获取值。
  • Set - 设置值。

在大多数情况下,最好也避免使用前导下划线。例如,这些是有效的标识符

  • i 循环值
  • numberOfCharacters 字符数量
  • number_of_chars 字符数量
  • num_chars 字符数量
  • get_number_of_characters() 获取字符数量
  • get_number_of_chars() 获取字符数量
  • is_character_limit() 这是字符限制吗?
  • is_char_limit() 这是字符限制吗?
  • character_max() 字符的最大数量
  • charMax() 字符的最大数量
  • CharMin() 字符的最小数量

这些也是有效的标识符,但你能说出它们的意思吗?

  • num1
  • do_this()
  • g()
  • hxq

以下标识符有效,但最好避免使用

  • _num 因为它可能被编译器/系统头文件使用
  • num__chars 因为它可能被编译器/系统头文件使用
  • main 因为它可能导致混淆
  • cout 因为它可能导致混淆

以下标识符无效

  • if 因为它是一个关键字
  • 4nums 因为它以数字开头
  • number of characters 因为标识符内不允许使用空格


显式或隐式

[编辑 | 编辑源代码]

这两种方法都可以支持。如果默认使用隐式,这意味着键入次数减少,但也可能对人类读者造成错误的假设,并且对于编译器(根据具体情况)来说需要做额外的工作,另一方面,如果您编写更多关键字并明确您的意图,则生成的代码将更清晰并减少错误(使隐藏的错误能够被发现),或者更明确(自文档化),但这可能也会导致代码演进的额外限制(就像我们将看到 const 的使用一样)。这是一条细线,必须根据项目的性质达成平衡,编辑器、代码补全、语法着色和悬停工具提示的功能减少了大部分工作。重要的是,与任何其他规则一样,保持一致性。

即使成员函数隐式内联,也选择使用 inline

除非您计划修改它,否则您可能最好使用 const 数据类型。编译器可以轻松地通过此限制进行更多优化,并且您不太可能意外损坏数据。确保您的方法采用 const 数据类型,除非您绝对必须修改参数。类似地,在实现私有成员数据的访问器时,在大多数情况下,您应该 return const。这将确保如果您正在操作的对象作为 const 传递,则不影响对象中存储的数据的方法仍按预期工作并可以被调用。例如,对于包含一个人的对象,getName() 应该 return const 数据类型,而 walk() 可能是非 const,因为它可能会更改 Person 中的一些内部数据,例如疲劳度。

通常的做法是避免使用 typedef 关键字,因为它如果使用不当可能会使代码变得模糊,或者可能导致程序员意外地误用大型结构,将其视为简单类型。如果使用,请为重命名的类型定义一组规则,并确保记录它们。

volatile

[编辑 | 编辑源代码]

此关键字告知编译器,它限定为 volatile(随时可能更改)的变量不包含在任何优化技术中。此变量的使用应保留用于已知由于程序的外部影响(无论是硬件更新、第三方应用程序还是应用程序中的另一个线程)而修改的变量。

由于 volatile 关键字会影响性能,因此您应该考虑不同的设计以避免这种情况:大多数需要此关键字的平台都提供了一种替代方案,有助于维持可扩展的性能。

请注意,使用 volatile 本意并非用作线程或同步原语,并且 volatile 变量上的操作也不能保证是原子的。

指针声明

[编辑 | 编辑源代码]

由于历史原因,一些程序员将特定用法称为

// C code style
int *z;

// C++ code style
int* z;

第二种变体是 C++ 程序员迄今为止最喜欢的,它将有助于识别 C 程序员或遗留代码。

反对 C++ 代码风格版本的一个论点是在链接多个项目的声明时,例如

// C code style
int *ptrA, *ptrB;

// C++ code style
int* ptrC, ptrD;

如您所见,在这种情况下,C 代码风格更清楚地表明 ptrA 和 ptrB 是指向 int 的指针,而 C++ 代码风格则不太清楚 ptrD 是 int 而不是指向 int 的指针。

在 C++ 代码中很少使用多个对象的链,除了基本类型之外,即使如此,它也不常使用,并且在指针或其他复杂类型中看到它使用的情况极其罕见,因为它会使人类更难直观地解析代码。

// C++ code style
int* ptrC;
int D;

参考文献

[编辑 | 编辑源代码]

华夏公益教科书