练习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.youkuaiyun.com/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;
}
- 利用string的构造函数,来查找string类中的部分
- 如果传入字面值字符串,那么函数的传入类型如果是string,一定要是const类型。
练习10.5 在本节对名册(roster)调用equal 的例子中,如果两个名册中保存的都是C风格字符串而不是string,会发生什么?
C风格字符串是用指向字符的指针表示的,因此会比较两个指针的值(地址),而不会比较这两个字符串的内容。
在buildMap中,如果进行如下改写,会有什么效果?
trans_map[key] = value.substr(1); //改为 trans_map.insert({key, value.substr(1)});
当一个转换规则的关键字多次出现的时候,使用下标运算符会保留最后一次添加的规则,而用insert则保留第一次添加的规则。