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

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

10.4节再探迭代器 习题答案

练习10.26:解释三种插入迭代器的不同之处。

【出题思路】理解插入迭代器的概念,以及几种插入迭代器的不同。

【解答】

在书中前文,我们已经学习了一种插入迭代器back_inserter。插入迭代器又称插入器,本质上是一种迭代器适配器。如前所述,标准库算法为了保证通用性,并不直接操作容器,而是通过迭代器来访问容器元素。因此,算法不具备直接向容器插入元素的能力。而插入器正是帮助算法实现向容器插入元素的机制。

除了back_inserter,标准库还提供了另外两种插入器:front_inserter和inserter。三者的差异在于如何向容器插入元素:back_inserter调用push_back,front_inserter调用push_front,inserter则调用insert。显然,这也决定了它们插入元素位置的不同。back_inserter总是插入到容器尾元素之后,front_inserter总是插入到容器首元素之前,而inserter则是插入到给定位置(作为inserter的第二个参数传递给它)之前。因此,需要注意这些特点带来的元素插入效果的差异。例如,使用front_inserter向容器插入一些元素,元素最终在容器中的顺序与插入顺序相反,但back_inserter和inserter则不会有这个问题。

练习10.27:除了unique(参见10.2.3节,第343页)之外,标准库还定义了名为unique_copy的函数,它接受第三个迭代器,表示拷贝不重复元素的目的位置。编写一个程序,使用unique_copy将一个vector中不重复的元素拷贝到一个初始为空的list中。

【出题思路】

本题练习unique_copy的使用,以及使用插入迭代器帮助算法(unique_copy)实现向容器插入新元素。

【解答】

本题要求将vector中不重复元素按原顺序拷贝到空list中,因此使用back_inserter即可。需要注意的是,与unique一样,unique_copy也要求在源容器中重复元素是相邻存放的。因此,若vector未排序且重复元素未相邻存放,unique_copy就会失败。稳妥的方法是先对vector排序。

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

using namespace std;

int main(int argc, const char *argv[])
{
    vector<int> vi = {1, 2, 2, 3, 4, 5, 5, 5, 6};
    list<int> li;

    unique_copy(vi.begin(), vi.end(), back_inserter(li));

    for(auto v: li)
        cout << v << " ";
    cout << endl;
    return 0;
}

运行结果:

 练习10.28:一个vector中保存1到9共9个值,将其拷贝到三个其他容器中。分别使用inserter、back_inserter和front_inserter将元素添加到三个容器中。对每种inserter,估计输出序列是怎样的,运行程序验证你的估计是否正确。

【出题思路】

进一步理解三种插入迭代器的差异,并练习使用它们。

【解答】

若三个目的容器均为空,则显然inserter和back_inserter的输出结果是"1 2 3 4 56 7 8 9",而front_inserter的结果是"9 8 7 6 5 4 3 2 1"。但如果目的容器不空,则inserter的结果取决于传递给它的第二个参数(一个迭代器)指向什么位置。

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

using namespace std;

int main(int argc, const char *argv[])
{
    vector<int> vi = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    list<int> li1, li2, li3;

    unique_copy(vi.begin(), vi.end(), inserter(li1, li1.begin()));
    for(auto v:li1)
        cout << v << " ";
    cout << endl;

    unique_copy(vi.begin(), vi.end(), back_inserter(li2));
    for(auto v:li2)
        cout << v << " ";
    cout << endl;

    unique_copy(vi.begin(), vi.end(), front_inserter(li3));
    for(auto v:li3)
        cout << v << " ";
    cout << endl;

    return 0;
}

运行结果:

 练习10.29:编写程序,使用流迭代器读取一个文本文件,存入一个vector中的string里。

【出题思路】本题练习流迭代器的简单使用。

【解答】

虽然流不是容器,但标准库提供了通过迭代器访问流的方法。声明一个流迭代器时,需指出所绑定的流。对于本题,首先打开一个文本文件,将此文件的流作为参数提供给流迭代器的构造函数即可。当默认构造流迭代器时,得到一个尾后迭代器,对应文件结束。

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

using namespace std;

int main(int argc, const char *argv[])
{
    ifstream in(argv[1]);
    if(!in)
    {
        cout << "打开输入文件失败!" << endl;
        exit(1);
    }
    //创建流迭代器从文件读入字符串
    istream_iterator<string> in_iter(in);
    //尾后迭代器
    istream_iterator<string> eof;
    vector<string> words;

    while(in_iter != eof)
        words.push_back(*in_iter++);    //存入vector并递增迭代器

    for(auto word: words)
        cout << word << " ";
    cout << endl;

    return 0;
}

运行结果:

 练习10.30:使用流迭代器、sort和copy从标准输入读取一个整数序列,将其排序,并将结果写到标准输出。

【出题思路】

本题练习输出流迭代器的使用。

【解答】

使用流迭代器从标准输入读取整数序列的程序与上一题类似,创建流迭代器时指出是int,并用cin代替文件流对象即可。用copy将整数写到标准输出,需要声明一个输出流迭代器,作为第三个参数传递给copy。将cin传递给输出流迭代器的构造函数,copy即可将整数写到标准输出。将" "作为第二个参数传递给输出流迭代器的构造函数,表示在每个输出之后写一个空格,从而将整数分隔开来输出。

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

using namespace std;

int main(int argc, const char *argv[])
{
    cout << "输入整数" << endl;
    //创建流迭代器从标准输入整数
    istream_iterator<int> in_iter(cin);
    //尾后迭代器
    istream_iterator<int> eof;
    vector<int> vi;
    while(in_iter != eof)
        vi.push_back(*in_iter++);   //存入vector并递增迭代器

    sort(vi.begin(), vi.end());

    ostream_iterator<int> out_iter(cout, " ");
    copy(vi.begin(), vi.end(), out_iter);
    cout << endl;
//    for(auto v: vi)
//        cout << v << " ";
//    cout << endl;
    return 0;
}

运行结果:

 练习10.31:修改前一题的程序,使其只打印不重复的元素。你的程序应使用unique_copy(参见10.4.1节,第359页)。

【出题思路】

继续练习输出流迭代器的使用,并复习unique_copy的使用。

【解答】用unique_copy替代上题的copy即可。

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

using namespace std;

int main(int argc, const char *argv[])
{
    cout << "输入整数" << endl;
    //创建流迭代器从标准输入整数
    istream_iterator<int> in_iter(cin);
    //尾后迭代器
    istream_iterator<int> eof;
    vector<int> vi;
    while(in_iter != eof)
        vi.push_back(*in_iter++);   //存入vector并递增迭代器

    sort(vi.begin(), vi.end());

    ostream_iterator<int> out_iter(cout, " ");
    unique_copy(vi.begin(), vi.end(), out_iter);
    cout << endl;
//    for(auto v: vi)
//        cout << v << " ";
//    cout << endl;
    return 0;
}

运行结果:

练习10.32:重写1.6节(第21页)中的书店程序,使用一个vector保存交易记录,使用不同算法完成处理。使用sort和10.3.1节(第345页)中的compareIsbn函数来排序交易记录,然后使用find和accumulate求和。

【出题思路】

继续练习流迭代器的使用,并复习算法的使用。

【解答】

读取交易记录存入vector的程序与前两题类似。

将compareIsbn作为第三个参数传递给sort,即可实现将交易记录按ISBN排序。本题要求将ISBN相同的交易记录累加,而find算法查找与给定值相同的容器元素,需要逐个查找需要累加的元素,显然性能太差。我们使用find_if,并构造如下lambda:

lambda:[item](const Sales_item &item1) {return item1.isbn() != item.isbn();}

作为第三个参数传递给find_if,从而查找到第一个ISBN与当前交易l记录不同的记录r(ISBN相同的范围的尾后位置)。接下来调用accumulate即可实现范围[l, r)间交易记录的累加。最后将l移动到r,继续循环,计算下一段交易记录的查找和累加。

#ifndef SALES_ITEM_H
#define SALES_ITEM_H

#include <iostream>
#include <string>


class Sales_item {
// these declarations are explained section 7.2.1, p. 270
// and in chapter 14, pages 557, 558, 561
friend std::istream& operator>>(std::istream&, Sales_item&);
friend std::ostream& operator<<(std::ostream&, const Sales_item&);
friend bool operator<(const Sales_item&, const Sales_item&);
friend bool
operator==(const Sales_item&, const Sales_item&);
public:
    // constructors are explained in section 7.1.4, pages 262 - 265
    // default constructor needed to initialize members of built-in type
    Sales_item() = default;
    Sales_item(const std::string &book): bookNo(book) { }
    Sales_item(std::istream &is) { is >> *this; }
public:
    // operations on Sales_item objects
    // member binary operator: left-hand operand bound to implicit this pointer
    Sales_item& operator+=(const Sales_item&);

    // operations on Sales_item objects
    std::string isbn() const { return bookNo; }
    double avg_price() const;
// private members as before
private:
    std::string bookNo;      // implicitly initialized to the empty string
    unsigned units_sold = 0; // explicitly initialized
    double revenue = 0.0;
};


// used in chapter 10
inline
bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs)
{
    return lhs.isbn() < rhs.isbn();
}


// nonmember binary operator: must declare a parameter for each operand
Sales_item operator+(const Sales_item&, const Sales_item&);


inline bool
operator==(const Sales_item &lhs, const Sales_item &rhs)
{
    // must be made a friend of Sales_item
    return lhs.units_sold == rhs.units_sold &&
           lhs.revenue == rhs.revenue &&
           lhs.isbn() == rhs.isbn();
}


inline bool
operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
    return !(lhs == rhs); // != defined in terms of operator==
}


// assumes that both objects refer to the same ISBN
Sales_item& Sales_item::operator+=(const Sales_item& rhs)
{
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}


// assumes that both objects refer to the same ISBN
Sales_item
operator+(const Sales_item& lhs, const Sales_item& rhs)
{
    Sales_item ret(lhs);  // copy (|lhs|) into a local object that we'll return
    ret += rhs;           // add in the contents of (|rhs|)
    return ret;           // return (|ret|) by value
}


std::istream&
operator>>(std::istream& in, Sales_item& s)
{
    double price;
    in >> s.bookNo >> s.units_sold >> price;
    // check that the inputs succeeded
    if (in)
        s.revenue = s.units_sold * price;
    else
        s = Sales_item();  // input failed: reset object to default state
    return in;
}


std::ostream&
operator<<(std::ostream& out, const Sales_item& s)
{
    out << s.isbn() << " " << s.units_sold << " "
        << s.revenue << " " << s.avg_price();
    return out;
}


double Sales_item::avg_price() const
{
    if (units_sold)
        return revenue/units_sold;
    else
        return 0;
}

#endif // SALES_ITEM_H
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <iterator>
#include <algorithm>
#include <numeric>
#include "Sales_item.h"

using namespace std;

int main(int argc, const char *argv[])
{
    ifstream in(argv[1]);
    if(!in)
    {
        cout << "打开输入文件失败!" << endl;
        exit(1);
    }
        //创建流迭代器从文件读入字符串
    istream_iterator<string> in_iter(in);
    //尾后迭代器
    istream_iterator<string> eof;
    vector<Sales_item> vs;
    //读入交易记录,存入vector
    while(in_iter != eof)
        vs.push_back(*in_iter++);

    if(vs.empty())
    {
        //设没有输入!读取数据错误
        cerr << "No data?!" << endl;
        return -1;//表示读取数据失败
    }

    //将交易记录接ISBN排序
    sort(vs.begin(), vs.end(), compareIsbn);
    auto start = vs.begin();
    while(start != vs.end())
    {
        auto item = *start;//相同ISBN的交易记录中的第一条
        //在后续记录中查找第一个ISBN与item不同者
        auto r = find_if(start + 1, vs.end(), [item] (const Sales_item &item1) {return item1.isbn() != item.isbn();});
        //将范围[start, r)间的交易记录累加并输出
        cout << accumulate(start + 1, r, item) << endl;
        //start指向下一个段交易记录中的第一个
        start = r;
    }
    cout << endl;

    return 0;
}

data10_32.txt文件内容为:

0-201-70353-X 24.99
0-201-82470-1 45.39
0-201-88954-4 15.00
0-201-88954-4 12.00
0-201-88954-4 12.00
0-201-88954-4 12.00
0-399-82477-1 45.39
0-399-82477-1 45.39
0-201-78345-X 20.00
0-201-78345-X 25.00

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

 练习10.33:编写程序,接受三个参数:一个输入文件和两个输出文件的文件名。输入文件保存的应该是整数。使用istream_iterator读取输入文件。使用ostream_iterator将奇数写入第一个输出文件,每个值之后都跟一个空格。将偶数写入第二个输出文件,每个值都独占一行。

【出题思路】

本题通过一个稍大的例子巩固输入和输出流迭代器的使用。

【解答】

程序从命令行接受三个文件名参数,因此程序首先判断参数数目是否为4(包括程序名)。

然后依次打开输入文件和两个输出文件,再用这三个流初始化一个输入流迭代器和两个输出流迭代器。注意,第一个流迭代器输出以空格间隔的奇数,将" "作为第二个参数传递给构造函数;第二个流迭代器输出以换行间隔的偶数,将"\n"作为第二个参数传递给构造函数。

随后在循环中通过输入流迭代器读取文件中的整数,直至到达文件末尾。读入每个整数后,判断它是奇数还是偶数,分别写入两个输出文件。

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

using namespace std;

int main(int argc, const char *argv[])
{
    if(4 != argc)
    {
        cout << "用法:在命令行参数里添加 in_file out_file1 out_file2" << endl;
        return -1;
    }

    ifstream in(argv[1]);
    if(!in)
    {
        cout << "打开输入文件失败!" << endl;
        exit(1);
    }

    ofstream out1(argv[2]);
    if(!out1)
    {
        cout << "打开输出文件1失败!" << endl;
        exit(1);
    }

    ofstream out2(argv[3]);
    if(!out2)
    {
        cout << "打开输出文件2失败!" << endl;
        exit(1);
    }

    //创建流迭代器从文件读入整数
    istream_iterator<int> in_iter(in);
    //尾后迭代器
    istream_iterator<int> eof;
    //第一个输出文件以空格间隔整数
    ostream_iterator<int> out_iter1(out1, " ");
    //第二个输出文件以换行间隔整数
    ostream_iterator<int> out_iter2(out2, "\n");
    while(in_iter != eof)
    {
        if(*in_iter & 1)                //奇数写入第一个输出文件
            *out_iter1++ = *in_iter;
        else                            //偶数写入第二个输出文件
            *out_iter2++ = *in_iter;
        in_iter++;
    }
    cout << "run success==========" << endl;
    return 0;
}

设置命令行参数,

运行结果如下:

练习10.34:使用reverse_iterator逆序打印一个vector。

【出题思路】

本题练习反向迭代器的简单使用。

【解答】

我们可以用(c)rbegin获取反向遍历的起始位置(其实是容器的末尾元素位置),用(c)rend获取尾后迭代器(首元素之前的位置)。通过这两个迭代器控制循环,即可实现对容器的反向遍历。注意,循环中向前移动迭代器仍然用++,而非--。

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

using namespace std;

int main(int argc, const char *argv[])
{
    if(2 != argc)
    {
        cout << "在命令行添加命令参数 文件名路径" << endl;
        return -1;
    }

    ifstream in(argv[1]);
    if(!in)
    {
        cout << "打开输入文件失败!" << endl;
        exit(1);
    }

    vector<int> vi;
    int v;
    while(in >> v)//从文件中读取整数
    {
        vi.push_back(v);
    }
    cout << "原始顺序:" << endl;
    for(auto iter = vi.cbegin(); iter != vi.cend(); ++iter)
        cout << *iter << " ";
    cout << endl;
    cout << "新的顺序:" << endl;
    for(auto r_iter = vi.crbegin(); r_iter != vi.crend(); ++r_iter)
        cout << *r_iter << " ";
    cout << endl;

    return 0;
}

 data10_34.txt文件内容如下:

12 3 65 69 8 74 96 64 52 15 16 68

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

 练习10.35:使用普通迭代器逆序打印一个vector。

【出题思路】

体会反向迭代器和普通迭代器的差异。

【解答】

若使用普通迭代器反向遍历容器,首先通过cend获得容器的尾后迭代器,循环中递减该迭代器,直到它与begin相等为止。但需要注意的是,遍历所用迭代器的初值为尾后位置,终值为begin之后的位置。也就是说,在每个循环步中,它指向的都是我们要访问的元素之后的位置。因此,我们在循环中首先将其递减,然后通过它访问容器元素,而在循环语句的第三个表达式中就不再递减迭代器了。显然,对于反向遍历容器,使用反向迭代器比普通迭代器更清晰易懂。

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

using namespace std;

int main(int argc, const char *argv[])
{
    if(2 != argc)
    {
        cout << "在命令行添加命令参数 文件名路径" << endl;
        return -1;
    }

    ifstream in(argv[1]);
    if(!in)
    {
        cout << "打开输入文件失败!" << endl;
        exit(1);
    }

    vector<int> vi;
    int v;
    while(in >> v)//从文件中读取整数
    {
        vi.push_back(v);
    }
    cout << "原始顺序:" << endl;
    for(auto iter = vi.cbegin(); iter != vi.cend(); ++iter)
        cout << *iter << " ";
    cout << endl;
    cout << "新的顺序:" << endl;
    for(auto r_iter = vi.cend(); r_iter != vi.begin();)
        cout << *(--r_iter) << " ";
    cout << endl;

    return 0;
}

运行结果:

 练习10.36:使用find在一个int的list中查找最后一个值为0的元素。

【出题思路】

练习反向迭代器和算法的结合。

【解答】

借助反向迭代器,可以扩展算法的能力。例如,使用普通迭代器,find能查找给定值在容器中第一次出现的位置。如果要查找最后一次出现的位置,还使用普通迭代器的话,代码会很复杂。但借助反向迭代器,find可以逆序遍历容器中的元素,从而“第一次出现位置”实际上也就是正常顺序的最后一次出现位置了。

注意:

1.由于list是链表数据结构,元素不连续存储,其迭代器不支持算术运算。因此,程序中用一个循环来计数位置编号。

2.由于程序计数的是正向位置编号,因此,需要将find找到的反向迭代器last_z转换为普通迭代器(使用base成员函数)。但要注意,反向迭代器与普通迭代器的转换是左闭合区间的转换,而非精确位置的转换。last_z.base()指向的并非最后一个0,而是它靠近容器尾方向的邻居。因此,首先将last_z向容器首方向推进一个位置(++),然后再调用base,得到的就是指向最后一个0的普通迭代器了。读着可尝试对本例画出类似图10.2所示的迭代器位置关系图。

#include <iostream>
#include <list>
#include <algorithm>

using namespace std;

int main()
{
    list<int> li = {0, 1, 2, 0, 3, 4, 5, 0, 6};
    //利用反向迭代器查找最后一个0
    auto last_z = find(li.rbegin(), li.rend(), 0);
    //将迭代器向链表头方向推进一个位置,转换为普通迭代器时,将回到最后一个0的位置
    last_z++;
    int p = 1;
    //用base将last_z转换为普通迭代器,从链表头开始遍历,计数最后一个0的编号
    for(auto iter = li.begin(); iter != last_z.base(); iter++, p++)
        ;

    if(p >= li.size())  //未找到0
        cout << "容器中没有0" << endl;
    else
        cout << "最后一个0在第 " << p << " 个位置。" << endl;

    return 0;
}

运行结果:

 

练习10.37:给定一个包含10个元素的vector,将位置3到7之间的元素按逆序拷贝到一个list中。

【出题思路】

深入理解反向迭代器和普通迭代器间的差异及相互转换。

【解答】

反向迭代器和普通迭代器的转换是左闭合区间的转换。

对10个元素的vector vi,包含位置3〜7之间元素的迭代器区间如下所示:

0    1    2    3    4    5    6    7    8    9

            i1                            i2

第一个迭代器是vi.begin()+2,第二个迭代器指向位置8,即vi.begin()+7,当将这两个迭代器转换为反向迭代器时,位置如下:

0    1    2    3    4    5    6    7    8    9

     re                            rb

虽然与正向迭代器的位置不同,但左闭合区间[rb, re)仍然对应位置3〜7之间的元素。显然,普通-反向迭代器间的这种错位,恰恰是因为标准库的范围概念是左闭合区间造成的。

另外,注意back_inserter和流迭代器的使用。

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

using namespace std;

int main()
{
    ostream_iterator<int> out_iter(cout, " ");
    vector<int> vi = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    //用流迭代器和copy输出int序列
    copy(vi.begin(), vi.end(), out_iter);
    cout << endl;

    list<int> li;
    //vi[2],也就是第3个元素的位置转换为反向迭代器
    vector<int>::reverse_iterator re(vi.begin() + 2);
    //vi[7],也就是第8个元素的位置转换为反向迭代器
    vector<int>::reverse_iterator rb(vi.begin() + 7);
    //用反向迭代器将元素逆序拷贝到list
    copy(rb, re, back_inserter(li));
    copy(li.begin(), li.end(), out_iter);
    cout << endl;
    return 0;
}

运行结果:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值