zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C++ Primer Plus习题及答案-第十六章

C++ 答案 Plus 习题 primer 第十六章
2023-06-13 09:17:21 时间

习题选自:C++ Primer Plus(第六版) 内容仅供参考,如有错误,欢迎指正 ! 智能指针模板类

复习题

1. 考虑下面的类声明:

class RQ1
{
  private:
    char * st; // points to C-style string
  public:
    RQ1() { st = new char [1]; strcpy(st,""); }
    RQ1(const char * s)
    {st = new char [strlen(s) + 1]; strcpy(st, s); }
    RQ1(const RQ1 & rq)
    {st = new char [strlen(rq.st) + 1]; strcpy(st, rq.st); }
    ~RQ1() {delete [] st};
    RQ & operator=(const RQ & rq);
    // more stuff
};

将它转换为使用string对象的声明。哪些方法不再需要显式定义?

class RQ1
{
  private:
    string st; 
  public:
    RQ1():st("") {}
    RQ1(const char * s):st(s){}
    ~RQ1() {};
    // more stuff
};

因为string对象提供了自己的内存管理功能,所以不需要再显式定义复制构造函数、析构程序和赋值运算符。

2. 在易于使用方面,指出string对象至少两个优于C-风格字符串的地方。

string对象有自己的内存管理功能那个,不需要担心字符串超出存储容量的问题,而且可以将一个string对象赋值给另外一个string对象。

3. 编写一个函数,用string对象作为参数,将string对象转换为全部大写。

#include<string>
#include<cctype>
void ToUpper(std::string& s)
{
    for(int i = 0; i < s.size(); ++i)
        str[i] = toupper(str[i];)
}

4. 从概念上或语法上说,下面哪个不是正确使用auto_ptr的方法(假设已经包含了所需的头文件)?

auto_ptr<int> pia(new int[20]);
auto_ptr<string> (new string);
int rigue = 7;
auto_ptr<int>pr(&rigue);
auto_ptr dbl (new double);
auto_ptr<int> pia(new int[20]);  //错误,应该使用new,而不是new[]
auto_ptr<string> (new string);   //错误,没有给指针命名
int rigue = 7;                   
auto_ptr<int>pr(&rigue);         //错误,不能指向自动变量,因为rigue不是new出来的
auto_ptr dbl (new double);       //错误,缺少<double>,应为auto_ptr<double> dbl (new double);

5. 如果可以生成一个存储高尔夫球棍(而不是数字)的栈,为何它(从概念上说)是一个坏的高尔夫袋子?

栈的后入先出(LIFO)的特性意味着在取出目标球棍,需要删除在目标球棍入袋(入栈)之后入袋的所有球棍。

6. 为什么说对于逐洞记录高尔夫成绩来说,set容器是糟糕的选择?

set集合只存储每个值的一个拷贝,即具备去重机制,因此多个相同得分会被存储为一个得分。

7. 既然指针是一个迭代器,为什么STL设计人员没有简单地使用指针来代替迭代器呢?

迭代器的访问方式就是把不同集合的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果。使用迭代器使得能够使用接口类似于指针的对象遍历不以数组方式组织的数据,例如双向链表中的数据。

8. 为什么STL设计人员仅定义了迭代器基类,而使用继承来派生其他迭代器类型的类,并根据这些迭代器类来表示算法?

STL方法使得可以将STL函数用于指向常规数组的常规指针以及指向STL容器类的迭代器,因此提高了通用性。

9. 给出vector对象比常规数组方便的3个例子。

一个vector对象可以赋值给另外一个vector对象;vector可以自己管理自己的内存,自动调整长度;vector可以使用at()方法,可以自动检查边界。

10. 如果程序清单16.9是使用list(而不是vector)实现的,则该程序的哪些部分将是非法的?非法部分能够轻松修复吗?如果可以,如何修复呢?

list不支持随机访问,因此公用sort方法无法使用,需要使用list自己的sort方法。list不支持随机打乱,可以将其放到vector,然后使用vector打乱,然后再转回list

11. 假设有程序清单16.15所示的函数符TooBig,下面的代码有何功能?赋给bo的是什么值?

bool bo = TooBig<int>(10)(15);

TooBig的定义如下:

template <class T>
class TooBig {
private:
    T cutoff;

public:
    TooBig(const T& t):cutoff(t){}
    bool operator()(const T& v){return v > cutoff; }
};

对于TooBig<int>(10)(15)Tint,10是用来初始化cutoff,15对应的是operator()(const T& v)中的v,则bo = TooBig<int>(10)(15) = 15 > 10 = true

编程练习

1. 回文指的是顺读和逆读都一样的字符串。例如,“tot”和“otto”都是简短的回文。编写一个程序,让用户输入字符串,并将字符串引用传递给一个bool函数。如果字符串是回文,该函数将返回true,否则返回false。此时,不要担心诸如大小写、空格和标点符号这些复杂的问题。即这个简单的版本将拒绝“Otto”和“Madam,I'm Adam”。请查看附录F中的字符串方法列表,以简化这项任务。

根据回文的定义我们很容易想到:将一个字符串翻转然后对比翻转后的字符串是否与原字符串相同,来判断该字符串是否为回文字符串。具体实现如下:

#include <iostream>
#include <string>

bool palindrome(const std::string& s);

int main() {
    std::string input;
    std::cout << "Enter a word(q to quit): ";

    while (std::getline(std::cin, input) && input != "q") {
        if (palindrome(input))
            std::cout << input << " is palindrome!" << std::endl;
        else
            std::cout << input << "is not palindrome" << std::endl;
        std::cout << "Enter anthor word(q to quit): ";
    }

    std::cout << "Bye!~" << std::endl;

    return 0;
}

bool palindrome(const std::string& s) {
    std::string rev(s.rbegin(), s.rend());
    return (rev == s);
}

2. 与编程练习1中给出的问题相同,但要考虑诸如大小写、空格和标点符号这样的复杂问题。即“Madam,I'm Adam”将作为回文来测试。例如,测试函数可能会将字符串缩略为“madamimadam”,然后测试倒过来是否一样。不要忘了有用的cctype库,您可能从中找到几个有用的STL函数,尽管不一定非要使用它们。

main.cpp:

#include <iostream>
#include <string>
#include <cctype>

bool palindromePlus(const std::string& s);

int main() {
  std::string input;
  std::cout << "Enter a word(q to quit): ";

  while (std::getline(std::cin, input) && input != "q") {
    if (palindromePlus(input))
      std::cout << input << " is palindrome!" << std::endl;
    else
      std::cout << input << "is not palindrome" << std::endl;
    std::cout << "Enter anthor word(q to quit): ";
  }

  std::cout << "Bye!~" << std::endl;

  return 0;
}

bool palindromePlus(const std::string& s) {
  // data preprocessing
  std::string target_s;
  for (auto& c : s) {
    if (isalpha(c)) {
      if (isupper(c))
        target_s.push_back(tolower(c));
      else
        target_s.push_back(c);
    }
  }
  std::string rev(target_s.rbegin(), target_s.rend());
  return (rev == target_s);
}

3. 修改程序清单16.3,使之从文件中读取单词。一种方案是,使用vector<string>对象而不是string数组。这样便可以使用push_back( )将数据文件中的单词复制到vector<string>对象中,并使用size( )来确定单词列表的长度。由于程序应该每次从文件中读取一个单词,因此应使用运算符>>而不是getline( )。文件中包含的单词应该用空格、制表符或换行符分隔。

hangman.cpp:

// hangman.cpp -- some string methods
#include <cctype>
#include <cstdlib>
#include <ctime>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

using std::string;
using std::vector;

vector<string> readWordlist(const std::string &file_name);

int main() {
    using std::cin;
    using std::cout;
    using std::endl;
    using std::tolower;
    std::srand(std::time(0));
    auto wordlist = readWordlist("chapter16-3/wordlist.txt");
    char play;
    cout << "Will you play a word game? <y/n> ";
    cin >> play;
    play = tolower(play);
    while (play == 'y') {
        string target = wordlist[std::rand() % wordlist.size()];
        int length = target.length();
        string attempt(length, '-');
        string badchars;
        int guesses = 6;
        cout << "Guess my secret word. It has " << length
            << " letters, and you guess\n"
            << "one letter at a time. You get " << guesses << " wrong guesses.\n";
        cout << "Your word: " << attempt << endl;

        while (guesses > 0 && attempt != target) {
            char letter;
            cout << "Guess a letter: ";
            cin >> letter;
            if (badchars.find(letter) != string::npos ||
                attempt.find(letter) != string::npos) {
                cout << "You already guessed that. Try again.\n";
                continue;
            }
            int loc = target.find(letter);
            if (loc == string::npos) {
                cout << "Oh, bad guess!\n";
                --guesses;
                badchars += letter;  // add to string
            } else {
                cout << "Good guess!\n";
                attempt[loc] = letter;
                // check if letter appears again
                loc = target.find(letter, loc + 1);
                while (loc != string::npos) {
                    attempt[loc] = letter;
                    loc = target.find(letter, loc + 1);
                }
            }
            cout << "Your word: " << attempt << endl;
            if (attempt != target) {
                if (badchars.length() > 0) cout << "Bad choices: " << badchars << endl;
                cout << guesses << " bad guesses left\n";
            }
        }
        if (guesses > 0)
            cout << "That's right!\n";
        else
            cout << "Sorry, the word is " << target << ".\n";
        cout << "Will you play another? <y/n> ";
        cin >> play;
        play = tolower(play);
    }
    cout << "Bye\n";
    return 0;
}

vector<string> readWordlist(const std::string &file_name) {
    std::ifstream fin;
    fin.open(file_name);
    if (!fin.is_open()) {
        std::cout << file_name << " open fail!" << std::endl;
        std::exit(EXIT_FAILURE);
    }
    vector<string> wordlist;
    string word;
    while (fin >> word) wordlist.emplace_back(word);
    return wordlist;
}

4. 编写一个具有老式风格接口的函数,其原型如下:

int reduce(long ar[], int n);

实参应是数组名和数组中的元素个数。该函数对数组进行排序,删除重复的值,返回缩减后数组中的元素数目。请使用STL函数编写该函数(如果决定使用通用的unique( )函数,请注意它将返回结果区间的结尾)。使用一个小程序测试该函数。

main.cpp:

#include <algorithm>
#include <iostream>
#include <iterator>

int reduce(long ar[], int n);

int main() {
    long arr[10] = {15, 8, 5, 6, 11, 11, 6, 6, 198, 50};
    int newsize = reduce(arr, 10);
    std::ostream_iterator<long, char> out(std::cout, " ");
    std::copy(arr, arr + newsize, out);
    std::cout << std::endl;
    std::cout << "There are " << newsize << " numbers.";

    return 0;
}

int reduce(long ar[], int n) {
    std::sort(ar, ar + n);
    auto past_end = std::unique(ar, ar + n);

    return past_end - ar;
}

5. 问题与编程练习4相同,但要编写一个模板函数:

template <class T>
int reduce(T ar[], int n);

在一个使用long实例和string实例的小程序中测试该函数。

main.cpp:

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


template <class T>
    int reduce(T ar[], int n);

template <class T>
    void show(T ar[], int n);

int main() {
    long arr[10] = {15, 8, 5, 6, 11, 11, 6, 6, 198, 50};
    int newsize = reduce(arr, 10);
    show(arr, newsize);

    std::string arr_str[] = {"hello", "world", "hello", "hi"};
    newsize = reduce(arr_str, sizeof(arr_str) / sizeof(arr_str[0]));
    show(arr_str, newsize);

    return 0;
}

template <class T>
    int reduce(T ar[], int n) {
    std::sort(ar, ar + n);
    auto past_end = std::unique(ar, ar + n);

    return past_end - ar;
}

template <class T>
    void show(T ar[], int n) {
    std::ostream_iterator<T, char> out(std::cout, " ");
    std::copy(ar, ar + n, out);
    std::cout << std::endl;
    std::cout << "There are " << n << " numbers.";
}

6. 使用STL queue模板类而不是第12章的Queue类,重新编写程序清单12.12所示的示例。

customer.h:

#ifndef CUSTOMER_H_
#define CUSTOMER_H_

class Customer {
    private:
    long arrive;      // arrival time for customer
    int processtime;  // processing time for customer
    public:
    Customer() { arrive = processtime = 0; }
    void set(long when);
    long when() const { return arrive; }
    int ptime() const { return processtime; }
};

#endif  // CUSTOMER_H_

customer.cpp:

#include "customer.h"

#include <cstdlib>

void Customer::set(long when) {
    processtime = std::rand() % 3 + 1;
    arrive = when;
}

bank.cpp:

// bank.cpp -- using the Queue interface
// compile with queue.cpp
#include <cstdlib>  // for rand() and srand()
#include <ctime>    // for time()
#include <iostream>
#include <queue>

#include "customer.h"

const int MIN_PER_HR = 60;
bool newcustomer(double x);  // is there a new customer?
int main() {
    using std::cin;
    using std::cout;
    using std::endl;
    using std::ios_base;
    // setting things up
    std::srand(std::time(0));  // random initializing of rand()
    cout << "Case Study: Bank of Heather Automatic Teller\n";
    cout << "Enter maximum size of queue: ";
    int qs;
    cin >> qs;
    std::queue<Customer> line;  // line queue holds up to qs people
    cout << "Enter the number of simulation hours: ";
    int hours;  // hours of simulation
    cin >> hours;
    // simulation will run 1 cycle per minute
    long cyclelimit = MIN_PER_HR * hours;  // # of cycles
    cout << "Enter the average number of customers per hour: ";
    double perhour;  // average # of arrival per hour
    cin >> perhour;
    double min_per_cust;  // average time between arrivals
    min_per_cust = MIN_PER_HR / perhour;
    Customer temp;       // new customer data
    long turnaways = 0;  // turned away by full queue
    long customers = 0;  // joined the queue
    long served = 0;     // served during the simulation
    long sum_line = 0;   // cumulative line length
    int wait_time = 0;   // time until autoteller is free
    long line_wait = 0;  // cumulative time in line
    // running the simulation
    for (int cycle = 0; cycle < cyclelimit; cycle++) {
        if (newcustomer(min_per_cust))  // have newcomer
        {
            if (line.size() >= qs)
                turnaways++;
            else {
                customers++;
                temp.set(cycle);     // cycle = time of arrival
                line.emplace(temp);  // add newcomer to line
            }
        }
        if (wait_time <= 0 && !line.empty()) {
            line.pop();                // attend next customer
            wait_time = temp.ptime();  // for wait_time minutes
            line_wait += cycle - temp.when();
            served++;
        }
        if (wait_time > 0) wait_time--;
        sum_line += line.size();
    }
    // reporting results
    if (customers > 0) {
        cout << "customers accepted: " << customers << endl;
        cout << " customers served: " << served << endl;
        cout << " turnaways: " << turnaways << endl;
        cout << "average queue size: ";
        cout.precision(2);
        cout.setf(ios_base::fixed, ios_base::floatfield);
        cout << (double)sum_line / cyclelimit << endl;
        cout << " average wait time: " << (double)line_wait / served
            << " minutes\n";
    } else
        cout << "No customers!\n";
    cout << "Done!\n";
    return 0;
}
// x = average time, in minutes, between customers
// return value is true if customer shows up this minute
bool newcustomer(double x) { return (std::rand() * x / RAND_MAX < 1); }

7. 彩票卡是一个常见的游戏。卡片上是带编号的圆点,其中一些圆点被随机选中。编写一个lotto( )函数,它接受两个参数。第一个参数是彩票卡上圆点的个数,第二个参数是随机选择的圆点个数。该函数返回一个vector<int>对象,其中包含(按排列后的顺序)随机选择的号码。例如,可以这样使用该函数:

vector<int> winners;
winners = Lotto(51,6);

这样将把一个矢量赋给winner,该矢量包含1~51中随机选定的6个数字。注意,仅仅使用rand( )无法完成这项任务,因它会生成重复的值。提示:让函数创建一个包含所有可能值的矢量,使用random_shuffle( ),然后通过打乱后的矢量的第一个值来获取值。编写一个小程序来测试这个函数。

main.cpp:

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

std::vector<int> lotto(int tot_num, int select_num);

int main() {
    const int TOTAL = 51, SELECT_NUMS = 6;
    std::cout << "Play lotto? <y/n>: ";
    std::string choice;
    std::vector<int> winners;

    while (std::cin >> choice && choice != "n") {
        winners = lotto(TOTAL, SELECT_NUMS);
        for (auto it = winners.begin(); it != winners.end(); ++it)
            std::cout << *it << " ";
        std::cout << std::endl;

        std::cout << "Play lotto? <y/n>: ";
    }

    std::cout << "Bye~" << std::endl;
    return 0;
}

std::vector<int> lotto(int total, int select) {
    using std::vector;
    vector<int> all;
    for (int i = 1; i <= total; ++i) all.push_back(i);
    random_shuffle(all.begin(), all.end());

    vector<int> select_vec(all.begin(), all.begin() + select);
    std::sort(select_vec.begin(), select_vec.end());

    return select_vec;
}

8. Mat和Pat希望邀请他们的朋友来参加派对。他们要编写一个程序完成下面的任务。

  • 让Mat输入他朋友的姓名列表。姓名存储在一个容器中,然后按排列后的顺序显示出来。
  • 让Pat输入她朋友的姓名列表。姓名存储在另一个容器中,然后按排列后的顺序显示出来。
  • 创建第三个容器,将两个列表合并,删除重复的部分,并显示这个容器的内容。

main.cpp:

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

void getNames(std::vector<std::string>& name_vec);
inline void show(std::string& s) { std::cout << s << " "; }

int main() {
    using std::cin;
    using std::cout;
    using std::endl;
    using std::string;
    using std::vector;

    vector<string> mat_friends, pat_friends;
    string name;

    cout << "Mat! Enter your friends(press enter to end): ";
    getNames(mat_friends);

    cout << "Pat! Enter your friends(press enter to end): ";
    getNames(pat_friends);

    std::sort(mat_friends.begin(), mat_friends.end());
    std::sort(pat_friends.begin(), pat_friends.end());

    cout << "Mat's friends: " << endl;
    for_each(mat_friends.begin(), mat_friends.end(), show);
    cout << endl;
    cout << "Pat's friends: " << endl;
    for_each(pat_friends.begin(), pat_friends.end(), show);
    cout << endl;

    // merge to vectors
    vector<string> all_friends;
    all_friends.reserve(mat_friends.size() + pat_friends.size());
    all_friends.insert(all_friends.end(), mat_friends.begin(), mat_friends.end());
    all_friends.insert(all_friends.end(), pat_friends.begin(), pat_friends.end());

    std::sort(all_friends.begin(), all_friends.end());
    auto new_end = std::unique(all_friends.begin(), all_friends.end());
    cout << "All friends: " << endl;
    for_each(all_friends.begin(), new_end, show);
    cout << endl;

    return 0;
}

void getNames(std::vector<std::string>& name_vec) {
    std::string names;
    std::getline(std::cin, names);

    std::istringstream ins(names);
    std::copy(std::istream_iterator<std::string>(ins),
              std::istream_iterator<std::string>(), std::back_inserter(name_vec));
}

9. 相对于数组,在链表中添加和删除元素更容易,但排序速度更慢。这就引出了一种可能性:相对于使用链表算法进行排序,将链表复制到数组中,对数组进行排序,再将排序后的结果复制到链表中的速度可能更快;但这也可能占用更多的内存。请使用如下方法检验上述假设。

a.创建大型vector<int>对象vi0,并使用rand( )给它提供初始值。

b.创建vector<int>对象vilist<int>对象li,它们的长度都和初始值与vi0相同。

c.计算使用STL算法sort( )vi进行排序所需的时间,再计算使用list的方法sort( )li进行排序所需的时间。

d.将li重置为排序的vi0的内容,并计算执行如下操作所需的时间:将li的内容复制到vi中,对vi进行排序,并将结果复制到li中。

要计算这些操作所需的时间,可使用ctime库中的clock( )。正如程序清单5.14演示的,可使用下面的语句来获取开始时间:

clock_t start = clock();

再在操作结束后使用下面的语句获取经过了多长时间:

clock_t end = clock();
cout << (double)(end - start)/CLOCKS_PER_SEC;

这种测试并非绝对可靠,因为结果取决于很多因素,如可用内存量、是否支持多处理以及数组(列表)的长度(随着要排序的元素数增加,数组相对于列表的效率将更明显)。另外,如果编译器提供了默认生成方式和发布生成方式,请使用发布生成方式。鉴于当今计算机的速度非常快,要获得有意义的结果,可能需要使用尽可能大的数组。例如,可尝试包含100000、1000000和10000000个元素。

main.cpp:

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

const int SIZE = 1000000;

int main() {
    using std::cin;
    using std::cout;
    using std::endl;
    using std::list;
    using std::vector;

    vector<int> vi0(SIZE, 0);
    std::srand(std::time(0));
    for (int i = 0; i < SIZE; ++i) vi0.at(i) = rand();

    vector<int> vi(vi0);
    list<int> li(SIZE, 0);
    std::copy(vi0.begin(), vi0.end(), li.begin());

    clock_t start = std::clock();
    std::sort(vi.begin(), vi.end());
    clock_t end = clock();
    cout << "sort vector time: " << (double)(end - start) / CLOCKS_PER_SEC
        << endl;

    start = std::clock();
    li.sort();
    end = clock();
    cout << "sort list time: " << (double)(end - start) / CLOCKS_PER_SEC << endl;

    std::copy(vi0.begin(), vi0.end(), li.begin());
    start = std::clock();
    std::copy(li.begin(), li.end(), vi.begin());
    std::sort(vi.begin(), vi.end());
    std::copy(vi.begin(), vi.end(), li.begin());
    end = clock();
    cout << "copy2vec-sort_vec-copy2list time: "
        << (double)(end - start) / CLOCKS_PER_SEC << endl;

    return 0;
}

10.请按如下方式修改程序清单16.9(vect3.cpp)。

a.在结构Review中添加成员price

b.不使用vector<Review>来存储输入,而使用vector<shared_ptr<Review>>。别忘了,必须使用new返回的指针来初始化shared_ptr

c.在输入阶段结束后,使用一个循环让用户选择如下方式之一显示书籍:按原始顺序显示、按字母表顺序显示、按评级升序显示、按评级降序显示、按价格升序显示、按价格降序显示、退出。

下面是一种可能的解决方案:获取输入后,再创建一个shared_ptr矢量,并用原始数组初始化它。定义一个对指向结构的指针进行比较的operator < ( )函数,并使用它对第二个矢量进行排序,让其中的shared_ptr按其指向的对象中的书名排序。重复上述过程,创建按ratingprice排序的shared_ptr矢量。请注意,通过使用rbegin()rend(),可避免创建按相反的顺序排列的shared_ptr矢量。

vect3.cpp:

// vect3.cpp -- using STL functions
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct Review {
    std::string title;
    int rating;
    double price;
};

bool operator<(const std::shared_ptr<Review>& p1,
               const std::shared_ptr<Review>& p2);
bool RatingAsc(const std::shared_ptr<Review>& p1,
               const std::shared_ptr<Review>& p2);
bool PriceAsc(const std::shared_ptr<Review>& p1,
              const std::shared_ptr<Review>& p2);
bool PriceDesc(const std::shared_ptr<Review>& p1,
               const std::shared_ptr<Review>& p2);
bool FillReview(std::shared_ptr<Review>& rptr);
void ShowReview(const std::shared_ptr<Review>& rptr);
void ShowMenu();

int main() {
    using namespace std;

    vector<shared_ptr<Review> > books;

    // initialize books
    shared_ptr<Review> temp_ptr;
    while (FillReview(temp_ptr)) books.push_back(temp_ptr);

    if (books.size() > 0) {
        ShowMenu();
        char choice;
        while (cin >> choice && choice != '6') {
            vector<shared_ptr<Review> > books_cpy(books);
            switch (choice) {
                case '1':
                    cout << "Original order:" << endl;
                    cout << "Rating\tBook\tPrice\n";
                    for_each(books_cpy.begin(), books_cpy.end(), ShowReview);
                    break;
                case '2':
                    cout << "Alphabet order:" << endl;
                    cout << "Rating\tBook\tPrice\n";
                    sort(books_cpy.begin(), books_cpy.end());
                    for_each(books_cpy.begin(), books_cpy.end(), ShowReview);
                    break;
                case '3':
                    cout << "Rating ascending:" << endl;
                    cout << "Rating\tBook\tPrice\n";
                    sort(books_cpy.begin(), books_cpy.end(), RatingAsc);
                    for_each(books_cpy.begin(), books_cpy.end(), ShowReview);
                    break;
                case '4':
                    cout << "Price ascending:" << endl;
                    cout << "Rating\tBook\tPrice\n";
                    sort(books_cpy.begin(), books_cpy.end(), PriceAsc);
                    for_each(books_cpy.begin(), books_cpy.end(), ShowReview);
                    break;
                case '5':
                    cout << "Price descending:" << endl;
                    cout << "Rating\tBook\tPrice\n";
                    sort(books_cpy.begin(), books_cpy.end(), PriceDesc);
                    for_each(books_cpy.begin(), books_cpy.end(), ShowReview);
                    break;
                default:
                    break;
            }
            ShowMenu();
        }
    } else
        cout << "No entries. ";
    cout << "Bye.\n";
    return 0;
}

bool operator<(const std::shared_ptr<Review>& p1,
               const std::shared_ptr<Review>& p2) {
    return p1->title < p2->title;
}

bool RatingAsc(const std::shared_ptr<Review>& p1,
               const std::shared_ptr<Review>& p2) {
    return p1->rating < p2->rating;
}

bool PriceAsc(const std::shared_ptr<Review>& p1,
              const std::shared_ptr<Review>& p2) {
    return p1->price < p2->price;
}

bool PriceDesc(const std::shared_ptr<Review>& p1,
               const std::shared_ptr<Review>& p2) {
    return p1->price > p2->price;
}

bool FillReview(std::shared_ptr<Review>& rptr) {
    rptr = std::shared_ptr<Review>(new Review);

    std::cout << "Enter book title (quit to quit): ";
    std::getline(std::cin, rptr->title);
    if (rptr->title == "quit") return false;

    std::cout << "Enter book rating: ";
    std::cin >> rptr->rating;
    if (!std::cin) return false;

    std::cout << "Enter book price: ";
    std::cin >> rptr->price;
    if (!std::cin) return false;

    // get rid of rest of input line
    while (std::cin.get() != '\n') continue;

    return true;
}

void ShowReview(const std::shared_ptr<Review>& rptr) {
    std::cout << rptr->rating << "\t" << rptr->title << "\t" << rptr->price
        << std::endl;
}

void ShowMenu() {
    using std::cin;
    using std::cout;
    using std::endl;

    cout << "---------------------------------------------------------" << endl;
    cout << "1.original order   2.alphabet order    3.rating ascending" << endl;
    cout << "4.price ascending  3.price descending  6.quit" << endl;
    cout << "---------------------------------------------------------" << endl;
    cout << "Your choice: ";
}