要理解多个操作符组成的表达式,必先理解操作符的优先级、结合性和操作数的求值顺序。 4.1 算数操作符 当只有一个操作数为负数时,求模操作结果值的符号可依据分子(被除数)或分母(数)的符号而定。如果求模的结果随分子的符号,则除出来的值向零一侧取整;如果求模与分母的符号匹配,则除出来的值向负无穷一侧取整。 21 % 6; // ok: result is 3 21 % 7; // ok: result is 0 -21 % -8; // ok: result is -5 21 % -5; // machine-dependent: result is 1 or -4 21 / 6; // ok: result is 3 21 / 7; // ok: result is 3 -21 / -8; // ok: result is 2 21 / -5; // machine-dependent: result -4 or -5 4.2 关系操作符和逻辑操作符 逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算其右操作数。只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。这种求值策略为“短路求值(short-circuit evaluation)”。 4.3 sizeof操作符 使用 sizeof 的结果部分地依赖所涉及的类型: · 对 char 类型或值为 char 类型的表达式做 sizeof 操作保证得 1。 · 对引用类型做 sizeof 操作将返回存放此引用类型对象所需的内在空间大小。 · 对指针做 sizeof 操作将返回存放指针所需的内在大小;注意,如果要获 取该指针所指向对象的大小,则必须对指针进行引用。 int x[10]; int *p = x; cout << sizeof(x)/sizeof(*x) << endl; // 40/4 = 10 cout << sizeof(p)/sizeof(*p) << endl; // 4/4 = 1 结果为:10 1 例如:char a = 'a'; C语言:sizeof(a) = 1; sizeof('a') = 4;//保存为'a'的ascii码,一个整型值 C++语言:sizeof(a) = 1; sizeof('a') = 1;//测试的效果等同于sizeof(a),即测试char类型的大小 4.4 求值顺序 如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就变得相当重要: // oops! language does not define order of evaluation if (ia[index++] < ia[index]) 此表达式的行为没有明确定义。问题在于:< 操作符的左右操作数都使用了index 变量,是,左操作数更改了该变量的值。假设 index 初值为 0,编译器可以用下面两种方式之一求该表达式的值: if (ia[0] < ia[0]) // execution if rhs is evaluated first if (ia[0] < ia[1]) // execution if lhs is evaluated first 4.5 new 和 delete 表达式 动态创建对象的默认初始化如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同(第 2.3.4 节)。对于类类型的对象,用该类的默认构造函数初始化;而 内置类型的对象则无初始化。 string *ps = new string; // initialized to empty string int *pi = new int; // pi points to an uninitialized int C++ 没有明确定义如何释放指向不是用 new 分配的内存地址的指针。 如果指针的值为 0,则在其上做 delete 操作是合法的,但这样做没有任何意义: int *ip = 0; delete ip; // ok: always ok to delete a pointer that is equal to 0 C++ 保证:删除 0 值的指针是安全的。 警告:动态内存的管理容易出错 下面三种常见的程序错误都与动态内存分配相关: 1. 删除( delete )指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区。删 除动态分配内存失败称为“内存泄漏(memory leak)”。内存泄漏很难发现,一般需等应用程 序运行了一段时间后,耗尽了所有内存空间时,内存泄漏才会显露出 来。 2. 读写已删除的对象。如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出 这类错误。 3. 对同一个内存空间使用两次 delete 表达式。当两个指针指向同一个动态创建的对象,删除时 就会发生错误。如果在其中一个指针上做 delete 运算,将该对象的内存空间返还给自由存储 区,然后接着 delete 第二个指针,此时则自由存储区可能会被破坏。操纵动态分配的内存 时,很容易发生上述错误,但这些错误却难以跟踪和修正。 4.6 类型转换 4.6.1 何时发生隐式类型转换 编译器在必要时将类型转换规则应用到内置类型和类类型的对象上。在下列情况下,将发生隐式类型转换: · 在混合类型的表达式中,其操作数被转换为相同的类型: int ival; double dval; ival >= dval // ival converted to double · 用作条件的表达式被转换为 bool 类型: int ival; if (ival) // ival converted to bool while (cin) // cin converted to bool · 用一表达式初始化某个变量,或将一表达式赋值给某个变量,则该表达式被转换为该变量的类 型: int ival = 3.14; // 3.14 converted to int int *ip; ip = 0; // the int 0 converted to a null pointer of type int * 另外,在函数调用中也可能发生隐式类型转换,我们将在第七章学习这方面的内容。 4.6.2 算术转换 最简单转换为整型提升:对于所有比 int 小的整型,包括 char、signed char、unsigned char、 short 和 unsigned short,如果该类型的所有可能的值都能包容在 int 内,它们就会被提升为 int 型,否则,它们将被提升为unsigned int。如果将 bool 值提升为 int ,则 false 转换为 0,而 true 则转换为 1。 有符号与无符号类型之间的转换 若表达式中使用了无符号( unsigned )数值,所定义的转换规则需保护操作数的精度。 unsigned 操作数的转换依赖于机器中整型的相对大小,因此,这类转换本质上依赖于机器。 包含 short 和 int 类型的表达式, short 类型的值转换为 int 。如果int 型足够表示所有 unsigned short 型的值,则将 unsigned short 转换为int,否则,将两个操作数均转换为 unsigned int 在 32 位的机器上,long 和 int 型通常用一个字长表示,因此当表达式包含 unsigned int 和 long 两种类型,其操作数都应转换为 unsigned long 型。 4.6.3 其他隐式转换 1.指针转换 在使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针: int ia[10]; // array of 10 ints int* ip = ia; // convert ia to pointer to first element 不将数组转换为指针的例外情况有:数组用作取地址(&)操作符的操作数或 sizeof 操作符的操作数时,或用数组对数组的引用进行初始化时,不会将数组转换为指针。我们将在第 7.2.4 节学习如何定义指向数组的引用(或指针)。 C++ 还提供了另外两种指针转换:指向任意数据类型的指针都可转换为void* 类型;整型数值常量 0 可转换为任意指针类型。 2.转换为bool 类型 算术值和指针值都可以转换为 bool 类型。如果指针或算术值为 0,则其 bool 值为 false ,而其他值则为 true: if (cp) /* ... */ // true if cp is not zero while (*cp) /* ... */ // dereference cp and convert resulting char to bool 这里,if 语句将 cp 的非零值转换为 true。 while 语句则对 cp 进行解引用,操作结果产生一个 char 型的值。空字符( null )具有 0 值,被转换为 false,而其他字符值则转换为 true。 4.6.3 显式转换 显式转换也称为强制类型转换(cast),包括以下列名字命名的强制类型转换操作符: static_cast、dynamic_cast、const_cast 和 reinterpret_cast。 虽然有时候确实需要强制类型转换,但是它们本质上是非常危险的。 http://www.360doc.com/content/15/1028/09/7510008_508889811.shtmldynamic_cast dynamic_cast 支持运行时识别指针或引用所指向的对象,主要用于上下行转换,转换的成功与否取决于要转换的指针或引用的实际指向,但保证程序不会出错,也就是成功失败都有相应的返回值,对 dynamic_cast的讨论将在第 18.2 节中进行。 const_cast const_cast ,只能用于指针或引用,将转换掉表达式的 const 性质。例如,假设有函数 string_copy,只有唯一的参数,为 char* 类型,我们对该函数只读不写。在访问该函数时,最好的选择是修改它让它接受 const char* 类型的参数。如果不行,可通过 const_cast 用一个 const 值调用 string_copy 函数:const char *pc_str; char *pc = string_copy(const_cast<char*>(pc_str)); 只有使用 const_cast 才能将 const 性质转换掉。在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。类似地,除了添加或删除const 特性,用 const_cast 符来执行其他任何类型转换,都会引起编译错误。 static_cast 编译器隐式执行的任何类型转换都可以由 static_cast 显式完成,但是这种转换不提供运行时识别指针或引用所指向的对象,一旦遇到不能转换将会发生错误,主要用于内置类型的转换等。 double d = 97.0; // cast specified to indicate that the conversion is intentional char ch = static_cast<char>(d); 当我们显式地提供强制类型转换时,警告信息就会被关闭。如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的。例如,下面的程序使用 static_cast 找回存放在 void* 指针中的值(第4.2.2 节): 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,要求程序员完全理解所涉及的数据类型,以及编译器实现强制类型转换的细节。 例如,对于下面的强制转换: int *ip; char *pc = reinterpret_cast<char*>(ip); 程序员必须永远记得 pc 所指向的真实对象其实是 int 型,而并非字符数 组。任何假设 pc 是普通字符指针的应用,都有可能带来有趣的运行时错误 4.6.4 旧式强制类型转换 在引入命名的强制类型转换操作符之前,显式强制转换用圆括号将类型括起 来实现: char *pc = (char*) ip; 效果与使用 reinterpret_cast 符号相同,但这种强制转换的可视性比较 差,难以跟踪错误的转换。 标准 C++ 为了加强类型转换的可视性,引入命名的强制转换操作符,为程序员在必须使用强制转换时提供了更好的工具。例如,非指针的 static_cast 和const_cast 要比 reinterpret_cast 更安全。结果使程序员(以及读者和操纵程序的工具)可清楚地辨别代码中每个显式的强制转换潜在的风险级别。虽然标准 C++ 仍然支持旧式强制转换符号,但是我们 建议,只有在 C 语言或标准 C++ 之前的编译器上编写代码时,才使用这种语法。 旧式强制转换符号有下列两种形式: type (expr); // Function-style cast notation (type) expr; // C-language-style cast notation 旧式强制转换依赖于所涉及的数据类型,具有与 const_cast、 static_cast和 reinterpret_cast 一样的行为。在合法使用 static_cast 或 const_cast的地方,旧式强制转换提供了与各自对应的命名强制转换一样的功能。如果这两种强制转换均不合法,则旧式强制转换执行 reinterpret_cast 功能。例如,我们可用旧式符号重写上一节的强制转换: 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 语言的兼容性。 |
|