目录
Section 2.1 Primitive Built-in Types |
Section 2.2 Literal Constants |
Section 2.3 Variables |
Section 2.4 const Qualifier |
Section 2.5 References |
Section 2.6 Typedef Names |
Section 2.7 Enumerations |
Section 2.8 Class Types |
Section 2.9 Writing Our Own Header Files |
C++ 语言定义了几种基本类型:字符型、整型、浮点型等。C++ 还提供了可用于自定义数据类型的机制,标准库正是利用这些机制定义了许多更复杂的类型,比如可变长字符串 string、vector 等。此外,我们还能修改已有的类型以形成复合类型。本章介绍内置类型,并开始介绍 C++ 如何支持更复杂的类型。
Section 2.1 Primitive Built-in Types(基本内置类型)
本节主要注意两个问题:
(1) C++基本内置类型的最小存储空间,即表示该类型的二进制位。对于整数而言,不同类型也就决定了其最小取值范围,这样也就涉及到运算时结果可能溢出的问题。在《程序员面试宝典》第二版中即提及了这一问题。
例题:如何将a,b的值进行交换,并且不使用任何中间变量?
有的同学可能采用这种做法:
a=a+b; b=a-b; a=a-b;
这样做的缺陷就是如果a,b都是比较大的两个数,a=a+b就可能越界。而采用下面做法:a=a^b;b=a^b;a=a^b;就无须担心越界的问题。
(2)对象的类型决定对象的取值。这会引起一个疑问:当我们试着把一个超出其取值范围的值赋给一个指定类型的对象时,结果会怎样呢?答案取决于这种类型是 signed 还是 unsigned 的。
对于 unsigned 类型来说,编译器必须调整越界值使其满足要求。编译器会将该值对 unsigned 类型的可能取值数目求模,然后取所得值。比如 8 位的 unsigned char,其取值范围从 0 到 255(包括 255)。如果赋给超出这个范围的值,那么编译器将会取该值对 256 求模后的值。例如,如果试图将 336 存储到 8 位的 unsigned char 中,则实际赋值为 80,因为 80 是 336 对 256 求模后的值。
另外C++ 中,把负值赋给 unsigned 对象是完全合法的,其结果是该负数对该类型的取值个数求模后的值。所以,如果把 -1 赋给8位的 unsigned char,那么结果是 255,因为 255 是 -1 对 256 求模后的值。
Section 2.4 const Qualifier(const 限定符)
【魔术数字】
下列 for 循环语句有两个问题,两个都和使用 512 作为循环上界有关。
for (int index = 0; index != 512; ++index) {
// ...
}
第一个问题是程序的可读性。比较 index 与 512 有什么意思呢?循环在做什么呢?也就是说 512 作用何在?[本例中,512 被称为魔数(magic number),它的意义在上下文中没有体现出来。好像这个数是魔术般地从空中出现的。
第二个问题是程序的可维护性。假设这个程序非常庞大,512 出现了 100 次。进一步假设在这 100 次中,有 80 次是表示某一特殊缓冲区的大小,剩余 20 次用于其他目的。现在我们需要把缓冲区的大小增大到 1024。要实现这一改变,必须检查每个 512 出现的位置。我们必须确定(在每种情况下都准确地确定)哪些 512 表示缓冲区大小,而哪些不是。改错一个都会使程序崩溃,又得回过头来重新检查。
在林锐《高质量C++程序设计》中提及魔术数字的问题,解决方案一即是本节提到的const限定符 :const int bussize=512;
另外一种解决方案就是宏定义:#define bufsize 512
下面是const 与 #define的比较:
C++ 语言可以用const来定义常量,也可以用 #define来定义常量。但是前者比后者有更多的优点:
(1) const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。
(2) 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
Const 限定符的使用规则
【规则5-3-1】需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。
【规则5-3-2】如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。
Section 2.7 Enumerations(枚举)
我们经常需要为某些属性定义一组可选择的值。例如,文件打开的状态可能会有三种:输入、输出和追加。记录这些状态值的一种方法是使每种状态都与一个唯一的常数值相关联。我们可能会这样编写代码:
const int input = 0;
const int output = 1;
const int append = 2;
虽然这种方法也能奏效,但是它有个明显的缺点:没有指出这些值是相关联的。枚举提供了一种替代的方法,不但定义了整数常量集,而且还把它们聚集成组。
枚举的定义包括关键字 enum,其后是一个可选的枚举类型名,和一个用花括号括起来、用逗号分开的枚举成员列表。
// input is 0, output is 1, and append is 2
enum open_modes {input, output, append};
默认地,第一个枚举成员赋值为 0,后面的每个枚举成员赋的值比前面的大 1。在实际应用中,如在VC开发环境中,最好将第一个枚举成员赋值为较大数,避免与VC的资源值相同。
在本节还有一句话“每个 enum 都定义一种唯一的类型”耐人寻味,在实际应用中,我们希望函数的类型不仅仅是BOOL型这样简单了,更多的是希望函数的返回值不仅仅能说明函数正确执行与否,而且需要知晓执行错误的原因是什么,这是枚举enum即派上了用场。
示例:
// 错误代码的定义
enum ErrorStatus
{
Ok = 0,
FatalError = 1300, // 致命错误
UnknowError, // 未知错误
EmptyXdata, // 未包含XDATA
SetXDataError, // 设置XDATA失败
ReadFileError, // 读取文件失败
GetDwgEntityError // 获取实体失败
};
使用示例:ErrorStatus EmbedWatermarkIntoLayer(double* RationDouble,CMapXLayer presentlayer,int SelectedNum);
这样我们就可以根据函数返回值以及错误代码的定义弹出响应的错误提示了,而不是如下:BOOL EmbedWatermarkIntoLayer(double* RationDouble,CMapXLayer presentlayer,int SelectedNum);仅仅返回TRUE或者FALSE
Section 2.9 Writing Our Own Header Files(编写自己的头文件)
在这一节中需要注意一个问题:避免多重包含头文件
预处理器变量有两种状态:已定义或未定义。定义预处理器变量和检测其状态所用的预处理器指示不同。#define 指示接受一个名字并定义该名字为预处理器变量。#ifndef 指示检测指定的预处理器变量是否未定义。如果预处理器变量未定义,那么跟在其后的所有指示都被处理,直到出现 #endif。
可以使用这些设施来预防多次包含同一头文件:
#ifndef SALESITEM_H
#define SALESITEM_H
// Definition of Sales_itemclass and related functions goes here
#endif
条件指示
#ifndef SALESITEM_H
测试 SALESITEM_H 预处理器变量是否未定义。如果 SALESITEM_H 未定义,那么 #ifndef 测试成功,跟在 #ifndef 后面的所有行都被执行,直到发现 #endif。相反,如果 SALESITEM_H 已定义,那么 #ifndef 指示测试为假,该指示和 #endif 指示间的代码都被忽略。