《C++ Primer》第四章小节练习

注意: 一共 38 题,每 10 题一个链接

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

答:根据优先级和结合律先计算 10*20 得到 200 然后 200 / 2 得到 100,最后 5 + 100 的结果为 105 。

4.2 根据 4.12 节中的表,在下述表达式的合理位置添加括号,使得添加括号后运算对象的组合顺序与添加括号前一致。
  (a) *vec.begin()  (b)*vec.begin() + 1

答:上述两个表达式均先进入函数,得到返回值后再对返回值进行解引用操作。所以添加括号如下:
  (a) *(vec.begin())  (b)(*(vec.begin())) + 1zzwjz5

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

答:我认为这个观点是可以接受的。当二元运算符两边的表达式互不干扰时,编译器会根据效率选择合适的执行顺序。但是当二元运算符两边的表达式有一定的联系时(修改两个表达式中的同一个对象),就会产生潜在缺陷。所以,在编写类似代码时,应注意以下两点:
(1)拿不准的时候最好通过括号来强制让表达式的组合关系符合程序逻辑的要求。
(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

答:a. -86
b. -18
c. 0
d. -2

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

答:对 2 取余,若结果为 0 ,则为奇数,否则为偶数。代码如下:
在这里插入图片描述

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

答:当计算的结果超出类型所能表示的范围时,产生溢出。
代码如下:
在这里插入图片描述

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

答:逻辑与和逻辑或都是采用短路求值策略,先计算左边的表达式的值,若左边表达式的值可以确定结果则不计算右侧表达式。若逻辑与运算符左侧表达式为假,则整个表达式为假,不计算右侧表达式的值;若左侧表达式为真,则需要计算右侧表达式进行判断。而逻辑或运算符若左侧表达式为真,则整个表达式为真,不用计算右侧表达式;若左侧表达式为假,则需要计算右侧表达式进行判断。
相等性运算符C++并没有规定其运算顺序,两边的表达式都需要求值。

4.9 解释在下面的 if 语句中条件部分的判断过程。
  const char *cp = “Hello World”;
  if (cp && *cp)

答:若 cp 不为空指针并且 cp 指向的字符串不为空,则判断条件为真,否则为假。

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

答:使用逻辑与(&&)来编写代码,读取整数的操作放在左侧。如下代码:

// 读取整数,直到读取到 42 为止
int input;
while (cin >> input && input != 42)
{
	// ...

4.11 书写一条表达式用于测试 4 个值 a、b、c、d 的关系,确保 a 大于 b、b 大于 c、c大于 d。
答:使用两个逻辑与(&&)来编写代码,代码如下:

if (a > b && b > c && c > d)
	cout << "True" << endl;
else
	cout << "False" << endl;

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

答:按照优先级的规则,先计算表达式 i < k,若 i 小于 k,则该表达式结果为 true,否则为 false。然后此结果与整数 i 进行比较,该过程 bool 值进行整型提升,true 为 1,false 为 0。若 i 不为 0 和 1,则整个表达式的结果为 true,否则看 i 是否等于 i < k 表达式的结果。

4.13 在下述语句中,当赋值完成后 i 和 d 的值分别是多少?
  int i; double d;
 (a)d = i = 3.5; (b)i = d = 3.5;

答:a. i 的值为 3,d 的值为 3
b. i 的值为 3,d 的值为 3.5
浮点值赋值给整型变量,截断小数,只保留整数部分。

4.14 执行下述 if 语句后将发生什么情况?
 if (42 = i) // …
 if (i = 42) // …

答:第一条 if 语句的判断条件试图给字面值常量赋值,编译器会报错。赋值运算符左侧的运算对象必须是一个可修改的左值。
第二条语句的判断条件 i = 42 导致该判断条件一直为真。非 bool 值转换为 bool 值,非 0 为真,0 为假。

*4.15 下面的赋值是非法的,为什么?应该如何修改?
 double dval; int ival; int pi;
 dval = ival = pi = 0;

答:赋值运算符是右结合,所以先计算 pi = 0,然后返回 pi 计算 ival = pi,但是 pi 是一个 int 类型的指针,不能赋值给 int 类型的变量,编译器会报错。应修改为如下代码:

pi = 0;
dval = ival = 0;

4.16 尽管下面的语句合法,但它们实际执行的行为可能和预期并不一样,为什么?应该如何修改?
  (a)if (p = getPtr() != 0)  (b)if (i = 1024)

答:a. 赋值运算符的优先级低于关系运算符,所以应在原句加上括号,修改如下: if ((p = getPtr()) != 0)
b. 这里把关系运算符等于(==)写成了赋值运算符(=),导致判断条件一直为真,本意是比较 i 和 1024 的值是否相等。修改如下:if (i == 1024)

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

答:前置递增运算符先把运算对象加 1,然后把改变后的运算对象作为求值结果。后置递增运算符也会把运算对象加 1,但是求值结果是运算对象改变之前的那个值的副本。这两种运算符必须作用于左值运算对象,前置版本将对象本身作为左值返回,后置版本则将运算对象原始值的副本作为右值返回。

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

4.19 假设 ptr 的类型是指向 int 的指针、vec 的类型是 vector<int>、ival 的类型是 int,说明下面的表达式是何含义?如果有表达式不正确,为什么?应该如何修改?
(a)ptr != 0 && *ptr++   (b)ival++ && ival
(c)vec[ival++] <= vec[ival]

答:a. 该表达式首先判断指针 ptr 是否为空,为空则表达式最终结果为假,不为空则继续判断指针 ptr 所指向的值是否为 0,为 0,则表达式最终结果为假。其余情况均为真。判断完毕后,指针 ptr 加 1,指向下一个位置。若指针 ptr 指向数组中的某一元素,则加 1 后指向下一个元素。若指针 ptr 单纯指向一个整数,则加 1 的结果未定义。
b. 该表达式判断 ival 当前的值是否为 0,若为 0,则表达式最终的结果为假。不为 0,则判断 ival 加 1 后的值是否为 0,若为 0,则表达式最终的结果为假。其余情况均为真。
c. 该表达式的含义是比较 vec[ival] 和 vec[ival + 1] 的大小,前者小于等于后者则表达式为真,否则表达式为假。但是,该表达式出现了二元运算符的两个运算对象涉及同一个对象并改变对像值的情况(按照不同的求值顺序,有不同的结果)。应将表达式修改为:vec[ival] <= vec[ival + 1]

4.20 假设 iter 的类型是 vector<string>::iterator,说明下面的表达式是否合法。如果合法,表达式的含义是什么?如果不合法,错在何处?  (a)*iter++;  (b)(*iter)++;   (c)*iter.empty()  (d)iter->empty();  (e)++*iter;  (f)iter++ ->empty();

答:a. 合法,后置递增运算符的优先级高于解引用运算符,其含义是解引用迭代器 iter,得到其所指向的 string 对象,然后迭代器 iter 向后移动一位。
b. 不合法,先对迭代器 iter 解引用得到 string 对象,但是 string 对象没有后置递增操作,编译器报错。
c. 不合法,点运算符的优先级高于解引用运算符,所以表达式先计算 iter.empty(),但是迭代器并未定义方法 empty(),编译器报错。
e. 不合法,前置递增运算符和解引用运算符优先级一致,但是由于两个运算符都是右结合,所以先计算 *iter 得到 string 对象,但是 string 对象没有前置递增操作,编译器报错。
f. 合法,该表达式等价于 (*iter++).empty(); 。其含义是得到当前迭代器所指向的 string 对象,判断其是否为空返回然后返回相应 bool 值,最后把迭代器向后移动一位。

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

答:可以使用 for 循环、范围 for 或者迭代器中的任意一个。使用范围 for 时,由于需要改变奇数值,所以需要使用引用。

// 创建对象
vector<int> vt_i;
// 插入数据
for(int i = 0; i < 10; ++i)
	vt_i.push_back(i);
// 将奇数翻倍
for (auto& val : vt_i)
	val % 2 == 0 ? val : val *= 2;

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

答:代码如下:
(1)条件语句版本

finalgrade = (grade > 90) ? "high pass" : (high > 75) ? "pass"
			: (grade >= 60) ? "low pass" : "fail";

(2)if 语句版本

if (grade > 90)
	finalgrade = "high pass";
else if (grade > 75)
	finalgrade = "pass";
else if (grade >= 60)
	finalgrade = "low pass";
else
	finalgrade = "fail";

通过上述代码,很容易看出 if 版本的程序更加容易理解。条件表达式虽然也可以嵌套使用,但是嵌套的层数越少越好,如果嵌套的层数过多,则代码的可读性会变差。

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

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

答:该条件表达式首先计算 s + s[s.size() - 1],把字符串 s 的最后一个字符拼接到 s 的末尾,得到临时 string 对象 “wordd”,然后与字符 ‘s’ 进行比较。但是并没有定义 string 对象与 char 字符的比较操作,所以这是一个非法操作,编译器报错。应该在后面加上括号,修改如下:

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

这样条件表达式就是在判断 string 对象 s 的最后一个字符是否为 ‘s’。

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

答:原文的程序为:

finalgrade = (grade > 90) ? "high pass"
						  : (grade < 60) ? "fail" : "pass";

若条件运算符满足左结合律,则该程序变为:

finalgrade = ((grade > 90) ? "high pass" : (grade < 60)) ? "fail" : "pass";

如果按照上述加上括号后的代码进行运算,则第一个条件运算符先判断 grade < 90 是否成立,如果成立,则第一个条件表达式的值为 “high pass”;如果不成立,则第一个条件表达式的值为 grade < 60,即布尔值。但是条件运算符要求两个结果表达式的类型相同或者可以相互转化,所以上述表达式不成立,也不满足要求。

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

答:在位运算符中,左移运算符(<<)的优先级高于取反运算符(~),所以先对 ‘q’ 按位取反。但是,位运算符的运算对象通常为整型,所以对 ‘q’ 进行整型提升:00000000 00000000 00000000 01110001,然后取反:11111111 11111111 11111111 10001110,然后左移 6 位,11111111 11111111 11100011 10000000。
C++ 规定整数按照其补码的形式进行存储,上式得到的是其补码,解得原码:10000000 00000000 00011100 10000000,即最终结果的二进制形式,转换成 10 进制形式为:-7296

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

答:根据题目的要求,班级中有 30 位同学,我们用一个二进制位代表某个学生在一次测验中是否通过,则至少需要 30 个二进制位。原题中使用 unsigned long 作为 quiz1 的数据类型,因为 C++ 规定其在内存中至少占 32 位。但是如果使用 unsigned int 作为 quiz1 的数据类型,C++ 只规定了其所占空间的最小值为 16 位,所以在很多机器上,该类型无法满足题目需求。

4.27 下列表达式的结果是什么?
 unsigned long ul1 = 3, ul2 = 7;
 (a)ul1 &ul2  (b)ul1 | ul2
 (c)ul1 && ul2 (d)ul1 || ul2

答:两位数的二进制为:
ul1:00000000 00000000 00000000 00000011
ul2:00000000 00000000 00000000 00000111

a. 按位与的结果:00000000 00000000 00000000 00000011,即 3
b. 按位或的结果:00000000 00000000 00000000 00000111,即 7
c. 逻辑与的结果:true(所有非 0 整数对应的布尔值都是 true)
d. 逻辑或的结果:true

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

答:运用 sizeof 运算符来计算每一种内置类型所占的空间大小,但 C++ 只规定了每种类型所占的最小空间,实际具体占多少空间依赖具体实现。代码如下:

cout << "bool = " << sizeof(bool) << endl;
cout << "char = " << sizeof(char) << endl;
cout << "short = " << sizeof(short) << endl;
cout << "int = " << sizeof(int) << endl;
cout << "long = " << sizeof(long) << endl;
cout << "long long = " << sizeof(long long) << endl;
cout << "float = " << sizeof(float) << endl;
cout << "double = " << sizeof(double) << endl;
cout << "long double" << sizeof(long double) << endl;

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

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

答:结果一样。第一行代码计算数组 x 的元素个数,第二行代码用 int 类型的指针所占位数除以 int 类型所占位数。
在这里插入图片描述

4.30 根据 4.12 节中的表(第 147 页),在下述表达式的适当位置加上括号,使得加上括号之后的表达式的含义与原来的含义相同。 (a)sizeof x + y  (b)sizeof p->mem[i] (c)sizeof a < b  (d)sizeof f()

答:sizeof 运算符的优先级和前置++级别相同,均为右结合。
a. (sizeof x) + y
b. sizeof(p->mem[i])
c. (sizeof a) < b
d. sizeof(f())

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

答:在本节的程序中,使用前置版本和后置版本的递增递减运算符效果是一样的,没有区别。因为递增递减运算符与真正使用这两个变量的语句位于不同的表达式中,所以不会有什么影响。使用后置版本重写的程序如下:

vector<int> ivec;
vector<int>::size_type cnt = ivec.size();
// 将把从 size 到 1 的值赋值给 ivec 的元素
for (vector<int>::size_type 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)
{
	// ...
}

答:定义一个表达式常量 size,赋初值 5;然后以 size 为元素个数创建 int 数组 ia,并初始化为 1-5。for 语句的三个部分,初始化部分:执行一次,创建整型指针指向数组 ia 的首元素,创建整型变量 ix,赋初值为 0;条件部分,若 ix 的值不等于 size 并且指针 ptr 没有指向数组 ia 的尾后元素时,执行循环体;更新部分,分别令 ix 和 ptr 执行前置递增操作。

4.33 根据 4.12 节中的表(第 147 页)说明下面这条表达式的含义。
 someValue ? ++x, ++y : --x, --y

答:由于条件运算符的优先级高于逗号运算符,所以该表达式等价于 (someValue ? ++x, ++y : --x), --y。该表达式先判断 someValue 是否为真,若为真,则执行 ++x 和 ++y,然后再执行 --y;若为假则执行 --x 和 --y。

4.34 根据本节给出的变量定义,说明在下面的表达式中将发生什么样的类型转换:
(a)if (fval) (b)dval = fval + ival; (c)dval + ival * cval;

答:a. fval 转换为 bool 值,若 fval 为 0 则转换为 false,否则转换为 true
b. 首先 ival 转换为 float 值,然后与 fval 相加,相加的结果转换为 double 值,然后赋值给 dval
c. cval 先转换为 int 值与 ival 相乘,然后相乘的结果转换为 double 值和 dval 相加

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. 字符串常量 ‘a’ 转换为 int 值,与 3 相加,然后转换为 char 值赋值给 cval
b. ival 转换为 double 值和 1.0 相乘,ui 转换为 double 值与前面相乘的结果相加,然后相加的结果转换为 float 值赋值给 fval
c. ui 转换为 float 值与 fval 相加,然后相加的结果转换为 double 值赋值给 dval
d. ival 转换为 float 值与 fval 相加,相加的结果转换为 double 值然后与 dval 相加,最终的结果转换为 char 值赋值给 cval

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

答:使用强制类型转换:i* = (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;

答:a. pv = static_cast<void*>ps;
b. i = static_cast<int>(*ps);
c. pv = static_cast<void*>(&d);
d. pc = static_cast<char*>(pv);

4.38 说明下面这条表达式的含义。
  double slope = static_cast<double>(j/i);

答:把 j / i 的结果强制类型转换为 double 值赋值给变量 slope。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值