变量和类型

上一章中显示的“Hello World”程序的实用性是相当值得怀疑的。我们必须编写几行代码,编译它们,然后执行生成的程序,只是为了获得屏幕上写的一个简单句子的结果。如果我们自己输入输出句子肯定会快得多。

然而,编程不仅仅限于在屏幕上打印简单的文本。为了更进一步并能够编写执行有用任务的程序,真正节省我们的工作,我们需要引入变量的概念。

假设我要求你记住数字 5,然后我要求你同时记住数字 2。您刚刚在脑海中存储了两个不同的值(5 和 2)。现在,如果我让你在我说的第一个数字上加 1,你应该记住数字 6(即 5+1)和 2。例如,我们可以减去这些值并得到 4 作为结果。

上述整个过程类似于计算机如何处理两个变量。相同的过程可以用 C++ 语言用以下语句组来表达:

1
2
3
4
a = 5;
b = 2;
a = a + 1;
result = a - b;

显然,这是一个非常简单的例子,因为我们只使用了两个小整数值,但考虑到您的计算机可以同时存储数百万个这样的数字,并用它们进行复杂的数学运算。

我们现在可以将变量定义为内存的一部分来存储值。

每个变量都需要一个名称来标识它并将其与其他变量区分开来。例如,在前面的代码中,变量名称为 a 、 b 和 result ,但我们可以将变量称为我们可以想到的任何名称,只要它们是有效的 C++ 标识符

标识符

有效标识符是一个或多个字母、数字或下划线字符 ( _ ) 的序列。空格、标点符号和符号不能作为标识符的一部分。此外,标识符应始终以字母开头。它们也可以以下划线字符 ( _ ) 开头,但在大多数情况下,此类标识符被认为是为编译器特定的关键字或外部标识符以及在任何地方包含两个连续下划线字符的标识符保留的。在任何情况下都不能以数字开头。

C++使用许多关键字来标识操作和数据描述;因此,程序员创建的标识符无法与这些关键字匹配。不能用于程序员创建的标识符的标准保留关键字是:

1
2
3
4
5
6
alignas, alignof, and, and_eq, asm, auto, bitand, bitor, bool, break, case, catch, char, char16_t, char32_t, class, 
compl, const, constexpr, const_cast, continue, decltype, default, delete, do, double, dynamic_cast, else, enum,
explicit, export, extern, false, float, for, friend, goto, if, inline, int, long, mutable, namespace, new, noexcept,
not, not_eq, nullptr, operator, or, or_eq, private, protected, public, register, reinterpret_cast, return, short,
signed, sizeof, static, static_assert, static_cast, struct, switch, template, this, thread_local, throw, true, try,
typedef, typeid, typename, union, unsigned, using, virtual, void, volatile, wchar_t, while, xor, xor_eq

特定的编译器还可能有其他特定的保留关键字。

非常重要:C++语言是一种“区分大小写”的语言。这意味着用大写字母书写的标识符并不等同于另一个同名但用小写字母书写的标识符。因此,例如, RESULT 变量与 result 变量或 Result 变量不同。这是三个不同的标识符,标识三个不同的变量。

基本数据类型

变量的值以零和一的形式存储在计算机内存中未指定的位置。我们的程序不需要知道变量存储的确切位置;它可以简单地通过名称来引用它。程序需要了解变量中存储的数据类型。存储一个简单的整数与存储一个字母或一个大的浮点数是不同的;尽管它们都使用零和一表示,但它们的解释方式并不相同,并且在许多情况下,它们占用的内存量并不相同。

基本数据类型是由语言直接实现的基本类型,代表大多数系统本机支持的基本存储单元。它们主要可以分为:

字符型:它们可以表示单个字符,例如 ‘A’ 或 ‘$’ 。最基本的类型是 char ,它是一个单字节字符。还为更宽的字符提供了其他类型。

整型:它们可以存储整数值,例如 7 或 1024 。它们有多种大小,可以是有符号的,也可以是无符号的,具体取决于它们是否支持负值。

浮点型:它们可以表示具有不同精度级别的实际值,例如 3.14 或 0.01 ,具体取决于使用三种浮点类型中的哪一种。

布尔型:布尔类型在 C++ 中称为 bool ,只能表示 true 或 false 两种状态之一。

以下是 C++ 中基本类型的完整列表:

类型名称 尺寸/精度
字符型 char 大小正好是一个字节。至少 8 位。
char16_t 不小于 char 。至少 16 位。
char32_t 不小于 char16_t 。至少 32 位。
wchar_t 可以表示最大支持的字符集。
整型(有符号) signed char 与 char 大小相同。至少 8 位。
signed short int 不小于 char 。至少 16 位。
signed int 不小于 short 。至少 16 位。
signed long int 不小于 int 。至少 32 位。
signed long long int 不小于 long 。至少 64 位。
整型(无符号) unsigned char (与有符号整数相同)
unsigned short int
unsigned int
unsigned long int
unsigned long long int
浮点型 float
double 精度不低于 float
long double 精度不低于 double
布尔型 bool
空类型 void 没有存储空间
空指针 decltype(nullptr)

* 某些整型的名称可以缩写,不带 signedint 组件 - 仅需要非斜体部分来标识类型,斜体部分是可选的。即, signed short int 可以缩写为 signed shortshort int 或简称为 short ;它们都确定相同的基本类型。

在上面的每个组中,类型之间的区别仅在于它们的大小(即它们占用内存的大小):每个组中的第一个类型是最小的,最后一个类型是最大的,每个类型至少为与同一组中前面的一个一样大。除此之外,组中的类型具有相同的属性。

请注意,在上面的面板中,除了 char (其大小正好是一个字节)之外,没有任何基本类型指定了标准大小(但最多有一个最小大小)。因此,类型不需要(并且在许多情况下不需要)恰好是这个最小尺寸。这并不意味着这些类型的大小未确定,而是所有编译器和机器都没有标准大小;每个编译器实现都可以指定这些类型的大小,以最适合程序将要运行的体系结构。这种相当通用的类型大小规范为 C++ 语言提供了很大的灵活性,可以在当前和未来的各种平台上以最佳方式工作。

上述类型大小以位表示;类型的位数越多,它可以表示的不同值就越多,但同时也会消耗更多的内存空间

尺寸 有代表性的特殊值 笔记
8位 256 = 2^8
16位 65 536 = 2^16
32位 4 294 967 296 =2^32(约 40 亿)
64位 18 446 744 073 709 551 616 =2^64(约 180 亿)

对于整型来说,拥有更多可表示的值意味着它们可以表示的值的范围更大;例如,一个 16 位无符号整型能够表示 0 到 65535 范围内的 65536 个不同值,而在大多数情况下,其有符号整型能够表示 -32768 到 32767 之间的值。请注意,与无符号类型相比,有符号类型的正值大约减半,因为 16 位中的一位用于符号;这是范围上相对较小的差异,并且很少纯粹根据无符号类型可以表示的正值的范围来证明使用无符号类型是合理的。

对于浮点型,大小会影响其精度,因为其有效位和指数位的数量会增加或减少。

如果类型的大小或精度不是问题,则通常选择 char 、 int 和 double 来表示字符、整数和浮点数。分别为点值。各自组中的其他类型仅在非常特殊的情况下使用。

特定系统和编译器实现中基本类型的属性可以通过使用 numeric_limits 类来获取(请参阅标准头文件 < limits > )。如果由于某种原因需要特定大小的类型,库会在头文件 < cstdint > 中定义某些固定大小的类型别名。

上述类型(字符、整型、浮点和布尔)统称为算术类型。但是还存在两种额外的基本类型: void ,它标识缺少类型;以及类型 nullptr ,它是一种特殊类型的指针。这两种类型将在接下来有关指针的章节中进一步讨论。

C++ 基于上面讨论的基本类型支持多种类型;这些其他类型称为复合数据类型,是 C++ 语言的主要优势之一。我们还将在以后的章节中更详细地看到它们。

变量声明

C++ 是一种强类型语言,要求每个变量在首次使用之前都声明其类型。这通知编译器在内存中为变量保留的大小以及如何解释其值 。在 C++ 中声明新变量的语法很简单:我们只需编写类型,后跟变量名称(即其标识符)。例如:

1
2
int a;
float mynumber;

这是两个有效的变量声明。第一个声明了一个 int 类型的变量,其标识符为 a 。第二个声明了一个 float 类型的变量,其标识符为 mynumber 。声明后,变量 a 和 mynumber 可以在程序中其余作用域内使用。

如果声明多个相同类型的变量,则可以通过用逗号分隔标识符来在单个语句中声明它们。例如:

1
int a, b, c;

这声明了三个变量( a 、 b 和 c ),它们都是 int 类型,并且具有与:

1
2
3
int a;
int b;
int c;

为了了解程序中变量声明的实际效果,让我们看一下本章开头提出的关于心理记忆的示例的完整 C++ 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// operating with variables

#include <iostream>
using namespace std;

int main ()
{
// declaring variables:
int a, b;
int result;

// process:
a = 5;
b = 2;
a = a + 1;
result = a - b;

// print out the result:
cout << result;

// terminate the program:
return 0;
}

编辑和在线运行

如果变量声明本身之外的其他内容对您来说看起来有点奇怪,请不要担心。其中大部分内容将在接下来的章节中进行更详细的解释。

变量的初始化

当声明上例中的变量时,它们的值是不确定的,直到第一次被赋值为止。但变量有可能从声明之日起就具有特定值。这称为变量的初始化

在C++中,初始化变量的方法有3种。它们都是等价的,并且让人想起这些年来语言的演变:

第一个称为类 C 初始化(因为它是从 C 语言继承的),包括附加一个等号,后跟变量初始化的值:

例如,要声明一个名为 x 的 int 类型变量,并在声明的同一时刻将其初始化为零值,我们可以编写:

1
int x = 0;

第二种方法称为构造函数初始化(由 C++ 语言引入),将初始值括在括号 ( () ) 之间:

类型标识符(初始值);

例如:

1
int x(0);

最后,第三种方法称为统一初始化,与上面类似,但使用大括号 ( {} ) 而不是括号(这是由 2011 年 C++ 标准修订版引入的):

类型标识符{初始值};

例如:

1
int x{0};

初始化变量的所有三种方法在 C++ 中都是有效且等效的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// initialization of variables

#include <iostream>
using namespace std;

int main ()
{
int a=5; // initial value: 5
int b(3); // initial value: 3
int c{2}; // initial value: 2
int result; // initial value undetermined

a = a + b;
result = a - c;
cout << result;

return 0;
}

编辑和在线运行

类型推导:auto 和 decltype

当一个新变量被初始化时,编译器可以通过初始化器自动找出该变量的类型。为此,使用 auto 作为变量的类型说明符就足够了:

1
2
int foo = 0;
auto bar = foo; // the same as: int bar = foo;

这里, bar 被声明为具有 auto 类型;因此, bar的类型是用于初始化它的值的类型 :在本例中,它使用 foo 的类型,即 int 。

未初始化的变量也可以使用decltype说明符进行类型推导
1
2
int foo = 0;
decltype(foo) bar; // the same as: int bar;

这里, bar 被声明为与 foo 具有相同的类型。

auto 和 decltype 是最近添加到该语言中的强大功能。但是他们引入的类型推导功能旨在当无法通过其他方式获取类型或使用它提高代码可读性时使用。上面的两个例子可能都不是这些用例。事实上,它们可能会降低可读性 ,因为在阅读代码时,必须搜索 foo 的类型才能真正知道 bar 的类型。

字符串简介

基本类型表示由可能运行代码的机器处理的最基本类型。但 C++ 语言的主要优势之一是其丰富的复合类型集,其中基本类型只是构建块。

复合类型的一个示例是 string 类。这种类型的变量能够存储字符序列,例如单词或句子。一个非常有用的功能!

与基本数据类型的第一个区别是,为了声明和使用这种类型的对象(变量),程序需要包含在标准库中定义该类型的头文件(头文件 < string > ):

1
2
3
4
5
6
7
8
9
10
11
12
// my first string
#include <iostream>
#include <string>
using namespace std;

int main ()
{
string mystring;
mystring = "This is a string";
cout << mystring;
return 0;
}

编辑和在线运行

正如您在前面的示例中所看到的,可以使用任何有效的字符串文字来初始化字符串,就像可以将数值类型变量初始化为任何有效的数值文字一样。与基本类型一样,所有初始化格式对于字符串都有效:

1
2
3
string mystring = "This is a string";
string mystring ("This is a string");
string mystring {"This is a string"};

字符串还可以执行基本数据类型可以执行的所有其他基本操作,例如在没有初始值的情况下声明并在执行期间更改其值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// my first string
#include <iostream>
#include <string>
using namespace std;

int main ()
{
string mystring;
mystring = "This is the initial string content";
cout << mystring << endl;
mystring = "This is a different string content";
cout << mystring << endl;
return 0;
}

编辑和在线运行

注意:插入 endl 操纵符结束该行(打印换行符并刷新流)。

string类是复合类型 。正如您在上面的示例中所看到的,复合类型的使用方式与基本类型相同:使用相同的语法来声明变量并初始化它们。

有关标准 C++ 字符串的更多详细信息,请参阅 string 类参考。