Skip to content
This repository has been archived by the owner on Feb 15, 2021. It is now read-only.

Latest commit

 

History

History

第 4 章 表达式

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

4.1 基础

4.1.2 优先级与结合律

练习 4.1

表达式 5 + 10 * 20 / 2 的求值结果是多少?

5 + 10 * 20 / 2 = 105

练习 4.2

根据 4.12 节中的表,在下述表达式的合理位置添加括号,使得添加括号后运算对象的组合顺序与添加括号前一致。

(a) *vec.begin()

(b) *vec.begin() + 1

  • *(vec.begin())
  • *(vec.begin()) + 1

4.1.3 求值顺序

练习 4.3

C++ 语言没有明确规定大多数二元运算符的求值顺序,给编译器优化留下了余地。这种策略实际上是在代码生成效率和程序潜在缺陷之间进行了权衡,你认为这可以接受吗?请说出你的理由。

可以接受

4.2 算术运算符

练习 4.4

在下面的表达式中添加括号,说明其求值过程及最终结果。编写程序编译该(不加括号的)表达式并输出结果验证之前的推断。

12 / 3 * 4 + 5 * 15 + 24 % 4 / 2

((12 / 3) * 4) + (5 * 15) + ((24 % 4) / 2) = 91

练习 4.5

写出下列表达式的求值结果。

(a) -30 * 3 + 21 / 5

(b) -30 + 3 * 21 / 5

(c) 30 / 3 * 21 % 5

(d) -30 / 3 * 21 % 4

  • -30 * 3 + 21 / 5 = -86
  • -30 + 3 * 21 / 5 = -18
  • 30 / 3 * 21 % 5 = 0
  • -30 / 3 * 21 % 4 = -2

练习 4.6

写出一条表达式用于确定一个整数是奇数还是偶数。

int ival;
if (ival % 2 == 0) {
  cout << ival << "是偶数" << endl;
} else {
  cout << ival << "是奇数" << endl;
}

练习 4.7

溢出是何含义?写出三条将导致溢出的表达式。

溢出是指计算的结果超出该类型所能表示的范围

short shortVal1 = 32768;
short shortVal2 = 30000 * 2;
short shortVal3 = 32767 + 1;

4.3 逻辑和关系运算符

练习 4.8

说明在逻辑与、逻辑或及相等性运算符中运算对象求值的顺序。

  • 在逻辑与中,先计算左侧运算对象,若为真,再计算右侧运算对象
  • 在逻辑或中,先计算左侧运算对象,若为假,再计算右侧运算对象
  • 在相等性运算符中,先计算左侧运算对象,在计算右侧运算对象

练习 4.9

解释在下面的if语句中条件部分的判断过程。

const char *cp = "Hello World";
if (cp && *cp)

先检查指针 cp 是否指向一个对象,如果是,则检查 cp 指向的字符数组是否为空,若非空,则条件为真,否则条件为假

练习 4.10

为 while 循环写一个条件,使其从标准输入中读取整数,遇到 42 时停止。

int val;
while (cin >> val && val != 42) {
  /* ... */
}

练习 4.11

书写一条表达式用于测试 4 个值 a、b、c、d 的关系,确保 a 大于 b、b 大于 c、c 大于 d。

if (a > b && b > c && c > d) {
  /* ... */
}

练习 4.12

假设 i、j 和 k 是三个整数,说明表达式 i != j < k 的含义。

先判断 jk 的大小关系,若 j < k,则当 i != 1 时表达式为 true,否则表达式为 false;若 j >= k,则当 i != 0 时表达式为 true,否则表达式为 false

4.4 赋值运算符

练习 4.13

在下述语句中,当赋值完成后 i 和 d 的值分别是多少?

int i; double d;

(a) d = i = 3.5;

(b) i = d = 3.5;

  • i = 3, d = 3
  • i = 3, d = 3.5

练习 4.14

执行下述 if 语句后将发生什么情况?

if (42 = i)  // ...
if (i = 42)  // ...
  • 编译无法通过,字面值 42 是右值,无法赋值
  • 程序运行,i 被赋值为 42,if 条件始终为真

练习 4.15

下面的赋值是非法的,为什么?应该如何修改?

double dval;
int ival;
int *pi;
dval = ival = pi = 0;

pi 的类型为 int*,无法转换为 ivalint 类型。修改如下

double dval; int ival; int *pi;
dval = ival = 0; pi = 0;

练习 4.16

尽管下面的语句合法,但它们实际执行的行为可能和预期并不一样,为什么?应该如何修改?

(a) if (p = getPtr() != 0)

(b) if (i = 1024)

  • 赋值运算的优先级较低,修改为 if ((p = getPtr()) != 0)
  • 混淆了赋值运算符和相等运算符,修改为 if (i == 1024)

4.5 递增和递减运算符

练习 4.17

说明前置递增运算符和后置递增运算符的区别。

前置递增运算符首先将运算对象加 1,然后将改变后的对象作为左值返回;后置递增运算符将运算对象加 1,但是将改变前的运算对象作为右值返回

练习 4.18

如果 132 页那个输出 vector 对象元素的 while 循环使用前置递增运算符,将得到什么结果?

无法输出第一个元素,且若 vector 中无负值,将试图解引用一个不存在的元素,造成程序错误

练习 4.19

假设 ptr 的类型是指向 int 的指针、vec 的类型是 vector、ival 的类型是 int,说明下面的表达式是何含义?如果有表达式不正确,为什么?应该如何修改?

(a) ptr != 0 && *ptr++

(b) ival++ && ival

(c) vec[ival++] <= vec[ival]

  • ptr 是非空指针,且 ptr 所指对象不为 0 时表达式为真,并将 ptr 加 1
  • ival 不为 0 时表达式为真,并将 ival 加 1
  • 表达式为真,将 ival 加 1

4.6 成员访问运算符

练习 4.20

假设 iter 的类型是 vector::iterator, 说明下面的表达式是否合法。如果合法,表达式的含义是什么?如果不合法,错在何处?

(a) *iter++;

(b) (*iter)++;

(c) *iter.empty();

(d) iter->empty();

(e) ++*iter;

(f) iter++->empty();

  • 合法,返回 iter 所指元素的引用,iter 指向下一个元素
  • 不合法,返回 iter 所指元素的引用,但 string 对象不能递增
  • 不合法,解引用运算符的优先级低于点运算符,迭代器没有 empty() 成员
  • 合法,判断 iter 所指的 string 对象是否为空
  • 不合法,返回 iter 所指元素的引用,但 string 对象不能递增
  • 合法,判断 iter 所指的 string 对象是否为空,iter 指向下一个元素

4.7 条件运算符

编写一段程序,使用条件运算符从 vector<int> 中找到哪些元素的值是奇数,然后将这些奇数值翻倍。

本节的示例程序将成绩划分为 high pass、pass 和 fail 三种,扩展该程序使其进一步将 60 分到 75 分之间的成绩设定为 low pass。要求程序包含两个版本:一个版本只使用条件运算符;另一个版本使用 1 个或多个 if 语句。哪个版本的程序更容易理解呢?为什么?

使用 if 语句的版本更容易理解,条件运算符嵌套过多

练习 4.23

因为运算符的优先级问题,下面这条表达式无法通过编译。根据 4.12 节中的表(第 147 页)指出它的问题在哪里?应该如何修改?

string s = "word";
string pl = s + s[s.size() - 1] == 's' ? "" : "s";

条件运算符的优先级非常低,原条件表达式判断 s + s[s.size() - 1]'s' 是否相等,而 string 对象和字符字面值无法相互比较。修改如下

string s = "word";
string pl = s + ((s[s.size() - 1] == 's') ? "" : "s");

练习 4.24

本节的示例程序将成绩划分为 high pass、pass、和 fail 三种,它的依据是条件运算符满足右结合律。假如条件运算符满足的是左结合律,求值过程将是怎样的?

第一个条件检查成绩是否在 90 分以上,如果是,得到 "high pass",执行后面的条件表达式:字符串不为空,条件成立,得到 "fail";如果否,执行后面的条件表达式:判断成绩是否在 60 分以下,如果是,得到 "fail";否则得到 "pass"

4.8 位运算符

练习 4.25

如果一台机器上 int 占 32 位、char 占 8 位,用的是 Latin-1 字符集,其中字符 'q' 的二进制形式是 01110001,那么表达式 'q' << 6 的值是什么?

输出 -7296char 类型的运算对象会提升成 int 类型,提升时运算对象原来的位保持不变,往高位添加 0

练习 4.26

在本节关于测验成绩的例子中,如果使用 unsigned int 作为 quiz1 的类型会发生什么情况?

int 类型为 16 位的机器上会溢出,造成错误结果

练习 4.27

下列表达式的结果是什么?

unsigned long ul1 = 3, ul2 = 7;

(a) ul1 & ul2

(b) ul1 | ul2

(c) ul1 && ul2

(d) ul1 || ul2

  • ul1 & ul2 = 3
  • ul1 | ul2 = 7
  • ul1 && ul2 =1
  • ul1 || ul2 = 1

4.9 sizeof 运算符

编写一段程序,输出每一种内置类型所占空间的大小。

练习 4.29

推断下面代码的输出结果并说明理由。实际运行这段程序,结果和你想象的一样吗?如果不一样,为什么?

int x[10];
int *p = x;
cout << sizeof(x) / sizeof(*x) << endl;
cout << sizeof(p) / sizeof(*p) << endl;

输出

10
2

int* 类型的对象(指针)占 8 个字节,int 类型的对象占 4 个字节

练习 4.30

根据 4.12 节中的表(第 147 页),在下述表达式的适当位置加上括号,使得加上括号之后的表达式的含义与原来的含义相同。

(a) sizeof x + y

(b) sizeof p->mem[i]

(c) sizeof a < b

(d) sizeof f()

  • (sizeof x) + y
  • sizeof (p->mem[i])
  • (sizeof a) < b
  • (sizeof f())

4.10 逗号运算符

练习 4.31

本节的程序使用了前置版本的递增运算符和递减运算符,解释为什么要用前置版本而不用后置版本。要想使用后置版本的递增递减运算符需要做哪些改动?使用后置版本重写本节的程序。

后置递增运算符需要将运算对象的原始值存储下来,将运算对象加 1 后再返回原始值,而此程序无需运算对象的原始值,使用后置递增运算符会造成不必要的浪费。使用后置运算符的程序如下

auto cnt = ivec.size();
for (decltype(ivec.size()) ix = 0; ix != ivec.size(); ix++, cnt--) {
  ivec[ix] = cnt;
}

练习 4.32

解释下面这个循环的含义。

constexpr int size = 5;
int ia[size] = {1, 2, 3, 4, 5};
for (int *ptr = ia, ix = 0; ix != size && ptr != ia + size;
     ++ix, ++ptr) { /* ... */ }

指针 ptr 指向数组的第一个元素,索引 ix 初始值为 0。每次循环迭代 ptrix,只要 ix 小于数组大小且指针未指向最后一个元素的下一个位置,就执行循环体,然后将 ix 加 1,ptr 向后移动一个位置

练习 4.33

根据 4.12 节中的表(第 147 页)说明下面这条表达式的含义。

someValue ? ++x, ++y : --x, --y

判断 someValue 的值,如果为真(不为 0),则 xy 都递增 1;否则 xy 都递减 1

4.11 类型转换

4.11.1 算术转换

练习 4.34

根据本节给出的变量定义,说明在下面的表达式中奖发生什么样的类型转换:

(a) if (fval)

(b) dval = fval + ival;

(c) dval + ival * cval;

需要注意每种运算符遵循的是左结合律还是右结合律。

  • fval 转换成 bool
  • ival 转换成 float,然后该 float 值转换成 double
  • cval 提升成 int,然后该 int 值转换成 double

练习 4.35

假设有如下的定义:

char cval;
int ival;
unsigned int ui;
float fval;
double dval;

请回答在下面的表达式中发生了隐式类型转换吗?如果有,指出来。

(a) cval = 'a' + 3;

(b) fval = ui - ival * 1.0;

(c) dval = ui * fval;

(d) cval = ival + fval + dval;

  • 'a' 提升成 int,然后该 int 值转换成 char
  • ival 转换成 double,然后 ui 转换成 double,然后该 double 值转换成 float
  • ui 转换成 float,然后该 float 值转换成 double
  • ivalfval 转换成 double,然后该 double 值转换成 char

4.11.3 显式类型转换

练习 4.36

假设 i 是 int 类型,d 是 double 类型,书写表达式 i *= d 使其执行整数类型的乘法而非浮点类型的乘法。

i *= static_cast<int>(d)

练习 4.37

用命名的强制类型转换改写下列旧式的转换语句。

int i;
double d;
const string *ps;
char *pc;
void *pv;

(a) pv = (void*)ps;

(b) i = int(*pc);

(c) pv = &d;

(d) pc = (char*)pv;

  • pv = static_cast<void *>(const_cast<string *>(ps));
  • i = static_cast<int>(*pc);
  • pv = static_cast<void *>(&d);
  • pc = reinterpret_cast<char *>(pv);

练习 4.38

说明下面这条表达式的含义。

double slope = static_cast<double>(j / i);

j/i 的结果转换为 double 类型赋给 slope


上一章:第 3 章 字符串、向量和数组

下一章:第 5 章 语句