C++ Primer 5th 随堂练习
【C++ Primer】第三章 字符串、向量和数组 (练习)
第九章 顺序容器
练习 9.1
对于下面的程序任务,vector、deque 和 list 哪种容器最为适合?解释你的选择的理由。如果没有哪一种容器优于其他容器,也请解释理由。
- (a) 读取固定数量的单词,将它们按字典序插入到容器中。我们将在下一章中看到,关联容器更适合这个问题。
- (b) 读取未知数量的单词,总是将单词插入到末尾。删除操作在头部进行。
- (c) 从一个文件读取未知数量的整数。将这些数排序,然后将它们打印到标准输出。
解答
练习 9.2
定义一个 list 对象,其元素类型是 int 的 deque。
解答
#include <iostream>
#include <deque>
#include <list>
using namespace std;
int main()
{
list<deque<int>> obj;
return 0;
}
练习 9.3
构成迭代器范围的迭代器有何限制?
解答
练习 9.4
编写函数,接受一对指向 vector<int> 的迭代器和一个 int 值。在两个迭代器指定的范围中查找给定的值,返回一个布尔值来指出是否找到。
解答
bool find_val(vector<int>::const_iterator beg, vector<int>::const_iterator end, int val)
{
while(beg != end) {
if ((*beg) == val) {
return true;
}
++beg;
}
return false;
}
练习 9.5
重写上一题的函数,返回一个迭代器指向找到的元素。注意,程序必须处理未找到给定值的情况。
解答
// 由于需要返回迭代器, 用户可能希望修改元素, 故不再使用常量迭代器
vector<int>::iterator find_val(vector<int>::iterator beg, vector<int>::iterator end, int val)
{
for( ; beg != end; ++beg) { // 改用 for 循环亦可
if ((*beg) == val)
return beg;
}
return end;
}
练习 9.6
下面的程序有何错误?你应该如何修改它?
list<int> lst1; list<int>::iterator iter1 = lst1.begin(), iter2 = lst1.end(); while (iter1 < iter2) /* ... */
解答
迭代器的循环条件应为:
while (iter1 != iter2) /* ... */
练习 9.7
为了索引 int 的 vector 中的元素,应该使用什么类型?
解答
// 既可以
vector<int>::size_type
// 也可以
vector<int>::iterator
vector<int>::const_iterator
练习 9.8
为了读取 string 的 list 中的元素,应该使用什么类型?如果写入 list,又应该使用什么类型?
解答
// 读(2种)
list<string>::const_iterator // 常量迭代器
list<string>::value_type
// 写(2种)
list<string>::iterator // 迭代器
list<string>::reference
练习 9.9
begin 和 cbegin 两个函数有什么不同?
解答
练习 9.10
下面 4 个对象分别是什么类型?
vector<int> v1; const vector<int> v2; auto it1 = v1.begin(), it2 = v2.begin(); auto it3 = v1.cbegin(), it4 = v2.cbegin();
解答
it1 是 vector<int>::iterator 类型的,it2、it3、it4 均为 vector<int>::const_iterator 类型的。
练习 9.11
对 6 种创建和初始化 vector 对象的方法,每一种都给出一个实例。解释每个 vector 包含什么值。
解答
vector<int> vec; // 1 个 0
vector<int> vec(10); // 10 个 0
vector<int> vec(10, 1); // 10 个 1
vector<int> vec{ 1, 2, 3 }; // 1, 2, 3
vector<int> vec(vec2); // 拷贝 vec2 所有的元素
vector<int> vec(vec2.begin(), vec2.end()); // 拷贝 vec2 迭代器范围内所有的元素
练习 9.12
对于接受一个容器创建其拷贝的构造函数,和接受两个迭代器创建拷贝的构造函数,解释它们的不同。
解答
- 接受一个容器创建其拷贝的构造函数,容器类型和元素类型必须都相同。
- 接受两个迭代器创建拷贝的构造函数,只需要元素的类型能够相互转换,容器类型和元素类型可以不同。
练习 9.13
如何从一个 list<int> 初始化一个 vector<double> ?从一个 vector<int> 又该如何创建?编写代码验证你的答案。
解答
// 由于类型不匹配, 不可以使用拷贝初始化
// 但元素类型可以转换, 故需使用迭代器指定元素范围拷贝
list<int> lst = {0, 1, 2, 3, 4};
vector<double> dvec(lst.begin(), lst.end());
vector<int> ivec = {5, 6, 7, 8, 9};
vector<double> dvec2(ivec.begin(), ivec.end());
练习 9.14
编写程序,将一个 list 中的 char * 指针 (指向 C 风格字符串) 元素赋值给一个 vector 中的 string 。
解答
// 容器类型不同, 不能直接赋值
// 元素类型相容, 可以采用范围辅助
list<const char*> lst = {"hello", "world"};
vector<string> vec;
vec.assign(lst.begin(), lst.end());
练习 9.15
编写程序,判定两个 vector<int> 是否相等。
解答
源程序:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec = {1, 2, 3};
vector<int> ivec1 = {1, 2, 3};
vector<int> ivec2 = {1, 2};
vector<int> ivec3 = {1, 2, 4};
vector<int> ivec4 = {1, 3, 2};
cout << (ivec == ivec1) << endl;
cout << (ivec == ivec2) << endl;
cout << (ivec == ivec3) << endl;
cout << (ivec == ivec4) << endl;
ivec1.push_back(4);
ivec1.pop_back();
cout << ivec1.capacity() << " " << ivec1.size() << endl;
cout << (ivec == ivec1) << endl;
return 0;
}
输出:
1
0
0
0
6 3
1
练习 9.16
重写上一题的程序,比较一个 list 中的元素和一个 vector 中的元素。
解答
源程序:
#include <iostream>
#include <vector>
#include <list>
using namespace std;
bool isEqual(vector<int> &ivec, list<int> &ilst)
{
if(ivec.size() != ilst.size()) // 先比较长度
return false;
auto vbeg = ivec.cbegin();
auto vend = ivec.cend();
auto lbeg = ilst.cbegin();
auto lend = ilst.cend();
while((vbeg != vend) || (lbeg != lend)) { // 只写入其一也行
if((*vbeg) != (*lbeg))
return false;
++vbeg;
++lbeg;
}
return true;
}
int main()
{
vector<int> ivec = {1, 2, 3};
list<int> ilst = {1, 2, 3};
cout << isEqual(ivec, ilst) << endl;
ivec.push_back(4);
cout << isEqual(ivec, ilst) << endl;
ivec.pop_back();
ivec.pop_back();
cout << isEqual(ivec, ilst) << endl;
return 0;
}
输出:
1
0
0
练习 9.17
假定 c1 和 c2 是两个容器,下面的比较操作有何限制?
if (c1 < c2)
解答
- c1 和 c2 必须是相同类型的容器,且保存相同类型的元素
- 元素类型要支持小于关系运算符 <
练习 9.18
编写程序,从标准输入读取 string 序列,存入一个 deque 中。编写一个循环,用迭代器打印 deque 中的元素。
解答
源程序:
#include <iostream>
#include <string>
#include <deque>
using namespace std;
int main()
{
// 输入并保存元素
deque<string> deq;
string str;
while(cin >> str) {
deq.push_back(str);
}
// 通过常量迭代器打印元素
for(auto iter = deq.cbegin(); iter != deq.cend(); ++iter) {
cout << (*iter) << endl;
}
system("pause");
return 0;
}
输出:
hello world my dear
^Z
hello
world
my
dear
练习 9.19
重写上一题的程序,用 list 替代 deque。列出程序要做出哪些改变。
解答
- 头文件从 #include <deque> 改为 #include <list>
- 容器类型从 deque<string> 改为 list<string>
练习 9.20
编写程序,从一个 list<int> 拷贝元素到两个 deque 中。值为偶数的所有元素都拷贝到一个 deque 中,而奇数值元素都拷贝到另一个 deque 中。
解答
源程序:
#include <iostream>
#include <list>
#include <deque>
using namespace std;
int main()
{
// 初始化容器
list<int> lst = {0, 1, 2, 3, 4};
deque<int> odd, even;
// 通过迭代器拷贝
auto iter = lst.cbegin();
auto end = lst.cend();
while(iter != end) {
int num = *iter;
if(num % 2 == 1) // 等价于 if(num & 1) 查看最低位, 1:奇数, 0:偶数
odd.push_back(num);
else
even.push_back(num);
++iter;
}
// 等价于
// for (auto i : lst)
// (i & 0x1 ? odd : even).push_back(i);
// 打印结果验证
for(auto o : odd) { cout << o << " "; }
cout << endl;
for(auto e : even) { cout << e << " "; }
cout << endl;
system("pause");
return 0;
}
输出:
1 3
0 2 4
练习 9.21
如果我们将第 308 页中使用 insert 返回值将元素添加到 list 中的循环程序改写为将元素插入到 vector 中,分析循环将如何工作。
解答
源程序:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<string> svec;
string word;
auto iter = svec.begin();
while(cin >> word) {
iter = svec.insert(iter, word);
}
for(auto iter = svec.cbegin(); iter != svec.end(); ++iter) {
cout << *iter << endl;
}
system("pause");
return 0;
}
控制台交互:
hello world my dear
^Z
dear
my
world
hello
练习 9.22
假定 iv 是一个 int 的 vector,下面的程序存在什么错误?你将如何修改?
vector<int>::iterator iter = iv.begin(), mid = iv.begin() + iv.size() / 2; while (iter != mid) if (*iter == some_val) iv.insert(iter, 2 * some_val);
解答
源程序 - 方式一:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> iv = {2, 1, 1, 2, 1};
int some_val = 2; // 目标值
vector<int>::iterator iter = iv.begin();
int org_size = iv.size(), new_elem = 0; // 原始大小, 新元素数
// 每个循环步都重新计算 mid, 保证正确指向 iv 原中央元素
while(iter != (iv.begin() + org_size / 2 + new_elem)) {
if(*iter == some_val) {
iter = iv.insert(iter, 2*some_val); // iter 指向新元素
new_elem++; // 新元素数 +1
iter++; // 将 iter 移到新元素的下一个位置
}
iter++; // 将 iter 推进到旧元素的下一个位置
}
// 用 begin() 获取 vector 首元素迭代器, 遍历 vector 中的所有元素
for(iter = iv.begin(); iter != iv.end(); iter++)
cout << *iter << endl;
system("pause");
return 0;
}
输出:
4
2
1
1
2
1
源程序 - 方式二:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> iv = {2, 1, 1, 2, 1};
int some_val = 2; // 目标值
vector<int>::iterator iter = iv.begin();
int org_size = iv.size(), i = 0; // 原始大小, 循环次数
// 用循环遍历控制循环次数
while(i <= org_size / 2) {
if(*iter == some_val) {
iter = iv.insert(iter, 2*some_val); // iter 指向新元素
iter++; // 将 iter 移到新元素的下一个位置
}
iter++; // 将 iter 推进到旧元素的下一个位置
i++; // 循环次数 +1
}
// 用 begin() 获取 vector 首元素迭代器, 遍历 vector 中的所有元素
for(iter = iv.begin(); iter != iv.end(); iter++)
cout << *iter << endl;
system("pause");
return 0;
}
输出:
4
2
1
1
2
1
练习 9.23
在本节第一个程序中,若 c.size() 为1,则 val、val2、val3 和 val4 的值会是什么?
解答
四个变量的值会相同,均为容器中唯一元素的值。
练习 9.24
编写程序,分别使用 at、下标运算符、front 和 begin 提取一个 vector 中的第一个元素。在一个空 vector 上测试你的程序。
解答
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec;
cout << ivec.at(0) << endl; // terminating with uncaught exception of type std::out_of_range
cout << ivec[0] << endl; // Segmentation fault: 11
cout << ivec.front() << endl; // Segmentation fault: 11
cout << *(ivec.begin()) << endl; // Segmentation fault: 11
system("pause");
return 0;
}
练习 9.25
对于第 312 页中删除一个范围内的元素的程序,如果 elem1 与 elem2 相等会发生什么?如果 elem2 是尾后迭代器,或者 elem1 和 elem2 皆为尾后迭代器,又会发生什么?
解答
练习 9.26
使用下面代码定义的 ia,将 ia 拷贝到一个 vector 和一个 list 中。使用单迭代器版本的 erase 从 list 中删除奇数元素,从 vector 中删除偶数元素。
int ia[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89 };
解答
源程序:
#include <iostream>
#include <vector>
#include <list>
using namespace std;
int main()
{
// 基本数据
int ia[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89 };
vector<int> ivec;
list<int> ilst;
// 数据拷贝
ivec.assign(ia, ia+11);
ilst.assign(ia, ia+11);
// 删除奇数元素
auto iiter = ivec.begin(); // 等价于 vector<int>::iterator iiter = ivec.begin();
while(iiter != ivec.end()) {
if((*iiter) % 2)
iiter = ivec.erase(iiter);
else
++iiter;
}
// 删除偶数元素
for(auto liter = ilst.begin(); liter != ilst.end(); ) { // 等价于 list<int>::iterator liter = ilst.begin();
if(!(*liter % 2))
liter = ilst.erase(liter);
else
++liter;
}
// 打印结果
cout << "even vector: ";
for (auto v : ivec)
cout << v << " ";
cout << endl;
cout << "odd list: ";
for (auto l : ilst)
cout << l << " ";
cout << endl;
system("pause");
return 0;
}
输出:
even vector: 0 2 8
odd list: 1 1 3 5 13 21 55 89
练习 9.27
编写程序,查找并删除 forward_list<int> 中的奇数元素。
解答
源程序:
#include <iostream>
#include <forward_list>
using namespace std;
int main()
{
// 处理
forward_list<int> fwlst = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89 };
forward_list<int>::iterator prev = fwlst.before_begin(); // 伪头部 - 哨兵节点
forward_list<int>::iterator curr = fwlst.begin(); // 真头部 - 首节点
while(curr != fwlst.end()) {
if(*curr % 2 == 1) {
curr = fwlst.erase_after(prev);
}
else {
prev = curr;
++curr;
}
}
// 打印
cout << "even forward_list: ";
for(auto f : fwlst)
cout << f << " ";
cout << endl;
system("pause");
return 0;
}
输出:
even forward_list: 0 2 8
练习 9.28
编写函数,接受一个 forward_list<string> 和两个 string 共三个参数。函数应在链表中查找第一个 string,并将第二个 string 插入到紧接着第一个 string 之后的位置。若第一个 string 未在链表中,则将第二个 string 插入到链表末尾。
解答
源程序:
#include <iostream>
#include <forward_list>
#include <string>
using namespace std;
int main()
{
forward_list<string> f = {"aa", "bb", "cc", "ee"}; // 单链表
string x = "cc", y = "dd"; // 待寻找元素, 待插入元素
forward_list<string>::iterator prev = f.before_begin(); // 哨兵节点
forward_list<string>::iterator curr = f.begin(); // 头节点
while(curr != f.end()) {
if(*curr == x) {
f.insert_after(curr, y); // 中间位置插入插入并打印
for(auto n : f)
cout << n << " ";
cout << endl;
system("pasue");
return 0;
}
prev = curr; // 双指针后移
++curr;
}
f.insert_after(prev, y); // 末尾位置插入元素并打印
for(auto n : f)
cout << n << " ";
cout << endl;
system("pause");
return 0;
}
输出:
aa
bb
cc
dd
ee
练习 9.29
假定 vec 包含 25 个元素,那么 vec.resize(100) 会做什么?如果接下来调用 vec.resize(10) 会做什么?
解答
调用 vec.resize(100) 会将 75 个元素 (0 值初始化) 添加到 vec 的末尾;接下来调用 vec.resize(100) 则会将 vec 末尾的 90 个元素删除。
练习 9.30
接受单个参数的 resize 版本对元素类型有什么限制(如果有的话)?
解答
特别地,对类类型的元素而言,调用 resize 时应提供扩增数及各新增元素的初始值;若未提供初始值,则要求该类型必须提供有一个默认构造函数。
练习 9.31
第 316 页中删除偶数值元素并复制奇数值元素的程序不能用于 list 或 forward_list。为什么?修改程序,使之也能用于这些类型。
解答
对复合赋值运算而言,
iter += 1;
复合赋值语句所适用的容器为 string、vector、deque、array,所以要改为:
++iter; ++iter;
以达到相同的效果。
特别地,对 forward_list (基于单链表结构) 而言,还需要增加一个首前 (off-the-beginning) 迭代器 prev 并同时维护一对前驱/后继节点位置的迭代器:
auto prev = flst.before_begin(); // 哨兵节点
auto curr = flst.begin(); // 首节点
//...
curr = flst.insert_after(prev, *curr);
++curr; ++curr;
++prev; ++prev;
练习 9.32
在第 316 页的程序中,向下面语句这样调用 insert 是否合法?如果不合法,为什么?
iter = vi.insert(iter, *iter++);
解答
练习 9.33
在本节最后一个例子中,如果不将 insert 的结果赋予 begin,将会发生什么?编写程序,去掉此赋值语句,验证你的答案。
解答
源程序:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for(auto iter = data.begin(); iter != data.end(); ++iter) {
if(*iter % 2) {
iter = data.insert(iter, *iter); // 复制奇数元素
++iter;
}
else {
iter = data.erase(iter); // 删除偶数元素
}
}
for (auto i : data)
cout << i << " ";
cout << endl;
system("pause");
return 0;
}
输出:
1 3 5 7 9
练习 9.34
假定 vi 是一个保存 int 的容器,其中有偶数值也有奇数值,分析下面循环的行为,然后编写程序验证你的分析是否正确。
iter = vi.begin(); while (iter != vi.end()) if (*iter % 2) iter = vi.insert(iter, *iter); ++iter;
解答
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vi = {1, 2, 3, 4, 5, 6, 7, 8, 9};
auto iter = vi.begin();
while(iter != vi.end()) {
if(*iter % 2)
iter = vi.insert(iter, *iter);
for(auto begin = vi.cbegin(); begin != vi.cend(); ++begin)
cout << *begin << " ";
cout << endl;
}
++iter;
system("pause");
return 0;
}
// 无限循环输出 ...
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<int> vi = {1, 2, 3, 4, 5, 6, 7, 8, 9};
auto iter = vi.begin();
string tmp;
while(iter != vi.end()) {
if(*iter % 2) {
iter = vi.insert(iter, *iter);
++iter;
}
++iter;
}
for(auto begin = vi.begin(); begin != vi.end(); ++begin)
cout << *begin << " ";
cout << endl;
cin >> tmp;
system("pause");
return 0;
}
1 1 2 3 4 5 6 7 8 9
1 1 2 3 4 5 6 7 8 9
1 1 2 3 3 4 5 6 7 8 9
1 1 2 3 3 4 5 6 7 8 9
1 1 2 3 3 4 5 5 6 7 8 9
1 1 2 3 3 4 5 5 6 7 8 9
1 1 2 3 3 4 5 5 6 7 7 8 9
1 1 2 3 3 4 5 5 6 7 7 8 9
1 1 2 3 3 4 5 5 6 7 7 8 9 9
练习 9.35
解释一个 vector 的 capacity 和 size 有何区别。
解答
- capacity 返回已为 vector 分配内存空间大小 (单位:元素个数),即在不分配新空间的前提下,容器当前最多可以保存多少个元素
- size 返回容器当前已保存元素个数
练习 9.36
一个容器的 capacity 可能小于它的 size 吗?
解答
由练习 9.35 可知不可能。
练习 9.37
为什么 list 或 array 没有 capacity 成员函数?
解答
练习 9.38
编写程序,探究在你的标准实现中,vector 是如何增长的。
解答
源程序:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec;
for(int i = 0; i < 100; ++i) {
cout << "capacity: " << ivec.capacity() << " size: " << ivec.size() << endl;
ivec.push_back(i);
}
system("pause");
return 0;
}
输出:
capacity: 0 size: 0
capacity: 1 size: 1
capacity: 2 size: 2
capacity: 4 size: 3
capacity: 4 size: 4
capacity: 8 size: 5
capacity: 8 size: 6
capacity: 8 size: 7
capacity: 8 size: 8
capacity: 16 size: 9
capacity: 16 size: 10
capacity: 16 size: 11
capacity: 16 size: 12
capacity: 16 size: 13
capacity: 16 size: 14
capacity: 16 size: 15
capacity: 16 size: 16
capacity: 32 size: 17
capacity: 32 size: 18
capacity: 32 size: 19
capacity: 32 size: 20
capacity: 32 size: 21
capacity: 32 size: 22
capacity: 32 size: 23
capacity: 32 size: 24
capacity: 32 size: 25
capacity: 32 size: 26
capacity: 32 size: 27
capacity: 32 size: 28
capacity: 32 size: 29
capacity: 32 size: 30
capacity: 32 size: 31
capacity: 32 size: 32
capacity: 64 size: 33
capacity: 64 size: 34
capacity: 64 size: 35
capacity: 64 size: 36
capacity: 64 size: 37
capacity: 64 size: 38
capacity: 64 size: 39
capacity: 64 size: 40
capacity: 64 size: 41
capacity: 64 size: 42
capacity: 64 size: 43
capacity: 64 size: 44
capacity: 64 size: 45
capacity: 64 size: 46
capacity: 64 size: 47
capacity: 64 size: 48
capacity: 64 size: 49
capacity: 64 size: 50
capacity: 64 size: 51
capacity: 64 size: 52
capacity: 64 size: 53
capacity: 64 size: 54
capacity: 64 size: 55
capacity: 64 size: 56
capacity: 64 size: 57
capacity: 64 size: 58
capacity: 64 size: 59
capacity: 64 size: 60
capacity: 64 size: 61
capacity: 64 size: 62
capacity: 64 size: 63
capacity: 64 size: 64
capacity: 128 size: 65
capacity: 128 size: 66
capacity: 128 size: 67
capacity: 128 size: 68
capacity: 128 size: 69
capacity: 128 size: 70
capacity: 128 size: 71
capacity: 128 size: 72
capacity: 128 size: 73
capacity: 128 size: 74
capacity: 128 size: 75
capacity: 128 size: 76
capacity: 128 size: 77
capacity: 128 size: 78
capacity: 128 size: 79
capacity: 128 size: 80
capacity: 128 size: 81
capacity: 128 size: 82
capacity: 128 size: 83
capacity: 128 size: 84
capacity: 128 size: 85
capacity: 128 size: 86
capacity: 128 size: 87
capacity: 128 size: 88
capacity: 128 size: 89
capacity: 128 size: 90
capacity: 128 size: 91
capacity: 128 size: 92
capacity: 128 size: 93
capacity: 128 size: 94
capacity: 128 size: 95
capacity: 128 size: 96
capacity: 128 size: 97
capacity: 128 size: 98
capacity: 128 size: 99
可见编译器是成倍增长 vector 容量的。
练习 9.39
解释下面程序片段做了什么:
vector<string> svec; svec.reserve(1024); string word; while (cin >> word) svec.push_back(word); svec.resize(svec.size() + svec.size() / 2);
解答
首先,定义一个 vector 并为其分配 1024 个元素的空间。然后,通过一个循环从标准输入中读取字符串并末尾追加到 vector 中。循环结束后,改变 vector 的容器大小 (元素数量) 为原来的 1.5 倍,并使用元素的默认初始化值填充新增元素 (多出的 50%)。若容器大小超过 1024,vector 也会重新分配空间以容纳新增元素。
练习 9.40
如果上一题的程序读入了 256 个词,在 resize 之后容器的 capacity 可能是多少?如果读入了 512 个、1000 个、或1048 个呢?
解答
练习 9.41
编写程序,从一个 vector<char> 初始化一个 string。
解答
源程序:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<char> cvec = {'h', 'e', 'l', 'l', 'o'};
string str(cvec.data(), cvec.size()); // 传入首地址及大小
// 给定迭代器范围亦可 string str(cvec.begin(), cvec.end());
cout << str << endl;
system("pause");
return 0;
}
输出:
hello
练习 9.42
假定你希望每次读取一个字符存入一个 string 中,而且知道最少需要读取 100 个字符,应该如何提高程序的性能?
解答
由于已知至少读取 100 个字符,故可用 reverse 函数预先为 string分配 100 个字符的内存空间,然后再逐个读取字符,并用 push_back 末尾追加到 string 中。
源程序:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void input_string(string s)
{
s.reserve(100); // 预分配 100 个元素的空间
char c;
while(cin >> c)
s.push_back(c);
}
int main()
{
string str;
input_string(str);
cout << str << endl;
system("pause");
return 0;
}
练习 9.43
编写一个函数,接受三个 string 参数 s、oldVal 和 newVal。使用迭代器及 insert 和 erase 函数将 s 中所有 oldVal 替换为 newVal。测试你的程序,用它替换通用的简写形式,如,将 "tho" 替换为 "though",将 "thru" 替换为 "through" 。
解答
源程序:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void replace_string(string &s, const string &oldVal, const string &newVal) // 注意形参类型
{
if(oldVal.empty() || (s.size() < oldVal.size())) // 异常情况
return;
auto iter = s.begin(); // s 迭代器
while(iter <= (s.end()-oldVal.size())) {
auto iter1 = iter; // 拷贝 s 迭代器
auto iter2 = oldVal.begin(); // 新建 oldVal 迭代器
// s 中 iter 开始的子串必须每个字符都与 oldVal 相同
while(iter2 != oldVal.end() && *iter1 == *iter2) {
++iter1;
++iter2;
}
if(iter2 == oldVal.end()) { // oldVal 耗尽 —— 字符串相等
iter = s.erase(iter, iter+oldVal.size()); // 删除 s 中与 oldVal 相等部分 (迭代器范围)
if(newVal.size()) {
auto iter3 = newVal.end(); // 新建newVal 迭代器
do {
--iter3;
iter = s.insert(iter, *iter3);
} while(iter3 > newVal.begin());
}
iter += newVal.size(); // 迭代器移动到新插入内容之后
}
else
++iter; // s 迭代器后移一位
}
}
int main()
{
string s = "the thru tho!";
replace_string(s, "thru", "through");
cout << s << endl;
replace_string(s, "tho", "though");
cout << s << endl;
replace_string(s, "through", "");
cout << s << endl;
system("pause");
return 0;
}
输出:
the through tho!
the through though!
the though!
练习 9.44
重写上一题的函数,这次使用一个下标和 replace。
解答
源程序:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void replace_string(string &s, const string &oldVal, const string &newVal) // 注意形参类型
{
if(oldVal.empty() || (s.size() < oldVal.size())) // 异常情况
return;
auto index = 0;
auto range = s.size() - oldVal.size();
while(index <= range) {
auto index1 = index; // 拷贝当前 s 索引
auto index2 = 0; // 新建 oldVal 索引
// 逐个检查相等与否
while(index2 < oldVal.size() && s[index1] == oldVal[index2]) {
++index1;
++index2;
}
// 相等则替换并后移新增位数, 否则正常后移一位
if(index2 == oldVal.size()) {
s.replace(index, oldVal.size(), newVal);
index += newVal.size();
}
else
++index;
}
}
int main()
{
string s = "the thru tho!";
replace_string(s, "thru", "through");
cout << s << endl;
replace_string(s, "tho", "though");
cout << s << endl;
replace_string(s, "through", "");
cout << s << endl;
system("pause");
return 0;
}
输出:
the through tho!
the through though!
the though!
源程序:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void replace_string(string &s, const string &oldVal, const string &newVal) // 注意形参类型
{
int index = 0;
while((index = s.find(oldVal, index)) != string::npos) { // 在 s 中使用 find() 查找 oldVal
s.replace(index, oldVal.size(), newVal); // 将找到的子串替换为 newVal
index += newVal.size(); // 下标调整到新插入的内容后
}
}
int main()
{
string s = "the thru tho!";
replace_string(s, "thru", "through");
cout << s << endl;
replace_string(s, "tho", "though");
cout << s << endl;
replace_string(s, "through", "");
cout << s << endl;
system("pause");
return 0;
}
输出:
the through tho!
the through though!
the though!
练习 9.45
编写一个函数,接受一个表示名字的 string 参数和两个分别表示前缀(如 "Mr." 或 "Mrs." )和后缀(如 "Jr." 或 "III" )的字符串。使用迭代器及 insert 和 append 函数将前缀和后缀添加到给定的名字中,将生成的新 string 返回。
解答
源程序:
#include <iostream>
#include <string>
using namespace std;
string fix_prefix_and_suffix(string &name, const string &prefix, const string &suffix) // 注意形参类型
{
// name = prefix + name + suffix;
name.insert(name.begin(), 1, ' '); // 注意区别
name.insert(name.begin(), prefix.begin(), prefix.end()); // 不能只写个 prefix
name.append(" "); // 注意区别
name.append(suffix.begin(), suffix.end()); // 不能只写个 suffix
return name;
}
int main()
{
string s = "Wang";
cout << fix_prefix_and_suffix(s, "Dear", "x") << endl;
system("pause");
return 0;
}
输出:
Dear Wang x
练习 9.46
重写上一题的函数,这次使用位置和长度来管理string,并只使用insert。
解答
源程序:
#include <iostream>
#include <string>
using namespace std;
string fix_prefix_and_suffix(string &name, const string &prefix, const string &suffix) // 注意形参类型
{
name.insert(0, " ");
name.insert(0, prefix);
name.insert(name.size(), " ");
name.insert(name.size(), suffix);
return name;
}
int main()
{
string s = "Wang";
cout << fix_prefix_and_suffix(s, "Dear", "x") << endl;
system("pause");
return 0;
}
输出:
Dear Wang x
练习 9.47
编写程序,首先查找 string "ab2c3d7R4E6" 中每个数字字符,然后查找其中每个字母字符。编写两个版本的程序,第一个要使用 find_first_of,第二个要使用 find_first_not_of。
解答
稍微修改题求并混合书写,源程序:
#include <iostream>
#include <string>
using namespace std;
void find_char(string &s, const string& c)
{
int pos1 = 0;
while((pos1 = s.find_first_of(c, pos1)) != string::npos) {
cout << s[pos1] << " ";
++pos1;
}
cout << endl;
for(int pos2 = 0; (pos2 = s.find_first_not_of(c, pos2)) != string::npos; ++pos2) {
cout << s[pos2] << " ";
}
cout << endl;
}
int main()
{
string str = "ab2c3d7R4E6";
string number = "0123456789";
string alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
find_char(str, number);
find_char(str, alpha);
system("pause");
return 0;
}
输出:
2 3 7 4 6
a b c d R E
a b c d R E
2 3 7 4 6
练习 9.48
假定 name 和 numbers 的定义如 325 页所示,numbers.find(name) 返回什么?
解答
练习 9.49
如果一个字母延伸到中线之上,如 d 或 f,则称其有上出头部分(ascender)。如果一个字母延伸到中线之下,如 p 或 g,则称其有下出头部分(descender)。编写程序,读入一个单词文件,输出最长的既不包含上出头部分,也不包含下出头部分的单词。
解答
源程序:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
void find_longest_word(ifstream &in)
{
string s, longest_word;
string alpha = "bdfghjklpqty";
int max_length = 0;
while(in >> s) {
if(s.find_first_of(alpha) != string::npos)
continue;
cout << s << " ";
if(max_length < s.size()) {
max_length = s.size();
longest_word = s;
}
}
cout << "the longest specific string is " << longest_word << endl;
}
int main(int argc, char* argv[])
{
ifstream in(argv[1]); // 打开文件
if(!in) {
cerr << "can not open the input file" << endl;
return -1;
}
find_longest_word(in);
system("pause");
return 0;
}
练习 9.50
编写程序处理一个 vector<string>,其元素都表示整型值。计算 vector 中所有元素之和。修改程序,使之计算表示浮点值的 string 之和。
解答
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> svec = {"123", "+456", "-789"};
int sum = 0;
for(auto iter = svec.cbegin(); iter != svec.cend(); ++iter)
sum += stoi(*iter);
cout << "sum: " << sum << endl;
system("pause");
return 0;
}
sum: -210
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> svec = {"12.3", "-4.56", "+7.8e-2"};
float sum = 0;
for(auto iter = svec.cbegin(); iter != svec.cend(); ++iter)
sum += stof(*iter);
cout << "sum: " << sum << endl;
system("pause");
return 0;
}
sum: 7.818
练习 9.51
设计一个类,它有三个 unsigned 成员,分别表示年、月和日。为其编写构造函数,接受一个表示日期的 string 参数。你的构造函数应该能处理不同的数据格式,如 January 1,1900、1/1/1990、Jan 1 1900 等。
解答
练习 9.52
使用 stack 处理括号化的表达式。当你看到一个左括号,将其记录下来。当你在一个左括号之后看到一个右括号,从 stack 中 pop 对象,直至遇到左括号,将左括号也一起弹出栈。然后将一个值(括号内的运算结果)push到栈中,表示一个括号化的(子)表达式已经处理完毕,被其运算结果所替代。
解答
本题可延伸为 逆波兰求值 及 中缀转后缀表达式。
源程序:
#include <iostream>
#include <string>
#include <stack>
using namespace std;
int main()
{
string expression = "hello world (dear)";
bool bracket_seen = false;
stack<char> stack;
for (const auto &s : expression){
if(s == '(') {
bracket_seen = true;
continue;
}
else if(s == ')')
bracket_seen = false;
if(bracket_seen)
stack.push(s);
}
string reverse_str;
while (!stack.empty()){
reverse_str += stack.top();
stack.pop();
}
expression.replace(expression.find("(") + 1, reverse_str.size(), reverse_str);
cout << expression << endl;
system("pause");
return 0;
}
输出:
hello world (raed)