2.1 类型
下表列出了 C++ 标准规定的数据类型尺寸的最小值,同时允许编译器赋予这些类型更大的尺寸
类型 | 含义 | 最小尺寸 |
---|---|---|
bool | 布尔类型 | 未定义 |
char | 字符 | 8位 |
wchar_t | 宽字符 | 16位 |
char16_t | Unicode 字符 | 16位 |
char32_t | Unicode 字符 | 32位 |
short | 短整型 | 16位 |
int | 整型 | 16位 |
long | 长整型 | 32位 |
long long | 长整型 | 64位 |
float | 单精度浮点数 | 6位有效数字 |
double | 双精度浮点数 | 10位有效数字 |
long double | 扩展精度浮点数 | 10位有效数字 |
一个char
的空间应确保可以存放机器基本字符集中任意字符对应的数字值。也就是说,一个char
的大小和一个机器字节一样。
wchar_t
类型用于确保可以存放机器最大扩展字符集中的任意一个字符。
C++ 语言规定,一个int
至少和一个short
一样大,一个long
至少和一个int
一样大,一个long long
至少和一个long
一样大。
可寻址的最小内存块成称为“字节(byte)”,存储的基本单元称为“字(word)”,它通常有几个字节组成。
在 C++ 语言中,一个字节至少能容纳机器基本字符串中的字符。大多数机器的字节由8比特构成,字则由32或64比特构成,也就是4字节或8字节。
C++ 标准指定了一个浮点数有效位数的最小值,然而大多数编译器都实现了更高的精度。通常,float
以1个字(32比特)来表示,double
以2个字(64位)来表示,long double
以3或4个字(96或128比特)来表示。一般来说,float
和double
分别有7和16个有效位;类型long double
则常常被用于有特殊浮点要求的硬件,它的具体实现不同,精度也各不相同。
类型int
、short
、long
和long long
都是带符号的,通过在这些类型名前添加unsigned
就可以得到无符号类型。
char
、signed char
和unsigned char
,char
是否等于signed char
由编译器决定。
C++ 标准并没有规定带符号类型应如何表示,但是约定了在表示范围内正值和负值的量应该平衡。因此,8比特的signed char
理论上可以表示-127至127区间内的值,大多数现代计算机将实际的表示范围定为-128至127。
2.2 如何选择类型
明知数值不可能为负数,选用无符号类型。
如果数值超过了int
的表示范围,选用long long
,因为long
一般和int
有一样的尺寸。
算术表达式中不要使用bool
或char
。
执行浮点数运算选用double
。因为float
通常精度不够而且计算代价相差无几。long double
提供的精度一般情况下是没有必要的而且计算代价大。
2.3 类型转换
非布尔 -> 布尔:0 -> false
,其他 -> ture
。
布尔 -> 非布尔:false
-> 0,ture
-> 1。
浮点 -> 整型:仅保留小数点前。
整型 -> 浮点:小数部分记为0,如果该整数所占的空间超过浮点类型的容量,精度可能损失。
当我们赋给无符号类型一个超过它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。
当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的。此时,程序可能继续工作、可能崩溃,也可能生成垃圾数据。
当一个算术表达式中既有无符号数又有int
值时,那个int
会转换成无符号数。把int
转换成无符号数的过程和把int
赋值给无符号变量一样。
当从一个无符号数中减去一个值时,我们必须确保结果不是负值,否则实际结果会是取模后的值。
2.4 字面值常量
整型和浮点型字面值。
字符和字符串字面值:
- ‘a’ 和“a”,字符串字面值最后补’\0’
- 如果两个字符串字面值位置紧邻且仅由空格、缩进和换行分隔,则它们是一个整体
布尔字面值:true
、false
。
指针字面值:nullptr
。
2.5 指定字面值的类型
字符和字符串字面值
前缀 | 类型 |
---|---|
u | char16_t |
U | char32_t |
L | wchar_t |
u8 | char |
整型字面值
后缀 | 最小匹配类型 |
---|---|
u 或 U | unsigned |
l 或 L | long |
ll 或 LL | long long |
浮点型字面值
后缀 | 类型 |
---|---|
f 或 F | float |
l 或 L | long double |
2.6 列表初始化
int units_sold{0}
列表初始化的重要特点:如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错。
2.7 默认初始化
内置类型变量
- 定义于任何函数之外,初始化为0
- 定义于函数体内的局部静态变量,初始化为0
- 定义于函数体内的非局部静态变量,不被初始化,试图拷贝或以其他方式访问此类值将引发错误
每个类各自决定其初始化对象的方式。
2.8 分离式编译
将程序分割为若干个文件,每个文件可被独立编译
2.9 声明
extern int i;
extern
语句如果包含初始值就不再是声明,而变成定义了:extern int i = 0;
在函数体内部,试图初始化一个由extern
关键字标记的变量将引发错误。
静态类型语言:在编译阶段执行类型检查
2.10 C++ 标识符
字母、数字、下划线,必须以字母或下划线开头,长度无限制,大小写敏感。
C++ 为标准库保留了一些名字
- 自定义标识符中不能连续出现两个下划线
- 不能以下划线紧接大写字母开头
- 定义在函数体外的函数不能以下划线开头
2.11 作用域操作符 ::
若左侧为空,则指代全局作用域
2.12 复合类型
2.12.1 引用
int &b = a;
b指向a。
引用必须初始化。
引用本身不是一个对象,不能定义引用的引用。
引用必须绑定到对象上,不能绑定到字面值或者某个表达式的计算结果上。
除了两种例外,所有引用的类型都要和它所绑定的对象严格匹配:
- 初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可
- 允许将基类的引用绑定到派生类上
2.12.2 指针
int *b = &a;
。
指针是一个对象。
在块作用域内定义的指针如果未初始化,将拥有一个不确定的值。
除了两种例外,所有指针的类型都要和它所指向的对象严格匹配:
- 允许一个指向常量的指针指向一个非常量对象
- 允许将基类的指针绑定到派生类上
试图拷贝或以其他方式访问无效指针的值将引起错误,编译器不负责检查此类错误。
任何非零的指针对应的条件值都是true。
合法指针可以比较大小:== 或 !=。
void *
是一个特殊的指针类型,可用于存放任意对象的地址,只支持有限的操作:
- 拿它和别的指针比较
- 作为函数的输入或输出
- 赋给另外一个
void *
指针
2.13 理解复合类型的声明
变量的定义包括一个基本数据类型和一组声明符。
一条定义语句可能定义出不同类型的变量:int i = 1024, *p = &i, &r =i;
。
面对一条比较复杂的指针或者引用的声明语句时,从右往左阅读有助于弄清楚它的真实含义,例如:int \*&r;
首先 r 是一个引用,其次 r 是一个指针的引用,再次 r 是一个指向整型指针的引用。
2.14 const 限定符
因为const
对象一旦创建就无法改变其值,所以必须初始化。
当用一个对象去初始化另外一个对象,它们是不是const
都无关紧要,常量的特性仅仅在执行改变其值的操作时才会发挥作用。
默认情况下,const
对象仅在文件内有效。
如果想在多个文件之间共享const
对象,必须在变量的定义之前添加extern
关键字。
const
的引用,常量引用的初始化:初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值甚至是一个表达式。实际上是通过绑定到一个临时量对象上实现的。
对const
的引用可能引用一个并非const
的对象,常量引用仅对引用可参与的操作做了限制,对于引用的对象本身是不是常量未做限制。
2.15 指针和 const
2.15.1 指向常量的指针
允许一个指向常量的指针指向一个非常量对象。
仅对指向常量的指针可参与的操作做了限制,对于指向的对象本身是不是常量未做限制。
2.15.2 const 指针
指针是对象,可以把指针本身定为常量。
常量指针必须初始化,一经初始化就不能再改变。
把*放在const
关键字之前说明指针是一个常量。
2.15.3 顶层和底层 const
顶层const
:指针本身是个常量。
底层const
:指针所值的对象是一个常量。
更一般的,顶层const
可以表示任意的对象是常量,这一点对任意数据类型都适用。底层const
则与指针和引用等复合类型的基本类型部分有关。
执行对象的拷贝操作时,顶层const
不受影响;拷入和拷出的对象必须具有相同的底层const
资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之不行。
2.15.4 constexpr 和常量表达式
常量表达式:值不会改变并且在编译过程就能得到计算结果的表达式。
constexpr
变量:由编译器来验证变量的值是否是一个常量表达式,声明为constexpr
的变量一定是一个常量,并且必须用常量表达式初始化。
字面值类型:算术类型、引用、指针、字面值常量类、枚举。
声明constexpr
用到的类型为字面值类型。
一个constexpr
指针的初始值必须是nullptr
或者0,或者是存储于某个固定地址中的对象:
- 函数体内定义的变量一般并非存放在固定地址中,
constexpr
指针不能指向这样的变量(局部静态对象除外) - 定义于所有函数体之外的对象地址固定不变,能用来初始化
constexpr
指针
在constexpr
声明中如果定义了一个指针,限定符constexpr
仅对指针有效,与指针所指的对象无关。
constexpr
把它所定义的对象置为顶层const
。
2.16 处理类型
2.16.1 类型别名
typedef double wages;
using SI = Sales_item;
2.16.2 auto 类型说明符
让编译器自己去分析表达式所属的类型。
auto
也能在一条语句中声明多个变量。因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的基本数据类型都必须一样。
当引用被用作初始化auto
变量时,编译器以引用所引用对象的类型作为auto
的类型。
auto
一般会忽略顶层const
,同时底层const
会保留下来。
如果希望推断出的auto
类型是一个顶层const
,需要明确指出。
auto
引用auto &b = a;
初始值 a 的顶层const
会保留。
2.16.3 decltype 类型指示符
选择并返回操作数的数据类型,编译器分析表达式得到它的类型,却不实际计算表达式的值,编译器并不实际调用f。
如果decltype
使用的表达式是一个变量,则decltype
返回该变量的类型(包括顶层const
和引用在内),需要指出的是,引用从来都作为其所指对象的同义词出现,只有在decltype
处是一个例外。
如果decltype
使用的表达式不是一个变量,则decltype
返回该表达式的结果对应的类型:
- 因为 r 是一个引用,
decltype(r)
的结果是引用类型,如果想让结果类型是 r 所指的类型,可以把 r 作为表达式的一部分,显然这个表达式的结果是一个具体的值而非一个引用 - 如果表达式的内容是解引用操作,则
decltype
将得到引用类型
如果decltype
使用的表达式是一个变量:
- 不加括号,得到该变量的类型
- 加括号,得到引用类型
2.17 自定义数据结构:结构体和类
记得定义末尾的分号。可以类内初始化。
2.18 头文件保护符
头文件保护符依赖于预处理变量。
预处理变量由两个状态:已定义和未定义。
#define
指令把一个名字设定为预处理变量。
#ifdef
当且仅当变量已定义时为真。
#ifndef
当且仅当变量未定义时为真。
一旦检查结果为真,则执行后续操作直到遇到#endif
指令为止