重要的事情说三遍:
“Hello World” 程序在上一章中展示的作用相当有限。我们不得不编写几行代码,编译它们,然后执行生成的程序,仅仅是为了在屏幕上显示一个简单的句子。显然,我们自己输入输出句子会更快。
然而,编程不仅仅是将简单的文本打印到屏幕上。为了进一步学习并能够编写执行有用任务的程序,我们需要引入变量的概念。
假设我让你记住数字5,然后我让你同时记住数字2。你刚刚在记忆中存储了两个不同的值(5和2)。现在,如果我让你在第一个数字上加1,你应该在记忆中保留数字6(即5+1)和2。然后我们可以例如将这些值相减,得到4作为结果。
上面描述的整个过程是计算机可以用两个变量执行的操作的模拟。同样的过程可以用以下一组C++语句表示:
a = 5;
b = 2;
a = a + 1;
result = a - b;
显然,这是一个非常简单的示例,因为我们只使用了两个小的整数值,但请考虑你的计算机可以同时存储数百万个这样的数字,并使用它们进行复杂的数学运算。
我们现在可以将变量定义为存储值的一部分内存。
每个变量需要一个名字来标识它并将其与其他变量区分开。例如,在前面的代码中,变量名分别是a
、b
和result
,但我们可以为变量取任何名字,只要它们是有效的C++标识符。
标识符
有效的标识符是 由一个或多个字母、数字或下划线字符(_
) 组成的序列。空格、标点符号和符号不能成为标识符的一部分。此外,标识符应始终以字母开头。它们也可以以下划线字符(_
)开头,但这种标识符在大多数情况下被认为是保留给编译器特定关键字或外部标识符使用的,尤其是包含两个连续下划线字符的标识符。在任何情况下都不能以数字开头。
C++使用许多关键字来标识操作和数据描述;因此,程序员创建的标识符不能与这些关键字相匹配。不能用于程序员创建的标识符的标准保留关键字是:
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
。它们存在多种大小,并且可以是有符号的(signed)或无符号的(unsigned),取决于它们是否支持负值。 - 浮点型: 可以表示实数值,例如
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_ | 不小于short。至少为16位。 | |
_signed int_ | 不小于int。至少为16位。 | |
_signed long int_ | 不小于long。至少为32位。 | |
_signed long long int_ | 不小于long long。至少为64位。 | |
无符号整数类型 | unsigned char | 与有符号对应类型大小相同。 |
unsigned short int | 与有符号对应类型大小相同。 | |
unsigned int | 与有符号对应类型大小相同。 | |
unsigned long int | 与有符号对应类型大小相同。 | |
unsigned long long int | 与有符号对应类型大小相同。 | |
浮点类型 | float | 精度不小于float。 |
double | 精度不小于double。 | |
long double | 精度不小于long double。 | |
布尔类型 | bool | 仅表示true 或false 。 |
空类型 | void | 无存储。 |
空指针类型 | decltype(nullptr) | 无存储。 |
某些整数类型的名称可以省略它们的signed
和int
部分——仅需要部分即可标识类型,斜体部分是可选的。即,_signed_ short _int_
可以缩写为signed short
、short int
或简单地short
;它们都标识相同的基本类型。
在上面每组中的类型之间的区别仅在于它们的大小(即它们在内存中占用的大小):每组中的第一个类型是最小的,最后一个是最大的,每个类型至少与前一个类型一样大。除此之外,组中的类型具有相同的属性。
请注意,除了char
(其大小正好为一个字节)之外,没有任何基本类型具有标准指定的大小(但最多有最小大小)。因此,这些类型不需要(并且在许多情况下也不是)正好是这个最小大小。这并不意味着这些类型的大小是不确定的,而是没有所有编译器和机器通用的标准大小;每个编译器实现可以为这些类型指定最适合程序运行的架构的大小。这种类型大小的通用规范为C++语言提供了很大的灵活性,可以适应各种平台,无论是现在还是未来。
类型大小以比特为单位表示;类型的比特越多,它可以表示的不同值就越多,但同时也会消耗更多的内存空间:
大小 | 唯一可表示的值 | 备注 |
---|---|---|
8位 | 256 | = 2^8 |
16位 | 65 536 | = 2^16 |
32位 | 4 294 967 296 | = 2^32 (~42亿) |
64位 | 18 446 744 073 709 551 616 | = 2^64 (~18兆亿) |
对于整数类型,表示更多可表示的值意味着它们可以表示的值的范围更大;例如,一个16位无符号整数可以表示范围为0到65535的65536个不同的值,而它的有符号对应物在大多数情况下可以表示-32768到32767之间的值。请注意,由于16位中的一个位用于表示符号,与无符号类型相比,有符号类型的正值范围大约减少了一半;这是范围上的一个相对适中的差异,纯粹基于它们可以表示的正值范围来使用无符号类型很少是合理的。
对于浮点类型,大小影响其精度,因为它们有更多或更少的比特用于表示有效数字和指数。
如果类型的大小或精度不是问题,那么char
、int
和double
通常用于分别表示字符、整数和浮点值。它们各自组中的其他类型仅在非常特殊的情况下使用。
特定系统和编译器实现中基本类型的属性可以使用<limits>
头文件中的numeric_limits类获取。如果由于某种原因需要特定大小的类型,库在<cstdint>
头文件中定义了一些固定大小的类型别名。
上述类型(字符、整数、浮点和布尔)统称为算术类型。但还有两种基本类型存在:void
,它表示没有类型;以及nullptr
,它是一种特殊的指针类型。两种类型将在后续关于指针的章节中进一步讨论。
C++基于上述基本类型支持多种类型;这些其他类型称为 复合数据类型,它们是C++语言的主要优势之一。我们将在后续章节中更详细地讨论它们。
变量的声明
C++是一种强类型语言,要求每个变量在首次使用之前都必须声明其类型。这告知编译器为变量保留内存的大小以及如何解释其值。在C++中声明新变量的语法非常简单:我们只需编写类型后跟变量名(即其标识符)。例如:
int a;
float mynumber;
这是两个有效的变量声明。第一个声明了一个类型为int
的变量,标识符为a
。第二个声明了一个类型为float
的变量,标识符为mynumber
。一旦声明,变量a
和mynumber
就可以在程序的其余作用域内使用。
如果声明多个相同类型的变量,它们可以在一个语句中通过逗号分隔其标识符来声明。例如:
int a, b, c;
这声明了三个变量(a
、b
和c
),它们都类型为int
,其意义与以下语句完全相同:
int a;
int b;
int c;
为了了解变量声明在程序中的实际应用,让我们看看本章开头提出的关于记忆的示例的完整C++代码:
// 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++中,有三种方式初始化变量。它们都是等效的,反映了语言多年来的演变:
第一种方法,称为C风格的初始化(因为它继承自C语言),由等号后跟变量的初始值组成:
type identifier = initial_value;
例如,要声明一个类型为int
的变量x
,并在声明时将其初始化为零,我们可以写:
int x = 0;
第二种方法,称为构造函数初始化(由C++语言引入),将初始值括在括号(()
)内:
type identifier (initial_value);
例如:
int x (0);
最后一种方法,称为统一初始化,与上述方法相似,但使用大括号({}
)而不是括号(此方法由2011年修订的C++标准引入):
type identifier {initial_value};
例如:
int x {0};
以上三种初始化变量的方法在C++中都是有效且等效的。
// initialization of variables
#include <iostream>
using namespace std;
int main ()
{
int a=5; // 初始值: 5
int b(3); // 初始值: 3
int c{2}; // 初始值: 2
int result; // 初始值未确定
a = a + b;
result = a - c;
cout << result;
return 0;
}
类型推导:auto 和 decltype
当新变量被初始化时,编译器可以根据初始值自动推导出变量的类型。为此,只需使用auto
作为变量的类型说明符:
int foo = 0;
auto bar = foo; // 等效于: int bar = foo;
这里,bar
被声明为auto
类型;因此,bar
的类型是用来初始化它的值的类型:在这种情况下,它使用foo
的类型,即int
。
未初始化的变量也可以使用类型推导,通过使用decltype
说明符:
int foo = 0;
decltype(foo) bar; // 等效于: int bar;
这里,bar
被声明为与foo
相同的类型。
auto
和decltype
是最近添加到语言中的强大功能。但它们引入的类型推导特性应该在类型无法通过其他方式获得或使用它们可以提高代码可读性时使用。上面的两个例子可能都不是这些情况。实际上,它们可能降低了可读性,因为在阅读代码时,必须搜索foo
的类型才能知道bar
的类型。
字符串介绍
基本类型表示代码可能运行的机器处理的最基本类型。但C++语言的一个主要优势是其丰富的复合类型集合,其中基本类型只是构建块。
复合类型的一个例子是string
类。该类型的变量可以存储字符序列,例如单词或句子,非常有用!
与基本数据类型的第一个区别是,为了声明和使用这种类型的对象(变量),程序需要包含定义该类型的标准库头文件(头文件<string>
):
// my first string
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string mystring;
mystring = "This is a string";
cout << mystring;
return 0;
}
如上例所示,字符串可以用任何有效的字符串字面量初始化,就像数值类型变量可以用任何有效的数值字面量初始化一样。与基本类型一样,所有的初始化格式对字符串都是有效的:
string mystring = "This is a string";
string mystring ("This is a string");
string mystring {"This is a string"};
字符串还可以执行所有其他基本操作,例如声明时没有初始值,并在执行期间更改其值:
// 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类参考。