练习4.1:表达式 5 + 10 * 20 / 2 的求值结果是多少?
105 。
练习4.2:根据4.12节中的表,在下述表达式的合理位置添加括号,使得添加括号后运算对象的组合顺序与添加括号前一致。
(a)
*vec.begin()
点运算符(.) 和 函数调用运算符() 优先级相同,都高于 解引用符号(*)。
所以可以改为:
*( vec.begin() )
(b)
*vec.begin + 1
优先级: 点运算符 = 函数调用运算符 > 解引用运算符 > 算术运算符+
所以可以改为:
( *( vec.begin() ) ) + 1
练习4.3: C++语言中没有明确规定大多二元运算符的求值顺序,给编译器优化留下了余地。这种策略实际上是在代码生成效率和程序潜在缺陷之间的权衡,你认为可以接受吗?请说出你的理由。
(标准)
答:正如题目所说,C++只规定了非常少的二元运算符(逻辑与运算符、逻辑或运算符、逗号运算符)的求值顺序。这样做提高了代码生产的效率,但是可能引发潜在的缺陷。
所以在编写程序时注意以下两点:一是拿不准的时候最好用括号来强制让表达式的组合关系运算符合程序逻辑的要求;而是一旦改变了某个运算对象的值,在表达式的其他地方就不要再使用这个运算对象了。
练习4.4:在下面的表达式中添加括号,请说明其求值的过程及最终结果。编写程序编译该(不加括号的)表达式并输出其结果验证之前的推断。
12 / 3 * 4 + 5 * 15 + 24%4 / 2
( ( ( ( 12 / 3 ) * 4 ) + ( 5 * 15 ) )+ ( ( 24%4 ) / 2 ) )
结果都是91,正确。
练习4.5:写出下列表达式的求值结果。
(a)-86
(b)-18
(c) 0
(d)-2
练习4.6:写出一条表达式用于确定一个整数是奇数还是偶数。
num % 2
若 == 0, num为偶数;
若 == 1,num为奇数;
练习4.7:溢出是何含义?写出三条将导致溢出的表达式。
溢出是一种常见的算术运算错误。因为在计算机中存储某种类型的内存空间有限,所以该类型的范围也是有限的,当计算的结果值超出这个范围时,就会产生未定义的数值,这种错误成为溢出。
练习4.8:说明在逻辑与、逻辑或及相等性运算符中运算对象求值的顺序。
答:
逻辑与 和 逻辑或运算符 都是先求左侧运算对象的值。短路求值。
相等性运算符中运算对象求值的顺序是没有规定的。
练习4.9:解释在下面的if语句中条件部分的判断过程。
const char *cp = "Hello World";
if( cp && *cp )
先检查指针cp是否是有效指针。
如果cp是无效指针,则条件为假。
如果cp是有效指针
则检查*cp是否是空字符 '\o'
如果是,条件为假。
如果不是,条件为真。
练习4.10:为while循环写一个条件,使其从标准输入中读取整数,遇到42时停止。
int i = 0;
while( cin >> i && i != 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的含义。
因为 < 的优先级大于 != ,所以含义为:
先比较 j 和 k, 返回一个布尔值。
再比较 i 和 返回的布尔值是否相等。
练习4.13:在下述语句中,当赋值完成后 i 和 d 的值分别是多少?
int i;double d;
(a) d = i = 3.5; // i 为3, d为3.0
(b) i = d = 3.5; // d 为 3.5, i为3
练习4.14:执行下述if语句后将发生什么情况?
if( 42 = i ) //...
无法通过编译。不能对一个字面值常量赋值。
if( i = 42 ) //...
先将42赋值给变量 i。(赋值运算的结果是它的左侧运算对象,并且是一个左值)
然后进行条件判断,因为返回了 i,非0,条件为真。
练习4.15:下面的赋值是非法的,为什么?应该如何修改?
double dval; int ival; int *pi;
daval = ival = pi = 0;
不能将一个指针赋值给int变量。
修改:
dval = ival = 0;
pi = 0;
练习4.16:尽管下面的语句合法,但它们实际执行的行为可能和预期并不一样,为什么?应该如何修改?
(a) if( p = getPtr() != 0 )
预期:
将getPtr()赋值给p,然后判断p 是否不等于 0 。
实际:
先判断getPtr() 是否不等于 0,并返回一个布尔值。
再将返回的布尔值赋值给p。
最后判断p是否非0。
所以,修改为:
if ( ( p = getPtr() ) != 0 )
(b) if( i = 1024 )
预期:
判断i是否等于1024作为if语句的条件。
实际:
将1024赋值给i.
再将返回的i作为if语句的条件,判断i是否非0。
练习4.17:说明前置递增运算符和后置递增运算符的区别。
答:
前置版本首先对运算对象加1(或减1),然后把改变后的对象作为求值结果。
后置版本也将运算对象加1(或减1),但是求值结果是运算对象改变之前那个值的副本。
这两种运算符必须作用于左值运算对象。前置版本将对象本身作为左值返回,后置版本将对象原始值的副本作为右值返回。
练习4.18:如果第132页那个输出vector对象元素的while循环使用前置递增运算符,将得到什么结果。
可能产生两种错误:
第一种:无法输出vector对象的第一个元素。
第二种:若vector的元素都非负,那么当进行到最后一个循环步时,会解引用一个根本不存在的迭代器引发错误。
练习4.19:假设ptr的类型是指向int的指针、vec的类型是vector
(a) ptr != 0 && *ptr++
先检查ptr是否是有效指针;继续判断指针所指对象是否非0;并且指针向后移动一位(不一定有意义)。
(b) ival++ && ival
先检查ival是否非0:
若ival为0,表达式为假。ival自增1;
若ival非0,ival自增1:
然后判断自增后的ival是否非0;
(c) vec[ival++] <= vec[ival]
求值顺序不确定。
应改为:
vec[ival + 1] <= vec[ival]
练习4.20:假设iter的类型是vector
(a) *iter++; 合法。后置自增运算符的优先级大于解引用运算符。含义:解引用当前迭代器所指对象的内容,然后将迭代器向后移动一位。
(b) ( *iter )++ ;非法。含义:试图解引用迭代器所指对象的内容,然后将迭代器所指对象的内容(string类)自增1,但是string类没有自增操作,所以非法。
(c) *iter.empty(); 非法。 点运算符的优先级大于解引用运算符。所以,先进行iter.empty()操作,但迭代器iter并没有定义empty()成员函数。所以非法。
(d) iter->empty(); 合法。 含义:解引用iter所指对象的内容,并调用其成员函数empty()检查string是否为空串。
(e)++*iter;非法。先解引用iter得到一个string,但不能对string用递增运算符。
(f) iter++->empty(); 合法。->运算符的优先级大于后置递增运算符。含义:解引用iter所指对象的内容,并调用其成员函数empty()检查元素是否为空串。最后,迭代器向后移动一位。
练习4.21:编写一段程序,使用条件运算符从vector
#include
#include
#include
#include
using namespace std;
int main()
{
vector ivec( 10 );
srand( ( unsigned )time ( NULL ) );
cout << "容器的元素为:" << endl;
for( auto &i : ivec )
{
i = rand()%101;
cout << i << "\t";
}
cout << endl;
cout << "将其中的奇数元素翻倍后:" << endl;
for( auto &i : ivec )
cout << ( i % 2 == 0? i : 2 * i ) << "\t";
cout << endl;
return 0;
}
要求使用两个版本:一个版本只使用条件运算符;另外一个版本使用一或多个if语句。
哪个版本的程序更容易理解呢?
#include
using namespace std;
int main()
{
cout << "请输入一个成绩:" << endl;
unsigned grade = 0;
while( cin >> grade )
{
if( grade >= 0 && grade <= 100 )
cout << ((grade >= 90)? "high pass" : (grade >=75)? "pass" : (grade >= 60)? "low pass" : "fail" )
<< endl;
else
cout << "无效成绩" << endl;
char reply;
cout << "你是否希望继续?回答:Y OR N ?" << endl;
if( cin >> reply && ( reply == 'Y' || reply == 'y') )
cout << "请再输入一组成绩:" << endl;
else
break;
}
return 0;
}
随着条件运算嵌套层数的增加,代码的可读性急剧下降。
练习4.23:因为运算符的优先级问题,下面的表达式无法通过编译。根据4.12节中的表,指出它的问题在哪里?应如何修改?
string s = "word";
string p1 = s + s[ s.size() - 1 ] == 's' ? "" : "s";
优先级:
[ ] 大于 +大于 == 大于 ?: 大于 =
根据题意,应改为:
string p1 = s + (s[ s.size() - 1 ] == 's' ? "" : "s" );
练习4.24:本节的示例程序将成绩划分为high pass、pass和fail三种,它的依据是条件运算符满足右结合律。假如条件运算符满足的是左结合律,求值过程是怎么样的?
无法通过编译,也无法达到预期的题意。
练习4.25:如果一台机器上int占32位、char占8位,用的是Latin-1字符集,其中字符'q'的二进制形式是01110001,那么表达式~'q'<<6的值是多少?
因为位运算符的运算对象是整数类型,所以字符'q'被提升为int。
先对'q'进行求反,结果为1111 1111 1111 1111 1111 1111 1000 1110
再进行移位运算,左移6位,结果为1111 1111 1111 1111 1110 0011 1000 0000;
C++规定整数按照其补码形式存储。对以上结果求补,得到的结果为:
1000 0000 0000 0000 0001 1100 1000 0000 。
转换成十进制的形式为:
-7296
练习4.26:在本节关于测验成绩的例子中,如果使用unsigned int作为quiz1的类型会发生什么情况。
int类型只能确保至少占用16位,而我们至少需要27位。所以会可能造成信息丢失。
练习4.27:下列表达式的结果是什么?
unsigned long ul1 = 3, ul2 = 7;
(a) ul1 & ul2
结果为:3
(b) ul1 | ul2
结果为7
(c) ul1 && ul2
结果为1
(d) ul1 | | ul2
结果为1
练习4.28:编写一段程序,输出每一种内置类型所占空间的大小。
#include
using namespace std;
int main()
{
cout << "类型名称\t" << "所占空间" << endl;
cout << "bool\t\t" << sizeof( bool ) << endl;
cout << "char\t\t" << sizeof( char ) << endl;
cout << "wchar_t\t\t" << sizeof( wchar_t ) << endl;
cout << "char16_t\t" << sizeof( char16_t ) << endl;
cout << "long\t\t" << sizeof( long ) << endl;
cout << "int\t\t" << sizeof( int ) << endl;
cout << "short\t\t" << sizeof( short ) << endl;
cout << "long long\t" << sizeof( long long ) << endl;
cout << "float\t\t" << sizeof( float ) << endl;
cout << "double\t\t" << sizeof( double ) << endl;
cout << "long double\t" << sizeof( long double ) << endl;
return 0;
}
练习4.29:推断下面代码的输出结果并说明理由。实际运行这段程序,结果和你想象的一样吗?如果不一样,为什么?
int x[ 10 ]; int *p = x;
cout << sizeof(x) / sizeof( *x ) << endl;
sizeof( x ) 求x数组所有元素所占的空间大小。
sizeof( *x ) 求x数组第一个元素所占空间的大小。
所以输出结果为:数组的元素的个数。
cout << sizeof( p ) / sizeof( *p ) << endl;
sizeof( p ) 得到p指针本身所占的空间的大小
sizeof( *p ) 得到p所指对象的大小。也就是数组第一个元素的大小。
两者相除没有什么实际意义。
练习4.30:根据4.12节中的表,在下述表达式的适当位置加上括号,使得加上括号之后表达式的含义和原来的含义相同。
(a) sizeof x + y
sizeof( x ) + y
(b) sizeof P->men[i]
sizeof( p->men[i])
(c) sizeof a < b
sizeof(a) < b
(d) sizeof f()
sizeof( f() )
练习4.31:本阶的程序使用了前置版本的递增运算符和递减运算符,解释为什么要用前置版本而不用后置版本。要想使用后置版本的递增运算符需要做哪些改动?使用后置版本重写本节的程序。
前置版本的递增运算符避免了不必要的工作:递增之后直接返回运算对象。后置版本需要将原值存储下来以便于返回这个未修改的内容。
根据观察,本节的程序可以直接将前置版本的递增运算符改成后置递增运算符。
练习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)
{ /* ... ... */}
略了。
练习4.33:说明下面这条表达式的含义。
someValue ? ++x, ++y : --x, --y
第一次我没有对着优先级的表做,我做错了,我忽略了条件运算符的优先级大于逗号运算符。
以下是我做错的内容:
------------------------------------------------------------------------------------------------------------------------------------
条件运算符和逗号运算符都规定了求值顺序。
所以含义:
首先,判断someValue是否为真
若someValue为真,只执行 冒号:前的表达式 ++x, ++y 。x先自增1,y再自增1,然后返回y 。
若someValue为假。只执行 冒号:后的表达式 --x,--y 。 x先自减1,y再自减1,然后返回y 。
--------------------------------------------------------------------------------------------------------------------------------------
正确做法:
表达式的等效变换:
( someValue ? ++x, ++y : --x ) , --y;
所以,含义为:
首先判断someValue是否为真
若someValue为真,执行 ++x, ++y 。
再执行 --y,并且返回y 。
若someValue为假,执行--x,
再执行 --y,并且返回y 。
练习4.34:根据本节给出的变量定义,说明下面的表达式中将发生什么样的类型转换:
(a) if( fval )
fval单精浮点型转换成布尔型
(b) dval = fval + ival
ival转换成单精度浮点型 和 fval相加, 结果再转换成双精度浮点型。
(c) dval + ival * cval
cval转换成int 和 ival相乘,结果再转换成双精度浮点型和dval相加。
练习4.35:假设有如下的定义:
char cval; int ival; unsigned int ui;
float fval; double dval;
请回答在下面的表达式中发生了隐式类型转换吗?如果有,请指出来.
(a) cval = 'a' + 3;
字符'a'转换成int 和 3相加,结果再转化为char并赋值给cval 。
(b) fval = ui - ival*1.0
ival转换成double与1.0相乘,ui转换为double型减去刚才的结果,最终结果转换成单精度浮点型赋值给fval。
(c) dval = ui * fval
ui转换成float 与 fval相乘,相乘的结果转换为double并赋值给dval 。
(d) cval = ival + fval + dval
ival转换成float与fval相加,得出的结果再转换成double和dval相加,最终的结果再转换成char赋值给cval 。
练习4.36:假设i是int类型, d是double类型, 书写表达式 i *= d,使其执行整数的类型的乘法而非浮点型的乘法。
#include
using namespace std;
int main()
{
double d = 3.54;
int i = 3;
int j = 3;
i *= d;
cout << i << endl;
j *= static_cast (d);
cout << j << endl;
return 0;
}
int i; double d; const string *ps; char *pc; void *pv;
(a) pv = ( void * ) ps;
pv = static_cast
(b) i = int ( *pc ); //函数形式的强制类型转换
i = static_cast
(c) pv = &d;
pv = static_cast
(d) pc = ( char * ) pv;
pc= static_cast
练习4.38:说明下面这条表达式的含义。
double slope = static_cast
把 j / i 的值强制转换成double,赋值给slope。