分享

第一部分 基础语言之四表达式

 君王之王 2016-03-03
要理解多个操作符组成的表达式,必先理解操作符的优先级结合性操作数的求值顺序

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
例如:
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.shtml


dynamic_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 语言的兼容性。











    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多