首页 > 代码库 > C++primer第五章 表达式
C++primer第五章 表达式
表达式由一个或多个操作数通过操作符组合而成。最简单的表达式仅包含一个字面值常量或变量。较复杂的表达式则由操作符以及一个或多个操作数构成。
每个表达式都会产生一个结果。
5.1. 算术操作符
表 5.1 按优先级来对操作符进行分组——一元操作符优先级最高,其次是乘、除操作,接着是二元的加、减法操作。
算术操作符 +、-、* 和 / 具有直观的含义:加法、减法、乘法和除法。对两个整数做除法,结果仍为整数,如果它的商包含小数部分,则小数部分会被截除:
int ival1 = 21/6; // integral result obtained by truncating the remainderint ival2 = 21/7; // no remainder, result is an integral value
操作符 % 称为“求余(remainder)”或“求模(modulus)”操作符,用于计算左操作数除以右操作数的余数。该操作符的操作数只能为整型,包括bool、char、short 、int 和 long 类型,以及对应的 unsigned 类型:
int ival = 42;double dval = 3.14;ival % 12; // ok: returns 6ival % dval; // error: floating point operand
如果两个操作数为正,除法(/)和求模(%)操作的结果也是正数(或零);
如果两个操作数都是负数,除法操作的结果为正数(或零),而求模操作的结果则为负数(或零);
如果只有一个操作数为负数,这两种操作的结果取决于机器;求模结果的符号也取决于机器,而除法操作的值则是负数(或零):
5.2. 关系操作符和逻辑操作符
逻辑与、逻辑或操作符
逻辑操作符将其操作数视为条件表达式(第 1.4.1 节):首先对操作数求值;若结果为 0,则条件为假(false),否则为真(true)。仅当逻辑与(&&)操作符的两个操作数都为 true,其结果才得 true 。对于逻辑或(||)操作符,只要两个操作数之一为 true,它的值就为 true。给定以下形式:
expr1 && expr2 // logical ANDexpr1 || expr2 // logical OR
逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算其右操作数。只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。我们常常称这种求值策略为“短路求值(short-circuit evaluation)”。
对于逻辑与操作符,一个很有价值的用法是:如果某边界条件使 expr2 的计算变得危险,则应在该条件出现之前,先让 expr1 的计算结果为 false。
逻辑非操作符
逻辑非操作符(!)将其操作数视为条件表达式,产生与其操作数值相反的条件值。
不应该串接使用关系操作符
// oops! this condition does not determine if the 3 values are unequal
if (i < j < k) { /* ... */ }
这种写法只要 k 大于 1,上述表达式的值就为 true。这是因为第二个小于操作符的左操作数是第一个小于操作符的结果:true 或 false。也就是,该条件将 k 与整数 0 或 1 做比较。为了实现我们想要的条件检验,应重写上述表达式如下:
if (i < j && j < k) { /* ... */ }
相等测试与bool 字面值
if (val == true) { /* ... */ }
val 本身是 bool 类型,或者 val 具有可转换为 bool 类型的数据类型。如果 val 是 bool 类型,则该判断条件等效于:
if (val) { /* ... */ }
5.3. 位操作符
位操作符(表5-3)使用整型的操作数。位操作符将其整型操作数视为二进制位的集合,为每一位提供检验和设置的功能。
如果操作数为负数,则位操作符如何处理其操作数的符号位依赖于机器。
位求反操作符(~):将操作数的每一个二进制位取反:将 1 设置为 0、0 设置为 1,生成一个新值
unsigned char bits = 0227;bits = ~bits;
<< 和 >> 操作符提供移位操作,其右操作数标志要移动的位数。这两种操作符将其左操作数的各个位向左(<<)或向右(>>)移动若干个位(移动的位数由其右操作数指定),从而产生新的值,并丢弃移出去的位。
unsigned char bits = 1;bits << 1; // left shiftbits << 2; // left shiftbits >> 3; // right shift
位与操作(&)需要两个整型操作数,在每个位的位置,如果两个操作数对应的位都为 1,则操作结果中该位为 1,否则为 0。
unsigned char b1 = 0145;unsigned char b2 = 0257;unsigned char result = b1 & b2;
位异或(互斥或,exclusive or)操作符(^)也需要两个整型操作数。在每个位的位置,如果两个操作数对应的位只有一个(不是两个)为 1,则操作结果中该位为 1,否则为 0。
result = b1 ^ b2;
位或(包含或,inclusive or)操作符(|)需要两个整型操作数。在每个位的位置,如果两个操作数对应的位有一个或者两个都为 1,则操作结果中该位为 1,否则为 0。
result = b1 | b2;
5.3.1. bitset 对象或整型值的使用
bitset 类比整型值上的低级位操作更容易使用。
假设某老师带了一个班,班中有 30 个学生,每个星期在班上做一次测验,只有及格和不及格两种测验成绩,对每个学生用一个二进制位来记录一次测试及格或不及格,以方便我们跟踪每次测验的结果,这样就可以用一个bitset 对象或整数值来代表一次测验:
bitset<30> bitset_quiz1; // bitset solutionunsigned long int_quiz1 = 0; // simulated collection of bits
老师可以设置和检查每个位。例如,假设第27 位所表示的学生及格了,则可以使用下面的语句适当地设置对应的位:
bitset_quiz1.set(27); // indicate student number 27 passedint_quiz1 |= 1UL<<27; // indicate student number 27 passed
如果老师重新复核测验成绩,发现第 27 个学生实际上在该次测验中不及格,这时老师应把第 27 位设置为 0:
bitset_quiz1.reset(27); // student number 27 failedint_quiz1 &= ~(1UL<<27); // student number 27 failed
最后,可通过以下代码获知第 27 个学生是否及格:
bool status;status = bitset_quiz1[27]; // how did student number 27 do?
status = int_quiz1 & (1UL<<27); // how did student number 27 do?
5.3.2. 将移位操作符用于IO
输入输出标准库(IO library)分别重载了位操作符 >> 和 << 用于输入和输出。
IO 操作符为左结合
像其他二元操作符一样,移位操作符也是左结合的。这类操作符从左向右地结合,正好说明了程序员为什么可以把多个输入或输出操作连接为单个语句:
cout << "hi" << " there" << endl;
执行为:
( (cout << "hi") << " there" ) << endl;
移位操作符具有中等优先级:其优先级比算术操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。
cout << 42 + 10; // ok, + has higher precedence, so the sum isprintedcout << (10 < 42); // ok: parentheses force intended grouping; prints1cout << 10 < 42; // error: attempt to compare cout to 42!
5.4. 赋值操作符
赋值操作符的左操作数必须是非 const 的左值。下面的赋值语句是不合法的:
int i, j, ival;const int ci = i; // ok: initialization not assignment1024 = ival; // error: literals are rvaluesi + j = ival; // error: arithmetic expressions are rvaluesci = ival; // error: can‘t write to ci
数组名是不可修改的左值:因此数组不可用作赋值操作的目标。而下标和解引用操作符都返回左值,因此当将这两种操作用于非 const 数组时,其结果可作为赋值操作的左操作数:
int ia[10];ia[0] = 0; // ok: subscript is an lvalue*ia = 0; // ok: dereference also is an lvalue
然而,当左、右操作数的类型不同时,该操作实现的类型转换可能会修改被赋的值。此时,存放在左、右操作数里的值并不相同:
ival = 0; // result: type int value 0ival = 3.14159; // result: type int value
5.4.1. 赋值操作的右结合性
int ival, jval;ival = jval = 0; // ok: each assigned 0
与其他二元操作符不同,赋值操作具有右结合特性。
5.4.2. 赋值操作具有低优先级
int i = get_value(); // get_value returns an intwhile (i != 42) {// do something ...i = get_value(); }
改写后
int i;while ((i = get_value()) != 42) {// do something ...}
谨防混淆相等操作符和赋值操作符
5.4.3. 复合赋值操作符
+= -= *= /= %= // arithmetic operators
<<= >>= &= ^= |= // bitwise operators
这两种语法形式存在一个显著的差别:使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数。
5.5. 自增和自减操作符
自增(++)和自减(--)操作符为对象加1 或减1 操作提供了方便简短的实现方式。它们有前置和后置两种使用形式。
前自增操作,该操作使其操作数加1,操作结果是修改后的值。
5.6. 箭头操作符
C++ 语言为包含点操作符和解引用操作符的表达式提供了一个同义词:箭头操作符(->)
item1.same_isbn(item2); // run the same_isbn member of item1
如果有一个指向 Sales_item 对象的指针(或迭代器),则在使用点操作符前,需对该指针(或迭代器)进行解引用:
Sales_item *sp = &item1;
(*sp).same_isbn(item2); // run same_isbn on object to which sp points
5.7. 条件操作符
条件操作符是 C++ 中唯一的三元操作符,它允许将简单的 if-else 判断语句嵌入表达式中。条件操作符的语法格式为:
cond ? expr1 : expr2;
比较大小
int i = 10, j = 20, k = 30;// if i > j then maxVal = i else maxVal = jint maxVal = i > j ? i : j;
5.8. sizeof 操作符
sizeof 操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t(第 3.5.2 节),长度的单位是字节(第 2.1 节)
- sizeof (type name);
- sizeof (expr);
- sizeof expr;
Sales_item item, *p;// three ways to obtain size required to hold an object of typeSales_itemsizeof(Sales_item); // size required to hold an object of typeSales_itemsizeof item; // size of item‘s type, e.g., sizeof(Sales_item)sizeof *p; // size of type to which p points, e.g.,sizeof(Sales_item)
• 对 char 类型或值为 char 类型的表达式做 sizeof 操作保证得 1。
• 对引用类型做 sizeof 操作将返回存放此引用类型对象所需的内在空间
大小。
• 对指针做 sizeof 操作将返回存放指针所需的内在大小;注意,如果要获
取该指针所指向对象的大小,则必须对指针进行引用。
• 对数组做 sizeof 操作等效于将对其元素类型做 sizeof 操作的结果乘
上数组元素的个数。
5.9. 逗号操作符
逗号表达式是一组由逗号分隔的表达式,这些表达式从左向右计算。逗号表达式的结果是其最右边表达式的值。如果最右边的操作数是左值,则逗号表达式的值也是左值。
int cnt = ivec.size();// add elements from size... 1 to ivecfor(vector<int>::size_type ix = 0;ix != ivec.size(); ++ix, --cnt)ivec[ix] = cnt;
5.10. 复合表达式的求值
5.10.1. 优先级
圆括号凌驾于优先级之上
// parentheses on this expression match defaultprecedence/associativitycout << ((6 + ((3 * 4) / 2)) + 2) << endl; // prints 14// parentheses result in alternative groupingscout << (6 + 3) * (4 / 2 + 2) << endl; // prints 36cout << ((6 + 3) * 4) / 2 + 2 << endl; // prints 20cout << 6 + 3 * 4 / (2 + 2) << endl; // prints 9
5.10.2. 结合性
ival = jval = kval = lval // right associative(ival = (jval = (kval = lval))) // equivalent, parenthesized version
另一方面,算术操作符为左结合。表达式
ival * jval / kval * lval // left associative(((ival * jval) / kval) * lval) // equivalent, parenthesized version
优先级见课本
5.10.3. 求值顺序
当且仅当其右操作数确实影响了整个表达式的值时,才计算这两个操作符的右操作数。根据这个原则,可编写如下代码:
// iter only dereferenced if it isn‘t at endwhile (iter != vec.end() && *iter != some_val)
C++中,规定了操作数计算顺序的操作符还有条件(?:)和逗号操作符。
表达式
f1() * f2();
在做乘法操作之前,必须调用 f1 函数和 f2 函数,毕竟其调用结果要相乘。然而,我们却无法知道到底是先调用 f1 还是先调用 f2。
如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就
变得相当重要:
// oops! language does not define order of evaluationif (ia[index++] < ia[index])
下面两个指导原则有助于处理复合表达式:
1. 如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合。
2. 如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它。
5.11. new 和 delete 表达式
new 表达式返回指向新创建对象的指针,我们通过该指针来访问此对象:
int i; // named, uninitialized int variableint *pi = new int; // pi points to dynamically allocated,// unnamed, uninitialized int
动态创建对象的初始化
int i(1024); // value of i is 1024int *pi = new int(1024); // object to which pi points is 1024string s(10, ‘9‘); // value of s is "9999999999"string *ps = new string(10, ‘9‘); // *ps is "9999999999"
动态创建对象的默认初始化
对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。
string *ps = new string; // initialized to empty stringint *pi = new int; // pi points to an uninitialized int
耗尽内存
如果程序用完了所有可用的内存,new 表达式就有可能失败。如果 new 表达式无法获取需要的内存空间,系统将抛出名为 bad_alloc 的异常。
撤销动态创建的对象
C++ 提供了 delete 表达式释放指针所指向的地址空间。
delete pi;
一些安全的和不安全的 delete expressions 表达式。
int i;int *pi = &i;string str = "dwarves";double *pd = new double(33);delete str; // error: str is not a dynamic objectdelete pi; // error: pi refers to a localdelete pd; // ok
零值指针的删除
如果指针的值为 0,则在其上做 delete 操作是合法的,但这样做没有任何意义
在delete 之后,重设指针的值
删除指针后,该指针变成悬垂指针。
5.12. 类型转换
int ival = 0;ival = 3.541 + 3; // typically compiles with a warning
5.12.1. 何时发生隐式类型转换
• 在混合类型的表达式中,其操作数被转换为相同的类型:
int ival;double dval;ival >= dval // ival converted to double
• 用作条件的表达式被转换为 bool 类型:
int ival;if (ival) // ival converted to boolwhile (cin) // cin converted to bool
• 用一表达式初始化某个变量,或将一表达式赋值给某个变量,则该表达式被转换为该变量的类型:
int ival = 3.14; // 3.14 converted to intint *ip;ip = 0; // the int 0 converted to a null pointer of type int
5.12.2. 算术转换
最简单的转换为整型提升:
对于所有比 int 小的整型,包括 char、signedchar、unsigned char、short 和 unsigned short,如果该类型的所有可能的值都能包容在 int 内,它们就会被提升为 int 型,否则,它们将被提升为unsigned int。如果将 bool 值提升为 int ,则 false 转换为 0,而 true 则转换为 1。
有符号与无符号类型之间的转换
若表达式中使用了无符号( unsigned )数值,所定义的转换规则需保护操作数的精度。unsigned 操作数的转换依赖于机器中整型的相对大小,因此,这类转换本质上依赖于机器。
理解算术转换
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 doubledval + ival; // ival converted to doubledval + fval; // fval converted to doubleival = dval; // dval converted (by truncation) to intflag = dval; // if dval is 0, then flag is false, otherwise truecval + fval; // cval promoted to int, that int converted to floatsval + cval; // sval and cval promoted to intcval + lval; // cval converted to longival + ulval; // ival converted to unsigned longusval + ival; // promotion depends on size of unsigned short and intuival + lval; // conversion depends on size of unsigned int and long
指针转换
在使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针:
int ia[10]; // array of 10 intsint* ip = ia; // convert ia to pointer to first element
C++ 还提供了另外两种指针转换:指向任意数据类型的指针都可转换为void* 类型;整型数值常量 0 可转换为任意指针类型。
转换为bool 类型
如果指针或算术值为 0,则其bool 值为 false ,而其他值则为 true:
5.12.5. 何时需要强制类型转换
double dval;int ival;ival *= dval; // ival = ival * dval
为了去掉将 ival 转换为double 型这个不必要的转换,可通过如下强制将 dval 转换为 int 型:
ival *= static_cast<int>(dval); // converts dval to int
5.12.6. 命名的强制类型转换
dynamic_cast
dynamic_cast 支持运行时识别指针或引用所指向的对象。
const_cast
将转换掉表达式的 const 性质。
const char *pc_str;char *pc = string_copy(const_cast<char*>(pc_str));
static_cast
编译器隐式执行的任何类型转换都可以由 static_cast 显式完成:
double d = 97.0;// cast specified to indicate that the conversion is intentionalchar ch = static_cast<char>(d);
reinterpret_cast
int *ip;char *pc = reinterpret_cast<char*>(ip);
任何假设 pc 是普通字符指针的应用,都有可能带来有趣的运行时错误。例如,下面语句用 pc 来初始化一个 string 对象:
string str(pc);
它可能会引起运行时的怪异行为。
当我们用 int 型地址初始化 pc 时,由于显式地声明了这样的转换是正确的,因此编译器不提供任何错误或警告信息。后面对 pc 的使用都假设它存放的是 char* 型对象的地址,编译器确实无法知道 pc 实际上是指向 int 型对象的指针。因此用 pc 初始化 str 是完全正确的——虽然实际上是无意义的或是错误的。
5.12.7. 旧式强制类型转换
在引入命名的强制类型转换操作符之前,显式强制转换用圆括号将类型括起来实现:
char *pc = (char*) ip;
效果与使用 reinterpret_cast 符号相同,但这种强制转换的可视性比较差,难以跟踪错误的转换。
C++primer第五章 表达式