基本数据类型
1. 简介
最小的内存单位是二进制数字(也称为位),它可以保存 0 或 1 的值。内存被组织成称为内存地址(或简称地址)的顺序单元。字节是作为单元进行操作的一组位。现代标准是字节由 8 个顺序位组成。
基本数据类型
类型 | 类别 | 意义 | 例子 |
---|---|---|---|
float double long double |
浮点 | 带有小数部分的数字 | 3.14159 |
bool | 整形(布尔值) | 真或假 | true |
char wchar_t char8_t (C++20) char16_t (C++11) char32_t (C++11) |
整形(字符) | 文本的单个字符 | ‘c’ |
short int int long int long long int (C++11) |
整形(整数) | 正整数和负整数(包括0) | 64 |
std::nullptr_t (C++11) | 空指针 | 空指针 | nullptr |
void | Void | 无类型 | n/a |
C++ 还支持许多其他更复杂的类型,称为“复合类型”。较新版本的 C++ 中定义的许多类型(例如std::nullptr_t
)都使用 _t
后缀。这个后缀的意思是“类型”,它是应用于现代类型的常见术语。
在C++中,术语“integer”(整数)通常用来指代int数据类型,它用于存储整数值。然而,它有时也用来指代更广泛的数据类型集合,这些类型通常用于存储和显示整数值。这包括short、int、long、long long以及它们的有符号和无符号变体。
术语“integral”(整型)的意思是“像整数一样”。大多数情况下,“integral”(整型)是作为术语“integral type”(整型类型)的一部分来使用的,它包括更广泛的类型集合,这些类型在内存中作为整数存储,尽管它们的行为可能会有所不同(我们将在本章后面讨论字符类型时看到这一点)。这包括bool、整数类型以及各种字符类型。
2. Void
void 是不完整类型,不完整类型是已声明但尚未定义的类型。编译器知道此类类型的存在,但没有足够的信息来确定要为该类型的对象分配多少内存。void
是故意不完整的,因为它表示缺少类型,因此无法定义。不完整类型无法实例化:
1 | void value; // won't work, variables can't be defined with incomplete type void |
最常见的用法是void用于表示函数不返回值。
在 C 中,void 用于指示函数不带任何参数,如下示例:
1 | int getValue(void) // void here means no parameters |
尽管这将在 C++ 中编译(出于向后兼容性原因),但这种关键字 void 的使用在 C++ 中被视为已弃用。以下代码是等效的,在 C++ 中是首选的:
1 | int getValue() // empty function parameters is an implicit void |
3. 对象大小和 sizeof 运算符
C++ 标准没有定义任何基本类型的确切大小(以位为单位)。相反,该标准规定如下:
-
一个对象必须至少占用 1 个字节(以便每个对象都有不同的内存地址)。
-
字节必须至少为 8 位。
-
整型类型
char
、short
、int
、long
和long long
的最小大小分别为 8、16、16、32 和 64 位。 -
char
和char8_t
正好是 1 个字节(至少 8 位)。
类别 | 类型 | 最小大小 | 典型大小 |
---|---|---|---|
布尔 | bool | 1 byte | 1 byte |
字符 | char | 1 byte (exactly) | 1 byte |
wchar_t | 1 byte | 2 or 4 bytes | |
char8_t | 1 byte | 1 byte | |
char16_t | 2 bytes | 2 bytes | |
char32_t | 4 bytes | 4 bytes | |
整形 | short | 2 bytes | 2 bytes |
int | 2 bytes | 4 bytes | |
long | 4 bytes | 4 or 8 bytes | |
long long | 8 bytes | 8 bytes | |
浮点 | float | 4 bytes | 4 bytes |
double | 8 bytes | 8 bytes | |
long double | 8 bytes | 8, 12, or 16 bytes | |
指针 | std::nullptr_t | 4 bytes | 4 or 8 bytes |
为了确定特定计算机上数据类型的大小,C++ 提供了一个名为 sizeof
的运算符。sizeof 运算符是一个一元运算符,它采用类型或变量,并返回该类型对象的大小(以字节为单位)。编译并运行以下程序可以了解某些数据类型的大小:
1 |
|
4. 有符号整数
默认情况下,C++ 中的整数是有符号的,这意味着数字的符号作为值的一部分存储。因此,有符号整数可以同时包含正数和负数(和 0)。定义有符号整数的首选方法:
1 | short s; // prefer "short" instead of "short int" |
整数类型还可以采用可选的_有符号_关键字,按照惯例,该关键字通常放置在类型名称之前:
1 | signed short ss; |
首选不使用
int
后缀或signed
前缀的速记类型。
有符号整数范围表:
大小/类型 | 范围 |
---|---|
8-bit 有符号数 | -128 to 127 |
16-bit 有符号数 | -32,768 to 32,767 |
32-bit 有符号数 | -2,147,483,648 to 2,147,483,647 |
64-bit 有符号数 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
超过上述范围会溢出,进而导致未定义行为
5. 无符号整数
C++ 还支持无符号整数。无符号整数是只能保存非负整数的整数。定义方法:
1 | unsigned short us; |
范围如下:
大小/类型 | 范围 |
---|---|
8 bit 无符号数 | 0 to 255 |
16 bit 无符号数 | 0 to 65,535 |
32 bit 无符号数 | 0 to 4,294,967,295 |
64 bit 无符号数 | 0 to 18,446,744,073,709,551,615 |
当不需要负数时,无符号整数非常适合网络和内存很少的系统,因为无符号整数可以存储更多的正数而不占用额外的内存。
无符号值超出范围时会取模,示例:
1 |
|
输出:
1 | x was: 65535 |
另一个示例:
1 |
|
输出:
1 | x was: 0 |
在保存数量(甚至应该是非负的数量)和数学运算时,优先选择有符号数而不是无符号数。避免混合有符号和无符号数字。
使用无符号数的一些情况:
-
在处理位操作时,首选无符号数
-
在某些情况下,使用无符号数字仍然是不可避免的,主要是与数组索引有关的情况。
-
为嵌入式系统(例如 Arduino)或其他一些处理器/内存有限的环境进行开发,则出于性能原因,使用无符号数字更为常见和接受(在某些情况下,不可避免)。
6. 固定宽度整数和 std::siez_t
6.1 固定宽度整数
整数类型的大小通常是不固定的,由运行的机器决定。为了解决上述问题,C++11 提供了一组备用整数类型,保证在任何体系结构上具有相同的大小。由于这些整数的大小是固定的,因此称为固定宽度整数。
固定宽度整数定义(在<cstdint>
标头中)如下
名称 | 固定大小 | 固定范围 | 备注 |
---|---|---|---|
std::int8_t | 1 byte 有符号数 | -128 to 127 | 在许多系统上被视为有符号字符。 |
std::uint8_t | 1 byte 无符号数 | 0 to 255 | 在许多系统上被视为无符号字符。 |
std::int16_t | 2 byte 有符号数 | -32,768 to 32,767 | |
std::uint16_t | 2 byte 无符号数 | 0 to 65,535 | |
std::int32_t | 4 byte 有符号数 | -2,147,483,648 to 2,147,483,647 | |
std::uint32_t | 4 byte 无符号数 | 0 to 4,294,967,295 | |
std::int64_t | 8 byte 有符号数 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | |
std::uint64_t | 8 byte 无符号数 | 0 to 18,446,744,073,709,551,615 |
由于 C++ 规范中的疏忽,现代编译器通常将std::int8_t
和std::uint8_t
分别视为signed char
和unsigned char
。因此,在大多数现代系统上,8 位固定宽度整数类型的行为类似于 char 类型。
以下程序会一般会打印A
而不是65
1 |
|
固定宽度整数有一些潜在的缺点:
-
固定宽度整数并不保证在所有架构上都有定义。它们只存在于具有与它们宽度匹配并遵循特定二进制表示的基本整数类型的系统上。如果你的程序使用的固定宽度整数在某个不支持该整数的架构上,你的程序将无法编译。然而,鉴于现代架构已经标准化了8/16/32/64位变量,这不太可能成为问题,除非你的程序需要移植到一些奇异的大型机或嵌入式架构上。
-
固定宽度整数并不保证在所有架构上都有定义。它们只存在于具有与它们宽度匹配并遵循特定二进制表示的基本整数类型的系统上。如果你的程序使用的固定宽度整数在某个不支持该整数的架构上,你的程序将无法编译。然而,鉴于现代架构已经标准化了8/16/32/64位变量,这不太可能成为问题,除非你的程序需要移植到一些奇异的大型机或嵌入式架构上。
为了帮助解决上述缺点,C++还定义了两组保证存在的替代整数。
-
快速类型(
std::int_fast#_t
和std::uint_fast#_t
)提供了至少具有#位宽度(其中# = 8, 16, 32, 或 64)的最快有符号/无符号整数类型。例如,std::int_fast32_t
将给你至少32位的最快有符号整数类型。通过最快,我们指的是CPU可以最快处理的整数类型。 -
最小类型(
std::int_least#_t
和std::uint_least#_t
)提供了至少具有#位宽度(其中# = 8, 16, 32, 或 64)的最小有符号/无符号整数类型。例如,std::uint_least32_t
将给你至少32位的最小无符号整数类型。
一个示例:
1 |
|
输出:
1 | least 8: 8 bits |
6.2 std::sizez_t
std::size_t
是sizeof
的返回值类型。std::size_t是实现定义的无符号整型的别名。换句话说,编译器决定std::size_t
是否为 unsigned int、unsigned long、unsigned long long 等等,std::size_t
实际上是一个 typedef。如果要使用std::size_t
,最好包含标头<cstddef>
。使用sizeof
不需要标头(即使它返回类型为std::size_t
的值)。示例:
1 |
|
7. 科学计数法
由于在 C++ 中输入或显示指数可能很困难,因此我们使用字母e
(有时也用E
)来表示方程的“乘以 10 次方”部分。例如, 1.2 x 10⁴
将写为1.2e4
, 5.9722 x 10²⁴
将写为5.9722e24
。
8. 浮点数
C++ 具有三种基本浮点数据类型:单精度float
、双精度double
和扩展精度long double
。与整数一样,C++ 没有定义这些类型的实际大小。
类别 | C++ 类型 | 典型大小 |
---|---|---|
floating point | float | 4 bytes |
double | 8 bytes | |
long double | 8, 12, or 16 bytes |
在现代体系结构中,浮点类型通常使用 IEEE 754 标准中定义的浮点格式之一来实现(请参阅https://en.wikipedia.org/wiki/IEEE_754 )。因此, float
几乎总是 4 个字节,而double
几乎总是 8 个字节。避免使用long double
使用浮点变量时,始终至少包含一位小数(即使小数为 0)。这有助于编译器理解该数字是浮点数而不是整数。
1 | int a { 5 }; // 5 means integer |
默认情况下,浮点文本默认为 double 类型。
f
后缀用于表示 float 类型的文字。
浮点类型的精度定义了它可以表示多少个有效数字而不丢失信息。当输出浮点数时, std::cout
默认精度为 6——也就是说,它假设所有浮点变量仅对 6 位有效(浮点数的最小精度),因此它将截断之后的任何内容。
在某些情况下,std::cout
将切换为以科学记数法输出数字。根据编译器的不同,指数通常会被填充到最小位数。不用担心,9.87654e+006 与 9.87654e6 相同,只是填充了一些 0。显示的最小指数位数是特定于编译器的(Visual Studio 使用 3,其他一些根据 C99 标准使用 2)。我们可以使用名为std::setprecision()
的输出操纵符来覆盖 std::cout
显示的默认精度。输出操纵器改变数据的输出方式,并在iomanip
标头中定义。示例:
1 |
|
输出操纵符(以及输入操纵符)是“粘性的”——这意味着一旦你设置了它们,它们就会保持设置状态。唯一的例外是
std::setw
,某些 IO 操作会重置std::setw
,因此每次需要时都应使用std::setw
。
十进制的0.1在二进制中并不是精确的,而是由无线序列表示:0.00011001100110011… 因此,当我们将 0.1 分配给浮点数时,我们会遇到精度问题。示例:
1 |
|
输出:
1 | 0.1 |
1 |
|
输出:
1 | 1 |
当数字无法精确存储时,就会出现舍入错误。即使是简单的数字(例如 0.1)也可能发生这种情况。因此,舍入误差可能而且确实一直会发生。舍入误差并不是例外——它们是常态。永远不要假设你的浮点数是精确的。谨慎使用浮点数表示金融或货币数据。
IEEE 754 兼容格式还支持一些特殊值:
-
Inf ,代表无穷大。 Inf 有符号,可以是正数 (+Inf) 或负数 (-Inf)。
-
NaN ,代表“不是数字”。有几种不同类型的 NaN(我们不会在这里讨论)。
-
有符号零,意味着“正零”(+0.0) 和“负零”(-0.0) 有单独的表示形式。
下面是一个显示所有三个的程序:
1 |
|
编译输出:
1 | inf |
9. 布尔值
示例:
1 | bool b1 { true }; |
当我们打印布尔值时, std::cout
为false
打印0
,为true
打印1
如果希望std::cout
打印true
或false
而不是0
或1
,可以输出std::boolalpha
。这不会输出任何内容,但会操纵std::cout
输出 bool 值的方式。
1 |
|
输出:
1 | 1 |
整数0
都会转换为false
,任何其他整数都会转换为true
默认情况下, std::cin
只接受布尔变量的数字输入。任何非数字值都将被解释为false
并导致std::cin
进入故障模式。要允许std::cin
接受单词false
和true
作为输入,你必须首先输入到std::boolalpha
:
1 |
|
当启用std::boolalpha
输入时,将不再接受数值(它们计算为false
并导致 std::cin
进入故障模式)
输入输出布尔值是独立的控件,可以单独打开或关闭。
10. if语句简介
if语句最简单的程序:
1 | if (condition) |
条件(condition)(也称为条件表达式)是计算结果为布尔值的表达式。示例程序:
1 |
|
11. 字符
char数据类型被设计为保存单个character
。字符可以是单个字母、数字、符号或空格。char 数据类型是整型,这意味着基础值存储为整数。char
变量存储的整数被解释为ASCII character
。
ASII字符表如图:
Code | Symbol | Code | Symbol | Code | Symbol | Code | Symbol |
---|---|---|---|---|---|---|---|
0 | NUL (null) | 32 | (space) | 64 | @ | 96 | ` |
1 | SOH (start of header) | 33 | ! | 65 | A | 97 | a |
2 | STX (start of text) | 34 | ” | 66 | B | 98 | b |
3 | ETX (end of text) | 35 | # | 67 | C | 99 | c |
4 | EOT (end of transmission) | 36 | $ | 68 | D | 100 | d |
5 | ENQ (enquiry) | 37 | % | 69 | E | 101 | e |
6 | ACK (acknowledge) | 38 | & | 70 | F | 102 | f |
7 | BEL (bell) | 39 | ’ | 71 | G | 103 | g |
8 | BS (backspace) | 40 | ( | 72 | H | 104 | h |
9 | HT (horizontal tab) | 41 | ) | 73 | I | 105 | i |
10 | LF (line feed/new line) | 42 | * | 74 | J | 106 | j |
11 | VT (vertical tab) | 43 | + | 75 | K | 107 | k |
12 | FF (form feed / new page) | 44 | , | 76 | L | 108 | l |
13 | CR (carriage return) | 45 | - | 77 | M | 109 | m |
14 | SO (shift out) | 46 | . | 78 | N | 110 | n |
15 | SI (shift in) | 47 | / | 79 | O | 111 | o |
16 | DLE (data link escape) | 48 | 0 | 80 | P | 112 | p |
17 | DC1 (data control 1) | 49 | 1 | 81 | Q | 113 | q |
18 | DC2 (data control 2) | 50 | 2 | 82 | R | 114 | r |
19 | DC3 (data control 3) | 51 | 3 | 83 | S | 115 | s |
20 | DC4 (data control 4) | 52 | 4 | 84 | T | 116 | t |
21 | NAK (negative acknowledge) | 53 | 5 | 85 | U | 117 | u |
22 | SYN (synchronous idle) | 54 | 6 | 86 | V | 118 | v |
23 | ETB (end of transmission block) | 55 | 7 | 87 | W | 119 | w |
24 | CAN (cancel) | 56 | 8 | 88 | X | 120 | x |
25 | EM (end of medium) | 57 | 9 | 89 | Y | 121 | y |
26 | SUB (substitute) | 58 | : | 90 | Z | 122 | z |
27 | ESC (escape) | 59 | ; | 91 | [ | 123 | { |
28 | FS (file separator) | 60 | < | 92 | |124 | | | |
29 | GS (group separator) | 61 | = | 93 | ] | 125 | } |
30 | RS (record separator) | 62 | > | 94 | ^ | 126 | ~ |
31 | US (unit separator) | 63 | ? | 95 | _ | 127 | DEL (delete) |
代码 32-126 称为可打印字符,它们代表大多数计算机用来显示基本英文文本的字母、数字字符和标点符号。
std::cin
将允许您输入多个字符。但是,变量只能保存 1 个字符。因此,只有第一个输入字符被提取到变量中。其余的用户输入保留在std::cin
使用的输入缓冲区中,并且可以通过后续调用std::cin
来提取。
如果想要提取空白字符,可以使用std::cin.get()
示例:
1 |
|
输出:
1 | Input a keyboard character: a b |
Char 由 C++ 定义为大小始终为 1 字节。默认情况下,char 可以是 signed 或 unsigned (尽管它通常是 signed)。如果使用 chars 来保存 ASCII 字符,则无需指定符号(因为有符号和无符号字符都可以保存 0 到 127 之间的值)。
C++ 中有一些字符序列具有特殊含义。这些字符称为转义序列。转义序列以 '' (反斜杠) 字符开头,然后是后面的字母或数字。转移序列表:
名称 | 符号 | 含义 |
---|---|---|
警报 | \a | 发出警报,例如蜂鸣声 |
退格 | \b | 将光标向后移动一个空格 |
换页 | \f | 将光标移动到下一页 |
换行 | \n | 将光标移动到下一行 |
回车 | \r | 将光标移动到行首 |
水平制表符 | \t | 打印一个水平制表符 |
垂直制表符 | \v | 打印一个垂直制表符 |
单引号 | ' | 打印一个单引号 |
双引号 | " | 打印一个双引号 |
反斜杠 | \ | 打印一个反斜杠 |
问号 | ? | 打印一个问号。不再相关。你可以直接使用问号而不需要转义。 |
八进制数 | (number) | 转换为由八进制表示的字符 |
十六进制数 | \x(number) | 转换为由十六进制数表示的字符 |
放在单引号和双引号中的区别:
-
单引号之间的文本被视为
char
文字,它表示单个字符。例如,'a'
表示字符a
,'+'
表示加号字符,'5'
表示字符5
(不是数字 5),'\n'
表示换行符。 -
双引号之间的文本(例如“Hello, world!”)被视为 C 样式字符串文字,它可以包含多个字符。
出于向后兼容性的原因,许多 C++ 编译器支持多字符文本,即包含多个字符的 char 文本(例如
'56'
)。如果支持,则这些具有 implementation-defined 值(这意味着它因编译器而异)。由于它们不是 C++ 标准的一部分,并且其值未严格定义,因此应避免使用多字符文本。
ASCII 之外最著名的映射是 Unicode 标准,该标准将超过 144,000 个整数映射为许多不同语言的字符。由于 Unicode 包含如此多的代码点,因此单个 Unicode 代码点需要 32 位来表示一个字符(称为 UTF-32)。但是,Unicode 字符也可以使用多个 16 位或 8 位字符(分别称为 UTF-16 和 UTF-8)进行编码。
C++11 中添加了char16_t
和char32_t
以提供对 16 位和 32 位 Unicode 字符的显式支持。这些 char 类型分别与std::uint_least16_t
和std::uint_least32_t
具有相同的大小(但是不同的类型)。 C++20 中添加了char8_t
以提供对 8 位 Unicode (UTF-8) 的支持。它是一种不同的类型,使用与unsigned char
相同的表示形式。
12. 类型转换和static_cast
C++ 允许我们将一种基本类型的值转换为另一种基本类型。将值从一种类型转换为另一种类型的过程称为类型转换。当编译器在没有我们明确要求的情况下代表我们进行类型转换时,我们称之为隐式类型转换。某些类型转换始终是安全的(例如int
到double
),而其他类型转换可能会导致值在转换过程中发生更改(例如double
到int
)。不安全的隐式转换通常会生成编译器警告,或者(在大括号初始化的情况下)生成错误。
C++ 支持第二种类型转换方法,称为显式类型转换。显式类型转换允许我们(程序员)显式地告诉编译器将值从一种类型转换为另一种类型,并且我们对该转换的结果负全部责任。如果这样的转换导致价值损失,编译器不会警告我们。static cast
的语法:
1 | static_cast<new_type>(expression) |
1 | static_cast<int>(5.5) |
大多数编译器分别将std::int8_t
和std::uint8_t
(以及相应的快速和最小固定宽度类型)定义为与signed char
和unsigned char
类型相同。如果要确保std::int8_t
或std::uint8_t
对象被视为整数,可以使用static_cast
将值转换为整数:
1 |
|
在std::int8_t
被视为 char 的情况下,来自控制台的输入也会导致问题:
1 |
|
输出:
1 | Enter a number between 0 and 127: 35 |
输入解释为字符序列,而不是整数。