《C++ Primer》第10章 10.2节习题答案

《C++ Primer》第10章 泛型算法

10.2节初识泛型算法 习题答案

练习10.3:用accumulate求一个vector<int>中的元素之和。

【出题思路】

练习泛型算法的使用。

【解答】

Accumulate的前两个参数仍然是指定范围的一对迭代器,第三个参数是和的初值。

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <numeric>  //accumulate()函数

using namespace std;

int main(int argc, const char *argv[])
{
    ifstream in(argv[1]);
    if(!in)
    {
        cout << "打开输入文件失败!" << endl;
        exit(1);
    }

    vector<int> vi;
    int val;
    while(in >> val)
    {
        vi.push_back(val);
    }

    cout << "序列中整数之和为 " << accumulate(vi.begin(), vi.end(), 0) << endl;

    return 0;
}

data10_03.txt文件内容如下:

25 36 98 74 24 588 25 694 24 258 636 5 25 478 24 58 24 15 47 24

设置命令行参数:

 运行结果:

 练习10.4:假定v是一个vector<double>,那么调用accumulate(v.cbegin(),v.cend(), 0)有何错误(如果存在的话)?

【出题思路】

理解accumulate的第三个参数。

【解答】

accumulate的第三个参数是和的初值,它还决定了函数的返回类型,以及函数中使用哪个加法运算符。

因此,本题中的调用是错误的,第三个参数0告知accumulate,和是整型的,使用整型加法运算符。读者可尝试输入带小数的值,函数返回的是一个整数。正确的调用方法是将0.0作为第三个参数传递给accumulate。读者可以修改、运行程序,观察运行结果。

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <numeric>

using namespace std;

int main(int argc, const char *argv[])
{
    ifstream in(argv[1]);
    if(!in)
    {
        cout << "打开输入文件失败!" << endl;
        exit(1);
    }

    vector<double> vd;
    double val;
    while(in >> val)
    {
        vd.push_back(val);
    }

    cout << "序列中浮点数之和为 " << accumulate(vd.begin(), vd.end(), 0.0) << endl;

    return 0;
}

data10_04.txt的内容如下:

25.3 36.5 98.4 74.7 24.1 588.5 25.4 694.9 24.7 258.2 636.3 5.8 25.7 478.9 24.8 58.7 24.3 

设置命令行参数,运行结果如下:

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

【出题思路】

理解equal如何比较元素。

【解答】

equal使用==运算符比较两个序列中的元素。string类重载了==,可比较两个字符串是否长度相等且其中元素对位相等。而C风格字符串本质是char*类型,用==比较两个char*对象,只是检查两个指针值是否相等,即地址是否相等,而不会比较其中字符是否相同。所以,只有当两个序列中的指针都指向相同的地址时,equal才会返回true。否则,即使字符串内容完全相同,也会返回false。

如下面的程序,q中的每个字符串是p中字符串的拷贝,虽然内容相同,但是不同对象指向不同地址,因此equal判定它们不等。而r中每个指针都是p中指针的拷贝,指向相同的地址,因此equal判定它们相等。

#include <fstream>
#include <vector>
#include <algorithm>
#include <string>

using namespace std;

int main(int argc, const char *argv[])
{
    const char *p[] = {"Hello", "World", "!"};
    const char *q[] = {strdup(p[0]), strdup(p[1]), strdup(p[2])};
    const char *r[] = {p[0], p[1], p[2]};

    cout << equal(begin(p), end(p), q) << endl;
    cout << equal(begin(p), end(p), r) << endl;

    return 0;
}

运行结果:

练习10.6:编写程序,使用fill_n将一个序列中的int值都设置为0。

【出题思路】

练习使用fill_n。

【解答】

fill_n接受一个迭代器,指出起始位置,还接受一个整数,指出设置的元素数目,第三个参数则是要设置的值。

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>

using namespace std;

int main(int argc, const char *argv[])
{
    ifstream in(argv[1]);
    if(!in)
    {
        cout << "打开输入文件失败!" << endl;
        exit(1);
    }

    vector<int> vi;
    int val;
    while(in >> val)
    {
        vi.push_back(val);
        cout << val << " ";
    }
    cout << endl;

    fill_n(vi.begin(), vi.size(), 0);
    for(auto iter = vi.begin(); iter != vi.end(); ++iter)
        cout << *iter << " ";
    cout << endl;

    return 0;
}

 运行结果:

 

练习10.7:下面程序是否有错误?如果有,请改成。

(a) vector<int> vec; list<int> lst; int i;
    while(cin >> i)
        lst.push_back(i);
    copy(lst.cbegin(), lst.cend(), vec.begin());

(b) vector<int> vec;
    vec.reserve(10);    //reverse将在9我.4节(第318页)介绍
    fill_n(vec.begin(), 10, 0);

【出题思路】

进一步理解泛型算法的特点。

【解答】

(a)是错误的。因为泛型算法的一个基本特点是:算法总是通过迭代器操作容器,因此不能直接向/从容器添加、删除元素,无法改变容器大小。因此,对于copy算法,要求目标序列至少要包含与源序列一样多的元素。而此程序中,vec进行缺省初始化,它是空的,copy无法进行。如需改变容器大小,需要使用一类特殊的称为插入器的迭代器。我们可以将第三个参数改为back_inserter(vec),通过它,copy算法即可将lst中元素的拷贝插入到vec的末尾。

#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <algorithm>

using namespace std;

int main(int argc, const char *argv[])
{
    ifstream in(argv[1]);
    if(!in)
    {
        cout << "打开输入文件失败!" << endl;
        exit(1);
    }

    list<int> lst;
    vector<int> vec;
    int val;
    while(in >> val)
    {
        lst.push_back(val);
    }

    copy(lst.begin(), lst.end(), back_inserter(vec));

    cout << equal(lst.begin(), lst.end(), vec.begin()) << endl;

    for(auto iter = vec.begin(); iter != vec.end(); ++iter)
        cout << *iter << " ";
    cout << endl;

    return 0;
}

运行结果:

 (b)这段程序仍然是错误的。粗看起来,reserve为vec分配了至少能容纳10个int的内存空间,调用fill_n时,vec已有足够空间。但泛型算法对于容器的要求并不是有足够的空间,而是足够的元素。此时vec仍然为空,没有任何元素。而算法又不具备向容器添加元素的能力,因此fill_n仍然失败。这里,我们还是需要使用back_inserter来让fill_n有能力向vec添加元素。其实,只有0有能力做到这一点,空间大小并不是问题,容器都能根据需要自动扩容。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(int argc, const char *argv[])
{
    vector<int> vec;
    vec.reserve(10);
    fill_n(back_inserter(vec), 10, 0);

    for(auto iter = vec.begin(); iter != vec.end(); ++iter)
    {
        cout << *iter <<  " ";
    }
    cout << endl;

    return 0;
}

运行结果:

 练习10.8:本节提到过,标准库算法不会改变它们所操作的容器的大小。为什么使用back_inserter不会使这一断言失效?

【出题思路】

深入理解泛型算法的这一特点。

【解答】

严格来说,标准库算法根本不知道有“容器”这个东西。它们只接受迭代器参数,运行于这些迭代器之上,通过这些迭代器来访问元素。因此,当你传递给算法普通迭代器时,这些迭代器只能顺序或随机访问容器中的元素,造成的效果就是算法只能读取元素、改变元素值、移动元素,但无法添加或删除元素。

但当我们传递给算法插入器,例如back_inserter时,由于这类迭代器能调用下层容器的操作来向容器插入元素,造成的算法执行的效果就是向容器中添加了元素。因此,关键要理解:标准库算法从来不直接操作容器,它们只操作迭代器,从而间接访问容器。能不能插入和删除元素,不在于算法,而在于传递给它们的迭代器是否具有这样的能力。

练习10.9:实现你自己的elimDups。测试你的程序,分别在读取输入后、调用unique后以及调用erase后打印vector的内容。

【出题思路】

本题练习重排元素的算法。

【解答】

unique“消除”重复值的方式并不是删除值重复的元素,执行unique后,容器的元素数目并未改变。不重复元素之后位置上的元素的值是未定义的。可以观察在你的编译器上这些元素是什么值,从而推测unique的实现方式。在作者的tdm-gcc8.1上,这些元素的值是被“删除”的重复值red和the,而且两者的顺序与原来是相反的。因此推测,unique的实现方式并不是删除重复值,而是将它们交换到了容器的末尾。

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

inline void output_words(vector<string> &words)
{
    for(auto iter = words.begin(); iter != words.end(); ++iter)
        cout << *iter << " ";
    cout << endl;
}

void elimDups(vector<string> &words)
{
    output_words(words);
    sort(words.begin(), words.end());
    output_words(words);

    auto end_unique = unique(words.begin(), words.end());
    output_words(words);

    words.erase(end_unique, words.end());
    output_words(words);
}

int main(int argc, const char *argv[])
{
    ifstream in(argv[1]);
    if(!in)
    {
        cout << "打开输入文件失败!" << endl;
        exit(1);
    }

    vector<string> words;
    string word;
    while(in >> word)
        words.push_back(word);

    elimDups(words);
    return 0;
}

data10_09.txt内容如下:

While deleting the derived data eventually worked for me, it didn't help until I rebooted the iPad and OS/X, emptied the trash after using the Finder to delete the derived data, and removed a BLE peripheral connected via a USB port. I don't know which of the steps was required--XCode later compiled with the BLE peripheral attached--but once all of those steps were added to deleting the derived data, the project compiled fine

设置命令行参数,运行结果:

 练习10.10:你认为算法不改变容器大小的原因是什么?

【出题思路】

让读者对语言的设计有自己的思考。

【解答】

泛型算法的一大优点是“泛型”,也就是一个算法可用于多种不同的数据类型,算法与所操作的数据结构分离。这对编程效率的提高是非常巨大的。要做到算法与数据结构分离,重要的技术手段就是使用迭代器作为两者的桥梁。算法从不操作具体的容器,从而也就不存在与特定容器绑定,不适用于其他容器的问题。算法只操作迭代器,由迭代器真正实现对容器的访问。不同容器实现自己特定的迭代器(但不同迭代器是相容的),算法操作不同迭代器就实现了对不同容器的访问。

因此,并不是算法应该改变或不该改变容器的问题。为了实现与数据结构的分离,为了实现通用性,算法根本就不该知道容器的存在。算法访问数据的唯一通道是迭代器。是否改变容器大小,完全是迭代器的选择和责任。当我们向fill_n传递back_inserter时,虽然最终效果是向容器添加了新的元素,但对fill_n来说,根本不知道这回事儿。它仍然像往常一样(通过迭代器)向元素赋予新值,只不过这次是通过back_inserter来赋值,而back_inserter选择将新值添加到了容器而已。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值