C++primer学习笔记及作业答案之第四章

本文详细介绍了C++中运算符的基础知识,包括算术、逻辑与关系、赋值、递增递减、成员访问、条件、位、sizeof、逗号和类型转换等运算符的用法和特性。同时,给出了课后习题解答,帮助读者巩固理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

笔记:

4.1 基础

对于那些没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误并产生未定义的行为。

算术运算符的运算对象和求值结果都是右值。

4.2 算术运算符

4.3 逻辑与关系运算符

4.4 赋值运算符

赋值运算符的优先级比关系运算符低,因此在条件语句中,赋值部分通常应该加上括号。

赋值运算符的左侧对象必须是左值,右侧运算对象可以是左值,也可以是右值。

4.5 递增和递减运算符

除非必须,否则尽量使用递增运算符和递减运算符的前置版本。

++i,将i的值加一之后返回i的值;i++,将i的值加一之后,返回i初始值的副本。

4.6 成员访问运算符

箭头运算符作用于一个指针类型的运算对象,结果是一个左值。

4.7 条件运算符

条件运算符的优先级非常低,但是比赋值运算符的优先级高。

4.8 位运算符

逻辑与&&,逻辑或||,逻辑非!;位与&,位或|,位非~。

移位运算符的优先级比算数运算符的优先级低,但比关系运算符、赋值运算符和条件运算符的优先级高。

4.9 sizeof运算符

返回一条表达式或者一个类型名字所占的字节数。

vector对象所占的默认字节为16,string对象所占的默认字节为28。在作者本人的机器中。

4.10 逗号运算符

4.11 类型转换

强制类型转换用static_cast,对于底层const用const_cast。

课后习题:

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

答:105。

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

(a) *vec.begin()
(b) *vec.begin() + 1
//这里涉及到的运算符的优先级关系为首先是成员选择运算符.、函数调用运算符()
//其次是解引用运算符*,最后是加法运算符+。因此可以改成:
(a) * (vec.beging())
(b) (* (vec.begin())) + 1

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

答:这是可以接受的。当C++中采⽤这种策略时,编译器可根据需要来即时抉择求值顺序,从⽽实现尽可能高的执行效率,并且程序员可以通过一些技巧来规避潜在执行缺陷。

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

12 / 3 * 4 + 5 * 15 + 24 % 4 / 2
//添加括号说明运算顺序,执行结果为91
((12 / 3) * 4)) + (5 * 15) + ((24 % 4) / 2)

练习 4.5:写出下列表达式的求值结果。

(a) -30 * 3 + 21 / 5    //-86
(b) -30 + 3 * 21 / 5    //-18
(c) 30 / 3 * 21 % 5     //0
(d) -30 / 3 * 21 % 4    //-2

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

num % 2 == 0
上述式子为真时,num是偶数;为假时,num是奇数。

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

答:当计算的结果超出类型所能表示的范围时,产生溢出。

int i = 2147483647 + 1 ;
int j = -100000 * 300000;
int k = 2015 * 2015 * 2015 * 2015;

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

答:逻辑与、逻辑非运算符都是先计算左侧运算对象的值在计算右侧运算对象的值,并当且仅当左侧运算对象的值不能确定表达式结果时才计算右侧运算对象的值。逻辑与、逻辑或是C++中少数规定了求值顺序的运算符,除此之外,还有条件运算符?:,和逗号运算符,。对于相等性运算符,C++没有规定其求值顺序。

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

const char *cp = "Hello Word";
if (cp && *cp)
//首先检查cp,即指针的状态是否是有效的,其次在检查*cp,即cp指针指向的字符是否为空字符‘\0’。
//在本例中,cp是一个指向字符串的指针,是一个有效指针,所以为真;
//*cp为‘H’,也为真,所以整个表达式的结果为真。

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

// 练习 4.10 
#include <iostream>
#include <vector>

using namespace std;

int main()
{
	int num;
	vector<int> a;
	//当输入流有效并且输入的数组不等于42时,将数字添加入vector对象中
	while (cin >> num && num != 42)
	{
		a.push_back(num);
	}
	//使用范围for循环输出vector对象中的元素
	for (auto &val : a)
		cout << val << " ";
	cout << endl;
	system("pause");
	return 0;
}

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

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,所以int向布尔转换的时候为1,所以表达式为真。

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

double dval; int ival; int *pi;
dval = ival = pi = 0;
//赋值运算符满足右结合律,因此首先将0赋值给指针pi,然后又将整型指针的值赋给int型的ival,这是非法的。
因此上述表达式是非法的。无法将指针类型转换为int类型。

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

(a) if (p = getPtr() != 0)
(b) if (i = 1024)
//(a) if((p = getPtr()) != 0) 赋值运算符的优先级低于关系运算符
//(b) if (i == 1024)

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

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

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

答:第一:无法输出vector对象的第一个元素,第二当所有元素都不为负的时候,移动到最后一个元素时,程序试图继续向前移动迭代器并解引用一个根本不存在的元素。

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

(a) ptr != 0 && *ptr++
(b) ival++ && ival
(c) vec[ival++] <= vec[ival]
//首先判断指针ptr是否为空,如果不为空,继续判断ptr指向的元素是否为0,然后令ptr加一。这里存在一定的风险,如果ptr是指向一个int型元素的指针,那么ptr+1将会引发错误。
//首先判断ival是否为0,如果不是的话,在判断ival+1是否为零。
//首先判断vec[ival]是否为0,如果不是的话,在判断vec[ival+1]是否为0。

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

(a) *iter++;           //解应用iter,再令iter的位置向后移动一位。
(b) (*iter)++;         //非法,首先解引用得到vector中的元素,但是string类型没有后置递增操作
(c) *iter.empty();     //非法,解引用运算符优先级低于点运算符,因此iter试图访问empty成员,出错
(d) iter->empty();     //iter解引用后调用empty成员,检查iter指向的元素是否为空
(e) ++*iter;           //非法,先解引用得到string类型的元素,但是没有前置递增操作
(f) iter++->empty();   //解引用当前迭代器的位置的对象内容,判断是否为空,在将迭代器后移一位

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

// 练习 4.21

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> a;
	//给vector对象中的元素赋值
	for (int i = 0; i < 10; ++i)
	{
		a.push_back(i);
	}
	cout << "Vector中初始的元素为: ";
	for (auto &val : a)
		cout << val << " ";
	cout << endl;
	//将vector中的奇数数值翻倍
	for (auto it = a.begin(); it != a.end(); ++it)
	{
		*it = (*it % 2 == 1) ? (*it * 2) : *it;
	}
	cout << "Vector中现在的元素为: ";
	for (auto &val : a)
		cout << val << " ";
	cout << endl;
	system("pause");
	return 0;
}

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

// 练习 4.22

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main()
{
	int grade;
	cout << "请输入一个分数: " << endl;
	cin >> grade;
	string finalgrade;
	//使用条件运算符判断成绩位于哪个阶段
	finalgrade = (grade > 90) ? "high pass" : (grade > 75) ? "pass" : (grade > 60) ? "low pass" : "fail";
	cout << finalgrade << endl;
	system("pause");
	return 0;
}
// 练习 4.22

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main()
{
	int grade;
	cout << "请输入一个分数: " << endl;
	cin >> grade;
	string finalgrade;
	//使用for循环判断成绩位于哪个阶段
	if (grade > 90)
		finalgrade = "high pass";
	else if (grade > 75)
		finalgrade = "pass";
	else if (grade >= 60)
		finalgrade = "low pass";
	else
		finalgrade = "fail";

	cout << finalgrade << endl;
	system("pause");
	return 0;
}

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

string s = "word";
string p1 = s + s[s.size() -1 ] == 's' ? "" : "s" ;
//运算符的优先级高低分别为:加法运算符、相等运算符、条件运算符、赋值运算符;
//因此上述表达式先将s与s的最后一个字符相加得到一个新的字符串,再将它与‘s’进行比较,这是非法的。
//应改成:
string p1 = s + (s[s.size() - 1] == 's' ? "" : "s") ;

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

finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass";
//如果是左结合律的话,则上面的式子等价于下面的式子,意思为如果grade>90,则输出high pass,
//如果grade<90,则输出grade<60,这是非法的。
finalgrde = ((grade > 90) ? "high pass" : (grade < 60)) ? "fail" : "pass";

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

答:取非运算符~的优先级高于<<,因此先将‘q’取反之后在左移6位。因为char占8位,int占16位,因此先将char转换成16位。00000000 00000000 00000000 01110001,取非为,11111111 11111111 11111111 10001110,左移6位之后得到,11111111 11111111 11100011 10000000,整数在计算机中都是已补码的形式存储的,即100000000 00000000 00011100 10000000,即十进制的-7296。(正数的补码就是本身,负数的补码是符号位不变,其他位取反之后再加一。)

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

答:C++规定unsigned int所占的位数最少为16位,因此不足以存放全部学生的信息,从而造成信息丢失,无法完成题目要求的任务。

练习 4.27:下列表达式的结果是什么?

unsigned long ull = 3, u12 = 7;
(a) ull & u12;
(b) u11 | u12;
(c) u11 && u12;
(d) u11 || u12;
//00000000 00000000 00000000 00000011 与 00000000 00000000 00000000 00000111做位与运算得到
//00000000 00000000 00000000 00000011,即十进制的3。

//上式做位或运算,得到 00000000 00000000 00000000 00000111,即十进制的7。

//3和7做逻辑与运算,结果为真,即1。

//3和7做逻辑或运算,结果为真,即1。

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

//练习 4.28
//C++只规定了每种类型所占的最小空间,类型实际占多少空间依赖具体实现环境
//只有int比较特殊,规定最小为16位,一般为32位,int float long 都为32位在我们的机器上。
//sizeof 输出的是所占字节数

#include <iostream>

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 << "char32_t\t" << sizeof(char32_t) << endl;
	cout << "short\t\t" << sizeof(short) << endl;
	cout << "int\t\t" << sizeof(int) << endl;
	cout << "long\t\t" << sizeof(long) << 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;

	system("pause");
	return 0;
}

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

int x[10]; 
int *p = x;
cout << sizeof(x) / sizeof(*x) << endl;   
//输出40 / 4 = 10,求数组容量的一种常规方法。
cout << sizeof(p) / sizeof(*p) << endl;   
//输出4 / 4 = 1,指针是int型,也占4个字节。

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

(a) sizeof x + y
//sizeof优先级大于加法,(sizeof x) + y
(b) sizeof p->men[i]
//成员选择->运算符优先级大于sizeof,sizeof (p->men[i])
(c) sizeof a < b
//sizeof优先级大于<,(sizeof a) < b
(d) sizeof f()
//sizeof优先级小于函数调用,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)
{ /*...*/ }
//首先定义了一个常量表达式size,然后用它作为数组的维度创建了一个整形数组ia,并进行了初始化。
for循环语句,首先定义了一个指向数组的指针,并令ix = 0,循环的判断条件是ix != size,
和指针没有指向数组尾元素的下一位置时,令ix和指针递增。

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

someValue ? ++x, ++y : --x, --y
//因为条件运算符的优先级大于逗号运算符,所以上式等价于 (someVlaue ? ++x, ++y : --x), --y
//所以如果someValue为真时,执行++x, ++y,在执行--y。
//someValue为假时,执行--x,在执行--y。

练习 4.34:根据本节给出的变量定义,说明在下面的表达式中将发生什么样的类型转换,需要注意每种运算符遵循的是左结合律还是右结合律。

(a) if (fval) 
//float转换成bool型
(b) dval = fval + ival;
//int首先转换成float,然后float转换成double
(c) dval + ival * cval;
//char首先转换成int,然后int转换成double

练习 4.35:假设有如下的定义,请问答在下面的表达式中发生了隐式类型转换吗?如果有,指出来。

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

(a) cval = 'a' + 3;
//首先char转换成int,相加后得到的int在转换成char。
(b) fval = ui - ival * 1.0;
//int首先转换成double,相乘得到的double,然后ui转换为double,最终相减的结果在转换为float。
(c)dval = ui * fval;
//unsigned int 转化成float,之后在转化为double
(d)cval = ival + fval + dval;
//int首先转化成float,在传成double,再转成char

练习 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;
pv = static_cast<void*> (const_cast<string> (ps))
(b) i = int(*pc);
i = static_cast<int>(*pc)
(c) pv = &d;
pv = static_cast<void*>(&d)
(d) pc = (char*) pv;
pc = static_cast<char*>(pv)

练习 4.38:说明下面这条表达式的含义。

double slope = static_cast<double>(j/i);
//将j/i的值强制类型转换成double,然后赋值给slope,请注意:如果i和j都是int,
//那么j/i的结果任然是int,即使除不尽,也是保留整数部分,最后在转换为double。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值