Bootstrap

【C++从入门到精通】第2篇:C++基础知识(中)


2.1 iostream介绍:cout、cin和endl

本节,我们将更多地讨论在“Hello, World!”程序中使用的std::cout语句,将文本“Hello, World!”输出到控制台。我们还将探讨如何从用户获取输入,我们将使用这些使我们的程序更具交互性。

2.1.1 输入/输出库

输入/输出库(io 库)是处理基本输入和输出的C++标准库的一部分。我们将使用此库中的功能从键盘获取输入并将数据输出到控制台。iostream的io部分代表输入/输出。

要使用iostream库中定义的功能,我们需要在使用iostream中定义的内容的任何代码文件的顶部包含iostream 标头,如下所示:

#include <iostream>

// 此处使用iostream功能的其余代码

2.1.2 std::cout

iostream库包含一些预定义的变量供我们使用。最有用的之一是std::cout,它允许我们将数据发送到控制台以打印为文本。cout代表“字符输出”。

作为复习,这是我们的“Hello, World!”程序:

#include <iostream> // 为了使用 std::cout

int main()
{
    std::cout << "Hello world!"; // 打印 Hello world! 到控制台

    return 0;
}

在这个程序中,我们包含了iostream,以便可以使用std::cout。在我们的主函数中,我们使用std::cout以及插入运算符(<<)来发送文本Hello world!到要打印的控制台。

std::cout不仅可以打印文本,还可以打印数字:

#include <iostream> // 为了使用 std::cout

int main()
{
    std::cout << 4; // 打印 4 到控制台

    return 0;
}

这将产生结果:

4

它还可用于打印变量的值:

#include <iostream> // 为了使用 std::cout

int main()
{
    int x{ 5 }; // 定义整形变量x,初始化为值5
    std::cout << x; // 将x的值(5)打印到控制台
    return 0;
}

这将产生结果:

5

要在同一行上打印多个内容,可以在单个语句中多次使用插入运算符(<<)来连接。例如:

#include <iostream> // 为了使用 std::cout

int main()
{
    std::cout << "Hello" << " world!";
    return 0;
}

此程序打印:

Hello world!

下面是另一个示例,我们在同一语句中同时打印文本和变量的值:

#include <iostream>

int main()
{
    int x{ 5 };
    std::cout << "x is equal to: " << x;
    return 0;
}

此程序打印:

x is equal to: 5

2.1.3 std::endl

您觉得此程序会打印什么?

#include <iostream> // for std::cout

int main()
{
    std::cout << "Hi!";
    std::cout << "My name is Alex.";
    return 0;
}

您可能会对结果感到惊讶:

Hi!My name is Alex.

单独的输出语句不会导致控制台上的输出行单独(即换行)。

如果我们想将单独的输出行打印到控制台,我们需要告诉控制台何时将光标移动到下一行。

一种方法是使用std::endl。使用std::cout输出时,std::endl会将换行符打印到控制台(导致光标转到下一行的开头)。在这种情况下,endl代表“终点线”。

例如:

#include <iostream> // 为了使用 std::cout 和 std::endl

int main()
{
    std::cout << "Hi!" << std::endl; // std::endl 将使光标移动到控制台的下一行
    std::cout << "My name is Alex." << std::endl;

    return 0;
}

这将打印:

Hi!
My name is Alex.

拓展:
在上面的程序中,第二个std::endl实际上不是必需的,因为程序会立即结束。但是,它有一些有用的目的。

  • 首先,它有助于指示输出行是一个“完整的思想”(而不是在代码后面的某个地方完成的部分输出)。从这个意义上说,它的功能类似于在标准英语中使用句点。
  • 其次,它将光标定位在下一行,这样如果我们稍后添加额外的输出行(例如,让程序说“Bye!”),这些行将出现在我们期望的位置(而不是附加到前一行输出)。
  • 第三,从命令行运行可执行文件后,某些操作系统在再次显示命令提示符之前不会输出新行。如果我们的程序没有以新行上的光标结尾,则命令提示符可能会附加到前一行输出,而不是用户期望的新行的开头。

最佳实践
每当一行输出完成时,就输出换行符。

2.1.4 std::cout是缓冲的

考虑您最喜欢在游乐园乘坐过山车。乘客出现(以某种可变的速度)并排队。火车定期到达并登上乘客(直到火车的最大容量)。当火车满员或经过足够的时间后,火车带着一批乘客出发,然后开始乘坐。任何无法登上当前列车的乘客都等待下一班列车。

这个类比类似于发送到std::cout的输出通常在C++中处理的方式。程序中的语句请求将输出发送到控制台。但是,该输出通常不会立即发送到控制台。相反,请求的输出“排队”,并存储在为收集此类请求而预留的内存区域中(称为缓冲区)。缓冲区会定期刷新,这意味着缓冲区中收集的所有数据都将传输到其目标(在本例中为控制台)。

这也意味着,如果程序在刷新缓冲区之前崩溃、中止或暂停(例如出于调试目的),则不会显示缓冲区中仍在等待的任何输出。

关键点
缓冲输出的反面是无缓冲输出。对于无缓冲输出,每个单独的输出请求都直接发送到输出设备。
将数据写入缓冲区通常很快,而将数据批处理传输到输出设备相对较慢。缓冲可以最大程度地减少在存在多个输出请求时需要执行的慢速传输数,从而显著提高性能。

2.1.5 std::endl与\n

使用std::endl可能有点低效,因为它实际上执行两项工作:将光标移动到控制台的下一行,并刷新缓冲区。将文本写入控制台时,我们通常不需要刷新每行末尾的缓冲区。让系统定期自行冲洗会更有效(它被设计为这样有效地做)。

因此,通常首选使用“\n”字符。“\n”字符将光标移动到控制台的下一行,但不请求刷新,因此它通常会执行得更好。“\n”字符也更简洁,因为它既短又可以嵌入到现有文本中。

下面是以两种不同方式使用“\n”的示例:

#include <iostream>

int main()
{
    int x{ 5 };
    std::cout << "x is equal to: " << x << '\n'; // 独立使用'\n'
    std::cout << "And that's all, folks!\n"; // 使用嵌入到双引号文本中的'\n'(注意:这样使用时没有单引号)
    return 0;
}

这将打印:

x is equal to: 5
And that's all, folks!

当’\n’单独用于将光标移动到控制台的下一行时,它应该用单引号引起来。当嵌入到已用双引号引号的文本中时,不需要其他引号。

最佳实践
将文本输出到控制台时,选择“\n”而不是std::endl

警告
'\n’使用反斜杠(与C++中的所有特殊字符一样),而不是正斜杠。改用正斜杠(例如“/n”)可能会导致意外错误。

2.1.6 std::cin

std::cin是iostream库中定义的另一个预定义变量。而使用插入运算符(<<)将数据打印到控制台,(代表“字符输入”)使用提取运算符(>>)从键盘读取输入。std::cin输入必须存储在要使用的变量中。

#include <iostream>  // 为了使用 std::cout 和 std::cin

int main()
{
    std::cout << "Enter a number: "; // 向用户询问一个数

    int x{ }; // 定义整形变量x来保存用户输入(将其初始化为0)
    std::cin >> x; // 从键盘上获取数字并存储在变量x中

    std::cout << "You entered " << x << '\n';
    return 0;
}

尝试编译此程序并自己运行它。运行程序时,第5行将打印“Enter a number: ”。当代码到达第8行时,您的程序将等待您输入输入。输入数字(然后按回车键)后,您输入的数字将被分配给变量x。最后,在第10行,程序将打印“You entered”,后跟您刚刚输入的数字。

例如(我输入了 4):

Enter a number: 4
You entered 4

这是一种从用户那里获取键盘输入的简单方法,我们将在以后的许多示例中使用它。请注意,接受输入时不需要使用“\n”,因为用户需要按下Enter键才能接受其输入,这会将光标移动到控制台的下一行。

就像可以在一行中输出多位文本一样,也可以在一行中输入多个值:

#include <iostream>

int main()
{
    std::cout << "Enter two numbers separated by a space: ";

    int x{ }; // 定义变量x来保存用户输入(将其初始化为零)
    int y{ }; // 定义变量y来保存用户输入(将其初始化为零)
    std::cin >> x >> y; // 得到两个数字,分别存储在变量x和y中

    std::cout << "You entered " << x << " and " << y << '\n';

    return 0;
}

这将产生输出:

Enter two numbers separated by a space: 5 6
You entered 5 and 6

2.1.7 总结

新程序员经常混淆std::cinstd::cout、插入运算符(<<)和提取运算符(>>)。这里有一个简单的记忆方法:

  • std::cinstd::cout 始终位于语句的左侧。
  • std::cout 用于输出值(cout = 字符输出)
  • std::cin 用于获取输入值(cin = 字符输入)
  • <<std::cout 一起使用,并显示数据的移动方向(如果 std::cout 表示控制台,则输出数据从变量移动到控制台)。
    std::cout << 4 将值 4 移动到控制台
  • >>std::cin 一起使用,并显示数据的移动方向(如果 std::cin 表示键盘,则输入数据从键盘移动到变量)。
    std::cin >> x 将用户从键盘输入的值移动到 x 中

2.1.8 练习时间

练习1
考虑我们上面使用的以下程序:

#include <iostream>

int main()
{
    std::cout << "Enter a number: ";
    int x{};
    std::cin >> x;
    std::cout << "You entered " << x << '\n';
    return 0;
}

程序希望您输入一个整数值,因为用户输入的变量x是一个整数变量。

多次运行此程序,并描述输入以下类型的输入时会发生什么情况:

  1. 一个字母,例如h
  2. 带有小数分量的数字。尝试小数部分小于0.5和大于0.5的数字(例如3.2和3.7)。
  3. 一个小的负整数,如-3
  4. 一个词,例如Hello
  5. 一个非常大的数字(至少30亿)
  6. 一个小数字后跟一些字母,例如123abc

2.2 未初始化的变量和未定义的行为

2.2.1 未初始化的变量

与某些编程语言不同,C/C++不会自动将大多数变量初始化为给定值(如0)。因此,当一个变量被赋予一个用于存储数据的内存地址时,该变量的默认值恰好已经存在于该内存地址中的那个(垃圾)值!没有被赋予已知值(通常通过初始化或赋值)的变量称为未初始化变量

拓展
许多读者期望术语“初始化”和“未初始化”是严格的对立,但事实并非如此!初始化意味着在定义时为对象提供了初始值。未初始化意味着对象尚未被赋予已知值(通过任何方式,包括赋值)。因此,一个没有初始化但后来被赋值的对象不再是未初始化的(因为它已经被赋予了一个已知的值)。
回顾一下:

  • 初始化 --> 对象在定义时被赋予一个已知值。
  • 赋值 --> 对象被赋予一个超出定义点的已知值。
  • 未初始化 --> 对象尚未被赋予已知值。

拓展
缺乏初始化是从C继承来的性能优化,当时计算机速度很慢。想象一下,你要从一个文件中读入100000个值。在这种情况下,您可以创建100000个变量,然后用文件中的数据填充它们。
如果C++在创建时用默认值初始化所有这些变量,这将导致100000次初始化(很慢),并且几乎没有好处(因为无论如何都要覆盖这些值)。
现在,您应该始终初始化变量,因为这样做的成本与收益相比微不足道。一旦您对语言更加熟悉,可能会在某些情况下出于优化目的而省略初始化。但这应该始终是有选择性和有意识的。

使用未初始化变量的值可能会导致意外结果。考虑以下简短程序:

#include <iostream>

int main()
{
    // 定义整形变量x
    int x; // 这个变量是未初始化的,因为我们没有给它赋予已知值

    // 将x的值打印到屏幕
    std::cout << x << '\n'; // 谁知道我们会获得什么呢!因为我们没有给它赋值

    return 0;
}

在这种情况下,计算机会将一些未使用的内存分配给x。然后,它将内存位置的值发送到std::cout,后者将打印该值(输出为整数)。但是它会打印什么值呢?答案是“谁知道呢!”,每次运行程序时,答案可能会(也可能不会)改变。当作者在Visual Studio中运行这个程序时,std::cout一次打印值7177728 ,下一次打印值5277592 。您可以自己编译并运行该程序(您的计算机不会爆炸)。

警告
当您使用调试生成配置时,某些编译器(如VisualStudio)会将内存内容初始化为某个预设值。使用发布构建配置时不会发生这种情况。因此,如果你想自己运行上面的程序,请确保你使用的是发布版本配置。例如,如果您在Visual Studio调试配置中运行上述程序,它将始终打印-858993460,因为这是Visual Studio在调试配置中初始化内存的值(输出为整数)。

大多数现代编译器将尝试检测是否在未给定值的情况下使用变量。如果他们能够检测到这一点,他们通常会发出编译时警告或错误。例如,在Visual Studio上编译上述程序会产生以下警告:

warning C4700: uninitialized local variable 'x' used

如果你的编译器不允许你编译和运行上面的程序(例如因为它将此问题视为错误),以下是解决此问题的可能解决方案:

#include <iostream>

void doNothing(int&) // 现在不要担心&是什么,我们只是用它来欺骗编译器认为使用了变量x
{
}

int main()
{
    // 定义整形变量x
    int x; // 变量是未初始化的

    doNothing(x); // 让编译器认为我们在给这个变量赋值

    // 将x的值打印到屏幕上(谁知道我们会得到什么,因为x没有初始化)
    std::cout << x << '\n';

    return 0;
}

使用未初始化的变量是新手程序员最常犯的错误之一,不幸的是,它也可能是调试最具挑战性的错误之一(因为如果未初始化的变量碰巧被分配到内存中具有合理值的位置,例如0,程序可能会运行得很好)。

这是最佳实践“始终初始化变量”的主要原因。

2.2.2 未定义行为

使用未初始化变量的值是我们第一个未定义行为的例子。未定义行为(Undefined behavior,通常缩写为UB)是执行C++语言未定义行为的代码的结果。在这种情况下,C++语言没有任何规则来确定如果使用尚未给定已知值的变量的值会发生什么。因此,如果你真的这样做,将导致未定义的行为。

实现未定义行为的代码可能会出现以下症状:

  • 程序每次运行都会产生不同的结果。
  • 你的程序总是产生同样的错误结果。
  • 你的程序行为不一致(有时产生正确的结果,有时不)。
  • 你的程序看起来像是在工作,但是在程序的后面产生了不正确的结果。
  • 你的程序崩溃了,要么立即崩溃,要么稍后崩溃。
  • 你的程序可以在某些编译器上工作,但在其他编译器上不行。
  • 你的程序可以正常工作,直到你改变了其他一些看似无关的代码。

或者,您的代码实际上可能会产生正确的行为。

C++包含许多情况,如果你不小心,可能会导致未定义的行为。注意这些情况,并确保避免它们。

规则
注意避免所有导致未定义行为的情况,例如使用未初始化的变量。

2.2.3 明确定义的行为和不明确定义的行为

明确定义的行为意味着一些语法的行为留给实现(编译器)来定义。这样的行为必须是一致的并且有文档记录的,但是不同的编译器可能会产生不同的结果。

让我们看一个实现定义的行为的简单示例:

#include <iostream>

int main()
{
	std::cout << sizeof(int); // 打印一个int变量占用多少字节的内存

	return 0;
}

在大多数编译器上,这将产生4 ,但在其他编译器上,它可能会产生2 。

未明确定义的行为与明确定义的行为几乎相同,因为行为由实现决定,但不要求实现记录行为。

我们通常希望避免实现定义的和未指定的行为,因为这意味着如果在不同的编译器上编译,我们的程序可能无法按预期工作(甚至如果我们更改影响实现行为的项目设置,则在同一编译器上编译!)

最佳实践
尽可能避免实现定义的和未指定的行为,因为它们可能导致程序在其他实现上出现故障。

2.2.4 练习时间

练习2
什么是未初始化的变量?为什么要避免使用它们?

练习3
什么是未定义的行为?如果你做了一些表现出未定义行为的事情,会发生什么?


2.3 关键字和命名标识符

2.3.1 关键字

C++保留了92个关键字(从C++23开始)供己使用。这些词称为关键字(或保留字),每个关键字在C++语言中都有特殊的含义。

以下是所有C++关键字的列表(到C++23):

-1234
1alignasalignofandand_eq
2asmautobitandbitor
3boolbreakcasecatch
4charchar8_t (C++20起)char16_tchar32_t
5classcomplconcept (C++20起)const
6consteval (C++20起)constexprconstinit (C++20起)const_cast
7continueco_await (C++20起)co_return (C++20起)co_yield (C++20起)
8decltypedefaultdeletedo
9doubledynamic_castelseenum
10explicitexportexternfalse
11floatforfriendgoto
12ifinlineintlong
13mutablenamespacenewnoexcept
14notnot_eqnullptroperator
15oror_eqprivateprotected
16publicregisterreinterpret_castrequires (C++20起)
17shortsignedsizeofstatic
18static_caststructswitchtemplate
19thread_localthrowtruetry
20typeidtypenameunionunsigned
21virtualvoidvolatilewchar_t
22whilexorxor_eq

标记了“C++20起”的关键字是在在C++20中添加的。如果您的编译器不兼容C++20(或者具有C++20功能,但默认情况下关闭),则这些关键字可能不起作用。

C++还定义了特殊标识符:overridefinalimportmodule。这些在某些上下文中使用时具有特定含义,但在其他情况下不保留。

您已经遇到了其中的一些关键字,包括intreturn。沿着一组运算符,这些关键字和特殊标识符定义了整个C++语言(预处理器命令除外)。由于关键字和特殊标识符具有特殊的含义,IDE可能会更改这些单词的文本颜色,使它们从其他标识符中脱颖而出。

2.3.2 标识符命名规则

变量(或函数,类型或其他类型的项)的名称称为标识符。C++有着很大的灵活性,可以根据你的意愿命名标识符。但是,在命名标识符时必须遵循以下几条规则:

  1. 标识符不能是关键字。
  2. 标识符只能由字母(小写或大写)、数字和下划线字符组成。这意味着标识符不能包含符号(下划线除外)或空格(空格或制表符)。
  3. 标识符必须以字母(小写或大写)或下划线开始。不能以数字开头。
  4. C++是区分大小写的。 value 不同于 Value 不同于 VALUE 。

2.3.3 标识符命名最佳实践

现在你已经知道了如何命名一个变量,让我们来谈谈你应该怎样命名一个变量(或函数)。

首先,C++中的一个约定是变量名应该以小写字母开始。如果变量名是一个单词,则整个内容都应该用小写字母书写。

int value; // 正规的

int Value; // 非正规的(应该以小写字母开头)
int VALUE; // 非正规的(应该以小写字母开头)
int VaLuE; // 非正规的(去看心理医生吧)

大多数情况下,函数名也以小写字母开头(尽管在这一点上存在一些分歧)。我们将遵循这个约定,因为函数main(所有程序都必须有)以小写字母开头,就像C++标准库中的所有函数一样。

以大写字母开头的标识符名称通常用于用户定义的类型(例如结构、类和枚举,我们将在后面介绍所有这些)。

如果变量或函数名是多字的,则有两种常见约定:用下划线分隔的单词(有时称为snake_case),或用大写字母分隔的单词(有时称为camelCase,因为大写字母像骆驼的驼峰一样突出)。

int my_variable_name;   // 正规的(由下划线分隔)
int my_function_name(); // 正规的(由下划线分隔)

int myVariableName;   // 正规的(由大写字母分隔)
int myFunctionName(); // 正规的(由大写字母分隔)

int my variable name;   // 无效的(不允许空格)
int my function name(); // 无效的(不允许空格)

int MyVariableName;   // 非常规(应该以小写字母开头)
int MyFunctionName(); // 非常规(应该以小写字母开头)

在本教程中,我们通常使用第二种方法,因为它更容易阅读(在密集的代码块中很容易将下划线误认为空格)。但两者都很常见——C++标准库对变量和函数都使用下划线方法。有时你会看到两者的混合:下划线用于变量,大写字母用于函数。

值得注意的是,如果你正在使用别人的代码,通常认为与你正在使用的代码的风格相匹配比严格遵循上面列出的命名约定更好。

其次,应该避免以下划线开头命名标识符,因为这些名称通常是为操作系统、库和(或)编译器保留的。

第三,你的标识符应该明确它们所持有的值意味着什么(特别是如果单位不明显的话)。标识符的命名方式应该能够帮助那些不知道你的代码是什么的人尽快弄清楚。长时间后,当你再看你的程序时,你会感谢自己选择了有意义的变量名。

然而,给一个微不足道的变量起一个过于复杂的名字会妨碍对程序正在做什么的全面理解,就像给一个广泛使用的标识符一个不适当的名字一样。因此,一个好的经验法则是使标识符的长度与它的使用范围成正比。具有普通用途的标识符可以具有短名称(例如i)。更广泛使用的标识符(例如从程序中的许多不同位置调用的函数)应该具有更长且更具描述性的名称(例如使用openFileOnDisk而不是open)。

标识符好/坏原因
int ccount“count”前的“c”代表什么?
int customerCount清楚我们在计数什么
int i也许如果是不重要的,那么就是好的,否则就是坏的
int index也许如果索引知道是什么就是好的
int totalScore也许如果只有一个东西的分数,那就是好的,否则它太模糊
int _count不要使用下划线开头
int count也许如果清楚计数的是什么,那就是好的
int data什么数据?
int time秒,分,还是小时?
int minutesElapsed描述清楚
int value1, value2也许很难区分两者
int numApples描述清楚
int monstersKilled描述清楚
int x, y也许如果是不重要的,那么就是好的,否则就是坏的

在任何情况下,避免缩写(除非它们是明确的)。虽然它们减少了编写代码所需的时间,但它们使代码更难阅读。阅读代码的次数比编写代码的次数多,你在编写代码时节省的时间是每个读者,包括未来的你,在阅读代码时浪费的时间。如果您希望更快地编写代码,请使用编辑器的自动完成功能。

对于变量声明,使用注释来描述变量的用途或解释其他可能不明显的内容是很有用的。例如,假设我们声明了一个名为numberOfChars的变量,它应该存储一段文本中的字符数。文本“Hello World!”有10个、11个还是12个字符?这取决于我们是否包括空格或标点符号。与其命名为变量numberOfCharsIncludingWhitespaceAndPunctuation,这是相当冗长的,在声明行上或上方放置适当的注释应该有助于用户理解它:

// 一段文本中的字符数,包括空格和标点符号
int numberOfChars;

2.3.4 练习时间

练习4
根据如何命名一个变量,指出每个变量名是否正规或无效,以及为什么。

(1)

int sum {}; // 假设我们求的和是显而易见的

(2)

int _apples {};

(3)

int VALUE {};

(4)

int my variable name {};

(5)

int TotalCustomers {};

(6)

int void {};

(7)

int numFruit {};

(8)

int 3some {};

(9)

int meters_of_pipe {};

2.4 空白和基本格式

空白是一个术语,指用于格式化的字符。在C++中,这主要是指空格、制表符和换行符。C++中的空白通常用于3件事:分隔某些语言元素、在文本内部以及用于格式化代码。

2.4.1 某些语言元素必须以空格分隔

该语言的语法要求某些元素用空格分隔。当两个关键字或标识符必须连续放置以便编译器可以区分它们时,通常会出现这种情况。

例如,变量声明必须用空格分隔:

int x; // int 和 x 之间用空格分隔

如果我们输入intx ,编译器会将其解释为标识符,然后抱怨它不知道标识符intx是什么。

另一个例子,函数的返回类型和名称必须用空格分隔:

int main(); // int 和 main 之间用空格分隔

当需要空白作为分隔符时,编译器不关心使用了多少空白,只要存在一些空白即可。

以下变量定义均有效:

int x;
int                y;
            int
z;

在某些情况下,换行符用作分隔符。单行注释以换行符结束。

举个例子,做这样的事情会让你陷入麻烦:

std::cout << "Hello world!"; // 这是注释的一部分
这不是注释的一部分

预处理器指令(例如#include <iostream>)也必须放在单独的行上:

#include <iostream>
#include <string>

2.4.2 引用的文本按字面意思使用空格的数量

在引用的文本中,空格的数量是按字面意思计算的。

std::cout << "Hello world!";

不同于:

std::cout << "Hello          world!";

引用的文本中不允许有换行符:

std::cout << "Hello
     world!"; // 不允许!

引用的文本由空白(空格、制表符或换行符)分隔,将被连接:

std::cout << "Hello "
     "world!"; // 打印"Hello world!"

2.4.3 使用空白来格式化代码

空白通常被忽略。这意味着我们可以在任何我们喜欢的地方使用空白来格式化代码,以使其更容易阅读。

例如,以下内容很难阅读:

#include <iostream>
int main(){std::cout<<"Hello world";return 0;}

以下是更好的(但仍然相当密集):

#include <iostream>
int main() {
std::cout << "Hello world";
return 0;
}

如果需要,可以将语句拆分为多行:

#include <iostream>

int main()
{
    std::cout
        << "Hello world"; // 正常工作
    return 0;
}

这对于特别长的语句很有用。

2.4.4 基本格式

与其他语言不同,C++不对程序员施加任何格式限制。因此,我们说C++是一个空白独立的语言。

这是喜忧参半的。一方面,有做任何你喜欢的事情的自由是很好的。另一方面,这些年来已经开发了许多不同的格式化C++程序的方法,你会发现(有时是重要的和分散注意力的)关于哪些是最好有很多分歧。我们的基本经验法则是,最好的样式是生成最可读的代码,并提供最大一致性的样式。

以下是我对基本格式的建议:

  1. 可以使用制表符或空格进行缩进(大多数IDE都有这个设置,可以将制表符按下转换为适当数量的空格)。
  2. 函数大括号有两种可接受的样式。

Google C++样式指南建议将左花括号放在与语句相同的行上:

int main() {
}

这样做的理由是,它减少了垂直空格的数量(除了开始的花括号之外,您不会将整行都用于其他内容),因此您可以在屏幕上容纳更多的代码。屏幕上的代码越多,程序就越容易理解。

但是,我更喜欢常见的替代方案,其中左大括号出现在自己的行上:

int main()
{
}

这增强了可读性,并且不容易出错,因为大括号对应该总是缩进在同一级别。如果由于大括号不匹配而出现编译器错误,很容易看出错误的位置。

  1. 花括号内的每个语句都应该从它所属函数的左括号开始一个制表符。举例来说:
int main()
{
    std::cout << "Hello world!\n"; // 一个制表符(4个空格)
    std::cout << "Nice to meet you.\n"; // 一个制表符(4个空格)
}
  1. 行不应该太长。通常,80个字符已经成为行的最大长度的事实标准。如果一行代码需要很长,它应该被分割(在一个合理的点)成多行。
int main()
{
    std::cout << "这是一个很长很长很长很长很长很长很长很长很长很长很长很长"
        "很长的代码\n"; // 延续行的一个额外缩进

    std::cout << "这是另一一个很长很长很长很长很长很长很长很长很长很长很长很长"
                 "很长的代码\n"; // 文本与上一行对齐,作为延续行

    std::cout << "这一行比较短\n";
}

最佳实践
考虑将行的长度控制在80个字符或更少。

  1. 如果使用操作符(例如<<或+),则运算符应放在下一行的开头,而不是当前行的结尾。
std::cout << 3 + 4
    + 5 + 6
    * 7 * 8;
  1. 使用空白可以通过对齐值或注释或在代码块之间添加间距来使代码更易于阅读。

更难阅读:

cost = 57;
pricePerItem = 24;
value = 5;
numberOfItems = 17;

更容易阅读:

cost          = 57;
pricePerItem  = 24;
value         = 5;
numberOfItems = 17;

更难阅读:

std::cout << "Hello world!\n"; // cout 在 iostream 库中
std::cout << "It is very nice to meet you!\n"; // 这些注释使程序难以阅读
std::cout << "Yeah!\n"; // 特别是不同行的时候

更容易阅读:

std::cout << "Hello world!\n";                  // cout 在 iostream 库中
std::cout << "It is very nice to meet you!\n";  // 这些注释使程序更容易阅读
std::cout << "Yeah!\n";                         // 尤其是当所有行都排队的时候

2.5 本章答案

练习1

  1. x为0。
  2. 小数部分被舍弃。
    由于列表初始化不允许收缩转换,新程序员有时会搞不清楚为什么这里允许删除分数。x的初始化发生在第6行,对收缩转换的限制只适用于这一行的列表初始化。用户的输入被处理并赋给第7行的x(此处此类限制不适用,因为此语句不是列表初始化)。
  3. 正常运行。
  4. x为0。
  5. 你最有可能得到的号码是2147483647。这是因为x只能容纳一定大小的数字。如果您输入的值大于x可以容纳的最大数值,它将被设置为x可以容纳的最大数值(可能是2147483647,但在您的系统上可能有所不同)。
  6. x获取数值(例如123)。

练习2
未初始化的变量是一个没有被程序赋予值的变量(通常通过初始化或赋值)。使用存储在未初始化变量中的值将导致未定义的行为。

练习3
未定义的行为是执行行为没有被语言很好定义的代码的结果。结果几乎可以是任何东西,包括行为正确的东西。

练习4
(1)正规。
(2)不正规。变量名不应该以下划线开头。
(3)不正规。变量名应该以小写字母开头。
(4)无效。变量名不能包含空格。
(5)非正规。变量名应该以小写字母开头。
(6)无效。void是关键字。
(7)正规。
(8)无效。变量名不能以数字开头。
(9)正规。

;