【C++ Primer】第九章 顺序容器 (练习)

C++ Primer 5th 随堂练习

【C++ Primer】第一章 开始 (练习)

【C++ Primer】第二章 变量和基本类型 (练习)

【C++ Primer】第三章 字符串、向量和数组 (练习)

【C++ Primer】第四章 表达式 (练习)

【C++ Primer】第五章 语句 (练习)

【C++ Primer】第六章 函数 (练习)

【C++ Primer】第七章 类 (练习)

【C++ Primer】第八章 IO 库 (练习)

【C++ Primer】第九章 顺序容器 (练习)

【C++ Primer】第十章 泛型算法 (练习)

【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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值