基本内置类型

类型 含义 最小存储空间
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

整型

表示 整数字符布尔值 的算术类型合称为整型。

  • 字符类型:有 charwchar_t 两种。char 类型保证了有足够的空间,能够存储机器基本字符集中任何字符相应的数值,因此, char 类型通常是单个机器字节(byte) 。wchar_t 类型用于扩展字符集,比如汉字和日语,这些字符集中的一些字符不能用单个 char 表示。
  • 整型值: shortintlong 类型都表示整型值,存储空间的大小不同。一般, short 类型为半个机器字长,int 类型为一个机器字长,而 long 类型为一个或两个机器字长 (在 32 位机器中 int 类型和 long 类型通常字长是相同的)。
  • 布尔值: bool 类型表示真值 truefalse。可以将算术类型的任何值赋给 bool 对象。0 值算术类型代表 false, 任何非 0 的值 都代表 true。

处理溢出

  • unsigned 类型:unsigned类型的数总是正值或0,负数总是超出其取值范围[1]。并遵守 算数模 2n 定律 ,其中n是该类型占用的位数。例如,如果试图将 336 存储到 8 位的 unsigned char 中,则实际赋值为 80,因为 80 是 336 对 256 求模后的值。
  • signed 类型:由编译器决定实际赋的值。在实际操作中,很多的编译器处理 signed 类型的方式和 unsigned 类型类似。也就是说,赋值时是取该值对该类型取值数目求模后的值。然而我们不能保证编译器都会这样处理 signed 类型。

浮点型

类型 float、 double 和 long double 分别表示单精度浮点数、双精度浮点数和扩展精度浮点数。一般 float 类型用一个字(32 位)来表示,double 类型用两个字(64 位)来表示,long double 类型用三个或四个字(96 或 128 位)来表示。类型的取值范围决定了浮点数所含的有效数字位数。

对于实际的程序来说, float 类型精度通常是不够的——float 型只能保证 6 位有效数字 ,而 double 型至少可以保证 10 位有效数字,能满足大多数计算的需要。

类型转换

隐式类型转换

编译器在必要时将类型转换规则应用到内置类型和类类型的对象上。在下列情况下,将发生隐式类型转换:

  • 在混合类型的表达式中,其操作数被转换为相同的类型:
1
2
3
int ival; 
double dval;
ival >= dval // ival converted to double
  • 用作条件的表达式被转换为 bool 类型:
1
2
3
int ival; 
if (ival) // ival converted to bool
while (cin) // cin converted to bool

条件操作符(?:)中的第一个操作数以及逻辑非(!)、逻辑与(&&)和逻辑或(||)的操作数都是条件表达式。出现在 if、while、for 和 do while 语句中的同样也是条件表达式。

  • 用一表达式初始化某个变量,或将一表达式赋值给某个变量,则该表达式被转换为该变量的类型:
1
2
3
int ival = 3.14; // 3.14 converted to int 
int *ip;
ip = 0; // the int 0 converted to a null pointer of type 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool      flag;         char           cval; 
short sval; unsigned short usval;
int ival; unsigned int uival;
long lval; unsigned long ulval;
float fval; double dval;
3.14159L + 'a'; // promote 'a' to int, then convert to long double
dval + ival; // ival converted to double
dval + fval; // fval converted to double
ival = dval; // dval converted (by truncation) to int
flag = dval; // if dval is 0, then flag is false, otherwise true
cval + fval; // cval promoted to int, that int converted to float
sval + cval; // sval and cval promoted to int
cval + lval; // cval converted to long
ival + ulval; // ival converted to unsigned long
usval + ival; // promotion depends on size of unsigned short and int
uival + lval; // conversion depends on size of unsigned int and long

显式类型转换

显式转换也称为 强制类型转换(cast) ,包括以下列名字命名的强制类型转换操作符: static_castdynamic_castconst_castreinterpret_cast

  1. 不要使用 C 风格类型转换。 而应该使用 C++ 风格。
  2. 用 [static\_cast](#static\_cast) 替代 C 风格的值转换, 或某个类指针需要明确的向上转换为父类指针时。
  3. 用 [const\_cast](#const\_cast) 去掉 const 限定符。
  4. 用 [reinterpret\_cast](#reinterpret\_cast) 指针类型和整型或其它指针之间进行不安全的相互转换。仅在你对所做一切了然于心时使用。
  5. [dynamic\_cast](#dynamic\_cast) 测试代码以外不要使用。 除非是单元测试, 如果你需要在运行时确定类型信息, 说明有设计缺陷。

何时需要强制类型转换

  1. 因为要覆盖通常的标准转换,所以需显式使用强制类型转换。
  2. 可能存在多种转换时,需要选择一种特定的类型转换。

命名的强制类型转换

命名的强制类型转换符号的一般形式如下:

1
cast-name<type>(expression); 

其中 cast-name 为 static_castdynamic_cast、const_cast 和 reinterpret_cast 之一,type 为转换的目标类型,而 expression 则是被强制转换的值。强制转换的类型指定了在 expression 上执行某种特定类型的转换。

static_cast

编译器 隐式执行的任何类型转换 都可以由 static_cast` 显式完成

1
2
3
double d = 97.0; 
// cast specified to indicate that the conversion is intentional
char ch = static_cast<char>(d);

当需要将一个较大的算术类型赋值给较小的类型时,使用强制转换非常有用。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不关心潜在的精度损失。对于从一个较大的算术类型到一个较小类型的赋值,编译器通常会产生警告。当我们显式地提供强制类型转换时,警告信息就会被关闭。

如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的。例如,下面的程序使用 static_cast 找回存放在 void * 指针中的值:

1
2
3
void* p = &d; // ok: address of any data object can be stored in a void* 
// ok: converts void* back to the original pointer type
double *dp = static_cast<double*>(p);

可通过 static_cast 将存放在 void * 中的指针值强制转换为原来的指针类型,此时我们应确保保持指针值。也就是说,强制转换的结果应与原来的地址值相等。

reinterpret_cast

reinterpret_cast 通常为操作数的位模式提供 较低层次的重新解释

reinterpret_cast 本质上依赖于机器。为了安全地使用 reinterpret_cast,要求程序员完全理解所涉及的数据类型,以及编译器实现强制类型转换的细节。

例如,对于下面的强制转换:

1
2
int *ip; 
char *pc = reinterpret_cast<char*>(ip);

程序员必须永远记得 pc 所指向的真实对象其实是 int 型,而并非字符数组。任何假设 pc 是普通字符指针的应用,都有可能带来有趣的运行时错误。例如,下面语句用 pc 来初始化一个 string 对象:

1
string str(pc); 

它可能会引起运行时的怪异行为。

const_cast

const_cast ,顾名思义,将 转换掉 表达式的 const 性质。例如,假设有函数 string_copy,只有唯一的参数,为 char * 类型,我们对该函数只读不写。在访问该函数时,最好的选择是修改它让它接受 const char * 类型的参数。如果不行,可通过 const_cast 用一个 const 值调用 string_copy 函数:

1
2
const char *pc_str;
char *pc = string_copy(const_cast<char*>(pc_str));

只有使用 const_cast 才能将 const 性质转换掉。在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。类似地,除了添加或删除 const 特性,用 const_cast 符来执行其他任何类型转换,都会引起编译错误。

dynamic_cast

dynamic_cast 支持运行时识别指针或引用所指向的对象。

旧式强制类型转换

旧式强制转换符号有下列两种形式:

1
2
type (expr); // Function-style cast notation 
(type) expr; // C-language-style cast notation

旧式强制转换依赖于所涉及的数据类型,具有与 const_caststatic_castreinterpret_cast 一样的行为。在合法使用 static_castconst_cast 的地方,旧式强制转换提供了与各自对应的命名强制转换一样的功能。如果这两种强制转换均不合法,则旧式强制转换执行 reinterpret_cast 功能。例如,我们可用旧式符号重写上一节的强制转换:

1
2
3
4
5
6
int ival; double dval; 
ival += int (dval); // static_cast: converts double to int
const char* pc_str;
string_copy((char*)pc_str); // const_cast: casts away const
int *ip;
char *pc = (char*)ip; // reinterpret_cast: treats int* as char*
支持旧式强制转换符号是为了对“在标准 C++ 之前编写的程序”保持向后兼容性,并保持与 C 语言的兼容性。虽然标准 C++ 仍然支持旧式强制转换符号,但是我们建议, 只有在 C 语言或标准 C++ 之前的编译器上编写代码时 ,才使用这种语法。

变量

变量名,即变量的标识符,可以由字母、数字和下划线组成。变量名必须以字母或下划线开头,并且区分大小写字母:C++ 中的标识符都是大小写敏感的。

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
2
3
20     // decimal 
024 // octal
0x14 // hexadecimal

以 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
2
128u     /* unsigned   */          1024UL    /* unsigned long   */ 
1L /* long */ 8Lu /* unsigned long */

const 常量

  1. 在 C++ 程序中尽量使用 const 常量而不使用宏常量,即 const 常量完全取代宏常量
  2. 除非特别说明, 在全局作用域声明的 const 变量是定义该对象的文件的局部变量 。此变量只存在于那个文件中,不能被其他文件访问。通过指定 const 变更为 extern ,就可以在整个程序中访问 const 对象:
1
2
3
4
5
6
7
8
// file_1.cc 
// defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_2.cc
extern const int bufSize; // uses bufSize from file_1
// uses bufSize defined in file_1
for (int index = 0; index != bufSize; ++index)
// ...

本程序中,file_1.cc 通过函数 fcn 的返回值来定义和初始化 bufSize。而 bufSize 定义为 extern,也就意味着 bufSize 可以在其他的文件中使用。file_2.cc 中 extern 的声明同样是 extern;这种情况下,extern 标志着 bufSize 是一个声明,所以没有初始化式。

声明和定义

  • 定义(definition):用于为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且只有一个定义。
  • 声明(declaration):用于向程序表明变量的类型和名字。定义也是声明:当定义变量时我们声明了它的类型和名字。 可以通过使用extern关键字声明变量而不定义它
  • 在 C++ 语言中,变量必须且仅能定义一次,而且在使用变量之前必须定义或声明变量。
1
2
extern int i;    //  declares but does not define i
int i; // declares and defines i

extern声明不是定义,也不分配存储空间 。事实上,它 只是说明变量定义在程序的其他地方 。程序中变量可以声明多次,但只能定义一次。

只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。如果声明有初始化式,那么它可被当作是定义,即使声明标记为extern:

1
extern double pi = 3.1416;    // definition

虽然使用了extern,但是这条语句还是定义了pi,分配并初始化了存储空间。 只有当extern声明位于函数外部时,才可以含有初始化式

因为已初始化的extern声明被当作是定义,所以该变量任何随后的定义都是错误的:

1
2
extern double pi = 3.1416;    // definition
double pi; // error: redefinition of pi

同样,随后的含初始化式的extern声明也是错误的:

1
2
3
extern double pi = 3.1416;    // definition
extern double pi; // ok: declaration not definition
extern double pi = 3.1416; // error: redefinition of pi

  1. C++ 中,把负值赋给 unsigned 对象是完全合法的,其结果是该负数对该类型的取值个数求模后的值。所以,如果把 -1 赋给 8 位的 unsigned char,那么结果是 255,因为 255 是 -1 对 256 求模后的值。 ↩︎

  2. 没有 short 类型的字面值常量。 ↩︎

Comments