类型 | 含义 | 最小存储空间 |
---|---|---|
bool |
boolean | NA |
char |
character | 8 bits |
wchar_t |
wide character | 16 bits |
short |
short integer | 16 bits |
int |
integer | 16 bits |
long |
long integer | 32 bits |
float |
single-precision floating-point | 6 significant digits |
double |
double-precision floating-point | 10 significant digits |
long double |
externded-precision floating-point | 10 significant digits |
表示 整数 、 字符 和 布尔值 的算术类型合称为整型。
char
和 wchar_t
两种。char
类型保证了有足够的空间,能够存储机器基本字符集中任何字符相应的数值,因此, char 类型通常是单个机器字节(byte) 。wchar_t 类型用于扩展字符集,比如汉字和日语,这些字符集中的一些字符不能用单个 char 表示。short
、 int
和 long
类型都表示整型值,存储空间的大小不同。一般, short 类型为半个机器字长,int 类型为一个机器字长,而 long 类型为一个或两个机器字长 (在 32 位机器中 int 类型和 long 类型通常字长是相同的)。bool
类型表示真值 true
和 false
。可以将算术类型的任何值赋给 bool 对象。0 值算术类型代表 false, 任何非 0 的值 都代表 true。类型 float、 double 和 long double 分别表示单精度浮点数、双精度浮点数和扩展精度浮点数。一般 float 类型用一个字(32 位)来表示,double 类型用两个字(64 位)来表示,long double 类型用三个或四个字(96 或 128 位)来表示。类型的取值范围决定了浮点数所含的有效数字位数。
对于实际的程序来说, float 类型精度通常是不够的——float 型只能保证 6 位有效数字 ,而 double 型至少可以保证 10 位有效数字,能满足大多数计算的需要。
编译器在必要时将类型转换规则应用到内置类型和类类型的对象上。在下列情况下,将发生隐式类型转换:
1 | int ival; |
1 | int ival; |
条件操作符(?:)中的第一个操作数以及逻辑非(!)、逻辑与(&&)和逻辑或(||)的操作数都是条件表达式。出现在 if、while、for 和 do while 语句中的同样也是条件表达式。
1 | int ival = 3.14; // 3.14 converted to int |
另外,在函数调用中也可能发生隐式类型转换。
算术转换保证在执行操作之前,将二元操作符(如算术或逻辑操作符)的两个操作数转换为同一类型,并使表达式的值也具有相同的类型。
算术转换规则定义了一个类型转换层次,该层次规定了操作数应按什么次序转换为表达式中最宽的类型。在包含多种类型的表达式中,转换规则要确保计算值的精度。例如,如果一个操作数的类型是 long double,则无论另一个操作数是什么类型,都将被转换为 long double。
最简单的转换为 整型提升 :对于所有比 int 小的整型,包括 char、signed char、unsigned char、short 和 unsigned short,如果该类型的所有可能的值都能包容在 int 内,它们就会被提升为 int 型,否则,它们将被提升为 unsigned int。如果将 bool 值提升为 int ,则 false 转换为 0,而 true 则转换为 1。
下面大部分例题中,要么是将操作数转换为表达式中的最大类型,要么是在赋值表达式中将右操作数转换为左操作数的类型。
1 | bool flag; char cval; |
显式转换也称为 强制类型转换(cast) ,包括以下列名字命名的强制类型转换操作符: static_cast
、dynamic_cast
、 const_cast
和 reinterpret_cast
。
命名的强制类型转换符号的一般形式如下:
1 | cast-name<type>(expression); |
其中 cast-name 为 static_cast
、dynamic_cast
、const_cast 和 reinterpret_cast
之一,type 为转换的目标类型,而 expression 则是被强制转换的值。强制转换的类型指定了在 expression 上执行某种特定类型的转换。
编译器 隐式执行的任何类型转换 都可以由 static_cast` 显式完成:
1 | double d = 97.0; |
当需要将一个较大的算术类型赋值给较小的类型时,使用强制转换非常有用。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不关心潜在的精度损失。对于从一个较大的算术类型到一个较小类型的赋值,编译器通常会产生警告。当我们显式地提供强制类型转换时,警告信息就会被关闭。
如果编译器不提供自动转换,使用 static_cast
来执行类型转换也是很有用的。例如,下面的程序使用 static_cast
找回存放在 void *
指针中的值:
1 | void* p = &d; // ok: address of any data object can be stored in a void* |
可通过 static_cast
将存放在 void *
中的指针值强制转换为原来的指针类型,此时我们应确保保持指针值。也就是说,强制转换的结果应与原来的地址值相等。
reinterpret_cast
通常为操作数的位模式提供 较低层次的重新解释 。
reinterpret_cast
本质上依赖于机器。为了安全地使用 reinterpret_cast
,要求程序员完全理解所涉及的数据类型,以及编译器实现强制类型转换的细节。
例如,对于下面的强制转换:
1 | int *ip; |
程序员必须永远记得 pc 所指向的真实对象其实是 int 型,而并非字符数组。任何假设 pc 是普通字符指针的应用,都有可能带来有趣的运行时错误。例如,下面语句用 pc 来初始化一个 string 对象:
1 | string str(pc); |
它可能会引起运行时的怪异行为。
const_cast
,顾名思义,将 转换掉 表达式的 const 性质。例如,假设有函数 string_copy
,只有唯一的参数,为 char *
类型,我们对该函数只读不写。在访问该函数时,最好的选择是修改它让它接受 const char *
类型的参数。如果不行,可通过 const_cast
用一个 const 值调用 string_copy
函数:
1 | const char *pc_str; |
只有使用 const_cast
才能将 const 性质转换掉。在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。类似地,除了添加或删除 const 特性,用 const_cast
符来执行其他任何类型转换,都会引起编译错误。
dynamic_cast
支持运行时识别指针或引用所指向的对象。
旧式强制转换符号有下列两种形式:
1 | type (expr); // Function-style cast notation |
旧式强制转换依赖于所涉及的数据类型,具有与 const_cast
、static_cast
和 reinterpret_cast
一样的行为。在合法使用 static_cast
或 const_cast
的地方,旧式强制转换提供了与各自对应的命名强制转换一样的功能。如果这两种强制转换均不合法,则旧式强制转换执行 reinterpret_cast
功能。例如,我们可用旧式符号重写上一节的强制转换:
1 | int ival; double dval; |
变量名,即变量的标识符,可以由字母、数字和下划线组成。变量名必须以字母或下划线开头,并且区分大小写字母:C++ 中的标识符都是大小写敏感的。
C++ 保留了一组词用作该语言的关键字。关键字不能用作程序的标识符。下表列出了 C++ 所有的关键字。
asm | do | if | return | try |
auto | double | inline | short | typedef |
bool | dynamic_cast | int | signed | typeid |
break | else | long | sizeof | typename |
case | enum | mutable | static | union |
catch | explicit | namespace | static_cast | unsigned |
char | export | new | struct | using |
class | extern | operator | switch | virtual |
const | false | private | template | void |
const_cast | float | protected | this | volatile |
continue | for | public | throw | wchar_t |
default | friend | register | true | while |
delete | goto | reinterpret_cast |
C++ 还保留了一些词用作各种操作符的替代名。这些替代名用于支持某些不支持标准 C++操作符号集的字符集。它们也不能用作标识符。下表列出了这些替代名。
and | bitand | compl | no | or_eq | xor_eq |
and_eq | bitor | not | or | xor |
除了关键字,C++ 标准还保留了一组标识符用于标准库。 标识符不能包含两个连续的下划线,也不能以下划线开头后面紧跟一个大写字母。有些标识符(在函数外定义的标识符)不能以下划线开头 。
像 42 这样的值,在程序中被当作字面值常量。称之为字面值是因为只能用它的值称呼它,称之为常量是因为它的值不能修改。每个字面值都有相应的类型,例如:0 是 int 型,3.14159 是 double 型。只有内置类型存在字面值,没有类类型的字面值。因此,也没有任何标准库类型的字面值。
定义字面值整数常量可以使用以下三种进制中的任一种:十进制、八进制和十六进制。当然这些进制不会改变其二进制位的表示形式。例如,我们能将值 20 定义成下列三种形式中的任意一种:
1 | 20 // decimal |
以 0(零)开头的字面值整数常量表示八进制,以 0x 或 0X 开头的表示十六进制。
字面值整数常量的类型默认为 int 或 long 类型[2]。其精度类型决定于字面值——其值适合 int 就是 int 类型,比 int 大的值就是 long 类型。通过增加后缀,能够强制将字面值整数常量转换为 long、unsigned 或 unsigned long 类型。通过在数值后面加 L 或者 l(字母“l”大写或小写)指定常量为 long 类型。
定义长整型时,应该使用大写字母 L。小写字母 l 很容易和数值 1 混淆。
类似地,可通过在数值后面加 U 或 u 定义 unsigned 类型。同时加 L 和 U 就能够得到 unsigned long 类型的字面值常量。但其后缀不能有空格:
1 | 128u /* unsigned */ 1024UL /* unsigned long */ |
extern
,就可以在整个程序中访问 const 对象:1 | // file_1.cc |
本程序中,file_1.cc 通过函数 fcn 的返回值来定义和初始化 bufSize。而 bufSize 定义为 extern,也就意味着 bufSize 可以在其他的文件中使用。file_2.cc 中 extern 的声明同样是 extern;这种情况下,extern 标志着 bufSize 是一个声明,所以没有初始化式。
1 | extern int i; // declares but does not define i |
extern声明不是定义,也不分配存储空间 。事实上,它 只是说明变量定义在程序的其他地方 。程序中变量可以声明多次,但只能定义一次。
只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。如果声明有初始化式,那么它可被当作是定义,即使声明标记为extern:
1 | extern double pi = 3.1416; // definition |
虽然使用了extern,但是这条语句还是定义了pi,分配并初始化了存储空间。 只有当extern声明位于函数外部时,才可以含有初始化式 。
因为已初始化的extern声明被当作是定义,所以该变量任何随后的定义都是错误的:
1 | extern double pi = 3.1416; // definition |
同样,随后的含初始化式的extern声明也是错误的:
1 | extern double pi = 3.1416; // definition |