现代C++编程:从入门到实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.1 基本类型

基本类型是最基本的对象类型,包括整数、浮点数、字符、布尔、bytesize_tvoid。有些人把基本类型称为原始类型或内置类型,因为它们是核心语言的一部分,几乎总是使用的。这些类型可以在任何平台上工作,但它们的特性,如大小和内存布局,则取决于具体的实现。

基本类型取得了一种平衡。一方面,它们试图映射从C++结构到计算机硬件的直接关系;另一方面,它们简化了跨平台代码的编写,允许程序员写一次代码就可以在许多平台上运行。下面的几节将详细介绍这些基本类型。

2.1.1 整数类型

整数类型存储的是整数。四种大小的整数类型分别是short intint、long intlong long int。每个类型都可以是有符号(signed)或无符号(unsigned)的。有符号变量可以是正数、负数或零,无符号变量必须是非负数。

整数类型默认是有符号的int类型,这意味着我们可以在程序中使用简写符号shortlonglong long,而不是short intlong intlong long int。表2-1列出了所有可用的C++整数类型,展示了每种类型有无符号,在不同平台上的大小(以字节为单位),以及每种类型的格式指定符。

表2-1 整数类型及其大小和格式指定符

注意,不同平台下整数类型的大小不同:64位Windows和Linux/Mac的long大小不同(分别为4字节和8字节)。

通常情况下,编译器会在格式指定符和整数类型不匹配时发出警告。但是,在printf语句中使用格式指定符时,必须确保它们是正确的。这里列出格式指定符是为了让你可以在后面的例子中向控制台打印整数。

注意 如果想确保整数的大小,那么可以使用<cstdint>库中的整数类型。例如,如果你需要一个正好是8、16、32或64位的有符号整数,则可以使用int8_tint16_tint32_tint64_t。你可以在这个库中找到速度最快、最小、最大、有符号和无符号整数类型,以满足各种要求。但由于这个头文件并不总是在每个平台上都可用,因此你应该只在没有其他选择时使用cstdint类型。

字面量是程序中的硬编码值。我们可以使用四种硬编码的、整数字面量表示:

❑ 二进制:使用前缀0b

❑ 八进制:使用前缀0

❑ 十进制:这是默认的。

❑ 十六进制:使用前缀0x

这是同一组整数的四种不同写法。例如,代码清单2-1显示了如何使用每一种非十进制表示来赋值整数变量。

代码清单2-1 给几个整数变量赋值并以适当的格式指定符打印它们的程序

这个程序使用非十进制表示整数(二进制❶、八进制❷和十六进制❸),并使用表2-1中列出的格式指定符将它们用printf打印出来。每个printf的输出都显示在代码下方。

注意 整数字面量可以包含任何数量的单引号('),以方便阅读。编译器会完全忽略这些引号。例如,10000001'000'000都是表示一百万的字面量。

有时,打印无符号整数的十六进制表示或八进制表示(较少见)是很有用的。我们可以使用printf格式指定符%x%o实现这个目的,如代码清单2-2所示。

代码清单2-2 一个使用无符号整数的八进制和十六进制表示的程序

十进制数3669732608的十六进制表示是dabbad00,由于十六进制格式指定符%x❶,因此它出现在输出的第一行。十进制数69的八进制表示是105。无符号整数的格式指定符%u❷和八进制整数的格式指定符%o❸分别对应于参数❹和❺。printf语句将这些量❷替换到格式化字符串中,产生信息There are 69, 105 leaves here.

警告 八进制前缀是B语言的遗留问题,可追溯到PDP-8计算机和八进制无处不在的时代。C以及C++延续了这个可疑的传统。例如,在对美国邮政编码进行硬编码时必须小心:

去除十进制数的前面的零;否则,它们将不再是十进制数。这一行无法编译,因为9不是八进制数字。

默认情况下,整数字面量的类型一般是intlonglong long。整数字面量的类型是这三种类型中最小的那种。这是由语言定义的,并将由编译器强制执行。

如果想更灵活,则可以给整数字面量提供后缀来指定它的类型(后缀不区分大小写,所以你可以选择自己最喜欢的风格)。

unsigned对应后缀uU

long对应后缀lL

long long对应后缀llLL

unsigned后缀和long后缀或long long后缀结合起来可以指定整数类型的符号性和大小。表2-2显示了后缀组合可能对应的类型。允许的类型用复选标记(√)表示。对于二进制、八进制和十六进制字面量,可以省略后缀uU。这些都用星号(*)来描述。

表2-2 整数后缀

允许的最小类型仍能表示整数字面量的类型就是最终类型。这意味着,在特定整数允许的所有类型中,最小的类型将被应用。例如,整数字面量112114可以是intlonglong long类型的。由于int可以存储112114,因此最终的整数字面量是int类型的。如果真的想采用long类型,则可以指定为112114L(或112114l)。

2.1.2 浮点类型

浮点类型存储的是实数(在这里可以定义为任何带有小数点和小数部分的数字,如0.33333或98.6)的近似值。虽然无法在计算机内存中准确地表示某些实数,但可以存储一个近似值。如果这看起来很难相信,那么可以想一想像π这样的数字,它有无限多的位数。在有限的计算机内存中,怎么可能表示无限多位的数字?

与所有其他类型一样,浮点类型占用的内存是有限的,这被称为类型的精度。浮点类型的精度越高,它对实数的近似就越准确。C++为近似值提供了三个级别的精度:

float:单精度。

double:双精度。

long double:扩展精度。

和整数类型一样,每种浮点表示都取决于实现。本节不会详细介绍浮点类型,但请注意,这些实现方式存在大量的细微差别。在主流桌面操作系统上,float通常有4字节的精度。doublelong double通常有8字节的精度(双精度)。

大多数不参与科学计算的用户可以安全地忽略浮点表示的细节。在这种情况下,可以使用double

注意 对于那些不能安全地忽略浮点表示细节的人来说,可以看看与自己硬件平台相关的浮点规范。浮点存储和算术的主要实现方式在《IEEE浮点算术标准》(IEEE 754)中有所概述。

1.浮点字面量

浮点字面量默认为双精度。如果需要单精度字面量,则使用fF后缀;如果需要扩展精度字面量,则使用lL,如下所示:

字面量也可以使用科学计数法:

基数❶和指数❷之间不允许有空格。

2.浮点格式指定符

格式指定符%f显示带有小数位的浮点数,而%e则以科学计数法显示相同的数字。我们也可以让printf使用%g格式指定符,选择%e%f中更紧凑的一个。

对于double,只需在说明符前面加上小写字母l,而对于long double,在前面加上大写字母L。例如,如果想要一个带小数位的double,则可以指定%lf%le%lg;对于long double,则可以指定%Lf%Le%Lg

考虑代码清单2-3,它探讨了打印浮点数的不同选项。

代码清单2-3 一个打印浮点数的程序

这个程序声明了一个名为andouble❶。格式指定符%le❷输出科学计数法结果6.022141e+23,而%lf❸输出小数点表示602214090000000006225920.000000%lg❹指定符选择了科学计数法结果6.02214e+23。名为hp❺的浮点数(float)使用%e%f指定符产生类似的printf输出。但是格式指定符%g决定输出十进制表示9.75而不是科学计数法结果。

一般来说,使用%g来打印浮点类型。

注意 在实践中,可以省略double格式指定符中的l前缀,因为printf会将浮点数参数提升为双精度类型。

2.1.3 字符类型

字符类型存储人类语言信息。这六种字符类型是:

char:默认类型,总是1个字节。可能是也可能不是有符号的(例如:ASCII)。

char16_t:用于2字节的字符集(例如:UTF-16)。

char32_t:用于4字节的字符集(例如:UTF-32)。

signed char:与char相同,但保证是有符号的。

unsigned char:与char相同,但保证是无符号的。

wchar_t:足够大以包含实现平台地区环境语言设置中的最大字符(例如:Unicode)。

字符类型charsigned charunsigned char被称为窄字符,而char16_tchar32_twchar_t由于其相对的存储要求,被称为宽字符。

1.字符字面量

字符字面量是一个单一的、恒定的字符。所有字符都用单引号('')括起来。如果字符是char以外的其他类型,还必须提供一个前缀:L代表wchar_tu代表char16_t,而U代表char32_t。例如,'J'声明一个char字面量,L'J'声明一个wchar_t字面量。

2.转义序列

有些字符不能在屏幕上显示。相反,它们会迫使显示器做一些事情,比如将光标移到屏幕的左边(回车)或将光标向下移动一行(换行)。其他字符可以在屏幕上显示,但它们是C++语法的一部分,如单引号或双引号,所以必须非常小心地使用它们。为了将这些字符转换为char,可以使用转义序列,如表2-3中所示。

表2-3 保留字符和它们的转义序列

3.Unicode转义字符

我们可以使用通用字符名(universal character name)来指定Unicode字符字面量,使用通用字符名的方式有两种:前缀\u加后面4位的Unicode码位,或前缀\U加后面8位的Unicode码位。例如,可以将A字符表示为'\u0041',将啤酒杯字符㊣表示为U'\U0001F37A'

4.格式指定符

charprintf格式指定符为%cwchar_t的格式指定符是%lc。代码清单2-4初始化了两个字符字面量xy,用来构建printf调用。

代码清单2-4 一个为几个字符型变量赋值并打印它们的程序

这个程序输出Windows binaries start with MZ.。尽管M是窄字符,而Z是宽字符,但printf仍能工作,因为该程序使用了正确的格式指定符。

注意 所有Windows二进制文件的前两个字节是字符M和Z,这是对MS-DOS可执行二进制文件格式的设计者Mark Zbikowski的致敬。

2.1.4 布尔类型

布尔类型有两种状态:真和假。布尔类型只有一个,即bool。整数类型和布尔类型可以互相转换:true状态转换为1,false状态转换为0,任何非零的整数都转换为true,而0则转换为false

1.布尔字面量

要初始化布尔类型,需要使用两个布尔字面量,即truefalse

2.格式指定符

bool没有格式指定符,但我们可以在printf中使用int格式指定符%d来产生1(代表true)或0(代表false)。原因是printf将任何小于int的整数值提升为int。代码清单2-5说明了如何声明布尔变量并检查其值。

代码清单2-5 用printf语句打印布尔变量

该程序把b1初始化为true❶,b2初始化为false❷。然后,把b1b2打印成整数(使用%d格式指定符),得到对应b1的1和对应b2的0❸。

3.比较运算符

运算符是对操作数进行计算的函数(详见7.1节)。操作数是一种简单对象。关于使用bool类型的有意义的例子,详见本节“比较运算符”和“逻辑运算符”。

我们可以使用几个运算符来构建布尔表达式。回忆一下,比较运算符接受两个参数并返回一个布尔值。可用的运算符有相等(==)、不等(!=)、大于(>)、小于(<)、大于或等于(>=)、小于或等于(<=)。

代码清单2-6显示了如何使用这些运算符来产生布尔运算。

代码清单2-6 使用比较运算符

每次比较都会产生一个布尔值结果❷,printf语句将布尔值打印成一个int结果❶。

4.逻辑运算符

逻辑运算符在bool类型上处理布尔逻辑。我们可以通过操作数的数量来对运算符分类。一元运算符需要一个操作数,二元运算符需要两个,三元运算符需要三个,以此类推。我们还可以通过操作数的类型进一步对运算符分类。

取否运算符(!)接受一个操作数,并返回与操作数相反的结果。换句话说,!true产生false,而!false则产生true

逻辑运算符“与”(&&)和“或”(||)是二元的。逻辑“与”(AND)只在两个操作数都为true时返回true。逻辑“或”(OR)只要有操作数为true就返回true

注意 阅读布尔表达式时,!的发音是“not”,如表达式a&&!b表示“a AND not b”。

逻辑运算符一开始可能令人困惑,但它们很快就会变得直观。代码清单2-7展示了逻辑运算符。

代码清单2-7 一个展示逻辑运算符用法的程序

在这里,我们可以看到取否运算符❶,逻辑“与”运算符❷❸,以及逻辑“或”运算符❹❺。

2.1.5 std::byte类型

系统程序员有时会直接使用原始内存,原始内存是一个没有类型的位(bit)集合。这种情况下可以使用std::byte类型,它定义在<cstddef>头文件中。std::byte类型允许按位进行逻辑运算(见第7章)。使用这种类型而不是整数类型来处理原始数据,常常可以避免难以调试的编程错误。

注意,与<cstddef>中的大多数其他基本类型不同,std::byte在C语言中没有确切的对应类型(“C类型”)。像C++一样,C语言也有charunsigned char。这些类型使用起来不太安全,因为它们支持许多std::byte不支持的运算。例如,可以对char进行算术运算,比如加法(+),但不能对std::byte进行该运算。这个看起来很奇怪的std::前缀被称为命名空间,详见8.3.2节(现在,暂且把命名空间std::当作类型名称的一部分)。

注意 关于std的发音,有两种观点。一种是把它当作“ess-tee-dee”的首字母缩写,另一种是把它当作“stood”的缩写。当提到std命名空间中的类时,说话者通常会暗示命名空间操作符::。因此,可以把std::byte读作“stood byte”或者“ess-tee-dee colon colon byte”。

2.1.6 size_t类型

size_t类型(也在<cstddef>头文件中)用来表示对象的大小。size_t对象保证其最大值足以代表所有对象的最大字节数。从技术上讲,这意味着size_t可以占用2个字节,也可以占用200个字节,具体取决于实现方式。在实践中,它通常与64位架构系统的unsigned long long相同。

注意 size_t<stddef>头文件中的一个C类型,但它与C++的size_t相同,后者位于std命名空间中。偶尔,我们可以看到用(技术上正确的)std::size_t来代替。

1.sizeof

一元运算符sizeof接受一个类型并返回该类型的大小(以字节为单位)。sizeof运算符总是返回一个size_t对象。例如,sizeof(float)返回存储float所需的字节数。

2.格式指定符

size_t的格式指定符通常是%zd(十进制表示)或%zx(十六进制表示)。代码清单2-8显示了如何检查某个系统的几种整数类型的大小。

代码清单2-8 打印几种整数类型的字节数的程序(输出来自Windows 10 x64)

代码清单2-8分别计算char❶、short❷、int❸、long❹和long long❺的大小,并使用%zd格式指定符打印它们的大小。结果会因操作系统的不同而不同。从表2-1可以看出,每个环境都为整数类型定义了自己的大小。请特别注意代码清单2-8中long的返回值;Linux和macOS都定义了8字节的long类型。

2.1.7 void

void类型表示一个空的值集合。因为void对象不能拥有值,所以C++不允许使用void对象。我们只在特殊情况下使用void,比如用作不返回任何值的函数的返回类型。例如,函数taunt不返回值,因此我们声明它的返回类型为void

其他特殊的void用法见第3章。