C++ primer错题集

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

  • 错题(b):

原始的错误解题思路:ival++返回的是ival的初始数值,则&&前面判断ival初始数值是否为0。&&后面也在判断ival初始数值是否为0。&&判断两次ival初始值是否为0后把ival数值+1。

纠正:首先运算符左右都使用了同一对象,又改变了该对象的数值,首先要判断求值顺序,符号&&明确规定了先算左侧运算对象。

ival++返回的是ival的初始数值,先判断ival初始数值是否为0。(假设不为0)然后ival的数值+1,原始思路把ival+1的顺序搞错了,不是在最后+1,返回原始数值后+1。然后判断&&右边的运算对象,它虽然是ival,但已经不是原始数值了,前面已经+1了,所以&&右侧对象判断的是ival+1后数值是否为0。

  • 错题(c):

纠正:没有判断左右两侧的求值顺序,因为<=符号没有规定求值顺序,因此该表达式是未定义的。

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

#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::vector;

int main()
{
	vector<int> ivec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	for (auto i : ivec)
	{
		cout << ((i & 0x1) ? i * 2 : i) << " ";
	}
	cout << endl;

	return 0;
}

 在平时判断数值是奇还是偶时一般用模2的方法,今天遇到一种新的方法:i& 0x1。该法将i与16进制1按位与,效果是取i二进制最近一位的数值,是0就是偶数,是1就是奇数。实在是妙,mark一下。(我试了下,可以不用十六进制,八进制,十进制,二进制都有效,不知为何原作者一定要用16进制)

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

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

 这种问题首先应该找一行代码中有多少个表达式,再找连接这些表达式的符号是什么,查表判断符号的优先级,就能判断系统的运行逻辑。

表达式有:string pl、s、s[s.size()-1]、's'、“ "、”s“。其中s[s.size()-1]还可以拆分,但这里可以把它当成整体。连接符号有:=、+、[]、==、? :。优先级顺序:'[]' > '+' > '==' > '?:' > '='。所以该式的运行顺序为:

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

这里字符串(s+s[s.size()-1])和字面值常量字符's'无法比较,因此编译错误。

改正:

string pl=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" ,而字符串字面值的类型是 const char *,非空所以为真。因此第二个条件表达式的结果是 "fail"。这样就出现了自相矛盾的逻辑。

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

在位运算符中,运算符~的优先级高于<<,因此先对q按位求反,因为位运算符的运算对象应该是整数类型,所以字符'q'首先转换为整数类型。如题所示,char占8位而int占32位,所以字符'q'转换后得到00000000 0000000 0000000001110001,按位求反得到11111111 11111111 11111111 10001110;接着执行移位操作,得到11111111 11111111 11100011 10000000。C++规定整数按照其补码形式存储,对上式求补,得到10000000 000000000011100 10000000,即最终结果的二进制形式,转换成十进制形式是-7296。
——————————————
原文链接:https://blog.csdn.net/chenyijun/article/details/120110459

我在这里忘记C++整数按照补码形式存储,所以出错。

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

 解答:

相当于如下代码

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

 符号优先级的题错误率有点高,要注意。

 练习5.4:说明下列例子的含义,如果存在问题,试着修改它。
(a)while(string::iterator iter!= s.end())/*...*/

 控制结构中定义的变量只在内部有效,对于while语句不要在控制结构中定义变量,这样会不断重复定义变量这个过程。

练习5.12:修改统计元音字母的程序,使其能统计以下含有两个字符的字符序列的数量:
ff、fl和fi。

#include <iostream>

using std::cin; using std::cout; using std::endl;

int main()
{
	unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, spaceCnt = 0, tabCnt = 0, newLineCnt = 0, ffCnt = 0, flCnt = 0, fiCnt = 0;
	char ch, prech = '\0';
	while (cin >> std::noskipws >> ch)
	{
		switch (ch)
		{
		case 'a':
		case 'A':
			++aCnt;
			break;
		case 'e':
		case 'E':
			++eCnt;
			break;
		case 'i':
			if (prech == 'f') ++fiCnt;
		case 'I':
			++iCnt;
			break;
		case 'o':
		case 'O':
			++oCnt;
			break;
		case 'u':
		case 'U':
			++uCnt;
			break;
		case ' ':
			++spaceCnt;
			break;
		case '\t':
			++tabCnt;
			break;
		case '\n':
			++newLineCnt;
			break;
		case 'f':
			if (prech == 'f') ++ffCnt;
			break;
		case 'l':
			if (prech == 'f') ++flCnt;
			break;
		}
		prech = ch;
	}

	cout << "Number of vowel a(A): \t" << aCnt << '\n'
		<< "Number of vowel e(E): \t" << eCnt << '\n'
		<< "Number of vowel i(I): \t" << iCnt << '\n'
		<< "Number of vowel o(O): \t" << oCnt << '\n'
		<< "Number of vowel u(U): \t" << uCnt << '\n'
		<< "Number of space: \t" << spaceCnt << '\n'
		<< "Number of tab char: \t" << tabCnt << '\n'
		<< "Number of new line: \t" << newLineCnt << '\n'
		<< "Number of ff: \t" << ffCnt << '\n'
		<< "Number of fl: \t" << flCnt << '\n'
		<< "Number of fi: \t" << fiCnt << endl;

	return 0;
}

 代码中noskipws的意义是为了在读取中内容时不跳过空白。

prech='\0'代表给prech赋值‘\0’,‘\0’在内存中为0的意思,这样在接下来的代码段中prech不会与任意字符重合。(我一直以为'\0'仅仅是在字符数组中作终结符的作用)

在switch语句结束后还有一句prech = ch。(就是这句话我没看见,这段代码疑惑了很久)它将旧字符赋值给prech,ch接收新字符,这样prech就代表ch前面的一个字符。

 练习5.17:假设有两个包含整数的vector对象,编写一段程序,检验其中一个vector对象是否是另一个的前缀。为了实现这一目标,对于两个不等长的vector对象,只需挑出长度较短的那个,把它的所有元素和另一个vector对象比较即可。例如,如果两个vector对象的元素分别是0、1、1、2和0、1、1、2、3、5、8,则程序的返回结果应该为真。

#include <iostream>
#include <vector>

using std::cout; using std::vector;

bool is_prefix(const vector<int>& lhs, const vector<int>& rhs)
{
	if (lhs.size() > rhs.size())
		return is_prefix(rhs, lhs);
	for (unsigned i = 0; i != lhs.size(); ++i)
		if (lhs[i] != rhs[i]) 
			return false;
	return true;
}

int main()
{
	vector<int> l{ 0, 1, 1, 2 };
	vector<int> r{ 0, 1, 1, 2, 3, 5, 8 };
	cout << (is_prefix(r, l) ? "yes\n" : "no\n");

	return 0;
}

在自己写的时候想要挑选出长度最短的那个vector这个逻辑不会写,可以用if else写出来,但是代码过于冗余,基本同样的代码要写两遍。而答案的代码非常巧妙,使用递归问题就迎刃而解。

 练习5.19:编写一段程序,使用do while循环重复地执行下述任务:首先提示用户输入两个string对象,然后挑出较短的那个并输出它。

#include <iostream>
#include <string>

using std::cout; using std::cin; using std::endl; using std::string;

int main()
{
	string rsp;
	do
	{
		cout << "Input two strings: ";
		string str1, str2;
		cin >> str1 >> str2;
		cout << (str1 <= str2 ? str1 : str2)
			<< " is less than the other. " << "\n\n"
			<< "More? Enter yes or no: ";
		cin >> rsp;
	} while (tolower(rsp[0]) == 'y');
	return 0;
}

这里挑选出长度较短的字符串并输出,采用的方法是条件运算符,虽然之前已经学完了,但是做题时没有想起来。

练习6.15 说明find_char 函数中的三个形参为什么是现在的类型,特别说明为什么s是常量引用而occurs是普通引用?为什么s和occurs是引用类型而c不是?如果令s是普通引用会发生什么情况?如果令occurs是常量引用会发生什么情况?

 原代码在《C++ primer》190页。

  • 因为字符串可能很长,因此使用引用避免拷贝;而在函数中我们不希望改变 s 的内容,所以令 s 为常量。
  • occurs 是要传到函数外部的变量,所以使用引用,occurs 的值会改变,所以是普通引用。
  • 因为我们只需要 c 的值,这个实参可能是右值(右值实参无法用于引用形参),所以 c 不能用引用类型。
  • 如果 s 是普通引用,也可能会意外改变原来字符串的内容。
  • occurs 如果是常量引用,那么意味着不能改变它的值,那也就失去意义了。

思考:

引用的作用:当字符串过长时,使用引用可以避免拷贝;可以将函数中的数值传递到外面。

避免使用引用的地方:希望可以改变数值,实参可能是右值。

常量的作用:希望函数中不改变原变量的内容,可以用常量。

 编写一个函数,令其交换两个int指针。

答案很简单,但是我要留下两个我蠢到家的两个错误:

1. C++中的变量一旦建立其所占的内存地址是不会改变的,直到程序结束。我做题时竟然妄想交换两个变量的地址,根据题目要求,应该是建立两个指针变量,交换两个指针变量的内容。

2. 假设有变量i,则&i应该是一个右值,利用右值初始化引入变量时,这个引入变量应该是常量。

下面的函数合法吗?如果合法,说明其功能;如果不合法,修改其中的错误并解释原因。

int &get(int *array, int index) { return array[index]; }
int main()
{
    int ia[10];
    for (int i = 0; i != 10; ++i)
        get(ia, i) = i;
}

 合法。

这个程序总看着很别扭,但是的确合法。

1. get函数中返回的数组元素都是未定义的,虽然是未定义的,但是使用它并不会出错,“未定义”代表的仅仅是其中的内容无法预知,但是变量是可以正常使用的。该程序把数组中未定义的元素提取出来作为左值,并被赋值。

2. get函数中array[index]可以返回数组的元素,如果返回类型是int,则返回右值,如果返回类型是int &,则返回数组本身,左值。

练习7.25 Screen 能安全地依赖于拷贝和赋值操作的默认版本吗?如果能,为什么?如果不能?为什么? 

能。Screen 的成员只有内置类型和 string,因此能安全地依赖于拷贝和赋值操作的默认版本。管理动态内存的类则不能依赖于拷贝和赋值操作的默认版本,而且也应该尽量使用string 和 vector 来避免动态管理内存的复杂性。 

练习7.51 vector 将其单参数的构造函数定义成 explicit 的,而string则不是,你觉得原因何在? 

假如我们有一个这样的函数:

int getSize(const std::vector<int>&);

 如果vector没有将单参数构造函数定义成 explicit 的,我们就可以这样调用:

getSize(34);//这样还很容易分不清34是元素本身,还是容器中有34个元素。

很明显这样调用会让人困惑,函数实际上会初始化一个拥有34个元素的vector的临时量,然后返回34。但是这样没有任何意义。而 string 则不同,string 的单参数构造函数的参数是 const char * ,因此凡是在需要用到 string 的地方都可以用 const char * 来代替(字面值就是 const char *)。(也就是说const char*类型的变量由于构造函数隐式转换的存在,自动转换成string类型)如:

void print(std::string);
print("hello world");

练习 9.26 使用下面代码定义的ia,将ia 拷贝到一个vector和一个list中。是用单迭代器版本的erase从list中删除奇数元素,从vector中删除偶数元素。

int ia[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89 };
vector<int> vec(ia, end(ia));
list<int> lst(vec.begin(), vec.end());

for (auto it = lst.begin(); it != lst.end(); )
	if (*it & 0x1)
		it = lst.erase(it);
	else 
		++it;

for (auto it = vec.begin(); it != vec.end(); )
	if (!(*it & 0x1))
		it = vec.erase(it);
	else
		++it;			

此处关键是在使用for循环时,变量自增不能能向原来一样直接放在条件语句中,erase会改变容器容量,则lst.end()将不再指向真正的窗口尾部后空间。

正确的做法是在不同的情况下分别对迭代器做不同的操作,如果元素满足要求,将迭代器指向erase的返回值,它会返回删除元素的后一个位置,如果元素不满足要求,则迭代器前进一位。

练习9.43 编写一个函数,接受三个string参数是s、oldVal 和newVal。使用迭代器及insert和erase函数将s中所有oldVal替换为newVal。测试你的程序,用它替换通用的简写形式,如,将"tho"替换为"though",将"thru"替换为"through"。 

#include <iostream>
#include <string>

using namespace std;

void replace(string& s, const string& oldVal, const string& newVal)
{
	auto curr = s.begin();
	while (curr != s.end() - oldVal.size())
	{
		if (oldVal == string(curr, curr + oldVal.size()))
		{
			curr = s.erase(curr, curr + oldVal.size());
			curr = s.insert(curr, newVal.begin(), newVal.end());
			curr += newVal.size();
		}
		else
		{
			++curr;
		}
	}
}

int main()
{
	string s("To drive straight thru is a foolish, tho courageous act.");
	replace(s,"tho","though");
	replace(s, "thru", "through");

	cout << s;
}
  1. 利用string的构造函数,来查找string类中的部分
  2. 如果传入字面值字符串,那么函数的传入类型如果是string,一定要是const类型。

练习10.5 在本节对名册(roster)调用equal 的例子中,如果两个名册中保存的都是C风格字符串而不是string,会发生什么?

 C风格字符串是用指向字符的指针表示的,因此会比较两个指针的值(地址),而不会比较这两个字符串的内容。

在buildMap中,如果进行如下改写,会有什么效果?

trans_map[key] = value.substr(1);
//改为
trans_map.insert({key, value.substr(1)});

 当一个转换规则的关键字多次出现的时候,使用下标运算符会保留最后一次添加的规则,而用insert则保留第一次添加的规则。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值