C++ Primer Plus(第 6 版)第十至十八章课后习题答案

转载公众号: iDoitnow 用于学习笔记使用。

目录

 

第十章课后习题答案 

复习题

编程练习

 第十一章课后习题答案

复习题

编程练习

 第十二章课后习题答案

复习题

编程练习

第十三章课后习题答案

复习题

编程练习

第十四章课后习题答案

复习题

编程练习

第十五章课后习题答案

复习题

编程练习

第十六章课后习题答案

 复习题

编程练习

第十七章课后习题答案

复习题

 编程练习

第十八章课后习题答案

复习题

编程练习

 

第十章课后习题答案 

复习题

1.什么是类?

类是用户定义的类型的定义。类声明指定了数据将如何存储,同时提供了访问和操作这些数据的方法。

2.类如何实现抽象、封装和数据隐藏?

用户可以根据类的公有接口对类对象执行的操作,这是抽象。类的数据成员可以是私有的(默认值),这意味着只能通过类成员函数来对数据进行访问,这是数据隐藏。实现的具体细节(如数据的表示和方法的代码)都是隐藏的,这是封装。

3.对象和类之间的关系是什么?

类定义了一种类型,包括如何使用它。对象是一个变量或其他的数据对象(如new生成的),并根据类定义被创建和使用。类和对象之间的关系同标准类型与其变量之间的关系。

4.除了是函数之外,类函数成员与类数据成员之间的区别是什么?

如果创建给定类的多个对象,则每个对象都有其自己的数据内存空间;但所有的对象都使用同一组成员函数(通常,这个方法是公有的,而数据是私有的,但这只是策略方面的问题,而不是对类的要求)

5.定义一个类来表示银行账户、数据成员包括储户姓名、帐号(使用字符串)和存款。成员函数执行如下操作:

  • 创建一个对象并将其初始化。

  • 显示储户姓名、帐号和存款。

  • 存入参数指定的存款。

  • 取出参数指定的款项。

请提供类声明,而不用给出方法实现。(编程练习1将要求编写实现)

#ifndef BANKACCOUNT_H
#define BANKACCOUNT_H
#include <string>

using namespace std;

class BankAccount
{
private:
    std::string name_str;
    std::string accountNum_str;
    double balance;

public:
    BankAccount(const string &name, const string &accountNum, double bal = 0.0);
    void show();
    void deposit(double cash);
    void withdraw(double cash);
};

#endif

6.类构造函数在何时被调用?类析构函数呢?

在创建类对象或显示调用构造函数时,类的构造函数被调用。当函数过期时,析构函数被调用。

7.给出复习题5中的银行账户的构造函数的代码。

#include "BankAccount.h"
#include <iostream>
using namespace std;

BankAccount::BankAccount(const string &name, const string &accountNum, double bal)
{
    name_str = name;
    accountNum_str = accountNum;
    balance = bal;
}

void BankAccount::show()
{
    cout << "Account Name : " << name_str << endl;
    cout << "Account Number : " << accountNum_str << endl;
    cout << "Account Balance : " << balance << endl;
}

void BankAccount::withdraw(double cash)
{
    balance -= cash;
}

void BankAccount::deposit(double cash)
{
    balance += cash;
}

8.什么是默认构造函数,拥有默认构造函数有何好处?

默认构造函数是没有参数或所有参数都有默认值的构造函数。拥有默认构造函数后,可以声明对象,而不初始化它,即使已经定义了初始化构造函数。它还使得能够声明数组。

9.修改Stock类的定义(stock20.h中的版本),使之包含返回各个数据成员值的成员函数。注意:返回公司名的成员函数不应该为修改数组提供便利,也就是说,不能简单的返回string引用。

原stock20.h的版本:

//Listing 10.7 stock20.h
// stock20.h -- augmented version
#ifndef STOCK20_H_
#define STOCK20_H_
#include <string>
class Stock
{
private:
    std::string company;
    int shares;
    double share_val;
    double total_val;
    void set_tot() { total_val = shares * share_val; }

public:
    Stock(); // default constructor
    Stock(const std::string &co, long n = 0, double pr = 0.0);
    ~Stock(); // do-nothing destructor
    void buy(long num, double price);
    void sell(long num, double price);
    void update(double price);
    void show() const;
    const Stock &topval(const Stock &s) const;
};
#endif

修改后:

#ifndef STOCK20_H_
#define STOCK20_H_
#include <string>
class Stock
{
private:
    std::string company;
    int shares;
    double share_val;
    double total_val;
    void set_tot() { total_val = shares * share_val; }

public:
    Stock();
    Stock(const std::string &co, long n = 0, double pr = 0.0);
    ~Stock();
    void buy(long num, double price);
    void sell(long num, double price);
    void update(double price);
    void show() const;
    const Stock &topval(const Stock &s) const;
    int shares() const { return shares; }
    double shareVal() const { return share_val; }
    double totalVal() const { return total_val; }
    const std::string &comp_name() const { return company; }
};
#endif

10.this和*this是什么?

this指针是类方法可以使用的指针,它指向用于调用方法的对象。因此,this是对象的地址,*this是对象本身。

编程练习

1.为复习题5描述的类提供方法定义,并编写一个小程序来演示所有的特性。

BankAccount.h:

#ifndef BANKACCOUNT_H
#define BANKACCOUNT_H
#include <string>

using namespace std;

class BankAccount
{
private:
    std::string name_str;
    std::string accountNum_str;
    double balance;

public:
    BankAccount(const string &name, const string &accountNum, double bal = 0.0);
    void show();
    void deposit(double cash);
    void withdraw(double cash);
};

#endif

BankAccount.cpp:

#include "BankAccount.h"
#include <iostream>
using namespace std;

BankAccount::BankAccount(const string &name, const string &accountNum, double bal)
{
    name_str = name;
    accountNum_str = accountNum;
    balance = bal;
}

void BankAccount::show()
{
    cout << "Account Name : " << name_str << endl;
    cout << "Account Number : " << accountNum_str << endl;
    cout << "Account Balance : " << balance << endl;
}

void BankAccount::withdraw(double cash)
{
    balance -= cash;
}

void BankAccount::deposit(double cash)
{
    balance += cash;
}

main.cpp:

#include "BankAccount.h"
#include <iostream>

using namespace std;

int main()
{
    string name, account;
    double num;
    cout << "enter name : ";
    getline(cin, name);
    cout << "enter bank account : ";
    getline(cin, account);

    BankAccount ba(name, account);
    cout << "enter the deposit amount : ";
    cin >> num;
    cin.get();
    ba.deposit(num);
    cout << "your current bank account information : ";
    ba.show();

    cout << "enter the withdrawal amount: ";
    cin >> num;
    cin.get();
    ba.withdraw(num);
    cout << "your current bank account information : ";
    ba.show();

    return 0;
}

2. 下面是一个非常简单的类定义:

class Person
{
private:
    static const int LIMIT = 25;
    string lname;      // Person’s last name
    char fname[LIMIT]; // Person’s first name
public:
    Person()
    {
        lname = "";
        fname[0] = '\0';
    }                                                    // #1
    Person(const string &ln, const char *fn = "Heyyou"); // #2
    // the following methods display lname and fname
    void Show() const;       // firstname lastname format
    void FormalShow() const; // lastname, firstname format
};

它使用了一个string对象和一个字符数组,让您能够比较它们的用法。请提供未定义的方法的代码,以完成这个类的实现。再编写一个使用这个类的程序,它使用了三种可能的构造函数的调用(没有参数、一个参数和两个参数)以及两种显示方法。下面是一个使用这些构造函数和方法的例子:

Person one;                      // use default constructor
Person two("Smythecraft");       // use #2 with one default argument
Person three("Dimwiddy", "Sam"); // use #2, no defaults one.Show();
cout << endl;
one.FormalShow();
// etc. for two and three

Person.h :

#ifndef PERSON_H
#define PERSON_H

#include <string>
using std::string;

class Person
{
private:
    static const int LIMIT = 25;
    string lname;      // Person’s last name
    char fname[LIMIT]; // Person’s first name
public:
    Person()
    {
        lname = "";
        fname[0] = '\0';
    }                                                    // #1
    Person(const string &ln, const char *fn = "Heyyou"); // #2
    // the following methods display lname and fname
    void Show() const;       // firstname lastname format
    void FormalShow() const; // lastname, firstname format
};

#endif

Person.cpp :

#include "Person.h"
#include <iostream>
using std::cout;
using std::endl;

Person::Person(const string &ln, const char *fn)
{
    lname = ln;
    strcpy(fname, fn);
}

void Person::FormalShow() const
{
    cout << fname << " " << lname << endl;
}

void Person::Show() const
{
    cout << fname << " , " << lname << endl;
}

main.cpp :

#include "Person.h"
#include <iostream>
using std::cout;
using std::endl;
int main()
{
    Person one;
    Person two("Smythecraft");
    Person three("Dimwiddy", "Sam");
    cout << "Person one : " << endl;
    one.Show();
    one.FormalShow();
    cout << "Person two : " << endl;
    two.Show();
    two.FormalShow();
    cout << "Person two : " << endl;
    three.Show();
    three.FormalShow();
    return 0;
}

3.完成第9章的编程练习1,但要用正确的golf类声明替换那里的代码。用带合适参数的构造函数替换setgolf(golf &, const char * , int),以提供初始值。保留setgolf()的交互版本,但要用构造函数来实现它(例如,setgolf()的代码应该获得数据,将数据传递给构造函数来创建一个临时对象,并将其赋给调用对象,即*this)。

Golf.h:

#ifndef GOLF_H
#define GOLF_H

class Golf
{
public:
    Golf();
    Golf(const char *name, int hc);
    int setgolf();
    void sethandicap(int hc);
    void showgolf() const;

private:
    static const int Len = 40;
    char fullname[Len];
    int handicap;
};

#endif

Golf.cpp:

#include "Golf.h"
#include <iostream>

using namespace std;

Golf::Golf()
{
    strcpy(fullname, "DefaultName");
    handicap = 0;
}
Golf::Golf(const char *name, int hc)
{
    strcpy(fullname, name);
    handicap = hc;
}

int Golf::setgolf()
{
    cout << "please enter fullname : ";
    cin.getline(fullname, Len);
    if (strlen(fullname) == 0)
        return 0;
    else
    {
        cout << "please enter handicap : ";
        cin >> handicap;
        cin.get();
        return 1;
    }
}

void Golf::sethandicap(int hc)
{
    handicap = hc;
}

void Golf::showgolf() const
{
    cout << "fullname : " << fullname << ", handicap : " << handicap << endl;
}

main.cpp:

#include "Golf.h"
#include <iostream>

using namespace std;

int main()
{
    Golf ann("Ann Birdfree", 24), andy, arrGolf[3];
    ann.showgolf();

    andy.showgolf();
    andy.setgolf();
    andy.showgolf();
    andy.sethandicap(20);
    andy.showgolf();

    int i = 0;
    while (i < 3 && arrGolf[i].setgolf())
    {
        arrGolf[i].showgolf();
        i++;
        if (i < 3)
            cout << "next one: " << endl;
    }

    return 0;
}

4.完成第9章的编程练习4,但将Sales结构及相关的函数转换为一个类及其方法。用构造函数替换setSales(sales &,  double [], int)函数。用构造函数实现setSales(Slaes &)方法的交互版本。将类保留在名称空间SALES 中。

sales.h:

//sales.h-----头文件
#ifndef SALES_H
#define SALES_H

namespace SALES
{
    const int QUARTERS = 4;
    class Sales
    {
    public:
        Sales();
        Sales(const double ar[], int n);
        void showSales();

    private:
        double sales[QUARTERS];
        double average;
        double max;
        double min;
    };
}

#endif

sales.cpp:

//sales.cpp-----源代码文件
#include "sales.h"
#include <iostream>

using namespace std;
namespace SALES
{
    Sales::Sales(const double ar[], int n)
    {
        double min = 0, max = 0, sum = 0;
        min = max = ar[0];
        for (int i = 0; i < n; i++)
        {
            sales[i] = ar[i];
            sum += ar[i];
            if (ar[i] > max)
            {
                max = ar[i];
            }
            if (ar[i] < min)
            {
                min = ar[i];
            }
        }
        average = sum / n;
    }

    Sales::Sales()
    {
        cout << "Please enter 4 quarters for sales:" << endl;
        cout << "the 1 quarter :";
        cin >> sales[0];
        min = max = sales[0];
        for (int i = 1; i < 4; i++)
        {
            cout << "the " << i << " quarter :";
            cin >> sales[i];
            if (max < sales[i])
            {
                max = sales[i];
            }
            if (min > sales[i])
            {
                min = sales[i];
            }
        }
        average = (sales[0] + sales[1] + sales[2] + sales[3]) / 4;
    }

    void Sales::showSales()
    {
        cout << "Display all information in sales : " << endl;
        cout << "The 4 quarters are $" << sales[0] << ", $" << sales[1] << ", $" << sales[2] << ", $" << sales[3] << endl;
        cout << "The average income is $" << average << endl;
        cout << "The maximum income is $" << max << endl;
        cout << "The minimum income is $" << min << endl;
    }
}

main.cpp:

#include "sales.h"
#include <iostream>

using namespace SALES;
int main()
{
    double arr[4] = {3.4, 5.6, 2.5, 6.1};
    Sales s1(arr, 4);
    s1.showSales();
    Sales s2;
    s2.showSales();
    return 0;
}

5.考虑下面的结构声明:

struct customer {
char fullname[35];
double payment;
};

编写一个程序,它从栈中添加和删除customer结构(栈用Stack类声明表示)。每次customer结构被删除时,其payment的值都被加入到总数中,并报告总数。注意:应该可以直接使用Stack类而不作修改;只需修改typedef声明,使Item的类型为customer,而不是unsigned long即可.

stack.h :

#ifndef STACK_H
#define STACK_H

struct customer
{
    char fullname[35];
    double payment;
};

typedef customer Item;

class Stack
{
public:
    Stack();
    bool pop(Item &it);
    bool push(const Item &it);
    bool isfull() const;
    bool isempty() const;

private:
    double total;
    int top;
    enum
    {
        MAX = 10
    };
    Item item[MAX];
};

#endif

stack.cpp :

#include "stack.h"
#include <iostream>

using namespace std;

Stack::Stack()
{
    top = 0;
    total = 0;
}

bool Stack::isempty() const
{
    return top == 0;
}

bool Stack::isfull() const
{
    return top == MAX;
}

bool Stack::pop(Item &it)
{
    if (top > 0)
    {
        it = item[--top];
        total += it.payment;
        cout << "An order has been processed, current total revenue : " << total << endl;
        return true;
    }
    else
    {
        return false;
    }
}

bool Stack::push(const Item &it)
{
    if (top < MAX)
    {
        item[top++] = it;
        return true;
    }
    else
    {
        return false;
    }
}

main.cpp :

#include "stack.h"
#include <iostream>

using namespace std;

int main()
{
    Stack stack;
    customer cu;
    char ch;
    cout << "Press a to add a customer, P to process an order, and Q to exit." << endl;
    while (cin >> ch && toupper(ch) != 'Q')
    {
        while (cin.get() != '\n')
        {
            continue;
        }
        if (!isalpha(ch))
        {
            cout << '\a';
            continue;
        }
        switch (ch)
        {
        case 'a':
        case 'A':
            if (stack.isfull())
            {
                cout << "The order of 10 customers has been filled. Please process the existing order first !" << endl;
            }
            else
            {
                cout << "Add customer name : ";
                cin.getline(cu.fullname, 35);
                cout << "Add the customer's consumption amount : ";
                cin >> cu.payment;
                cout << "dsssd : " << stack.push(cu);
            }
            break;
        case 'p':
        case 'P':
            if (stack.isempty())
            {
                cout << " There are currently no unprocessed orders." << endl;
            }
            else
            {
                stack.pop(cu);
            }
            break;
        default:
            cout << " Input error!!!" << endl;
            break;
        }
        cout << "Press a to add a customer, P to process an order, and Q to exit." << endl;
    }
    return 0;
}

6.下面是一个类声明:

class Move
{
private:
    double x;
    double y;

public:
    Move(double a = 0, double b = 0); //sets x, y to a, b
    showmove() const;                 // shows current x, y values
    Move add(const Move &m) const;
    // this function adds x of m to x of invoking object to get new x,
    // adds y of m to y of invoking object to get new y, creates a new
    // move object initialized to new x, y values and returns it
    reset(double a = 0, double b = 0); // resets x,y to a, b
}

请提供成员函数的定义和测试这个类的程序。

move.h :

#ifndef MOVE_H
#define MOVE_H

class Move
{
private:
    double x;
    double y;

public:
    Move(double a = 0, double b = 0); //sets x, y to a, b
    void showmove() const;            // shows current x, y values
    Move add(const Move &m) const;
    // this function adds x of m to x of invoking object to get new x,
    // adds y of m to y of invoking object to get new y, creates a new
    // move object initialized to new x, y values and returns it
    void reset(double a = 0, double b = 0); // resets x,y to a, b
};

#endif

move.cpp :

#include "move.h"
#include <iostream>

Move::Move(double a, double b)
{
    x = a;
    y = b;
}

Move Move::add(const Move &m) const
{
    return Move(x + m.x, y + m.y);
}

void Move::showmove() const
{
    std::cout << "x is :" << x << std::endl;
    std::cout << "y is :" << y << std::endl;
}
void Move::reset(double a, double b)
{
    x = a;
    y = b;
}

main.cpp :

#include "move.h"
#include <iostream>

int main()
{
    Move m1(1, 2), m2(3, 4);
    m1.showmove();
    m2.showmove();
    m1.add(m2).showmove();
    m1.reset(5, 6);
    m1.showmove();
    return 0;
}

7.Betelgeusean plorg有这些特征.

数据:

  • plorg的名称不超过19个字符;

  • plorg有满意指数(CI),这是一个整数。

操作:

  • 新的plorg将有名称,其CI值为50;

  • plorg的CI可以修改;

  • plorg可以报告其名称和CI;

  • plorg的默认名称为“Plorga”。

请编写一个Plorg类声明(包括数据成员和成员函数原型)来表示plorg,并编写成员函数的函数定义。然后编写一个小程序,以演示Plorg类的所有特性。

plorg.h :

#ifndef PLORG_H
#define PLORG_H

class Plorg
{
private:
    char name[19];
    int CI;

public:
    Plorg();
    Plorg( const char *n, int ci);
    void show();
    void setCI(int ci);
};

#endif

plorg.cpp :

#include "plorg.h"
#include <iostream>

Plorg::Plorg()
{
    strcpy(name, "Plorg");
    CI = 0;
}
Plorg::Plorg(const char *n, int ci)
{
    strcpy(name, n);
    CI = ci;
}

void Plorg::setCI(int ci)
{
    CI = ci;
}

void Plorg::show()
{
    std::cout << "name : " << name << ", CI: " << CI << std::endl;
}

main.cpp :

#include "plorg.h"
#include <iostream>

int main()
{
    Plorg p1, p2("plorg2", 50);
    p1.show();
    p2.show();
    p1.setCI(30);
    p1.show();
}

8.可以将简单列表描述成下面这样:

  • 可存储0或多个类型的列表;

  • 可创建空列表

  • 可在列表中添加数据项;

  • 可确定列表是否为空;

  • 可确定列表是否已满;

  • 可访问列表中每一个数据项,并对它执行某种操作。

可以看到,这个列表确实很简单,例如它不允许插入或删除数据项。

请设计一个List类来表示这中数据类型。您应提供头文件list.h和实现文件list.cpp.前者包含定义,后者包含实现这个类的方法。您还应创建一个简短的程序来实现这个类。

该表的规范很简单,这主要旨在简化这个编程练习,可以选择使用数组或链表来实现这个列表,但公有结构不应依赖与说做的选择。也就是说,公有接口不应有数组索引,节点指针等。应使用通用概念来表达创建列表、在列表中添加数据项等操作。对于访问数据项以及执行操作,通常应使用将函数指针做为参数的函数来处理:

void visit(void (*pf) (Item&));

其中,pf指向一个将Item引用作为参数的函数(不是成员函数),Item是列表中数据项的类型,visit()函数将该函数用于列表中的每个数据项。

list.h :

#ifndef LIST_H
#define LIST_H

typedef int Item;
const int MAX = 10;
class List
{
private:
    Item ITEM[MAX];
    int COUNT;

public:
    List();
    bool isfull();
    bool isempty();
    bool addItem(Item it);
    Item *item();
    int count();
    void visit(void (*pf)(Item &));
};

#endif

list.cpp :

#include "list.h"
#include <iostream>

List::List()
{
    COUNT = 0;
}

bool List::isfull()
{
    return COUNT == MAX;
}

bool List::isempty()
{
    return COUNT == 0;
}
bool List::addItem(Item it)
{
    if (this->isfull())
    {
        std::cout << "full already, add fail. " << std::endl;
        return false;
    }
    else
    {
        ITEM[COUNT++] = it;
        return true;
    }
}

Item *List::item()
{
    return ITEM;
}

int List::count()
{
    return COUNT;
}

void List::visit(void (*pf)(Item &))
{
    for (int i = 0; i < COUNT; i++)
    {
        (*pf)(ITEM[i]);
    }
}

main.cpp :

#include "list.h"
#include <iostream>

void intadd2(int &n);

int main()
{
    List l;
    l.addItem(1);
    l.addItem(2);
    l.addItem(3);
    for (int i = 0; i < 3; i++)
    {
        std::cout << l.item()[i] << std::endl;
    }
    l.visit(intadd2);
    for (int i = 0; i < 3; i++)
    {
        std::cout << l.item()[i] << std::endl;
    }
    return 0;
}

void intadd2(int &n)
{
    n += 2;
}

 第十一章课后习题答案

复习题

1.使用成员函数Stonewt类重载乘法运算符,该运算符将数据成员与double类型的值相乘。注意使用英石和磅表示时,需要进位。也就是说,将10英石8磅乘以2等于21英石2磅(1 英石=14 磅)。

Stonewt Stonewt::operator*(double a)
{
    this->stone=this->stone*a+this->pounds*a/14;
    this->pounds=(this->pounds*a)%14;
    return *this;
}

2.友元函数和成员函数之间的区别是什么?

成员函数是类定义的一部分,通过特定的对象来调用。成员函数可以隐式访问调用对象的成员,而无需使用成员运算符。友元函数不是类的组成部分,因此被称为直接函数调用。友元函数不能隐式访问类成员,而需要将成员运算符用作参数传递的对象。

3.非成员函数必须是友元才能访问类成员吗?

要访问私有成员,它必须是友元,但要访问共有成员,可以不是友元。

4.使用友元函数为Stonewt类重载乘法运算符,该运算符将double值与Stone值相乘。

friend Stonewt Stonewt::operator*(double a, Stonewt &s);


Stonewt operator*(double a, Stonewt &s)
{
    s.stone = s.stone * a + s.pounds * a / 14;
    s.pounds=( s.pounds * a ) % 14;
    return s;
}

5.那些运算符不能重载?

  • sizeof

  • .

  • .*

  • ::

  • ?:

6.在重载运算符=、( )、[ ]和->时,有什么限制?

这些运算符必须使用成员函数来定义。

7.为Vector类定义一个转换函数,将Vector类转换为一个double类型的值,后者表示矢量的长度。

operator double() {return mag}

编程练习

1.修改程序清单11.15,使之将一系列连续的随机漫步者位置写到文件中。对于每个位置,用步号进行标示。 另外,让该程序将初始条件(目标距离和步长)以结果小结写入到该文件中。该文件的内容与下面类似

Target Distance: 100, Step Size: 20
0: (x,y) = (0, 0)
1: (x,y) = (-11.4715, 16.383)
2: (x,y) = (-8.68807, -3.42232)
...
26: (x,y) = (42.2919, -78.2594)
27: (x,y) = (58.6749, -89.7309)
After 27 steps, the subject has the following location:
(x,y) = (58.6749, -89.7309)
or
(m,a) = (107.212, -56.8194)
Average outward distance per step = 3.97081

vect.h:

// vect.h -- Vector class with <<, mode state
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
namespace VECTOR
{
    class Vector
    {
    public:
        enum Mode {RECT, POL};
    // RECT for rectangular, POL for Polar modes
    private:
        double x;          // horizontal value
        double y;          // vertical value
        double mag;        // length of vector
        double ang;        // direction of vector in degrees
        Mode mode;         // RECT or POL
    // private methods for setting values
        void set_mag();
        void set_ang();
        void set_x();
        void set_y();
    public:
       Vector();
        Vector(double n1, double n2, Mode form = RECT);
        void reset(double n1, double n2, Mode form = RECT);
        ~Vector();
        double xval() const {return x;}       // report x value
        double yval() const {return y;}       // report y value
        double magval() const {return mag;}   // report magnitude
        double angval() const {return ang;}   // report angle
        void polar_mode();                    // set mode to POL
        void rect_mode();                     // set mode to RECT
    // operator overloading
        Vector operator+(const Vector & b) const;
        Vector operator-(const Vector & b) const;
        Vector operator-() const;
        Vector operator*(double n) const;
    // friends
        friend Vector operator*(double n, const Vector & a);
        friend std::ostream & operator<<(std::ostream & os, const Vector & v);
    };

}   // end namespace VECTOR
#endif

vect.cpp:

// vect.cpp -- methods for the Vector class
#include <cmath>
#include "vect.h"   // includes <iostream>
using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;

namespace VECTOR
{
    // compute degrees in one radian
    const double Rad_to_deg = 45.0 / atan(1.0);
    // should be about 57.2957795130823

    // private methods
    // calculates magnitude from x and y
    void Vector::set_mag()
    {
        mag = sqrt(x * x + y * y);
    }

    void Vector::set_ang()
    {
        if (x == 0.0 && y == 0.0)
            ang = 0.0;
        else
            ang = atan2(y, x);
    }

    // set x from polar coordinate
    void Vector::set_x()
    {
        x = mag * cos(ang);
    }

    // set y from polar coordinate
    void Vector::set_y()
    {
        y = mag * sin(ang);
    }

    // public methods
    Vector::Vector()             // default constructor
    {
        x = y = mag = ang = 0.0;
        mode = RECT;
    }

    // construct vector from rectangular coordinates if form is r
    // (the default) or else from polar coordinates if form is p
    Vector::Vector(double n1, double n2, Mode form)
    {
        mode = form;
        if (form == RECT)
         {
             x = n1;
             y = n2;
             set_mag();
             set_ang();
        }
        else if (form == POL)
        {
             mag = n1;
             ang = n2 / Rad_to_deg;
             set_x();
             set_y();
        }
        else
        {
             cout << "Incorrect 3rd argument to Vector() -- ";
             cout << "vector set to 0\n";
             x = y = mag = ang = 0.0;
             mode = RECT;
        }
    }

    // reset vector from rectangular coordinates if form is
    // RECT (the default) or else from polar coordinates if
    // form is POL
    void Vector:: reset(double n1, double n2, Mode form)
    {
        mode = form;
        if (form == RECT)
         {
             x = n1;
             y = n2;
             set_mag();
             set_ang();
        }
        else if (form == POL)
        {
             mag = n1;
             ang = n2 / Rad_to_deg;
             set_x();
             set_y();
        }
        else
        {
             cout << "Incorrect 3rd argument to Vector() -- ";
             cout << "vector set to 0\n";
             x = y = mag = ang = 0.0;
             mode = RECT;
        }
    }

    Vector::~Vector()    // destructor
    {
    }

    void Vector::polar_mode()    // set to polar mode
    {
        mode = POL;
    }

    void Vector::rect_mode()     // set to rectangular mode
    {
        mode = RECT;
    }

    // operator overloading
    // add two Vectors
    Vector Vector::operator+(const Vector & b) const
    {
        return Vector(x + b.x, y + b.y);
    }

    // subtract Vector b from a
    Vector Vector::operator-(const Vector & b) const
    {
        return Vector(x - b.x, y - b.y);
    }

    // reverse sign of Vector
    Vector Vector::operator-() const
    {
        return Vector(-x, -y);
    }

    // multiply vector by n
    Vector Vector::operator*(double n) const
    {
        return Vector(n * x, n * y);
    }

    // friend methods
    // multiply n by Vector a
    Vector operator*(double n, const Vector & a)
    {
        return a * n;
    }

    // display rectangular coordinates if mode is RECT,
    // else display polar coordinates if mode is POL
    std::ostream & operator<<(std::ostream & os, const Vector & v)
    {
        if (v.mode == Vector::RECT)
             os << "(x,y) = (" << v.x << ", " << v.y << ")";
        else if (v.mode == Vector::POL)
        {
             os << "(m,a) = (" << v.mag << ", "
                 << v.ang * Rad_to_deg << ")";
        }
        else
             os << "Vector object mode is invalid";
        return os;
    }

}  // end namespace VECTOR

randwalk.cpp:

// randwalk.cpp -- using the Vector class
// compile with the vect.cpp file
#include <iostream>
#include <cstdlib>      // rand(), srand() prototypes
#include <ctime>        // time() prototype
#include "vect.h"
#include <fstream>
int main()
{
    using namespace std;
    using VECTOR::Vector;
    srand(time(0));     // seed random-number generator
    double direction;
    Vector step;
    Vector result(0.0, 0.0);
    unsigned long steps = 0;
    double target;
    double dstep;

    //for write
    ofstream fout;
    fout.open("walkinfo.txt");

    cout << "Enter target distance (q to quit): ";
    while (cin >> target)
    {
        cout << "Enter step length: ";
        if (!(cin >> dstep))
            break;

        //write to file
        fout << "Target Destance: " << target << ", ";
        fout << "Step Size: " << dstep << endl;
        while (result.magval() < target)
        {
            direction = rand() % 360;
            step.reset(dstep, direction, Vector::POL);
            result = result + step;
            fout << steps << ": " << result << endl;
            steps++;
        }
        cout << "After " << steps << " steps, the subject "
            "has the following location:\n";
        cout << result << endl;
        result.polar_mode();
        cout << " or\n" << result << endl;
        cout << "Average outward distance per step = "
            << result.magval()/steps << endl;

        fout << "After " << steps << " steps, the subject "
            "has the following location:\n";
        fout << result << endl;
        result.polar_mode();
        fout << " or\n" << result << endl;
        fout << "Average outward distance per step = "
            << result.magval()/steps << endl;
        steps = 0;
        result.reset(0.0, 0.0);
        cout << "Enter target distance (q to quit): ";
    }
    cout << "Bye!\n";
/* keep window open
    cin.clear();
    while (cin.get() != '\n')
        continue;
    cin.get();
*/
    fout.close();
    return 0;
}

2.对vector类的头文件(程序清单11.13)和实现文件(程序清单11.14)进行修改,使其不再存储矢量的长度和角度,而是在magval()和angval()被调用时计算他们。应保留公有接口不变(公有方法及其参数不变),但对私有部分(包括一些私有方法)和方法实现进行修改。然后,使用程序清单11.15对修改后的版本进行测试,结果应该与以前相同,因为vector类的公有接口与原来相同。

vect.h:

// vect.h -- Vector class with <<, mode state
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
namespace VECTOR
{
    class Vector
    {
    public:
        enum Mode {RECT, POL};
    // RECT for rectangular, POL for Polar modes
    private:
        double x;          // horizontal value
        double y;          // vertical value
        Mode mode;         // RECT or POL
    // private methods for setting values
        double get_mag() const;
        double get_ang() const;
        double get_x();
        double get_y();
    public:
       Vector();
        Vector(double n1, double n2, Mode form = RECT);
        void reset(double n1, double n2, Mode form = RECT);
        ~Vector();
        double xval() const {return x;}       // report x value
        double yval() const {return y;}       // report y value
        double magval() const {return get_mag();}   // report magnitude
        double angval() const {return get_ang();}   // report angle
        void polar_mode();                    // set mode to POL
        void rect_mode();                     // set mode to RECT
    // operator overloading
        Vector operator+(const Vector & b) const;
        Vector operator-(const Vector & b) const;
        Vector operator-() const;
        Vector operator*(double n) const;
    // friends
        friend Vector operator*(double n, const Vector & a);
        friend std::ostream & operator<<(std::ostream & os, const Vector & v);
    };

}   // end namespace VECTOR
#endif

vect.cpp:

// vect.cpp -- methods for the Vector class
#include <cmath>
#include "vect.h"   // includes <iostream>
using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;

namespace VECTOR
{
    // compute degrees in one radian
    const double Rad_to_deg = 45.0 / atan(1.0);
    // should be about 57.2957795130823

    // private methods
    // calculates magnitude from x and y
    double Vector::get_mag() const
    {
        return sqrt(x * x + y * y);
    }

    double Vector::get_ang() const
    {
        if (x == 0.0 && y == 0.0)
            return 0.0;
        else
            return atan2(y, x);
    }

    // set x from polar coordinate
    double Vector::get_x()
    {
        return get_mag() * cos(get_ang());
    }

    // set y from polar coordinate
    double Vector::get_y()
    {
        return get_mag() * sin(get_ang());
    }

    // public methods
    Vector::Vector()             // default constructor
    {
        x = y  = 0.0;
        mode = RECT;
    }

    // construct vector from rectangular coordinates if form is r
    // (the default) or else from polar coordinates if form is p
    Vector::Vector(double n1, double n2, Mode form)
    {
        mode = form;
        if (form == RECT)
         {
             x = n1;
             y = n2;
        }
        else if (form == POL)
        {
             x =n1 * cos(n2);
             y =n1 * sin(n2);
        }
        else
        {
             cout << "Incorrect 3rd argument to Vector() -- ";
             cout << "vector set to 0\n";
             x = y  = 0.0;
             mode = RECT;
        }
    }

    // reset vector from rectangular coordinates if form is
    // RECT (the default) or else from polar coordinates if
    // form is POL
    void Vector:: reset(double n1, double n2, Mode form)
    {
        mode = form;
        if (form == RECT)
         {
             x = n1;
             y = n2;
        }
        else if (form == POL)
        {
            x =n1 * cos(n2);
            y =n1 * sin(n2);
        }
        else
        {
             cout << "Incorrect 3rd argument to Vector() -- ";
             cout << "vector set to 0\n";
             x = y = 0.0;
             mode = RECT;
        }
    }

    Vector::~Vector()    // destructor
    {
    }

    void Vector::polar_mode()    // set to polar mode
    {
        mode = POL;
    }

    void Vector::rect_mode()     // set to rectangular mode
    {
        mode = RECT;
    }

    // operator overloading
    // add two Vectors
    Vector Vector::operator+(const Vector & b) const
    {
        return Vector(x + b.x, y + b.y);
    }

    // subtract Vector b from a
    Vector Vector::operator-(const Vector & b) const
    {
        return Vector(x - b.x, y - b.y);
    }

    // reverse sign of Vector
    Vector Vector::operator-() const
    {
        return Vector(-x, -y);
    }

    // multiply vector by n
    Vector Vector::operator*(double n) const
    {
        return Vector(n * x, n * y);
    }

    // friend methods
    // multiply n by Vector a
    Vector operator*(double n, const Vector & a)
    {
        return a * n;
    }

    // display rectangular coordinates if mode is RECT,
    // else display polar coordinates if mode is POL
    std::ostream & operator<<(std::ostream & os, const Vector & v)
    {
        if (v.mode == Vector::RECT)
             os << "(x,y) = (" << v.x << ", " << v.y << ")";
        else if (v.mode == Vector::POL)
        {
             os << "(m,a) = (" << v.magval() << ", "
                 << v.angval() * Rad_to_deg << ")";
        }
        else
             os << "Vector object mode is invalid";
        return os;
    }

}  // end namespace VECTOR

randwalk.cpp:

// randwalk.cpp -- using the Vector class
// compile with the vect.cpp file
#include <iostream>
#include <cstdlib>      // rand(), srand() prototypes
#include <ctime>        // time() prototype
#include "vect.h"
int main()
{
    using namespace std;
    using VECTOR::Vector;
    srand(time(0));     // seed random-number generator
    double direction;
    Vector step;
    Vector result(0.0, 0.0);
    unsigned long steps = 0;
    double target;
    double dstep;
    cout << "Enter target distance (q to quit): ";
    while (cin >> target)
    {
        cout << "Enter step length: ";
        if (!(cin >> dstep))
            break;

        while (result.magval() < target)
        {
            direction = rand() % 360;
            step.reset(dstep, direction, Vector::POL);
            result = result + step;
            steps++;
        }
        cout << "After " << steps << " steps, the subject "
            "has the following location:\n";
        cout << result << endl;
        result.polar_mode();
        cout << " or\n" << result << endl;
        cout << "Average outward distance per step = "
            << result.magval()/steps << endl;
        steps = 0;
        result.reset(0.0, 0.0);
        cout << "Enter target distance (q to quit): ";
    }
    cout << "Bye!\n";
/* keep window open
    cin.clear();
    while (cin.get() != '\n')
        continue;
    cin.get();
*/
    return 0;
}

3.修改程序清单11.15,使之报告N次测试中的提高、最低和平均步数(其中N是用户输入的整数),而不是报告每次测试的结果。

vect.h:

// vect.h -- Vector class with <<, mode state
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
namespace VECTOR
{
    class Vector
    {
    public:
        enum Mode {RECT, POL};
    // RECT for rectangular, POL for Polar modes
    private:
        double x;          // horizontal value
        double y;          // vertical value
        double mag;        // length of vector
        double ang;        // direction of vector in degrees
        Mode mode;         // RECT or POL
    // private methods for setting values
        void set_mag();
        void set_ang();
        void set_x();
        void set_y();
    public:
       Vector();
        Vector(double n1, double n2, Mode form = RECT);
        void reset(double n1, double n2, Mode form = RECT);
        ~Vector();
        double xval() const {return x;}       // report x value
        double yval() const {return y;}       // report y value
        double magval() const {return mag;}   // report magnitude
        double angval() const {return ang;}   // report angle
        void polar_mode();                    // set mode to POL
        void rect_mode();                     // set mode to RECT
    // operator overloading
        Vector operator+(const Vector & b) const;
        Vector operator-(const Vector & b) const;
        Vector operator-() const;
        Vector operator*(double n) const;
    // friends
        friend Vector operator*(double n, const Vector & a);
        friend std::ostream & operator<<(std::ostream & os, const Vector & v);
    };

}   // end namespace VECTOR
#endif

vect.cpp:

// vect.cpp -- methods for the Vector class
#include <cmath>
#include "vect.h"   // includes <iostream>
using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;

namespace VECTOR
{
    // compute degrees in one radian
    const double Rad_to_deg = 45.0 / atan(1.0);
    // should be about 57.2957795130823

    // private methods
    // calculates magnitude from x and y
    void Vector::set_mag()
    {
        mag = sqrt(x * x + y * y);
    }

    void Vector::set_ang()
    {
        if (x == 0.0 && y == 0.0)
            ang = 0.0;
        else
            ang = atan2(y, x);
    }

    // set x from polar coordinate
    void Vector::set_x()
    {
        x = mag * cos(ang);
    }

    // set y from polar coordinate
    void Vector::set_y()
    {
        y = mag * sin(ang);
    }

    // public methods
    Vector::Vector()             // default constructor
    {
        x = y = mag = ang = 0.0;
        mode = RECT;
    }

    // construct vector from rectangular coordinates if form is r
    // (the default) or else from polar coordinates if form is p
    Vector::Vector(double n1, double n2, Mode form)
    {
        mode = form;
        if (form == RECT)
         {
             x = n1;
             y = n2;
             set_mag();
             set_ang();
        }
        else if (form == POL)
        {
             mag = n1;
             ang = n2 / Rad_to_deg;
             set_x();
             set_y();
        }
        else
        {
             cout << "Incorrect 3rd argument to Vector() -- ";
             cout << "vector set to 0\n";
             x = y = mag = ang = 0.0;
             mode = RECT;
        }
    }

    // reset vector from rectangular coordinates if form is
    // RECT (the default) or else from polar coordinates if
    // form is POL
    void Vector:: reset(double n1, double n2, Mode form)
    {
        mode = form;
        if (form == RECT)
         {
             x = n1;
             y = n2;
             set_mag();
             set_ang();
        }
        else if (form == POL)
        {
             mag = n1;
             ang = n2 / Rad_to_deg;
             set_x();
             set_y();
        }
        else
        {
             cout << "Incorrect 3rd argument to Vector() -- ";
             cout << "vector set to 0\n";
             x = y = mag = ang = 0.0;
             mode = RECT;
        }
    }

    Vector::~Vector()    // destructor
    {
    }

    void Vector::polar_mode()    // set to polar mode
    {
        mode = POL;
    }

    void Vector::rect_mode()     // set to rectangular mode
    {
        mode = RECT;
    }

    // operator overloading
    // add two Vectors
    Vector Vector::operator+(const Vector & b) const
    {
        return Vector(x + b.x, y + b.y);
    }

    // subtract Vector b from a
    Vector Vector::operator-(const Vector & b) const
    {
        return Vector(x - b.x, y - b.y);
    }

    // reverse sign of Vector
    Vector Vector::operator-() const
    {
        return Vector(-x, -y);
    }

    // multiply vector by n
    Vector Vector::operator*(double n) const
    {
        return Vector(n * x, n * y);
    }

    // friend methods
    // multiply n by Vector a
    Vector operator*(double n, const Vector & a)
    {
        return a * n;
    }

    // display rectangular coordinates if mode is RECT,
    // else display polar coordinates if mode is POL
    std::ostream & operator<<(std::ostream & os, const Vector & v)
    {
        if (v.mode == Vector::RECT)
             os << "(x,y) = (" << v.x << ", " << v.y << ")";
        else if (v.mode == Vector::POL)
        {
             os << "(m,a) = (" << v.mag << ", "
                 << v.ang * Rad_to_deg << ")";
        }
        else
             os << "Vector object mode is invalid";
        return os;
    }

}  // end namespace VECTOR

randwalk.cpp:

// randwalk.cpp -- using the Vector class
// compile with the vect.cpp file
#include <iostream>
#include <cstdlib>      // rand(), srand() prototypes
#include <ctime>        // time() prototype
#include "vect.h"
int main()
{
    using namespace std;
    using VECTOR::Vector;
    srand(time(0));     // seed random-number generator
    double direction;
    Vector step;
    Vector result(0.0, 0.0);
    unsigned long steps = 0;
    double target;
    double dstep;
    int times;
    unsigned long max, min;
    max = min = 0;

    cout << "Enter Times:";
    if(!(cin >> times))
    {
        cout << "Input error !, program ended." << endl;
        return 0;
    }
    cout << "Enter target distance (q to quit): ";
    for(int i = 0; i < times; ++i)
    {
       cin >> target;
       cout << "Enter step length: ";
       if(!(cin >> dstep))
       {
           break;
       }

       while(result.magval() < target)
       {
           direction = rand() % 360;
           step.reset(dstep, direction, Vector::POL);
           result = result + step;
           steps++;
       }
       if(min == 0)
       {
           min = steps;
       }

       if(steps > max)
       {
          max = steps;
       }
       else if(steps < min)
       {
           min = steps;
       }

       result.reset(0.0, 0.0);
       if(i < times - 1)
       {
           cout << "Enter target distance (q to quit): ";
       }
    }
    cout << "Max: " << max << endl;
    cout << "Min: " << min << endl;
    cout << "Bye!\n";
/* keep window open
    cin.clear();
    while (cin.get() != '\n')
        continue;
    cin.get();
*/
    return 0;
}

4.重新编写最后的Time类示例(程序清单11.10、程序清单11.11和程序清单11.12),使用友元函数来实现所有的重载运算符。

mytime3.h:

// mytime3.h -- Time class with friends
#ifndef MYTIME3_H_
#define MYTIME3_H_
#include <iostream>

class Time
{
private:
    int hours;
    int minutes;
public:
    Time();
    Time(int h, int m = 0);
    void AddMin(int m);
    void AddHr(int h);
    void Reset(int h = 0, int m = 0);
    friend Time operator+(const Time & t1, const Time & t2);
    friend Time operator-(const Time & t1, const Time & t2);
    friend Time operator*(const Time & t, double m);
    friend Time operator*(double m, const Time & t)
        { return t * m; }   // inline definition
    friend std::ostream & operator<<(std::ostream & os, const Time & t);

};
#endif

mytime3.cpp:

// mytime3.cpp  -- implementing Time methods
#include "mytime3.h"

Time::Time()
{
    hours = minutes = 0;
}

Time::Time(int h, int m )
{
    hours = h;
    minutes = m;
}

void Time::AddMin(int m)
{
    minutes += m;
    hours += minutes / 60;
    minutes %= 60;
}

void Time::AddHr(int h)
{
    hours += h;
}

void Time::Reset(int h, int m)
{
    hours = h;
    minutes = m;
}

Time operator+(const Time & t1, const Time & t2)
{
    Time sum;
    sum.minutes = t1.minutes + t2.minutes;
    sum.hours = t1.hours + t2.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;
}

Time operator-(const Time & t1, const Time & t2)
{
    Time diff;
    int tot1, tot2;
    tot1 = t1.minutes + 60 * t1.hours;
    tot2 = t2.minutes + 60 * t2.hours;
    diff.minutes = (tot2 - tot1) % 60;
    diff.hours = (tot2 - tot1) / 60;
    return diff;
}

Time operator*(const Time & t, double m)
{
    Time result;
    long totalminutes = t.hours * m * 60 + t.minutes * m;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}

std::ostream & operator<<(std::ostream & os, const Time & t)
{
    os << t.hours << " hours, " << t.minutes << " minutes";
    return os;
}

usetime3.cpp:

//usetime3.cpp -- using the fourth draft of the Time class
// compile usetime3.cpp and mytime3.cpp together
#include <iostream>
#include "mytime3.h"

int main()
{
    using std::cout;
    using std::endl;
    Time aida(3, 35);
    Time tosca(2, 48);
    Time temp;

    cout << "Aida and Tosca:\n";
    cout << aida<<"; " << tosca << endl;
    temp = aida + tosca;     // operator+()
    cout << "Aida + Tosca: " << temp << endl;
    temp = aida* 1.17;  // member operator*()
    cout << "Aida * 1.17: " << temp << endl;
    cout << "10.0 * Tosca: " << 10.0 * tosca << endl;
    // std::cin.get();
    return 0;
}

5.重新编写Stonewt类(程序清单11.16和程序清单11.17),使他有一个状态成员,由该成员控制对象转换为英石格式、整数磅格式还是浮点磅格式。重载`<<`运算符,使用它来替换```show_stn()```和`show_lbs()`方法。重载加法、减法和乘法运算符,以便可以对Stonewt值进行加、减、乘运算。编写一个使用所有类方法和友元的小程序,来测试这个类。

stonewt.h:

// stonewt.h -- definition for the Stonewt class
#ifndef STONEWT_H_
#define STONEWT_H_
#include<iostream>

class Stonewt
{
public:
    enum Mode{STN, PDS};
private:
    enum {Lbs_per_stn = 14};      // pounds per stone
    int stone;                    // whole stones
    double pds_left;              // fractional pounds
    double pounds;                // entire weight in pounds
    Mode mode;
public:
    Stonewt(double lbs, Mode m = STN);          // constructor for double pounds
    Stonewt(int stn, double lbs, Mode m = STN); // constructor for stone, lbs
    Stonewt();                    // default constructor
    ~Stonewt();

    void set_mode(Mode m){ mode = m;}
    Stonewt operator+(const Stonewt & st) const;
    Stonewt operator-(const Stonewt & st) const;
    Stonewt operator*(double n) const;

    friend Stonewt operator*(double n, const Stonewt & st)
    {
        return st * n;
    }
    friend std::ostream & operator<<(std::ostream & os, const Stonewt & st);

};
#endif

stonewt.cpp:

// stonewt.cpp -- Stonewt methods
#include <iostream>
using std::cout;
#include "stonewt.h"

// construct Stonewt object from double value
Stonewt::Stonewt(double lbs, Mode m)
{
    stone = int (lbs) / Lbs_per_stn;    // integer division
    pds_left = int (lbs) % Lbs_per_stn + lbs - int(lbs);
    pounds = lbs;
    mode = m;
}

// construct Stonewt object from stone, double values
Stonewt::Stonewt(int stn, double lbs, Mode m)
{
    stone = stn;
    pds_left = lbs;
    pounds =  stn * Lbs_per_stn +lbs;
    mode = m;
}

Stonewt::Stonewt()          // default constructor, wt = 0
{
    stone = pounds = pds_left = 0;
    mode = STN;
}

Stonewt::~Stonewt()         // destructor
{
}


Stonewt Stonewt::operator+(const Stonewt & st) const
{
    double sum = pounds + st.pounds;
    return Stonewt(sum);
}

Stonewt Stonewt::operator-(const Stonewt & st) const
{
    double diff = pounds - st.pounds;
    return Stonewt(diff);
}

Stonewt Stonewt::operator*(double n) const
{
    double multi = pounds * n;
    return Stonewt(multi);
}

std::ostream & operator<<(std::ostream & os, const Stonewt & st)
{
    if(st.mode == Stonewt::STN)
    {
        std::cout << st.stone << " stone, " << st.pds_left << "pounds";
    }
    else if(st.mode == Stonewt::PDS)
    {
        std::cout << st.pounds << "pounds";
    }
    else
    {
        std::cout << "Mode error!" << std::endl;
    }
}

main.cpp:

#include "stonewt.h"
#include <iostream>

int main()
{
    using std::cout;
    using std::endl;

    Stonewt s1;
    Stonewt s2 = Stonewt(1, 2);
    Stonewt s3 = Stonewt(20.65, Stonewt::PDS);

    cout << "s1(STN): " << s1 << endl;
    cout << "s2(STN): " << s2 << endl;
    cout << "s3(PDS): " << s3 << endl;
    s3.set_mode(Stonewt::STN);
    cout << "s3(STN): " << s3 << endl;
    cout << "s3 - s2 = " << s3 - s2 << endl;
    cout << "s3 + s2 = " << s3 + s2 << endl;
    cout << "2*s2 = " << 2 * s2 << endl;
    cout << "s2*2 = " << s2 * 2 << endl;

    return 0;
}

6.重新编写Stonewt类(程序清单11.16和程序清单11.17),重载全部6个关系云算符。运算符对pounds成员进行比较,并返回一个bool值。编写一个程序,它声明一个包含6个Stonewt对象的数组,并在数组声明中初始化前3个对象。然后使用循环来读取用于设置剩余3个数组元素的值。接着报告最小的元素、最大的元素以及大于或等于11英石的元素数量(最简单的方法是创建一个Stonewt对象,并将其初始化为11英石,然后将其同其他对象进行比较)。

stonewt.h:

// stonewt.h -- definition for the Stonewt class
#ifndef STONEWT_H_
#define STONEWT_H_

class Stonewt
{
private:
    enum {Lbs_per_stn = 14};      // pounds per stone
    int stone;                    // whole stones
    double pds_left;              // fractional pounds
    double pounds;                // entire weight in pounds
public:
    Stonewt(double lbs);          // constructor for double pounds
    Stonewt(int stn, double lbs); // constructor for stone, lbs
    Stonewt();                    // default constructor
    ~Stonewt();

    void show_stn() const;
    void show_lbs() const;

    bool operator==(const Stonewt& st)const;
    bool operator!=(const Stonewt& st)const;
    bool operator>(const Stonewt& st)const;
    bool operator<(const Stonewt& st)const;
    bool operator>=(const Stonewt& st)const;
    bool operator<=(const Stonewt& st)const;


};
#endif

stonewt.cpp:

// stonewt.cpp -- Stonewt methods
#include <iostream>
using std::cout;
#include "stonewt.h"

// construct Stonewt object from double value
Stonewt::Stonewt(double lbs)
{
    stone = int (lbs) / Lbs_per_stn;    // integer division
    pds_left = int (lbs) % Lbs_per_stn + lbs - int(lbs);
    pounds = lbs;
}

// construct Stonewt object from stone, double values
Stonewt::Stonewt(int stn, double lbs)
{
    stone = stn;
    pds_left = lbs;
    pounds =  stn * Lbs_per_stn +lbs;
}

Stonewt::Stonewt()          // default constructor, wt = 0
{
    stone = pounds = pds_left = 0;
}

Stonewt::~Stonewt()         // destructor
{
}

// show weight in stones
void Stonewt::show_stn() const
{
    cout << stone << " stone, " << pds_left << " pounds\n";
}

// show weight in pounds
void Stonewt::show_lbs() const
{
    cout << pounds << " pounds\n";
}


bool Stonewt::operator==(const Stonewt& st)const
{
    return pounds == st.pounds;
}
bool Stonewt::operator!=(const Stonewt& st)const
{
     return pounds != st.pounds;
}
bool Stonewt::operator>(const Stonewt& st)const
{
     return pounds > st.pounds;
}
bool Stonewt::operator<(const Stonewt& st)const
{
     return pounds < st.pounds;
}
bool Stonewt::operator>=(const Stonewt& st)const
{
     return pounds >= st.pounds;
}
bool Stonewt::operator<=(const Stonewt& st)const
{
     return pounds <= st.pounds;
}

main.cpp:

#include "stonewt.h"
#include <iostream>

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

    Stonewt arr[6] = {30, 12, 26};
     for(int i = 3; i < 6; i++)
     {
         cout << "Stonewt[" << i << "]: ";
         double st;
         cin >> st;
         arr[i] = Stonewt(st);
     }

     Stonewt max = arr[0], min = arr[0];
     int num = 0;
     Stonewt eleven = Stonewt(11, 0.0);
     for(int i = 0; i < 6; ++i)
     {
         if(arr[i] > max)
             max = arr[i];
         if(arr[i] < min)
             min = arr[i];
         if(arr[i] > eleven)
             num++;
     }

     cout << "max: " << endl;
     max.show_lbs();
     cout << "min: " << endl;
     min.show_lbs();
     cout << num << " elements > 11 stones" << endl;

    return 0;
}

7.复数有两个部分组成:实数部分和虚数部分。复数的一种写法方式是:(3.0, 4.0),其中,3.0是实数部分,4.0是虚数部分。假设a=(A,Bi),c=(C,Di),则下面是一些复数运算。

  • 加法:a+c=(A+C,(B+D)i)。

  • 减法:a-c=(A-C,(B-D)i)。

  • 乘法:a*c=(A*C-B*D, (A*D+B*C)i)。

  • 乘法:x*c=(x*C,x*Di)。

  • 共轭:a=(A,-Bi)。

请定义一个复数类,以便下面的程序可以使用它来获得正确的结果。

#include<iostream>
using namespace std;
#include "complex0.h"
int main()
{
    complex a(3.0, 4.0);
    complex c;
    cout << "Enter a complex number (q to quit):\n";
    while(cin >> c)
    {
        cout << "c is " << c <<'\n';
        cout << "complex conjugate is " << ~c <<'\n';
        cout << "a is " << a << '\n';
        cout << "a + c is " << a + c << '\n';
        cout << "a - c is " << a - c << '\n';
        cout << "a * c is " << a * c << '\n';
        cout << "2 * c is " << 2 * c << '\n';
        cout << "Enter a complex number (q to quit):\n"; 
    }
    cout << "Done! \n";
    return 0;
}

注意,必须重载运算符<<和>>。标准c++使用头文件complex提供了比这个示例更广泛的复数支持,因此应将自己定义的头文件命名为complex0.h,以免发生冲突。应尽可能使用const。

下面是该程序的运行情况。

Enbter a complex number (q to quit):
real:10
imaginary:12
c is (10,12i)
complex conjugate is (10, -12i)
a is (3,4i)
a + c is (13,16i)
a - c is (-7,78i)
a * c is (-18,76i)
2 + c is (20,24i)
Enbter a complex number (q to quit):
real:q
Done!

请注意,经过重载后,cin>>c 将提示用户输入实数和虚数部分。

complex0.h:

#ifndef COMPLEX0_H
#define COMPLEX0_H
#include<iostream>

class complex
{
private:
    double real;
    double img;
public:
    complex();
    complex(double r, double i);
    ~complex();

    complex operator+(const complex & c) const;
    complex operator-(const complex & c) const;
    complex operator*(const complex & c) const;
    complex operator*(double n) const;
    complex operator~() const;

    friend complex operator*(double n, const complex &c);
    friend std::istream & operator>>(std::istream & is, complex & c);
    friend std::ostream & operator<<(std::ostream & os, const complex & c);
};

#endif // COMPLEX0_H

complex0.cpp:

#include "complex0.h"

complex::complex()
{
    real = img = 0;
}

complex::complex(double r, double i)
{
    real = r;
    img = i;
}
complex::~complex(){};

complex complex::operator+(const complex & c) const
{
    complex sum;
    sum.real = real + c.real;
    sum.img = img + c.img;
    return sum;
}

complex complex::operator-(const complex & c) const
{
    complex diff;
    diff.real = real - c.real;
    diff.img = img - c.img;
    return diff;
}

complex complex::operator*(const complex & c) const
{
    complex multi;
    multi.real = real * c.real - img * c.img;
    multi.img = real * c.img + img * c.real;
    return multi;
}

complex complex::operator*(double n) const
{
    complex multi;
    multi.real = real * n;
    multi.img = img * n;
    return multi;
}

complex complex::operator~() const
{
    complex conj;
    conj.real = real;
    conj.img = -img;
    return conj;
}

complex operator*(double n, const complex &c)
{
    return c * n;
}

std::istream & operator>>(std::istream & is, complex & c)
{
    std::cout << "real: ";
    is >> c.real;
    std::cout << "imaginary: ";
    is >>c.img;
    return is;
}

std::ostream & operator<<(std::ostream & os, const complex & c)
{
    os << "(" << c.real << ", " << c.img << "i)";
    return os;
}

 第十二章课后习题答案

复习题

1. 假设String 类有如下私有成员:

class String
{
   private:
      char * str;  //points to string allocated by new
      int len;    //holds length of string
   //...
};

a. 下述默认构造函数有什么问题?

String ::String() {}

b. 下述构造函数有什么问题?

String:: String (const char* s)
{
     str = s:
     len = strlen(s);
}

c. 下述构造西数有什么问题?

String: :String (const char* s)
{
     strcpy(str, s);
     len = strlen(s);
}

a. 语法是正确的,但该构造函数没有初始化str指针。该构造函数应该使用new[]来初始化它,或者将其设置为NULL。

b. 该构造函数没有创建新的字符串,只是复制了原有字符串的地址,它应当使用new[]和strcpy()。

c. 它虽然复制了字符串,但没有给他分配存储空间,应使用new char[len+1]来分配适当数量的内存。

2. 如果您定义了一个类,其指针成员是使用 new 初始化的,请指出可能出现的3个问题以及如何纠正这些问题。

可能出现的:

  • 问题一:当对象过期的时候,对象的成员指针指向的数据仍保留在内存中,造成内存泄漏。

    在析构函数中删除构造函数中new分配的内存,来解决该问题。

  • 问题二:如果使用对象初1始化为另一个对象2时,采用默认初始化的方式,则在对象1过期后,在对象2过期过程中,其析构函数会对同一块内存释放两次,造成错误。

    定义一个复制构造函数,使初始化复制指针指向的数据,而不是复制指针指向的地址。

  • 问题三:将一个对象赋给另一个对象也将导致两个指针指向同一个数据。

    重载赋值运算符,使之复制数据,而不是指针。

3. 如果没有显式提供类方法,编译器将自动生成哪些类方法?请描述这些隐式生成的函数的行为。

如果没有显示提供方法,c++将自动生成以下成员函数:

  • 构造函数:默认构造函数不完成任何工作,但使得能够声明数组和未初始化对象。

  • 复制构造函数:默认赋值构造函数使用成员赋值。

  • 赋值运算符:默认赋值运算法使用成员赋值。

  • 析构函数:默认析构函数不完成任何工作。

  • 地址运算符:隐式地址运算符返回调用对象的地址(即this指针的值)。

4. 找出并改正下述类声明中的错误:

class nifty
{
    // data
     char personality[];
     int talents;
    // methods
     nifty();
     nifty(char * s);
     ostream & operator<<(ostream & os, nifty & n);
}

nifty:nifty()
{
     personality = NULL;
     talents = 0;
}

nifty:nifty(char * s)
{
     personality = new char [strlen(s)];
     personality = s;
     talents = 0;
}

ostream & nifty:operator<<(ostream & os, nifty & n)
{
     os << n;
}

应将personality成员声明为字符数组或car指针,或者将其声明为String对象。该声明没有将方法设置为公有的。修改后:

#include <iostream>
#include <cstring>
using namespace std;
class nifty
{
    private: // optional
    char personality[40]; // provide array size
    int talents;
    public: // needed
    // methods
    nifty();
    nifty(const char * s);
    friend ostream & operator<<(ostream & os, const nifty & n);
}; // note closing semicolon
nifty::nifty()
{
    personality[0] = '\0';
    talents = 0;
}
nifty::nifty(const char * s)
{
    strcpy(personality, s);
    talents = 0;
}
ostream & operator<<(ostream & os, const nifty & n)
{
    os << n.personality << '\n';
    os << n.talent << '\n';
    return os;
}

5.对于下面的类声明:

class Golfer
{
    private:
     char * fullname; // points to string containing golfer's name
     int games; // holds number of golf games played
     int * scores; // points to first element of array of golf scores
    public:
     Golfer();
     Golfer(const char * name, int g= 0);
     // creates empty dynamic array of g elements if g > 0
     Golfer(const Golfer & g);
     ~Golfer();
};

a.下列各条语句将调用哪些类方法?

Golfer nancy;                     // #1
Golfer lulu(“Little Lulu”);       // #2
Golfer roy(“Roy Hobbs”, 12);      // #3
Golfer * par = new Golfer;        // #4
Golfer next = lulu;               // #5
Golfer hazzard = “Weed Thwacker”; // #6
*par = nancy;                     // #7
nancy = “Nancy Putter”;           // #8

b. 很明显,类需要有另外几个方法才能更有用,但是类需要哪些方法才能防止数据被损坏呢?

a.

 Golfer nancy;                     // 默认构造函数
   Golfer lulu("Little Lulu");       // Golfer(const char * name, int g)
   Golfer roy("Roy Hobbs", 12);      // Golfer(const char * name, int g)
   Golfer* par = new Golfer;         // 默认构造函数
   Golfer next = lulu;               // Golfer(const Golfer &g)
   Golfer hazard = "Weed Thwacker";  // Golfer(const char * name, int g)
   *par = nancy;                     // 默认赋值运算符
   nancy = "Nancy Putter";           // 先调用Golfer(const char * name, int g), 再默认赋值运算符

b. 有以下两个方法:

  1. 防止拷贝,将赋值运算符(面向对象拷贝给对象的)/复制构造函数,放在私有部分;

  2. 类应定义一个复制数据(而不是地址)的赋值运算符。

编程练习

1. 对于下面的类声明:

class Cow {
    char name[20];
    char * hobby;
    double weight;
  public:
    Cow();
    Cow(const char * nm, const char * ho, double wt);
    Cow(const Cow c&);
    ~Cow();
    Cow & operator=(const Cow & c);
    void ShowCow() const; // display all cow data
};

给这个类提供实现,并编写一个使用所有成员函数的小程序。

cow.h:

#ifndef COW_H_
#define COW_H_

class Cow {
    private:
        char name[20];
        char * hobby;
        double weight;

    public:
        Cow(); 
        Cow(const char * nm, const char * ho, double wt);
        Cow(const Cow & c);
        ~Cow() { delete [] hobby; }
        Cow & operator=(const Cow & c);
        void show() const;
};

#endif

cow.cpp:

#include "cow.h"

#include <cstring>
#include <iostream>

Cow::Cow() {
  name[0] = '\0';
  hobby = nullptr;
  weight = 0.0;
}

Cow::Cow(const char* nm, const char* ho, double wt) {
  std::strncpy(name, nm, 20);
  int len = std::strlen(ho) + 1;
  hobby = new char[len];
  std::strcpy(hobby, ho);

  weight = wt;
}

Cow::Cow(const Cow& c) {
  std::strncpy(name, c.name, 20);
  int len;
  len = std::strlen(c.hobby);
  hobby = new char[len];
  std::strcpy(hobby, c.hobby);
  weight = c.weight;
}

Cow& Cow::operator=(const Cow& c) {
  if (this == &c) return *this;  // object assigned to itself
  std::strncpy(name, c.name, 20);
  delete[] hobby;
  int len;
  len = std::strlen(c.hobby);
  hobby = new char[len];
  std::strcpy(hobby, c.hobby);
  weight = c.weight;
  return *this;
}

void Cow::show() const {
  std::cout << "name: " << name << std::endl;
  std::cout << "hobby: " << hobby << std::endl;
  std::cout << "weight: " << weight << std::endl;
}

main.cpp:

#include <iostream>
#include "cow.h"

int main() {
  Cow c1("xiao", "eating", 100);
  c1.show();
  Cow c2 = c1;
  c2.show();
  Cow c3;
  c3 = c2;
  c3.show();
  return 0;
}

2. 通过下面的工作来改进String类声明(即将String1.h升级为String2.h)。

a. 对+运算符进行重载,使之可将两个字符串合并成一个。

b. 提供一个Stringlow()成员函数,将字符串中所有的字母字符转换为小写(别忘了cctype系列字符函数)。

c. 提供String()成员函数,将字符串中所有字母字符转换成大写。

d. 提供一个这样的成员函数,它接受一个char参数,返回该字符在字符串中出现的次数。

使用下面的程序来测试您的工作:

// pe12_2.cpp
#include <iostream>
using namespace std;
#include "string2.h"
int main()
{
    String s1(" and I am a C++ student.");
    String s2 = "Please enter your name: ";
    String s3;
    cout << s2; // overloaded << operator
    cin >> s3; // overloaded >> operator
    s2 = "My name is " + s3; // overloaded =, + operators
    cout << s2 << ".\n";
    s2 = s2 + s1;
    s2.stringup(); // converts string to uppercase
    cout << "The string\n" << s2 << "\ncontains " << s2.has('A')
        << " 'A' characters in it.\n";
    s1 = "red"; // String(const char *),
    // then String & operator=(const String&)
    String rgb[3] = { String(s1), String("green"), String("blue")};
    cout << "Enter the name of a primary color for mixing light: ";
    String ans;
    bool success = false;
    while (cin >> ans)
    {
        ans.stringlow(); // converts string to lowercase
        for (int i = 0; i < 3; i++)
        {
            if (ans == rgb[i]) // overloaded == operator
            {
                cout << "That's right!\n";
                success = true;
                break;
            }
        }
        if (success)
            break;
        else
            cout << "Try again!\n";
    }
    cout << "Bye\n";
    return 0;
}

输出应与下面相似:

Please enter your name: Fretta Farbo
My name is Fretta Farbo.
The string
MY NAME IS FRETTA FARBO AND I AM A C++ STUDENT.
contains 6 'A' characters in it.
Enter the name of a primary color for mixing light: yellow
Try again!
BLUE
That's right!
Bye

string2.h:

#ifndef STRING2_H_
#define STRING2_H_

#include <iostream>
using std::istream;
using std::ostream;

class String {
private:
char *str;
int len;
static int num_strings;
static const int CINLIM = 80;

public:
String(const char *s);
String();
String(const String &s);
~String();
int length() const { return len; }
void stringlow();
void stringup();
int has(char x);

String &operator=(const String &s);
String &operator=(const char *s);
char &operator[](int i);
const char &operator[](int i) const;

String operator+(const String &s) const;
String operator+(const char *s) const;

friend bool operator<(const String &s1, const String &s2);
friend bool operator>(const String &s1, const String &s2);
friend bool operator==(const String &s1, const String &s2);
friend ostream &operator<<(ostream &os, const String &st);
friend istream &operator>>(istream &is, String &st);
friend String operator+(const char *, const String &);

static int HowMany();
};

#endif  // STRING2_H_

string.cpp:

#include "string2.h"

// #include <cctype>
#include <cstring>

// initialize static members
int String::num_strings = 0;

int String::HowMany() { return num_strings; }

String::String(const char *s) {
len = std::strlen(s);
str = new char[len + 1];
std::strcpy(str, s);
num_strings++;
}

String::String() {
len = 1;
str = new char[1];
str[0] = '\0';
num_strings++;
}

String::String(const String &s) {
len = s.len;
str = new char[len + 1];
std::strcpy(str, s.str);
num_strings++;
}

String::~String() {
--num_strings;
delete[] str;
}

void String::stringlow() {
for (int i = 0; i < len; ++i) {
 if (std::isupper(str[1])) str[i] = std::tolower(str[i]);
}
}
void String::stringup() {
for (int i = 0; i < len; ++i)
 if (std::islower(str[i])) str[i] = std::toupper(str[i]);
}

int String::has(char x) {
int count = 0;
for (int i = 0; i < len; ++i) {
 if (str[i] == x) count++;
}
return count;
}

String &String::operator=(const String &s) {
if (this == &s) return *this;
delete[] str;
len = s.len;
str = new char[len + 1];
std::strcpy(str, s.str);
return *this;
}

String &String::operator=(const char *s) {
delete[] str;
len = std::strlen(s);
str = new char[len + 1];
std::strcpy(str, s);
return *this;
}

char &String::operator[](int i) { return str[i]; }

const char &String::operator[](int i) const { return str[i]; }

String String::operator+(const String &s) const {
int total_len = len + s.len;
char *tmp = new char[total_len + 1];
std::strcpy(tmp, str);
std::strcat(tmp, s.str);
String str_new = tmp;
delete[] tmp;
return str_new;
}

String String::operator+(const char *s) const {
String tmp = s;
String sum = *this + tmp;
return sum;
}

bool operator<(const String &s1, const String &s2) {
return (std::strcmp(s1.str, s2.str) < 0);
}
bool operator>(const String &s1, const String &s2) {
return (std::strcmp(s1.str, s2.str) > 0);
}
bool operator==(const String &s1, const String &s2) {
//比较相等的时候不考虑大小写,使用strcasecmp(linux)/stricmp(windows)
return (strcasecmp(s1.str, s2.str) == 0);
}
ostream &operator<<(ostream &os, const String &st) {
os << st.str;
return os;
}
istream &operator>>(istream &is, String &st) {
char tmp[String::CINLIM];
is.get(tmp, String::CINLIM);
if (is) st = tmp;
while (is && is.get() != '\n') continue;
return is;
}
String operator+(const char *s1, const String &s2) { return String(s1) + s2; }

3. 新编写程序清单10.7和程序清单10.8描述的Stock类,使之使用动态分配的内存,而不是string类对象来存储股票名称。另外,使用重

载的operator<<()定义代替show()成员函数。再使用程序清单10.9测试新的定义程序。

stock20.h:

// stock20.h -- augmented version
#ifndef STOCK20_H_
#define STOCK20_H_
#include <iostream>

class Stock {
 private:
  char* company;
  int shares;
  double share_val;
  double total_val;
  void set_tot() { total_val = shares * share_val; }

 public:
  Stock();  // default constructor
  Stock(const char* co, long n = 0, double pr = 0.0);
  ~Stock();  // do-nothing destructor
  void buy(long num, double price);
  void sell(long num, double price);
  void update(double price);
  friend std::ostream& operator<<(std::ostream& os, const Stock& st);
  const Stock& topval(const Stock& s) const;
};
#endif

stock.cpp:

// stock20.h -- augmented version
#ifndef STOCK20_H_
#define STOCK20_H_
#include <iostream>

class Stock {
 private:
  char* company;
  int shares;
  double share_val;
  double total_val;
  void set_tot() { total_val = shares * share_val; }

 public:
  Stock();  // default constructor
  Stock(const char* co, long n = 0, double pr = 0.0);
  ~Stock();  // do-nothing destructor
  void buy(long num, double price);
  void sell(long num, double price);
  void update(double price);
  friend std::ostream& operator<<(std::ostream& os, const Stock& st);
  const Stock& topval(const Stock& s) const;
};
#endif

usestock20.cpp:

// stock20.cpp -- augmented version
#include "stock20.h"

#include <cstring>
// constructors
Stock::Stock()  // default constructor
{
  company = new char[std::strlen("no name") + 1];
  std::strcpy(company, "no name");
  shares = 0;
  share_val = 0.0;
  total_val = 0.0;
}
Stock::Stock(const char* co, long n, double pr) {
  company = new char[std::strlen(co) + 1];
  std::strcpy(company, co);
  if (n < 0) {
    std::cout << "Number of shares can’t be negative; " << company
              << " shares set to 0.\n";
    shares = 0;
  } else
    shares = n;
  share_val = pr;
  set_tot();
}
// class destructor
Stock::~Stock()  // quiet class destructor
{
  delete[] company;
}
// other methods
void Stock::buy(long num, double price) {
  if (num < 0) {
    std::cout << "Number of shares purchased can’t be negative. "
              << "Transaction is aborted.\n";
  } else {
    shares += num;
    share_val = price;
    set_tot();
  }
}
void Stock::sell(long num, double price) {
  using std::cout;
  if (num < 0) {
    cout << "Number of shares sold can’t be negative. "
         << "Transaction is aborted.\n";
  } else if (num > shares) {
    cout << "You can’t sell more than you have! "
         << "Transaction is aborted.\n";
  } else {
    shares -= num;
    share_val = price;
    set_tot();
  }
}
void Stock::update(double price) {
  share_val = price;
  set_tot();
}

std::ostream& operator<<(std::ostream& os, const Stock& st) {
  using std::ios_base;
  // set format to #.###
  ios_base::fmtflags orig = os.setf(ios_base::fixed, ios_base::floatfield);
  std::streamsize prec = os.precision(3);
  os << "Company: " << st.company << " Shares: " << st.shares << '\n';
  os << " Share Price: $" << st.share_val;
  // set format to #.##
  os.precision(2);
  os << " Total Worth: $" << st.total_val << '\n';
  // restore original format
  os.setf(orig, ios_base::floatfield);
  os.precision(prec);
  return os;
}

const Stock& Stock::topval(const Stock& s) const {
  if (s.total_val > total_val)
    return s;
  else
    return *this;
}

4.请看下面程序清单10.10定义的Stack类的变量:

// stack.h -- class declaration for the stack ADT
typedef unsigned long Item;
class Stack
{
    private:
    enum {MAX = 10}; // constant specific to class
    Item * pitems; // holds stack items
    int size; // number of elements in stack
    int top; // index for top stack item
    public:
    Stack(int n = MAX); // creates stack with n elements
    Stack(const Stack & st);
    ~Stack();
    bool isempty() const;
    bool isfull() const;
    // push() returns false if stack already is full, true otherwise
    bool push(const Item & item); // add item to stack
    // pop() returns false if stack already is empty, true otherwise
    bool pop(Item & item); // pop top into item
    Stack & operator=(const Stack & st);
};

正如私有成员表明的,这个类使用动态分配的数组来保存栈项。请重新编写方法,以适应这种新的表示法,并编写一个程序来演示所有的方法,包括复制构造函数和赋值运算符。

stack.h:

// stack.h -- class declaration for the stack ADT
#ifndef STACK_H_
#define STACK_H_
#include <iostream>

typedef unsigned long Item;
class Stack {
 private:
  enum { MAX = 10 };  // constant specific to class
  Item* pitems;       // holds stack items
  int size;           // number of elements in stack
  int top;            // index for top stack item
 public:
  Stack(int n = MAX);  // creates stack with n elements
  Stack(const Stack& st);
  ~Stack();
  bool isempty() const;
  bool isfull() const;
  // push() returns false if stack already is full, true otherwise
  bool push(const Item& item);  // add item to stack
  // pop() returns false if stack already is empty, true otherwise
  bool pop(Item& item);  // pop top into item
  Stack& operator=(const Stack& st);
  friend std::ostream& operator<<(std::ostream& os, const Stack& st);
};

#endif  // STACK_H_

stack.cpp:

// stack.cpp -- Stack member functions
#include "stack.h"
Stack::Stack(int n)  // create an empty stack
{
  size = MAX;
  top = 0;
  pitems = new Item[size];
  for (int i = 0; i < size; ++i) pitems[i] = 0;
}

Stack::Stack(const Stack& st) {
  delete[] pitems;
  size = st.size;
  top = st.top;
  pitems = new Item[size];
  for (int i = 0; i < size; ++i) pitems[i] = st.pitems[i];
}

Stack::~Stack() { delete[] pitems; }

bool Stack::isempty() const { return top == 0; }
bool Stack::isfull() const { return top == MAX; }

bool Stack::push(const Item& item) {
  if (top < MAX) {
    pitems[top++] = item;
    return true;
  } else
    return false;
}

bool Stack::pop(Item& item) {
  if (top > 0) {
    item = pitems[--top];
    return true;
  } else
    return false;
}

Stack& Stack::operator=(const Stack& st) {
  if (this == &st) return *this;
  delete[] pitems;
  size = st.size;
  top = st.top;
  pitems = new Item[size];
  for (int i = 0; i < size; ++i) pitems[i] = st.pitems[i];
  return *this;
}

std::ostream& operator<<(std::ostream& os, const Stack& st) {
  for (int i = 0; i < st.top; i++) {
    os << st.pitems[i] << std::endl;
  }
  return os;
}

main.cpp:

// stacker.cpp -- testing the Stack class
#include <cctype>  // or ctype.h
#include <iostream>

#include "stack.h"
int main() {
  using namespace std;
  Stack st;  // create an empty stack
  char ch;
  unsigned long po;
  cout << "Please enter A to add a purchase order,\n"
       << "P to process a PO, or Q to quit.\n";
  while (cin >> ch && toupper(ch) != 'Q') {
    while (cin.get() != '\n') continue;
    if (!isalpha(ch)) {
      cout << '\a';
      continue;
    }
    switch (ch) {
      case 'A':
      case 'a':
        cout << "Enter a PO number to add: ";
        cin >> po;
        if (st.isfull())
          cout << "stack already full\n";
        else
          st.push(po);
        break;
      case 'P':
      case 'p':
        if (st.isempty())
          cout << "stack already empty\n";
        else {
          st.pop(po);
          cout << "PO #" << po << " popped\n";
        }
        break;
    }
    cout << "Please enter A to add a purchase order,\n"
         << "P to process a PO, or Q to quit.\n";
  }
  Stack st2;
  st2 = st;
  cout << "stack2 = stack is:\n" << st2;

  cout << "Bye\n";
  return 0;
}

5. Heather银行进行的研究表明,ATM客户不希望排队时间不超过1分钟。使用程序清单12.10中的模拟,找出要使平均等候时间为1分钟,每小时到达的客户数应为多少(试验时间不短于100小时)?

根据实际情况可知,每小时到达的客户数目越多,则平均等候时间越长,若要求平均等候时间为1分钟(即平均等候时间不超过1分钟时的临界情况)对应的每小时到达客户数,则我们可以让程序从每小时客户数从1开始计算(不小于100小时)其对应的平均排队时间,直到平均等待时间刚好超过1分钟的时候停止,此时对应的客户数(或者对应客户数-1)即为答案。

queue.h(未修改):

// queue.h -- interface for a queue
#ifndef QUEUE_H_
#define QUEUE_H_
// This queue will contain Customer items
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; }
};
typedef Customer Item;

class Queue {
 private:
  // class scope definitions
  // Node is a nested structure definition local to this class
  struct Node {
    Item item;
    struct Node* next;
  };
  enum { Q_SIZE = 10 };
  // private class members
  Node* front;      // pointer to front of Queue
  Node* rear;       // pointer to rear of Queue
  int items;        // current number of items in Queue
  const int qsize;  // maximum number of items in Queue
  // preemptive definitions to prevent public copying
  Queue(const Queue& q) : qsize(0) {}
  Queue& operator=(const Queue& q) { return *this; }

 public:
  Queue(int qs = Q_SIZE);  // create queue with a qs limit
  ~Queue();
  bool isempty() const;
  bool isfull() const;
  int queuecount() const;
  bool enqueue(const Item& item);  // add item to end
  bool dequeue(Item& item);        // remove item from front
};
#endif

queue.cpp(未修改):

// queue.cpp -- Queue and Customer methods
#include "queue.h"

#include <cstdlib>  // (or stdlib.h) for rand()
// Queue methods
Queue::Queue(int qs) : qsize(qs) {
  front = rear = NULL;  // or nullptr
  items = 0;
}
Queue::~Queue() {
  Node* temp;
  while (front != NULL)  // while queue is not yet empty
  {
    temp = front;         // save address of front item
    front = front->next;  // reset pointer to next item
    delete temp;          // delete former front
  }
}
bool Queue::isempty() const { return items == 0; }
bool Queue::isfull() const { return items == qsize; }
int Queue::queuecount() const { return items; }
// Add item to queue
bool Queue::enqueue(const Item& item) {
  if (isfull()) return false;
  Node* add = new Node;  // create node
  // on failure, new throws std::bad_alloc exception
  add->item = item;  // set node pointers
  add->next = NULL;  // or nullptr;
  items++;
  if (front == NULL)  // if queue is empty,
    front = add;      // place item at front
  else
    rear->next = add;  // else place at rear
  rear = add;          // have rear point to new node
  return true;
}
// Place front item into item variable and remove from queue
bool Queue::dequeue(Item& item) {
  if (front == NULL) return false;
  item = front->item;  // set item to first item in queue
  items--;
  Node* temp = front;   // save location of first item
  front = front->next;  // reset front to next item
  delete temp;          // delete former first item
  if (items == 0) rear = NULL;
  return true;
}
// customer method
// when is the time at which the customer arrives
// the arrival time is set to when and the processing
// time set to a random value in the range 1 - 3
void Customer::set(long when) {
  processtime = std::rand() % 3 + 1;
  arrive = when;
}

main.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.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;
  Queue line(qs);  // 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

  double perhour = 1;  // number of customers per hour starts from 1

  double min_per_cust;  // average time between arrivals
  min_per_cust = MIN_PER_HR / perhour;
  Item 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
  double average_time = 0;  // average time
  while (perhour++ && average_time <= 1) {
    while (!line.isempty()) {
      line.dequeue(temp);
    }
    min_per_cust = MIN_PER_HR / perhour;

    for (int cycle = 0; cycle < cyclelimit; cycle++) {
      if (newcustomer(min_per_cust)) {
        if (line.isfull())
          turnaways++;
        else {
          customers++;
          temp.set(cycle);
          line.enqueue(temp);
        }
      }
      if (wait_time <= 0 && !line.isempty()) {
        line.dequeue(temp);
        wait_time = temp.ptime();
        line_wait += cycle - temp.when();
        served++;
      }
      if (wait_time > 0) wait_time--;
      sum_line += line.queuecount();
    }

    if (customers > 0) {
      average_time = (double)line_wait / served;
      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: " << average_time << " minutes\n";
    } else
      cout << "No customers!\n";
  }
  cout << "When there comes " << perhour
       << " people per hour, the average wait time will be about 1 minute.\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); }

实验过程中,队列最大数为10,模拟小时数为100,结果如下:

Case Study: Bank of Heather Automatic Teller
Enter maximum size of queue: 10
Enter the number of simulation hours: 100
customers accepted: 217
  customers served: 217
         turnaways: 0
average queue size: 0.00
 average wait time: 0.06 minutes
customers accepted: 522
  customers served: 522
         turnaways: 0
average queue size: 0.01
 average wait time: 0.07 minutes
customers accepted: 941
  customers served: 941
         turnaways: 0
average queue size: 0.01
 average wait time: 0.09 minutes
customers accepted: 1465
  customers served: 1465
         turnaways: 0
average queue size: 0.02
 average wait time: 0.10 minutes
customers accepted: 2078
  customers served: 2078
         turnaways: 0
average queue size: 0.04
 average wait time: 0.12 minutes
customers accepted: 2821
  customers served: 2821
         turnaways: 0
average queue size: 0.07
 average wait time: 0.15 minutes
customers accepted: 3640
  customers served: 3640
         turnaways: 0
average queue size: 0.10
 average wait time: 0.17 minutes
customers accepted: 4525
  customers served: 4525
         turnaways: 0
average queue size: 0.14
 average wait time: 0.19 minutes
customers accepted: 5534
  customers served: 5534
         turnaways: 0
average queue size: 0.20
 average wait time: 0.21 minutes
customers accepted: 6617
  customers served: 6617
         turnaways: 0
average queue size: 0.26
 average wait time: 0.24 minutes
customers accepted: 7780
  customers served: 7780
         turnaways: 0
average queue size: 0.35
 average wait time: 0.27 minutes
customers accepted: 9094
  customers served: 9094
         turnaways: 0
average queue size: 0.47
 average wait time: 0.31 minutes
customers accepted: 10427
  customers served: 10427
         turnaways: 0
average queue size: 0.60
 average wait time: 0.34 minutes
customers accepted: 11989
  customers served: 11989
         turnaways: 0
average queue size: 0.82
 average wait time: 0.41 minutes
customers accepted: 13588
  customers served: 13588
         turnaways: 0
average queue size: 1.02
 average wait time: 0.45 minutes
customers accepted: 15277
  customers served: 15277
         turnaways: 0
average queue size: 1.25
 average wait time: 0.49 minutes
customers accepted: 17118
  customers served: 17116
         turnaways: 0
average queue size: 1.55
 average wait time: 0.54 minutes
customers accepted: 19000
  customers served: 18998
         turnaways: 0
average queue size: 1.90
 average wait time: 0.60 minutes
customers accepted: 21012
  customers served: 21009
         turnaways: 0
average queue size: 2.39
 average wait time: 0.68 minutes
customers accepted: 23139
  customers served: 23136
         turnaways: 0
average queue size: 2.94
 average wait time: 0.76 minutes
customers accepted: 25374
  customers served: 25368
         turnaways: 0
average queue size: 3.59
 average wait time: 0.85 minutes
customers accepted: 27661
  customers served: 27653
         turnaways: 0
average queue size: 4.36
 average wait time: 0.95 minutes
customers accepted: 30018
  customers served: 30010
         turnaways: 1
average queue size: 5.32
 average wait time: 1.06 minutes
When there comes 25.00 people per hour, the average wait time will be about 1 minute.
Done!

6.Heather银行想知道,如果再开设一台ATM,情况将如何。请对模拟进行修改,以包含两个队列。假设当第一台ATM前的排队人数少于第二台ATM时,客户将排在第一队,否则将排在第二队。然后再找出要使平均等候时间为1分钟,每小时到达的客户数应该为多少(注意,这是一个非线性问题,即将ATM数量加倍,并不能保证每小时处理的客户数量也翻倍,并确保客户等候的时间少于1分钟)?

queue.h(未修改):

// queue.h -- interface for a queue
#ifndef QUEUE_H_
#define QUEUE_H_
// This queue will contain Customer items
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; }
};
typedef Customer Item;

class Queue {
 private:
  // class scope definitions
  // Node is a nested structure definition local to this class
  struct Node {
    Item item;
    struct Node* next;
  };
  enum { Q_SIZE = 10 };
  // private class members
  Node* front;      // pointer to front of Queue
  Node* rear;       // pointer to rear of Queue
  int items;        // current number of items in Queue
  const int qsize;  // maximum number of items in Queue
  // preemptive definitions to prevent public copying
  Queue(const Queue& q) : qsize(0) {}
  Queue& operator=(const Queue& q) { return *this; }

 public:
  Queue(int qs = Q_SIZE);  // create queue with a qs limit
  ~Queue();
  bool isempty() const;
  bool isfull() const;
  int queuecount() const;
  bool enqueue(const Item& item);  // add item to end
  bool dequeue(Item& item);        // remove item from front
};
#endif

queue.cpp(未修改):

// queue.cpp -- Queue and Customer methods
#include "queue.h"

#include <cstdlib>  // (or stdlib.h) for rand()
// Queue methods
Queue::Queue(int qs) : qsize(qs) {
  front = rear = NULL;  // or nullptr
  items = 0;
}
Queue::~Queue() {
  Node* temp;
  while (front != NULL)  // while queue is not yet empty
  {
    temp = front;         // save address of front item
    front = front->next;  // reset pointer to next item
    delete temp;          // delete former front
  }
}
bool Queue::isempty() const { return items == 0; }
bool Queue::isfull() const { return items == qsize; }
int Queue::queuecount() const { return items; }
// Add item to queue
bool Queue::enqueue(const Item& item) {
  if (isfull()) return false;
  Node* add = new Node;  // create node
  // on failure, new throws std::bad_alloc exception
  add->item = item;  // set node pointers
  add->next = NULL;  // or nullptr;
  items++;
  if (front == NULL)  // if queue is empty,
    front = add;      // place item at front
  else
    rear->next = add;  // else place at rear
  rear = add;          // have rear point to new node
  return true;
}
// Place front item into item variable and remove from queue
bool Queue::dequeue(Item& item) {
  if (front == NULL) return false;
  item = front->item;  // set item to first item in queue
  items--;
  Node* temp = front;   // save location of first item
  front = front->next;  // reset front to next item
  delete temp;          // delete former first item
  if (items == 0) rear = NULL;
  return true;
}
// customer method
// when is the time at which the customer arrives
// the arrival time is set to when and the processing
// time set to a random value in the range 1 - 3
void Customer::set(long when) {
  processtime = std::rand() % 3 + 1;
  arrive = when;
}

main.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.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;
  Queue line1(qs);  // line1 queue holds up to qs people
  Queue line2(qs);  // line2 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

  double perhour = 1;  // number of customers per hour starts from 1

  double min_per_cust;  // average time between arrivals
  min_per_cust = MIN_PER_HR / perhour;
  Item 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 line1_size = 0;       // number of people in line1
  int line2_size = 0;       // number of people in line2
  int wait_time1 = 0;       // time until autoteller is free in line1
  int wait_time2 = 0;       // time until autoteller is free in line2
  long line_wait = 0;       // cumulative time in line
  double average_time = 0;  // average time
  while (perhour++ && average_time <= 1) {
    while (!line1.isempty()) {
      line1.dequeue(temp);
    }
    while (!line2.isempty()) {
      line2.dequeue(temp);
    }

    min_per_cust = MIN_PER_HR / perhour;

    for (int cycle = 0; cycle < cyclelimit; cycle++) {
      if (newcustomer(min_per_cust)) {
        if (line1.isfull() && line2.isfull())
          turnaways++;
        else if (line1_size < line2_size) {
          customers++;
          temp.set(cycle);
          line1.enqueue(temp);
          line1_size++;
        } else {
          customers++;
          temp.set(cycle);
          line2.enqueue(temp);
          line2_size++;
        }
      }
      if (wait_time1 <= 0 && !line1.isempty()) {
        line1.dequeue(temp);
        line1_size--;
        wait_time1 = temp.ptime();
        line_wait += cycle - temp.when();
        served++;
      }
      if (wait_time2 <= 0 && !line2.isempty()) {
        line2.dequeue(temp);
        line2_size--;
        wait_time2 = temp.ptime();
        line_wait += cycle - temp.when();
        served++;
      }
      if (wait_time1 > 0) wait_time1--;
      if (wait_time2 > 0) wait_time2--;
      sum_line += line1.queuecount() + line2.queuecount();
    }

    if (customers > 0) {
      average_time = (double)line_wait / served;
      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: " << average_time << " minutes\n";
    } else
      cout << "No customers!\n";
  }
  cout << "When there comes " << perhour
       << " people per hour, the average wait time will be about 1 minute.\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); }

实验过程中,队列最大数为10,模拟小时数为100,结果如下:

Case Study: Bank of Heather Automatic Teller
Enter maximum size of queue: 10
Enter the number of simulation hours: 100
customers accepted: 212
  customers served: 212
         turnaways: 0
average queue size: 0.00
 average wait time: 0.03 minutes
customers accepted: 520
  customers served: 520
         turnaways: 0
average queue size: 0.00
 average wait time: 0.06 minutes
customers accepted: 917
  customers served: 917
         turnaways: 0
average queue size: 0.01
 average wait time: 0.06 minutes
customers accepted: 1372
  customers served: 1372
         turnaways: 0
average queue size: 0.02
 average wait time: 0.07 minutes
customers accepted: 1987
  customers served: 1987
         turnaways: 0
average queue size: 0.03
 average wait time: 0.10 minutes
customers accepted: 2715
  customers served: 2715
         turnaways: 0
average queue size: 0.05
 average wait time: 0.11 minutes
customers accepted: 3471
  customers served: 3471
         turnaways: 0
average queue size: 0.07
 average wait time: 0.12 minutes
customers accepted: 4367
  customers served: 4367
         turnaways: 0
average queue size: 0.09
 average wait time: 0.13 minutes
customers accepted: 5368
  customers served: 5368
         turnaways: 0
average queue size: 0.13
 average wait time: 0.14 minutes
customers accepted: 6454
  customers served: 6454
         turnaways: 0
average queue size: 0.17
 average wait time: 0.16 minutes
customers accepted: 7639
  customers served: 7639
         turnaways: 0
average queue size: 0.22
 average wait time: 0.17 minutes
customers accepted: 8909
  customers served: 8909
         turnaways: 0
average queue size: 0.27
 average wait time: 0.18 minutes
customers accepted: 10292
  customers served: 10292
         turnaways: 0
average queue size: 0.33
 average wait time: 0.19 minutes
customers accepted: 11830
  customers served: 11830
         turnaways: 0
average queue size: 0.41
 average wait time: 0.21 minutes
customers accepted: 13347
  customers served: 13347
         turnaways: 0
average queue size: 0.48
 average wait time: 0.21 minutes
customers accepted: 15033
  customers served: 15033
         turnaways: 0
average queue size: 0.57
 average wait time: 0.23 minutes
customers accepted: 16837
  customers served: 16836
         turnaways: 0
average queue size: 0.67
 average wait time: 0.24 minutes
customers accepted: 18717
  customers served: 18716
         turnaways: 0
average queue size: 0.79
 average wait time: 0.25 minutes
customers accepted: 20775
  customers served: 20774
         turnaways: 0
average queue size: 0.91
 average wait time: 0.26 minutes
customers accepted: 22874
  customers served: 22873
         turnaways: 0
average queue size: 1.05
 average wait time: 0.28 minutes
customers accepted: 25038
  customers served: 25037
         turnaways: 0
average queue size: 1.20
 average wait time: 0.29 minutes
customers accepted: 27395
  customers served: 27394
         turnaways: 0
average queue size: 1.37
 average wait time: 0.30 minutes
customers accepted: 29826
  customers served: 29825
         turnaways: 0
average queue size: 1.54
 average wait time: 0.31 minutes
customers accepted: 32313
  customers served: 32312
         turnaways: 0
average queue size: 1.73
 average wait time: 0.32 minutes
customers accepted: 34782
  customers served: 34781
         turnaways: 0
average queue size: 1.90
 average wait time: 0.33 minutes
customers accepted: 37513
  customers served: 37511
         turnaways: 0
average queue size: 2.12
 average wait time: 0.34 minutes
customers accepted: 40250
  customers served: 40248
         turnaways: 0
average queue size: 2.33
 average wait time: 0.35 minutes
customers accepted: 43151
  customers served: 43149
         turnaways: 0
average queue size: 2.56
 average wait time: 0.36 minutes
customers accepted: 46116
  customers served: 46114
         turnaways: 0
average queue size: 2.80
 average wait time: 0.36 minutes
customers accepted: 49234
  customers served: 49232
         turnaways: 0
average queue size: 3.07
 average wait time: 0.37 minutes
customers accepted: 52400
  customers served: 52398
         turnaways: 0
average queue size: 3.35
 average wait time: 0.38 minutes
customers accepted: 55651
  customers served: 55648
         turnaways: 0
average queue size: 3.65
 average wait time: 0.39 minutes
customers accepted: 59012
  customers served: 59008
         turnaways: 0
average queue size: 3.97
 average wait time: 0.40 minutes
customers accepted: 62468
  customers served: 62464
         turnaways: 0
average queue size: 4.29
 average wait time: 0.41 minutes
customers accepted: 66015
  customers served: 66010
         turnaways: 0
average queue size: 4.62
 average wait time: 0.42 minutes
customers accepted: 69696
  customers served: 69691
         turnaways: 0
average queue size: 4.97
 average wait time: 0.43 minutes
customers accepted: 73509
  customers served: 73503
         turnaways: 0
average queue size: 5.38
 average wait time: 0.44 minutes
customers accepted: 77483
  customers served: 77476
         turnaways: 0
average queue size: 5.81
 average wait time: 0.45 minutes
customers accepted: 81552
  customers served: 81544
         turnaways: 0
average queue size: 6.24
 average wait time: 0.46 minutes
customers accepted: 85676
  customers served: 85668
         turnaways: 0
average queue size: 6.70
 average wait time: 0.47 minutes
customers accepted: 89893
  customers served: 89885
         turnaways: 0
average queue size: 7.18
 average wait time: 0.48 minutes
customers accepted: 94185
  customers served: 94177
         turnaways: 0
average queue size: 7.68
 average wait time: 0.49 minutes
customers accepted: 98572
  customers served: 98564
         turnaways: 0
average queue size: 8.22
 average wait time: 0.50 minutes
customers accepted: 103067
  customers served: 103058
         turnaways: 0
average queue size: 8.78
 average wait time: 0.51 minutes
customers accepted: 107697
  customers served: 107688
         turnaways: 0
average queue size: 9.38
 average wait time: 0.52 minutes
customers accepted: 112391
  customers served: 112382
         turnaways: 0
average queue size: 9.98
 average wait time: 0.53 minutes
customers accepted: 117190
  customers served: 117181
         turnaways: 0
average queue size: 10.67
 average wait time: 0.55 minutes
customers accepted: 122085
  customers served: 122075
         turnaways: 0
average queue size: 11.43
 average wait time: 0.56 minutes
customers accepted: 127075
  customers served: 127065
         turnaways: 0
average queue size: 12.17
 average wait time: 0.57 minutes
customers accepted: 132223
  customers served: 132212
         turnaways: 0
average queue size: 13.06
 average wait time: 0.59 minutes
customers accepted: 137410
  customers served: 137398
         turnaways: 0
average queue size: 14.04
 average wait time: 0.61 minutes
customers accepted: 142710
  customers served: 142697
         turnaways: 0
average queue size: 15.11
 average wait time: 0.63 minutes
customers accepted: 148126
  customers served: 148112
         turnaways: 0
average queue size: 16.21
 average wait time: 0.66 minutes
customers accepted: 153631
  customers served: 153617
         turnaways: 0
average queue size: 17.51
 average wait time: 0.68 minutes
customers accepted: 159249
  customers served: 159234
         turnaways: 0
average queue size: 19.50
 average wait time: 0.73 minutes
customers accepted: 164952
  customers served: 164936
         turnaways: 0
average queue size: 22.05
 average wait time: 0.80 minutes
customers accepted: 170746
  customers served: 170720
         turnaways: 0
average queue size: 24.75
 average wait time: 0.87 minutes
customers accepted: 176635
  customers served: 176603
         turnaways: 11
average queue size: 29.79
 average wait time: 1.01 minutes
When there comes 60.00 people per hour, the average wait time will be about 1 minute.
Done

第十三章课后习题答案

复习题

1. 派生类从基类那里继承了什么?

派生类继承了基类的公有成员、基类的保护成员和基类的私有成员,但派生类不能直接访问从基类继承过来的私有成员。

2. 派生类不能从基类那里继承什么?

派生类不能继承构造函、析构函数、赋值运算符合友元。

3. 假设baseDMA::operator=()函数的返回类型为void,而不是baseDMA &,这将有什么后果?如果返回类型为baseDMA,而不是baseDMA &,又将有什么后果?

如果返回值为void的,则baseDMA对象仍可以使用单个赋值,但是不能使用连续赋值。即:

baseDMA magazine("Pandering to Glitz", 1);
baseDMA gift1, gift2, gift3;
gift1 = magazine;          //ok
gitft2 = gift3 = gift1;    //no 不可用

如果方法返回类型为baseDMA,则该方法返回的是一个对象,不是引用,导致返回语句的时候需要复制对象,导致该方法执行速度会有所减慢。

4. 创建和删除派生类对象时,构造函数和析构函数调用的顺序是怎样的?

按照派生的顺序调用构造函数,最早的构造函数最先调用。调用析构函数的顺序正好相反。

5. 如果派生类没有添加任何数据成员,它是否需要构造函数?

需要,每个类都必须有自己的构造函数,如果派生类没有添加新成员,则构造函数可以为空,但必须存在。

6. 如果基类和派生类定义了同名的方法,当派生类对象调用该方法时,被调用的将是哪个方法?

调用派生类方法,它取代基类定义。仅当派生类没有重新定义方法或使用作用域解析运算符时,才会调用基类方法。

7. 在什么情况下,派生类应定义赋值运算符?

如果派生类构造函数使用new或者new[]运算符来初始化类的指针成员,则应定义一个赋值运算符。更普通的说,如果对于派生类成员来说,默认赋值不正确,则应定义赋值运算符。

8. 可以将派生类对象的地址赋给基类指针吗?可以将基类对象的地址赋给派生类指针吗?

可以将派生类对象的地址赋给基类指针。但只有通过显示类型转换,才可以将基类对象的地址赋给派生类指针(向下转换),而使用这样的指针不一定安全。

9. 可以将派生类对象赋给基类对象吗?可以将基类对象赋给派生类对象吗?

可以将派生类对象的地址赋值给基类对象,对于派生类中新增的数据成员都不会传递给基类对象,程序也将使用基类的赋值运算符。仅当派生类定义了转换运算符(即包含将基类引用作为唯一参数的构造函数)或使用基类为参数的赋值运算符时,相反的赋值才是可能的。

10. 假设定义了一个函数,它将基类对象的引用作为参数。为什么该函数也可以将派生类对象作为参数?

应为c++允许基类引用指向从该基类派生而来的任何类型。

11. 假设定义了一个函数,它将基类对象作为参数(即函数按值传递基类对象)。为什么该函数也可以将派生类对象作为参数?

按值传递对象将调用复制构造函数,由于形参是基类对象,因此将调用基类的复制构造函数,复制构造函数已基类引用为参数,该引用可以将指向作为参数传递的派生对象,最终的结构是,将生成一个新的基类对象,其成员对应于派生类对象的基类部分。

12. 为什么通常按引用传递对象比按值传递对象的效率更高?

按引用传递对象,这样可以确保函数从虚函数受益。另外,按引用传递对象可以节省内存和时间,尤其对于大型对象。按值传递对象的主要有点在于可以保护原始数据,但可以通过将引用作为const类型传递,来达到同样的目的。

13. 假设Corporation是基类,PublicCorporation是派生类。再假设这两个类都定义了head()函数,ph是指向Corporation类型的指针,且被赋给了一个PublicCorporation对象的地址。如果基类将head( )定义为:

a. 常规非虚方法;

b. 虚方法;

则ph->head()将被如何解释?

a. ph-head()调用Corporation::head();

b. ph-head()调用PublicCorporation::head();

14. 下述代码有什么问题?

class Kitchen
{
    private:
    double kit_sq_ft;
    public:
    Kitchen() { kit_sq_ft = 0.0; }
    virtual double area() const { return kit_sq_ft * kit_sq_ft; }
};
class House : public Kitchen
{
    private:
    double all_sq_ft;
    public: 
    House() { all_sq_ft += kit_sq_ft;}
    double area(const char *s) const { cout << s; return all_sq_ft; }
};

首先,这种情况不符合is-a模型,因此公有继承不适用。其次,House中area()定义成带参数的,将隐藏area()Kitchen版本。

编程练习

1. 以下面的类声明为基础:

// base class
class Cd { // represents a CD disk
    private:
    char performers[50];
    char label[20];
    int selections; // number of selections
    double playtime; // playing time in minutes
    public:
    Cd(char * s1, char * s2, int n, double x);
    Cd(const Cd & d);
    Cd();
    ~Cd();
    void Report() const; // reports all CD data
    Cd & operator=(const Cd & d);
};

派生出一个Classic类,并添加一组char成员,用于存储指出CD中主要作品的字符串。修改上述声明,使基类的所有函数都是虚的。如果上述定义声明的某个方法并不需要,则请删除它。使用下面的程序测试您的产品:

#include <iostream>
using namespace std;
#include "classic.h" // which will contain #include cd.h
void Bravo(const Cd & disk);
int main()
{
    Cd c1("Beatles", "Capitol", 14, 35.5);
    Classic c2 = Classic("Piano Sonata in B flat, Fantasia in C",
                         "Alfred Brendel", "Philips", 2, 57.17);
    Cd *pcd = &c1;
    cout << "Using object directly:\n";
    c1.Report(); // use Cd method
    c2.Report(); // use Classic method
    cout << "Using type cd * pointer to objects:\n";
    pcd->Report(); // use Cd method for cd object
    pcd = &c2;
    pcd->Report(); // use Classic method for classic object
    cout << "Calling a function with a Cd reference argument:\n";
    Bravo(c1);
    Bravo(c2);
    cout << "Testing assignment: ";
    Classic copy;
    copy = c2;
    copy.Report();
    return 0;
}
void Bravo(const Cd & disk)
{
    disk.Report();
}

classic.h:

// base class
class Cd {  // represents a CD disk
 private:
  char performers[50];
  char label[20];
  int selections;   // number of selections
  double playtime;  // playing time in minutes
 public:
  Cd(char* s1, char* s2, int n, double x);
  Cd(const Cd& d);
  Cd();
  virtual ~Cd();
  virtual void Report() const;  // reports all CD data
  virtual Cd& operator=(const Cd& d);
};

class Classic : public Cd {
 private:
  char* primary_work;

 public:
  Classic(char* sc, char* s1, char* s2, int n, double x);
  Classic(const Classic& c);
  Classic();
  virtual ~Classic();
  virtual void Report() const;
  virtual Classic& operator=(const Classic& c);
};

classic.cpp:

#include "classic.h"

#include <cstring>
#include <iostream>

Cd::Cd(char* s1, char* s2, int n, double x) {
  std::strncpy(performers, s1, 50);
  std::strncpy(label, s2, 20);
  selections = n;
  playtime = x;
}

Cd::Cd(const Cd& d) {
  std::strncpy(performers, d.performers, 50);
  std::strncpy(label, d.label, 20);
  selections = d.selections;
  playtime = d.playtime;
}
Cd::Cd() {
  performers[0] = '\0';
  label[0] = '\0';
  selections = 0;
  playtime = 0;
}
Cd::~Cd() {}

void Cd::Report() const {
  std::cout << "Performers: " << performers << std::endl;
  std::cout << "Label: " << label << std::endl;
  std::cout << "Selections: " << selections << std::endl;
  std::cout << "PlayTime: " << playtime << std::endl;
}

Cd& Cd::operator=(const Cd& d) {
  if (&d == this) return *this;
  std::strncpy(performers, d.performers, 50);
  std::strncpy(label, d.label, 20);
  selections = d.selections;
  playtime = d.playtime;
  return *this;
}

Classic::Classic(char* sc, char* s1, char* s2, int n, double x)
    : Cd(s1, s2, n, x) {
  primary_work = new char[std::strlen(sc) + 1];
  std::strcpy(primary_work, sc);
}

Classic::Classic(const Classic& c) : Cd(c) {
  primary_work = new char[std::strlen(c.primary_work) + 1];
  std::strcpy(primary_work, c.primary_work);
}

Classic::Classic() : Cd() { primary_work = nullptr; }

Classic::~Classic() { delete[] primary_work; }

void Classic::Report() const {
  Cd::Report();
  std::cout << "PrimaryWork: " << primary_work << std::endl;
}

Classic& Classic::operator=(const Classic& c) {
  if (&c == this) return *this;
  delete[] primary_work;
  Cd::operator=(c);
  primary_work = new char[std::strlen(c.primary_work) + 1];
  std::strcpy(primary_work, c.primary_work);
  return *this;
}

main.cpp:

#include <iostream>
using namespace std;
#include "classic.h"  // which will contain #include cd.h
void Bravo(const Cd &disk);
int main() {
  Cd c1("Beatles", "Capitol", 14, 35.5);
  Classic c2 = Classic("Piano Sonata in B flat, Fantasia in C",
                       "Alfred Brendel", "Philips", 2, 57.17);
  Cd *pcd = &c1;
  cout << "Using object directly:\n";
  c1.Report();  // use Cd method
  c2.Report();  // use Classic method
  cout << "Using type cd * pointer to objects:\n";
  pcd->Report();  // use Cd method for cd object
  pcd = &c2;
  pcd->Report();  // use Classic method for classic object
  cout << "Calling a function with a Cd reference argument:\n";
  Bravo(c1);
  Bravo(c2);
  cout << "Testing assignment: ";
  Classic copy;
  copy = c2;
  copy.Report();
  return 0;
}
void Bravo(const Cd &disk) { disk.Report(); }

2. 完成练习1,但让两个类使用动态内存分配而不是长度固定的数组来记录字符串。

classic.h:

// base class
class Cd {  // represents a CD disk
 private:
  char* performers;
  char* label;
  int selections;   // number of selections
  double playtime;  // playing time in minutes
 public:
  Cd(char* s1, char* s2, int n, double x);
  Cd(const Cd& d);
  Cd();
  virtual ~Cd();
  virtual void Report() const;  // reports all CD data
  virtual Cd& operator=(const Cd& d);
};

class Classic : public Cd {
 private:
  char* primary_work;

 public:
  Classic(char* sc, char* s1, char* s2, int n, double x);
  Classic(const Classic& c);
  Classic();
  virtual ~Classic();
  virtual void Report() const;
  virtual Classic& operator=(const Classic& c);
};

classic.cpp:

#include "classic.h"

#include <cstring>
#include <iostream>

Cd::Cd(char* s1, char* s2, int n, double x) {
  performers = new char[std::strlen(s1) + 1];
  label = new char[std::strlen(s2) + 1];
  std::strcpy(performers, s1);
  std::strcpy(label, s2);
  selections = n;
  playtime = x;
}

Cd::Cd(const Cd& d) {
  performers = new char[std::strlen(d.performers) + 1];
  label = new char[std::strlen(d.label) + 1];
  std::strcpy(performers, d.performers);
  std::strcpy(label, d.label);
  selections = d.selections;
  playtime = d.playtime;
}
Cd::Cd() {
  performers = nullptr;
  label = nullptr;
  selections = 0;
  playtime = 0;
}
Cd::~Cd() {
  delete[] performers;
  delete[] label;
}

void Cd::Report() const {
  std::cout << "Performers: " << performers << std::endl;
  std::cout << "Label: " << label << std::endl;
  std::cout << "Selections: " << selections << std::endl;
  std::cout << "PlayTime: " << playtime << std::endl;
}

Cd& Cd::operator=(const Cd& d) {
  if (&d == this) return *this;
  std::strncpy(performers, d.performers, 50);
  std::strncpy(label, d.label, 20);
  selections = d.selections;
  playtime = d.playtime;
  return *this;
}

Classic::Classic(char* sc, char* s1, char* s2, int n, double x)
    : Cd(s1, s2, n, x) {
  primary_work = new char[std::strlen(sc) + 1];
  std::strcpy(primary_work, sc);
}

Classic::Classic(const Classic& c) : Cd(c) {
  primary_work = new char[std::strlen(c.primary_work) + 1];
  std::strcpy(primary_work, c.primary_work);
}

Classic::Classic() : Cd() { primary_work = nullptr; }

Classic::~Classic() { delete[] primary_work; }

void Classic::Report() const {
  Cd::Report();
  std::cout << "PrimaryWork: " << primary_work << std::endl;
}

Classic& Classic::operator=(const Classic& c) {
  if (&c == this) return *this;
  delete[] primary_work;
  Cd::operator=(c);
  primary_work = new char[std::strlen(c.primary_work) + 1];
  std::strcpy(primary_work, c.primary_work);
  return *this;
}

main.cpp:

#include <iostream>
using namespace std;
#include "classic.h"  // which will contain #include cd.h
void Bravo(const Cd &disk);
int main() {
  Cd c1("Beatles", "Capitol", 14, 35.5);
  Classic c2 = Classic("Piano Sonata in B flat, Fantasia in C",
                       "Alfred Brendel", "Philips", 2, 57.17);
  Cd *pcd = &c1;
  cout << "Using object directly:\n";
  c1.Report();  // use Cd method
  c2.Report();  // use Classic method
  cout << "Using type cd * pointer to objects:\n";
  pcd->Report();  // use Cd method for cd object
  pcd = &c2;
  pcd->Report();  // use Classic method for classic object
  cout << "Calling a function with a Cd reference argument:\n";
  Bravo(c1);
  Bravo(c2);
  cout << "Testing assignment: ";
  Classic copy;
  copy = c2;
  copy.Report();
  return 0;
}
void Bravo(const Cd &disk) { disk.Report(); }

3. 修改baseDMA-lacksDMA-hasDMA类层次,让三个类都从一个ABC派生而来,然后使用与程序清单13.10相似的程序对结果进行测试。也就是说,它应使用ABC指针数组,并让用户决定要创建的对象类型。在类定义中添加virtual View()方法以处理数据显示。

abc.h:

#ifndef ABC_H_
#define ABC_H_

#include <iostream>
class ABC {
 private:
  char* label;
  int rating;

 public:
  ABC(const char* l = "null", int r = 1);
  ABC(const ABC& a);
  virtual ~ABC() = 0;
  virtual void View() const;
  ABC& operator=(const ABC& a);
  friend std::ostream& operator<<(std::ostream& os, const ABC& a);
};

class baseDMA : public ABC {
 private:
 public:
  baseDMA(const char* l = "null", int r = 0);
  friend std::ostream& operator<<(std::ostream& os, const baseDMA& rs);
};

class lacksDMA : public ABC {
 private:
  enum { COL_LEN = 40 };
  char color[COL_LEN];

 public:
  lacksDMA(const char* c = "blank", const char* l = "null", int r = 0);
  lacksDMA(const char* c, const ABC& a);
  virtual void View() const;
  friend std::ostream& operator<<(std::ostream& os, const lacksDMA& rs);
};

class hasDMA : public ABC {
 private:
  char* style;

 public:
  hasDMA(const char* s = "none", const char* l = "null", int r = 0);
  hasDMA(const char* s, const ABC& c);
  hasDMA(const hasDMA& hs);
  ~hasDMA();
  virtual void View() const;
  hasDMA& operator=(const hasDMA& rs);
  friend std::ostream& operator<<(std::ostream& os, const hasDMA& rs);
};

#endif  // ABC_H_

abc.cpp:

#include "abc.h"

#include <cstring>

ABC::ABC(const char* l, int r) {
  label = new char(std::strlen(l) + 1);
  std::strcpy(label, l);
  rating = r;
}
ABC::ABC(const ABC& a) {
  label = new char(std::strlen(a.label) + 1);
  std::strcpy(label, a.label);
  rating = a.rating;
}

ABC::~ABC() { delete[] label; }

void ABC::View() const { std::cout << *this << std::endl; }

ABC& ABC::operator=(const ABC& a) {
  if (&a == this) return *this;
  delete[] label;
  label = new char[std::strlen(a.label) + 1];
  std::strcpy(label, a.label);
  rating = a.rating;
}

std::ostream& operator<<(std::ostream& os, const ABC& a) {
  os << "label: " << a.label << ", rating: " << a.rating;
  return os;
}

/***************baseDMA************/

baseDMA::baseDMA(const char* l, int r) : ABC(l, r) {}

std::ostream& operator<<(std::ostream& os, const baseDMA& rs) {
  os << (const ABC&)rs;
  return os;
}

/***************lacksDMA************/
lacksDMA::lacksDMA(const char* c, const char* l, int r) : ABC(l, r) {
  std::strncpy(color, c, COL_LEN);
}

lacksDMA::lacksDMA(const char* c, const ABC& a) : ABC(a) {
  std::strncpy(color, c, COL_LEN);
}

void lacksDMA::View() const { std::cout << *this << std::endl; }

std::ostream& operator<<(std::ostream& os, const lacksDMA& rs) {
  os << (const ABC&)rs << ", color: " << rs.color;
  return os;
}

/***************hasDMA************/
hasDMA::hasDMA(const char* s, const char* l, int r) : ABC(l, r) {
  style = new char[std::strlen(s) + 1];
  std::strcpy(style, s);
}

hasDMA::hasDMA(const char* s, const ABC& c) : ABC(c) {
  style = new char[std::strlen(s) + 1];
  std::strcpy(style, s);
}

hasDMA::hasDMA(const hasDMA& hs) : ABC(hs) {
  style = new char[std::strlen(hs.style) + 1];
  std::strcpy(style, hs.style);
}

hasDMA::~hasDMA() { delete[] style; }

void hasDMA::View() const { std::cout << *this << std::endl; }

hasDMA& hasDMA::operator=(const hasDMA& hs) {
  if (this == &hs) return *this;
  ABC::operator=(hs);
  delete[] style;
  style = new char[std::strlen(hs.style) + 1];
  std::strcpy(style, hs.style);

  return *this;
}

std::ostream& operator<<(std::ostream& os, const hasDMA& rs) {
  os << (const ABC&)rs << ", style: " << rs.style;
  return os;
}

main.cpp:

#include <iostream>

#include "abc.h"

int main() {
  using std::cout;
  using std::endl;

  baseDMA shirt("Portrabelly", 8);
  lacksDMA balloon("red", "Blumpo", 4);
  hasDMA map("Mercator", "Buffalo Kyes", 5);
  cout << shirt << endl;
  cout << balloon << endl;
  cout << map << endl;
  lacksDMA balloon2(balloon);
  hasDMA map2;
  map2 = map;
  cout << balloon2 << endl;
  cout << map2 << endl;

  ABC* pts[3];
  pts[0] = &shirt;
  pts[1] = &balloon;
  pts[2] = &map;

  for (int i = 0; i < 3; ++i) cout << *pts[i] << endl;
  for (int i = 0; i < 3; ++i) pts[i]->View();

  return 0;
}

4. Benevolent Order of Programmers用来维护瓶装葡萄酒箱。为描述它,BOP Portmaster设置了一个Port类,其声明如下

#include <iostream>
using namespace std;
class Port
{
    private:
    char * brand;
    char style[20]; // i.e., tawny, ruby, vintage
    int bottles;
    public:
    Port(const char * br = "none", const char * st = "none", int b = 0);
    Port(const Port & p); // copy constructor
    virtual ~Port() { delete [] brand; }
    Port & operator=(const Port & p);
    Port & operator+=(int b); // adds b to bottles
    Port & operator-=(int b); // subtracts b from bottles, if available
    int BottleCount() const { return bottles; }
    virtual void Show() const;
    friend ostream & operator<<(ostream & os, const Port & p);
   };

show()方法按下面的格式显示信息:

Brand: Gallo
Kind: tawny
Bottles: 20

operator<<()函数按下面的格式显示信息(末尾没有换行符):

Gallo, tawny, 20

PortMaster完成了Por类的方法定义后派生了VintagePort类,然后被解职——因为不小心将一瓶45度Cockburn泼到了正在准备烤肉调料的人身上,VintagePort类如下所示:

class VintagePort : public Port // style necessarily = "vintage"
{
    private:
    char * nickname; // i.e., "The Noble" or "Old Velvet", etc.
    int year; // vintage year
    public:
    VintagePort();
    VintagePort(const char * br, int b, const char * nn, int y);
    VintagePort(const VintagePort & vp);
    ~VintagePort() { delete [] nickname; }
    VintagePort & operator=(const VintagePort & vp);
    void Show() const;
    friend ostream & operator<<(ostream & os, const VintagePort & vp);
};

您被指定负责完成VintagePort。

a. 第一个任务是重新创建Port方法定义,因为前任被开除时销毁了方法定义。

b. 第二个任务是解释为什么有的方法重新定义了,而有些没有重新定义。

c. 第三个任务是解释为何没有将operator=()和operator<<()声明为虚的。

d. 第四个任务是提供VintagePort中各个方法的定义。

port.h:

#ifndef PORT_H_
#define PORT_H_

#include <iostream>
using namespace std;
class Port {
 private:
  char* brand;
  char style[20];  // i.e., tawny, ruby, vintage
  int bottles;

 public:
  Port(const char* br = "none", const char* st = "none", int b = 0);
  Port(const Port& p);  // copy constructor
  virtual ~Port() { delete[] brand; }
  Port& operator=(const Port& p);
  //派生类的计算逻辑与基类一致,且在该方法中派生类未操作其新增成员,因此该函数在派生类中不需要重新定义
  Port& operator+=(int b);  // adds b to bottles
  //派生类的计算逻辑与基类一致,且在该方法中派生类未操作其新增成员,因此该函数在派生类中不需要重新定义
  Port& operator-=(int b);  // subtracts b from bottles, if available
  int BottleCount() const { return bottles; }
  virtual void Show() const;
  friend ostream& operator<<(ostream& os, const Port& p);
};

class VintagePort : public Port  // style necessarily = "vintage"
{
 private:
  char* nickname;  // i.e., "The Noble" or "Old Velvet", etc.
  int year;        // vintage year
 public:
  VintagePort();//派生类使用了动态内存分配,需要重新定义
  VintagePort(const char* br, int b, const char* nn, int y);//同上
  VintagePort(const VintagePort& vp);//同上
  ~VintagePort() { delete[] nickname; }//派生类使用了动态内存分配,且析构函数不能被继承,需要重新定义
  VintagePort& operator=(const VintagePort& vp);//增加了新的成员nickname和year,且赋值运算符不能被继承,需要重新定义
  void Show() const;//增加了新的成员nickname和year,需要重新定义
  friend ostream& operator<<(ostream& os, const VintagePort& vp);//友元函数不属于成员函数,无法继承
};

#endif  // PORT_H_

port.cpp:

#include "port.h"

#include <cstring>

Port::Port(const char* br, const char* st, int b) {
  brand = new char[strlen(br) + 1];
  strcpy(brand, br);
  strncpy(style, st, 20);
  bottles = b;
}

Port::Port(const Port& p) {
  brand = new char[strlen(p.brand) + 1];
  strcpy(brand, p.brand);
  strncpy(style, p.style, 20);
  bottles = p.bottles;
}

Port& Port::operator=(const Port& p) {
  if (&p == this) return *this;
  delete[] brand;
  brand = new char[strlen(p.brand) + 1];
  strcpy(brand, p.brand);
  strncpy(style, p.style, 20);
  bottles = p.bottles;
  return *this;
}

Port& Port::operator+=(int b) {
  bottles += b;
  return *this;
}

Port& Port::operator-=(int b) {
  bottles -= b;
  return *this;
}

void Port::Show() const {
  cout << "Brand: " << brand << endl;
  cout << "Style: " << style << endl;
  cout << "Bottles: " << bottles << endl;
}

ostream& operator<<(ostream& os, const Port& p) {
  os << p.brand << ", " << p.style << ", " << p.bottles;
  return os;
}

VintagePort::VintagePort() : Port() {
  nickname = new char[strlen("none") + 1];
  strcpy(nickname, "none");
  year = 0;
}

VintagePort::VintagePort(const char* br, int b, const char* nn, int y)
    : Port(br, "Vintage", b) {
  nickname = new char[strlen(nn) + 1];
  strcpy(nickname, nn);
  year = y;
}

VintagePort::VintagePort(const VintagePort& vp) : Port(vp) {
  nickname = new char[std::strlen(vp.nickname) + 1];
  std::strcpy(nickname, vp.nickname);
  year = vp.year;
}

VintagePort& VintagePort::operator=(const VintagePort& vp) {
  if (this == &vp) return *this;
  Port::operator=(vp);
  delete[] nickname;
  nickname = new char[std::strlen(vp.nickname) + 1];
  std::strcpy(nickname, vp.nickname);
  year = vp.year;

  return *this;
}

void VintagePort::Show() const {
  Port::Show();
  cout << "Nickname: " << nickname << endl;
  cout << "Year: " << year << endl;
}

ostream& operator<<(ostream& os, const VintagePort& vp) {
  os << (const Port&)vp;
  os << ", " << vp.nickname << ", " << vp.year;
  return os;
}

main.cpp:

#include <iostream>

#include "port.h"

int main() {
  Port p1;
  Port p2("Abc", "Bcc", 30);
  std::cout << p1 << std::endl;
  std::cout << p2 << std::endl;
  Port p3 = p2;
  p3.Show();
  p3 += 3;
  p3.Show();
  Port p4 = p2;
  p3 -= 2;
  p3.Show();

  VintagePort vp1("Vabc", 50, "hn", 1983);
  vp1.Show();
  VintagePort vp2;
  vp2.Show();
  vp1 -= 3;
  vp2 = vp1;
  std::cout << vp2 << std::endl;

  return 0;
}

第十四章课后习题答案

复习题

1. 派生类从基类那里继承了什么?

派生类继承了基类的公有成员、基类的保护成员和基类的私有成员,但派生类不能直接访问从基类继承过来的私有成员。

2. 派生类不能从基类那里继承什么?

派生类不能继承构造函、析构函数、赋值运算符合友元。

3. 假设baseDMA::operator=()函数的返回类型为void,而不是baseDMA &,这将有什么后果?如果返回类型为baseDMA,而不是baseDMA &,又将有什么后果?

如果返回值为void的,则baseDMA对象仍可以使用单个赋值,但是不能使用连续赋值。即:

baseDMA magazine("Pandering to Glitz", 1);
baseDMA gift1, gift2, gift3;
gift1 = magazine;          //ok
gitft2 = gift3 = gift1;    //no 不可用

如果方法返回类型为baseDMA,则该方法返回的是一个对象,不是引用,导致返回语句的时候需要复制对象,导致该方法执行速度会有所减慢。

4. 创建和删除派生类对象时,构造函数和析构函数调用的顺序是怎样的?

按照派生的顺序调用构造函数,最早的构造函数最先调用。调用析构函数的顺序正好相反。

5. 如果派生类没有添加任何数据成员,它是否需要构造函数?

需要,每个类都必须有自己的构造函数,如果派生类没有添加新成员,则构造函数可以为空,但必须存在。

6. 如果基类和派生类定义了同名的方法,当派生类对象调用该方法时,被调用的将是哪个方法?

调用派生类方法,它取代基类定义。仅当派生类没有重新定义方法或使用作用域解析运算符时,才会调用基类方法。

7. 在什么情况下,派生类应定义赋值运算符?

如果派生类构造函数使用new或者new[]运算符来初始化类的指针成员,则应定义一个赋值运算符。更普通的说,如果对于派生类成员来说,默认赋值不正确,则应定义赋值运算符。

8. 可以将派生类对象的地址赋给基类指针吗?可以将基类对象的地址赋给派生类指针吗?

可以将派生类对象的地址赋给基类指针。但只有通过显示类型转换,才可以将基类对象的地址赋给派生类指针(向下转换),而使用这样的指针不一定安全。

9. 可以将派生类对象赋给基类对象吗?可以将基类对象赋给派生类对象吗?

可以将派生类对象的地址赋值给基类对象,对于派生类中新增的数据成员都不会传递给基类对象,程序也将使用基类的赋值运算符。仅当派生类定义了转换运算符(即包含将基类引用作为唯一参数的构造函数)或使用基类为参数的赋值运算符时,相反的赋值才是可能的。

10. 假设定义了一个函数,它将基类对象的引用作为参数。为什么该函数也可以将派生类对象作为参数?

应为c++允许基类引用指向从该基类派生而来的任何类型。

11. 假设定义了一个函数,它将基类对象作为参数(即函数按值传递基类对象)。为什么该函数也可以将派生类对象作为参数?

按值传递对象将调用复制构造函数,由于形参是基类对象,因此将调用基类的复制构造函数,复制构造函数已基类引用为参数,该引用可以将指向作为参数传递的派生对象,最终的结构是,将生成一个新的基类对象,其成员对应于派生类对象的基类部分。

12. 为什么通常按引用传递对象比按值传递对象的效率更高?

按引用传递对象,这样可以确保函数从虚函数受益。另外,按引用传递对象可以节省内存和时间,尤其对于大型对象。按值传递对象的主要有点在于可以保护原始数据,但可以通过将引用作为const类型传递,来达到同样的目的。

13. 假设Corporation是基类,PublicCorporation是派生类。再假设这两个类都定义了head()函数,ph是指向Corporation类型的指针,且被赋给了一个PublicCorporation对象的地址。如果基类将head( )定义为:

a. 常规非虚方法;

b. 虚方法;

则ph->head()将被如何解释?

a. ph-head()调用Corporation::head();

b. ph-head()调用PublicCorporation::head();

14. 下述代码有什么问题?

class Kitchen
{
    private:
    double kit_sq_ft;
    public:
    Kitchen() { kit_sq_ft = 0.0; }
    virtual double area() const { return kit_sq_ft * kit_sq_ft; }
};
class House : public Kitchen
{
    private:
    double all_sq_ft;
    public: 
    House() { all_sq_ft += kit_sq_ft;}
    double area(const char *s) const { cout << s; return all_sq_ft; }
};

首先,这种情况不符合is-a模型,因此公有继承不适用。其次,House中area()定义成带参数的,将隐藏area()Kitchen版本。

编程练习

1. 以下面的类声明为基础:

// base class
class Cd { // represents a CD disk
    private:
    char performers[50];
    char label[20];
    int selections; // number of selections
    double playtime; // playing time in minutes
    public:
    Cd(char * s1, char * s2, int n, double x);
    Cd(const Cd & d);
    Cd();
    ~Cd();
    void Report() const; // reports all CD data
    Cd & operator=(const Cd & d);
};

派生出一个Classic类,并添加一组char成员,用于存储指出CD中主要作品的字符串。修改上述声明,使基类的所有函数都是虚的。如果上述定义声明的某个方法并不需要,则请删除它。使用下面的程序测试您的产品:

#include <iostream>
using namespace std;
#include "classic.h" // which will contain #include cd.h
void Bravo(const Cd & disk);
int main()
{
    Cd c1("Beatles", "Capitol", 14, 35.5);
    Classic c2 = Classic("Piano Sonata in B flat, Fantasia in C",
                         "Alfred Brendel", "Philips", 2, 57.17);
    Cd *pcd = &c1;
    cout << "Using object directly:\n";
    c1.Report(); // use Cd method
    c2.Report(); // use Classic method
    cout << "Using type cd * pointer to objects:\n";
    pcd->Report(); // use Cd method for cd object
    pcd = &c2;
    pcd->Report(); // use Classic method for classic object
    cout << "Calling a function with a Cd reference argument:\n";
    Bravo(c1);
    Bravo(c2);
    cout << "Testing assignment: ";
    Classic copy;
    copy = c2;
    copy.Report();
    return 0;
}
void Bravo(const Cd & disk)
{
    disk.Report();
}

classic.h:

// base class
class Cd {  // represents a CD disk
 private:
  char performers[50];
  char label[20];
  int selections;   // number of selections
  double playtime;  // playing time in minutes
 public:
  Cd(char* s1, char* s2, int n, double x);
  Cd(const Cd& d);
  Cd();
  virtual ~Cd();
  virtual void Report() const;  // reports all CD data
  virtual Cd& operator=(const Cd& d);
};

class Classic : public Cd {
 private:
  char* primary_work;

 public:
  Classic(char* sc, char* s1, char* s2, int n, double x);
  Classic(const Classic& c);
  Classic();
  virtual ~Classic();
  virtual void Report() const;
  virtual Classic& operator=(const Classic& c);
};

classic.cpp:

#include "classic.h"

#include <cstring>
#include <iostream>

Cd::Cd(char* s1, char* s2, int n, double x) {
  std::strncpy(performers, s1, 50);
  std::strncpy(label, s2, 20);
  selections = n;
  playtime = x;
}

Cd::Cd(const Cd& d) {
  std::strncpy(performers, d.performers, 50);
  std::strncpy(label, d.label, 20);
  selections = d.selections;
  playtime = d.playtime;
}
Cd::Cd() {
  performers[0] = '\0';
  label[0] = '\0';
  selections = 0;
  playtime = 0;
}
Cd::~Cd() {}

void Cd::Report() const {
  std::cout << "Performers: " << performers << std::endl;
  std::cout << "Label: " << label << std::endl;
  std::cout << "Selections: " << selections << std::endl;
  std::cout << "PlayTime: " << playtime << std::endl;
}

Cd& Cd::operator=(const Cd& d) {
  if (&d == this) return *this;
  std::strncpy(performers, d.performers, 50);
  std::strncpy(label, d.label, 20);
  selections = d.selections;
  playtime = d.playtime;
  return *this;
}

Classic::Classic(char* sc, char* s1, char* s2, int n, double x)
    : Cd(s1, s2, n, x) {
  primary_work = new char[std::strlen(sc) + 1];
  std::strcpy(primary_work, sc);
}

Classic::Classic(const Classic& c) : Cd(c) {
  primary_work = new char[std::strlen(c.primary_work) + 1];
  std::strcpy(primary_work, c.primary_work);
}

Classic::Classic() : Cd() { primary_work = nullptr; }

Classic::~Classic() { delete[] primary_work; }

void Classic::Report() const {
  Cd::Report();
  std::cout << "PrimaryWork: " << primary_work << std::endl;
}

Classic& Classic::operator=(const Classic& c) {
  if (&c == this) return *this;
  delete[] primary_work;
  Cd::operator=(c);
  primary_work = new char[std::strlen(c.primary_work) + 1];
  std::strcpy(primary_work, c.primary_work);
  return *this;
}

main.cpp:

#include <iostream>
using namespace std;
#include "classic.h"  // which will contain #include cd.h
void Bravo(const Cd &disk);
int main() {
  Cd c1("Beatles", "Capitol", 14, 35.5);
  Classic c2 = Classic("Piano Sonata in B flat, Fantasia in C",
                       "Alfred Brendel", "Philips", 2, 57.17);
  Cd *pcd = &c1;
  cout << "Using object directly:\n";
  c1.Report();  // use Cd method
  c2.Report();  // use Classic method
  cout << "Using type cd * pointer to objects:\n";
  pcd->Report();  // use Cd method for cd object
  pcd = &c2;
  pcd->Report();  // use Classic method for classic object
  cout << "Calling a function with a Cd reference argument:\n";
  Bravo(c1);
  Bravo(c2);
  cout << "Testing assignment: ";
  Classic copy;
  copy = c2;
  copy.Report();
  return 0;
}
void Bravo(const Cd &disk) { disk.Report(); }

2. 完成练习1,但让两个类使用动态内存分配而不是长度固定的数组来记录字符串。

classic.h:

// base class
class Cd {  // represents a CD disk
 private:
  char* performers;
  char* label;
  int selections;   // number of selections
  double playtime;  // playing time in minutes
 public:
  Cd(char* s1, char* s2, int n, double x);
  Cd(const Cd& d);
  Cd();
  virtual ~Cd();
  virtual void Report() const;  // reports all CD data
  virtual Cd& operator=(const Cd& d);
};

class Classic : public Cd {
 private:
  char* primary_work;

 public:
  Classic(char* sc, char* s1, char* s2, int n, double x);
  Classic(const Classic& c);
  Classic();
  virtual ~Classic();
  virtual void Report() const;
  virtual Classic& operator=(const Classic& c);
};

classic.cpp:

#include "classic.h"

#include <cstring>
#include <iostream>

Cd::Cd(char* s1, char* s2, int n, double x) {
  performers = new char[std::strlen(s1) + 1];
  label = new char[std::strlen(s2) + 1];
  std::strcpy(performers, s1);
  std::strcpy(label, s2);
  selections = n;
  playtime = x;
}

Cd::Cd(const Cd& d) {
  performers = new char[std::strlen(d.performers) + 1];
  label = new char[std::strlen(d.label) + 1];
  std::strcpy(performers, d.performers);
  std::strcpy(label, d.label);
  selections = d.selections;
  playtime = d.playtime;
}
Cd::Cd() {
  performers = nullptr;
  label = nullptr;
  selections = 0;
  playtime = 0;
}
Cd::~Cd() {
  delete[] performers;
  delete[] label;
}

void Cd::Report() const {
  std::cout << "Performers: " << performers << std::endl;
  std::cout << "Label: " << label << std::endl;
  std::cout << "Selections: " << selections << std::endl;
  std::cout << "PlayTime: " << playtime << std::endl;
}

Cd& Cd::operator=(const Cd& d) {
  if (&d == this) return *this;
  std::strncpy(performers, d.performers, 50);
  std::strncpy(label, d.label, 20);
  selections = d.selections;
  playtime = d.playtime;
  return *this;
}

Classic::Classic(char* sc, char* s1, char* s2, int n, double x)
    : Cd(s1, s2, n, x) {
  primary_work = new char[std::strlen(sc) + 1];
  std::strcpy(primary_work, sc);
}

Classic::Classic(const Classic& c) : Cd(c) {
  primary_work = new char[std::strlen(c.primary_work) + 1];
  std::strcpy(primary_work, c.primary_work);
}

Classic::Classic() : Cd() { primary_work = nullptr; }

Classic::~Classic() { delete[] primary_work; }

void Classic::Report() const {
  Cd::Report();
  std::cout << "PrimaryWork: " << primary_work << std::endl;
}

Classic& Classic::operator=(const Classic& c) {
  if (&c == this) return *this;
  delete[] primary_work;
  Cd::operator=(c);
  primary_work = new char[std::strlen(c.primary_work) + 1];
  std::strcpy(primary_work, c.primary_work);
  return *this;
}

main.cpp:

#include <iostream>
using namespace std;
#include "classic.h"  // which will contain #include cd.h
void Bravo(const Cd &disk);
int main() {
  Cd c1("Beatles", "Capitol", 14, 35.5);
  Classic c2 = Classic("Piano Sonata in B flat, Fantasia in C",
                       "Alfred Brendel", "Philips", 2, 57.17);
  Cd *pcd = &c1;
  cout << "Using object directly:\n";
  c1.Report();  // use Cd method
  c2.Report();  // use Classic method
  cout << "Using type cd * pointer to objects:\n";
  pcd->Report();  // use Cd method for cd object
  pcd = &c2;
  pcd->Report();  // use Classic method for classic object
  cout << "Calling a function with a Cd reference argument:\n";
  Bravo(c1);
  Bravo(c2);
  cout << "Testing assignment: ";
  Classic copy;
  copy = c2;
  copy.Report();
  return 0;
}
void Bravo(const Cd &disk) { disk.Report(); }

3. 修改baseDMA-lacksDMA-hasDMA类层次,让三个类都从一个ABC派生而来,然后使用与程序清单13.10相似的程序对结果进行测试。也就是说,它应使用ABC指针数组,并让用户决定要创建的对象类型。在类定义中添加virtual View()方法以处理数据显示。

abc.h:

#ifndef ABC_H_
#define ABC_H_

#include <iostream>
class ABC {
 private:
  char* label;
  int rating;

 public:
  ABC(const char* l = "null", int r = 1);
  ABC(const ABC& a);
  virtual ~ABC() = 0;
  virtual void View() const;
  ABC& operator=(const ABC& a);
  friend std::ostream& operator<<(std::ostream& os, const ABC& a);
};

class baseDMA : public ABC {
 private:
 public:
  baseDMA(const char* l = "null", int r = 0);
  friend std::ostream& operator<<(std::ostream& os, const baseDMA& rs);
};

class lacksDMA : public ABC {
 private:
  enum { COL_LEN = 40 };
  char color[COL_LEN];

 public:
  lacksDMA(const char* c = "blank", const char* l = "null", int r = 0);
  lacksDMA(const char* c, const ABC& a);
  virtual void View() const;
  friend std::ostream& operator<<(std::ostream& os, const lacksDMA& rs);
};

class hasDMA : public ABC {
 private:
  char* style;

 public:
  hasDMA(const char* s = "none", const char* l = "null", int r = 0);
  hasDMA(const char* s, const ABC& c);
  hasDMA(const hasDMA& hs);
  ~hasDMA();
  virtual void View() const;
  hasDMA& operator=(const hasDMA& rs);
  friend std::ostream& operator<<(std::ostream& os, const hasDMA& rs);
};

#endif  // ABC_H_

abc.cpp:

#include "abc.h"

#include <cstring>

ABC::ABC(const char* l, int r) {
  label = new char(std::strlen(l) + 1);
  std::strcpy(label, l);
  rating = r;
}
ABC::ABC(const ABC& a) {
  label = new char(std::strlen(a.label) + 1);
  std::strcpy(label, a.label);
  rating = a.rating;
}

ABC::~ABC() { delete[] label; }

void ABC::View() const { std::cout << *this << std::endl; }

ABC& ABC::operator=(const ABC& a) {
  if (&a == this) return *this;
  delete[] label;
  label = new char[std::strlen(a.label) + 1];
  std::strcpy(label, a.label);
  rating = a.rating;
}

std::ostream& operator<<(std::ostream& os, const ABC& a) {
  os << "label: " << a.label << ", rating: " << a.rating;
  return os;
}

/***************baseDMA************/

baseDMA::baseDMA(const char* l, int r) : ABC(l, r) {}

std::ostream& operator<<(std::ostream& os, const baseDMA& rs) {
  os << (const ABC&)rs;
  return os;
}

/***************lacksDMA************/
lacksDMA::lacksDMA(const char* c, const char* l, int r) : ABC(l, r) {
  std::strncpy(color, c, COL_LEN);
}

lacksDMA::lacksDMA(const char* c, const ABC& a) : ABC(a) {
  std::strncpy(color, c, COL_LEN);
}

void lacksDMA::View() const { std::cout << *this << std::endl; }

std::ostream& operator<<(std::ostream& os, const lacksDMA& rs) {
  os << (const ABC&)rs << ", color: " << rs.color;
  return os;
}

/***************hasDMA************/
hasDMA::hasDMA(const char* s, const char* l, int r) : ABC(l, r) {
  style = new char[std::strlen(s) + 1];
  std::strcpy(style, s);
}

hasDMA::hasDMA(const char* s, const ABC& c) : ABC(c) {
  style = new char[std::strlen(s) + 1];
  std::strcpy(style, s);
}

hasDMA::hasDMA(const hasDMA& hs) : ABC(hs) {
  style = new char[std::strlen(hs.style) + 1];
  std::strcpy(style, hs.style);
}

hasDMA::~hasDMA() { delete[] style; }

void hasDMA::View() const { std::cout << *this << std::endl; }

hasDMA& hasDMA::operator=(const hasDMA& hs) {
  if (this == &hs) return *this;
  ABC::operator=(hs);
  delete[] style;
  style = new char[std::strlen(hs.style) + 1];
  std::strcpy(style, hs.style);

  return *this;
}

std::ostream& operator<<(std::ostream& os, const hasDMA& rs) {
  os << (const ABC&)rs << ", style: " << rs.style;
  return os;
}

main.cpp:

#include <iostream>

#include "abc.h"

int main() {
  using std::cout;
  using std::endl;

  baseDMA shirt("Portrabelly", 8);
  lacksDMA balloon("red", "Blumpo", 4);
  hasDMA map("Mercator", "Buffalo Kyes", 5);
  cout << shirt << endl;
  cout << balloon << endl;
  cout << map << endl;
  lacksDMA balloon2(balloon);
  hasDMA map2;
  map2 = map;
  cout << balloon2 << endl;
  cout << map2 << endl;

  ABC* pts[3];
  pts[0] = &shirt;
  pts[1] = &balloon;
  pts[2] = &map;

  for (int i = 0; i < 3; ++i) cout << *pts[i] << endl;
  for (int i = 0; i < 3; ++i) pts[i]->View();

  return 0;
}

4. Benevolent Order of Programmers用来维护瓶装葡萄酒箱。为描述它,BOP Portmaster设置了一个Port类,其声明如下

#include <iostream>
using namespace std;
class Port
{
    private:
    char * brand;
    char style[20]; // i.e., tawny, ruby, vintage
    int bottles;
    public:
    Port(const char * br = "none", const char * st = "none", int b = 0);
    Port(const Port & p); // copy constructor
    virtual ~Port() { delete [] brand; }
    Port & operator=(const Port & p);
    Port & operator+=(int b); // adds b to bottles
    Port & operator-=(int b); // subtracts b from bottles, if available
    int BottleCount() const { return bottles; }
    virtual void Show() const;
    friend ostream & operator<<(ostream & os, const Port & p);
   };

show()方法按下面的格式显示信息:

Brand: Gallo
Kind: tawny
Bottles: 20

operator<<()函数按下面的格式显示信息(末尾没有换行符):

Gallo, tawny, 20

PortMaster完成了Por类的方法定义后派生了VintagePort类,然后被解职——因为不小心将一瓶45度Cockburn泼到了正在准备烤肉调料的人身上,VintagePort类如下所示:

class VintagePort : public Port // style necessarily = "vintage"
{
    private:
    char * nickname; // i.e., "The Noble" or "Old Velvet", etc.
    int year; // vintage year
    public:
    VintagePort();
    VintagePort(const char * br, int b, const char * nn, int y);
    VintagePort(const VintagePort & vp);
    ~VintagePort() { delete [] nickname; }
    VintagePort & operator=(const VintagePort & vp);
    void Show() const;
    friend ostream & operator<<(ostream & os, const VintagePort & vp);
};

您被指定负责完成VintagePort。

a. 第一个任务是重新创建Port方法定义,因为前任被开除时销毁了方法定义。

b. 第二个任务是解释为什么有的方法重新定义了,而有些没有重新定义。

c. 第三个任务是解释为何没有将operator=()和operator<<()声明为虚的。

d. 第四个任务是提供VintagePort中各个方法的定义。

port.h:

#ifndef PORT_H_
#define PORT_H_

#include <iostream>
using namespace std;
class Port {
 private:
  char* brand;
  char style[20];  // i.e., tawny, ruby, vintage
  int bottles;

 public:
  Port(const char* br = "none", const char* st = "none", int b = 0);
  Port(const Port& p);  // copy constructor
  virtual ~Port() { delete[] brand; }
  Port& operator=(const Port& p);
  //派生类的计算逻辑与基类一致,且在该方法中派生类未操作其新增成员,因此该函数在派生类中不需要重新定义
  Port& operator+=(int b);  // adds b to bottles
  //派生类的计算逻辑与基类一致,且在该方法中派生类未操作其新增成员,因此该函数在派生类中不需要重新定义
  Port& operator-=(int b);  // subtracts b from bottles, if available
  int BottleCount() const { return bottles; }
  virtual void Show() const;
  friend ostream& operator<<(ostream& os, const Port& p);
};

class VintagePort : public Port  // style necessarily = "vintage"
{
 private:
  char* nickname;  // i.e., "The Noble" or "Old Velvet", etc.
  int year;        // vintage year
 public:
  VintagePort();//派生类使用了动态内存分配,需要重新定义
  VintagePort(const char* br, int b, const char* nn, int y);//同上
  VintagePort(const VintagePort& vp);//同上
  ~VintagePort() { delete[] nickname; }//派生类使用了动态内存分配,且析构函数不能被继承,需要重新定义
  VintagePort& operator=(const VintagePort& vp);//增加了新的成员nickname和year,且赋值运算符不能被继承,需要重新定义
  void Show() const;//增加了新的成员nickname和year,需要重新定义
  friend ostream& operator<<(ostream& os, const VintagePort& vp);//友元函数不属于成员函数,无法继承
};

#endif  // PORT_H_

port.cpp:

#include "port.h"

#include <cstring>

Port::Port(const char* br, const char* st, int b) {
  brand = new char[strlen(br) + 1];
  strcpy(brand, br);
  strncpy(style, st, 20);
  bottles = b;
}

Port::Port(const Port& p) {
  brand = new char[strlen(p.brand) + 1];
  strcpy(brand, p.brand);
  strncpy(style, p.style, 20);
  bottles = p.bottles;
}

Port& Port::operator=(const Port& p) {
  if (&p == this) return *this;
  delete[] brand;
  brand = new char[strlen(p.brand) + 1];
  strcpy(brand, p.brand);
  strncpy(style, p.style, 20);
  bottles = p.bottles;
  return *this;
}

Port& Port::operator+=(int b) {
  bottles += b;
  return *this;
}

Port& Port::operator-=(int b) {
  bottles -= b;
  return *this;
}

void Port::Show() const {
  cout << "Brand: " << brand << endl;
  cout << "Style: " << style << endl;
  cout << "Bottles: " << bottles << endl;
}

ostream& operator<<(ostream& os, const Port& p) {
  os << p.brand << ", " << p.style << ", " << p.bottles;
  return os;
}

VintagePort::VintagePort() : Port() {
  nickname = new char[strlen("none") + 1];
  strcpy(nickname, "none");
  year = 0;
}

VintagePort::VintagePort(const char* br, int b, const char* nn, int y)
    : Port(br, "Vintage", b) {
  nickname = new char[strlen(nn) + 1];
  strcpy(nickname, nn);
  year = y;
}

VintagePort::VintagePort(const VintagePort& vp) : Port(vp) {
  nickname = new char[std::strlen(vp.nickname) + 1];
  std::strcpy(nickname, vp.nickname);
  year = vp.year;
}

VintagePort& VintagePort::operator=(const VintagePort& vp) {
  if (this == &vp) return *this;
  Port::operator=(vp);
  delete[] nickname;
  nickname = new char[std::strlen(vp.nickname) + 1];
  std::strcpy(nickname, vp.nickname);
  year = vp.year;

  return *this;
}

void VintagePort::Show() const {
  Port::Show();
  cout << "Nickname: " << nickname << endl;
  cout << "Year: " << year << endl;
}

ostream& operator<<(ostream& os, const VintagePort& vp) {
  os << (const Port&)vp;
  os << ", " << vp.nickname << ", " << vp.year;
  return os;
}

main.cpp:

#include <iostream>

#include "port.h"

int main() {
  Port p1;
  Port p2("Abc", "Bcc", 30);
  std::cout << p1 << std::endl;
  std::cout << p2 << std::endl;
  Port p3 = p2;
  p3.Show();
  p3 += 3;
  p3.Show();
  Port p4 = p2;
  p3 -= 2;
  p3.Show();

  VintagePort vp1("Vabc", 50, "hn", 1983);
  vp1.Show();
  VintagePort vp2;
  vp2.Show();
  vp1 -= 3;
  vp2 = vp1;
  std::cout << vp2 << std::endl;

  return 0;
}

第十五章课后习题答案

复习题

1. 下面建立友元的尝试有什么错误?

a.

class snap {
    friend clasp;
    ...
};
class clasp { ... };

b.

class cuff {
    public:
    void snip(muff &) { ... }
    ...
};
class muff {
    friend void cuff::snip(muff &);
    ...
};

c.

class muff {
friend void cuff::snip(muff &);
...
};
class cuff {
public:
void snip(muff &) { ... }
...
};

a. friend clasp;缺少class声明,应该改为friend class clasp;

b. 缺少muff的前项声明,应该在第一行添加class muff

c. cuff类声明应在muff类之前,以便编译器可以理解cuff::snip( )。同时编译器需要muff的一个前向声明,以便可以理解snip(muff &)。修改后应该为:

class muff; // forward declaration
class cuff {
    public:
    void snip(muff &) { ... }
    ...
};
class muff {
    friend void cuff::snip(muff &);
    ...
};

2. 您知道了如何建立相互类友元的方法。能够创建一种更为严格的友情关系,即类B只有部分成员是类A的友元,而类A只有部分成员是类B的友元吗?请解释原因。

不能,为了使类B部分成员是类A的友元,需要将类B的声明位于类A的前面,并且要在类A指出类B中要作为类A友元的成员。同样的使类A部分成员成为类A的友元,需要同样的要求,而这两个要求是互斥的,因此无法创建该友情关系。

3. 下面的嵌套类声明中可能存在什么问题

class Ribs
{
    private:
    class Sauce
    {
        int soy;
        int sugar;
        public:
        Sauce(int s1, int s2) : soy(s1), sugar(s2) { }
    };
    ...
};

嵌套类Sauce中的soysugar都是私有的,Ribs只能通过Sauce的构造函数创建它们。

4. throwreturn之间的区别何在?

假设函数f1( )调用函数f2( )。f2( )中的返回语句导致程序执行在函数f1( )中调用函数f2( )后面的一条语句。throw语句导致程序沿函数调用的当前序列回溯,直到找到直接或间接包含对f2( )的调用的try语句块为止。它可能在f1( )中、调用f1( )的函数中或其他函数中。找到这样的try语句块后,将执行下一个匹配的catch语句块,而不是函数调用后的语句。

5. 假设有一个从异常基类派生来的异常类层次结构,则应按什么样的顺序放置catch块?

应按从子孙到祖先的顺序排列catch语句块。

6. 对于本章定义的Grand、SuperbMagnificent类,假设pgGrand *指针,并将其中某个类的对象的地址赋给了它,而ps为Superb *指针,则下面两个代码示例的行为有什么不同?

if (ps = dynamic_cast<Superb *>(pg))
ps->say(); // sample #1
if (typeid(*pg) == typeid(Superb))
(Superb *) pg)->say(); // sample #2

对于dynamic_cast<Superb *>(pg),如果pg指向一个Superb对象或从Superb派生而来的任何类的对象,则if条件为true。具体地说,如果pg指向Magnificent对象,则if条件也为true。

对于typeid(*pg) == typeid(Superb),仅当指向Superb对象时,if条件才为true,如果指向的是从Superb派生出来的对象,则if条件不为true

7. static_cast运算符与dynamic_cast运算符有什么不同?

dynamic_cast运算符只允许沿类层次结构向上转换,而static_cast运算符允许向上转换和向下转换。static_cast运算符还允许枚

举类型和整型之间以及数值类型之间的转换。

编程练习

1. 对TvRemote类做如下修改:

  • a. 让它们互为友元;

  • b. 在`Remote`类中添加一个状态变量成员,该成员描述遥控器是处于常规模式还是互动模式;

  • c. 在`Remote`中添加一个显示模式的方法;

  • d. 在Tv类中添加一个对`Remote`中新成员进行切换的方法,该方法应仅当`TV`处于打开状态时才能运行。

编写一个小程序来测试这些新特性。

tv.h:

#ifndef TV_H_
#define TV_H_

#include <iostream>

class Remote;  // forward declaration

class Tv {
    public:
    friend class Remote;
    enum { Off, On };
    enum { MinVal, MaxVal = 20 };
    enum { Antenna, Cable };
    enum { TV, DVD };

    private:
    int state;
    int volume;
    int maxchannel;
    int channel;
    int mode;
    int input;

    public:
    Tv(int s = Off, int mc = 125)
        : state(s),
    volume(5),
    maxchannel(mc),
    channel(2),
    mode(Cable),
    input(DVD) {}
    void onoff() { state = (state == On) ? Off : On; }
    bool ison() const { return state == On; }
    bool volup();
    bool voldown();
    void chanup();
    void chandown();
    void set_mode() { mode = (mode == Antenna) ? Cable : Antenna; }
    void set_input() { input = (input == TV) ? DVD : TV; }
    void show_settings() const;
    void set_remote_mode(Remote& r) const;
};

class Remote {
    public:
    friend class Tv;
    enum { Normal, Interactive };

    private:
    int mode;
    int gmode;

    public:
    Remote(int m = Tv::TV) : mode(m), gmode(Normal) {}
    bool volup(Tv& t) { return t.volup(); }
    bool voldown(Tv& t) { return t.voldown(); }
    void onoff(Tv& t) { t.onoff(); }
    void chanup(Tv& t) { t.chanup(); }
    void chandown(Tv& t) { t.chandown(); }
    void set_chan(Tv& t, int c) { t.channel = c; }
    void set_mode(Tv& t) { t.set_mode(); }
    void set_input(Tv& t) { t.set_input(); }
    void show_remote_mode() const;
    void set_remote_mode();
};

inline void Remote::show_remote_mode() const {
    std::cout << (gmode == Normal ? "Normal" : "Interactive") << std::endl;
}

inline void Remote::set_remote_mode() {
    gmode = (gmode == Normal) ? Interactive : Normal;
}

#endif  // TV_H_

tv.cpp:

#include "tv.h"

bool Tv::volup() {
    if (volume < MaxVal) {
        volume++;
        return true;
    } else
        return false;
}

bool Tv::voldown() {
    if (volume > MinVal) {
        volume--;
        return true;
    } else
        return false;
}

void Tv::chanup() {
    if (channel < maxchannel)
        channel++;
    else
        channel = 1;
}

void Tv::chandown() {
    if (channel > 1)
        channel--;
    else
        channel = maxchannel;
}

void Tv::show_settings() const {
    std::cout << "TV is " << (state == On ? "On" : "Off") << std::endl;
    if (state == On) {
        std::cout << "Volume setting = " << volume << std::endl;
        std::cout << "Channel setting = " << channel << std::endl;
        std::cout << "Mode = " << (mode == Antenna ? "Antenna" : "Cable")
            << std::endl;
        std::cout << "Input = " << (input == TV ? "TV" : "DVD") << std::endl;
    }
}

void Tv::set_remote_mode(Remote& r) const {
    if (state == On) r.set_remote_mode();
}

main.cpp:

#include <iostream>

#include "tv.h"

int main() {
    Tv t;
    t.show_settings();

    Remote r;
    r.show_remote_mode();

    r.onoff(t);
    t.show_settings();

    r.voldown(t);
    t.show_settings();

    t.set_remote_mode(r);
    r.show_remote_mode();

    return 0;
}

2. 修改程序清单15.11,使两种异常类型都是从头文件<stdexcept>提供的logic_error类派生出来的类。让每个what()方法都报告函数名和问题的性质。异常对象不用存储错误的参数值,而只需支持what()方法。

exc_mean.h:

#ifndef EXC_MEAN_H_
#define EXC_MEAN_H_

#include <stdexcept>

class bad_hmean : public std::logic_error {
    public:
    bad_hmean() : std::logic_error("hmean() invalid arguments: a = -b\n") {}
};

class bad_gmean : public std::logic_error {
    public:
    bad_gmean() : std::logic_error("gmean() arguments should be >= 0\n") {}
};

#endif  // EXC_MEAN_H_

error4.cpp:

// error4.cpp ?using exception classes
#include <cmath>  // or math.h, unix users may need -lm flag
#include <iostream>

#include "exc_mean.h"
// function prototypes
double hmean(double a, double b);
double gmean(double a, double b);
int main() {
    using std::cin;
    using std::cout;
    using std::endl;

    double x, y, z;

    cout << "Enter two numbers: ";
    while (cin >> x >> y) {
        try {  // start of try block
            z = hmean(x, y);
            cout << "Harmonic mean of " << x << " and " << y << " is " << z << endl;
            cout << "Geometric mean of " << x << " and " << y << " is " << gmean(x, y)
                << endl;
            cout << "Enter next set of numbers <q to quit>: ";
        }                      // end of try block
        catch (bad_hmean& he)  // start of catch block
        {
            cout << he.what();
            cout << "Try again.\n";
            continue;
        } catch (bad_gmean& ge) {
            cout << ge.what();
            cout << "Sorry, you don't get to play any more.\n";
            break;
        }  // end of catch block
    }
    cout << "Bye!\n";
    return 0;
}

double hmean(double a, double b) {
    if (a == -b) throw bad_hmean();
    return 2.0 * a * b / (a + b);
}

double gmean(double a, double b) {
    if (a < 0 || b < 0) throw bad_gmean();
    return std::sqrt(a * b);
}

3. 这个练习与编程练习2相同,但异常类是从一个这样的基类派生而来的:它是从logic_error派生而来的,并存储两个参数值。异常类应该有一个这样的方法:报告这些值以及函数名。程序使用一个catch块来捕获基类异常,其中任何一种从该基类异常派生而来的异常都将导致循环结束。

exc_mean.h:

#ifndef EXC_MEAN_H_
#define EXC_MEAN_H_

#include <iostream>
#include <stdexcept>
#include <string>

class bad_base : public std::logic_error {
    private:
    double v1;
    double v2;
    std::string fun_name;

    public:
    bad_base(double a = 0, double b = 0, std::string name = "none")
        : std::logic_error("hmean() invalid arguments: a = -b\n"),
    v1(a),
    v2(b),
    fun_name(name) {}

    void mesg() {
        std::cout << v1 << ", " << v2 << ", " << fun_name << std::endl;
    }
};

class bad_hmean : public bad_base {
    public:
    bad_hmean(double a = 0, double b = 0, std::string name = "none")
        : bad_base(a, b, name) {}
};

class bad_gmean : public bad_base {
    public:
    bad_gmean(double a = 0, double b = 0, std::string name = "none")
        : bad_base(a, b, name) {}
};

#endif  // EXC_MEAN_H_

error4.cpp:

// error4.cpp ?using exception classes
#include <cmath>  // or math.h, unix users may need -lm flag
#include <iostream>

#include "exc_mean.h"
// function prototypes
double hmean(double a, double b);
double gmean(double a, double b);
int main() {
    using std::cin;
    using std::cout;
    using std::endl;

    double x, y, z;

    cout << "Enter two numbers: ";
    while (cin >> x >> y) {
        try {  // start of try block
            z = hmean(x, y);
            cout << "Harmonic mean of " << x << " and " << y << " is " << z << endl;
            cout << "Geometric mean of " << x << " and " << y << " is " << gmean(x, y)
                << endl;
            cout << "Enter next set of numbers <q to quit>: ";
        }                      // end of try block
        catch (bad_hmean& he)  // start of catch block
        {
            he.mesg();
            cout << "Try again.\n";
            continue;
        } catch (bad_gmean& ge) {
            ge.mesg();
            cout << "Sorry, you don't get to play any more.\n";
            break;
        }  // end of catch block
        catch (bad_base& ge) {
            ge.mesg();
            cout << "Sorry, you don't get to play any more.\n";
            break;
        }  // end of catch block
    }
    cout << "Bye!\n";
    return 0;
}

double hmean(double a, double b) {
    if (a == -b) throw bad_hmean(a, b, "hmean()");
    return 2.0 * a * b / (a + b);
}

double gmean(double a, double b) {
    if (a < 0 || b < 0) throw bad_gmean(a, b, "gmean()");
    return std::sqrt(a * b);
}

4. 程序清单15.16在每个try后面都使用两个catch块,以确保nbad_index异常导致方法label_val( )被调用。请修改该程序,在每个try块后面只使用一个catch块,并使用RTTI来确保合适时调用label_val( )

sales.h:

#ifndef SALES_H_
#define SALES_H_

// sales.h  -- exceptions and inheritance
#include <stdexcept>
#include <string>

class Sales {
    public:
    enum { MONTHS = 12 };  // could be a static const
    class bad_index : public std::logic_error {
        private:
        int bi;  // bad index value
        public:
        explicit bad_index(int ix,
                           const std::string& s = "Index error in Sales object\n");
        int bi_val() const { return bi; }
        virtual ~bad_index() throw() {}
    };
    explicit Sales(int yy = 0);
    Sales(int yy, const double* gr, int n);
    virtual ~Sales() {}
    int Year() const { return year; }
    virtual double operator[](int i) const;
    virtual double& operator[](int i);

    private:
    double gross[MONTHS];
    int year;
};

class LabeledSales : public Sales {
    public:
    class nbad_index : public Sales::bad_index {
        private:
        std::string lbl;

        public:
        nbad_index(const std::string& lb, int ix,
                   const std::string& s = "Index error in LabeledSales object\n");
        const std::string& label_val() const { return lbl; }
        virtual ~nbad_index() throw() {}
    };
    explicit LabeledSales(const std::string& lb = "none", int yy = 0);
    LabeledSales(const std::string& lb, int yy, const double* gr, int n);
    virtual ~LabeledSales() {}
    const std::string& Label() const { return label; }
    virtual double operator[](int i) const;
    virtual double& operator[](int i);

    private:
    std::string label;
};

#endif  // SALES_H_

sales.cpp:

// sales.cpp -- Sales implementation
#include "sales.h"
using std::string;

Sales::bad_index::bad_index(int ix, const string& s)
    : std::logic_error(s), bi(ix) {}

Sales::Sales(int yy) {
    year = yy;
    for (int i = 0; i < MONTHS; ++i) gross[i] = 0;
}

Sales::Sales(int yy, const double* gr, int n) {
    year = yy;
    int lim = (n < MONTHS) ? n : MONTHS;
    int i;
    for (i = 0; i < lim; ++i) gross[i] = gr[i];
    // for i > n and i < MONTHS
    for (; i < MONTHS; ++i) gross[i] = 0;
}

double Sales::operator[](int i) const {
    if (i < 0 || i >= MONTHS) throw bad_index(i);
    return gross[i];
}

double& Sales::operator[](int i) {
    if (i < 0 || i >= MONTHS) throw bad_index(i);
    return gross[i];
}

LabeledSales::nbad_index::nbad_index(const string& lb, int ix, const string& s)
    : Sales::bad_index(ix, s) {
        lbl = lb;
    }

LabeledSales::LabeledSales(const string& lb, int yy) : Sales(yy) { label = lb; }

LabeledSales::LabeledSales(const string& lb, int yy, const double* gr, int n)
    : Sales(yy, gr, n) {
        label = lb;
    }

double LabeledSales::operator[](int i) const {
    if (i < 0 || i >= MONTHS) throw nbad_index(Label(), i);
    return Sales::operator[](i);
}

double& LabeledSales::operator[](int i) {
    if (i < 0 || i >= MONTHS) throw nbad_index(Label(), i);
    return Sales::operator[](i);
}

use_sales.cpp:

// use_sales.cpp  -- nested exceptions
#include <iostream>
#include <typeinfo>

#include "sales.h"

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

    double vals1[12] = {1220, 1100, 1122, 2212, 1232, 2334,
                        2884, 2393, 3302, 2922, 3002, 3544};

    double vals2[12] = {12, 11, 22, 21, 32, 34, 28, 29, 33, 29, 32, 35};

    Sales sales1(2011, vals1, 12);
    LabeledSales sales2("Blogstar", 2012, vals2, 12);

    cout << "First try block:\n";
    try {
        int i;
        cout << "Year = " << sales1.Year() << endl;
        for (i = 0; i < 12; ++i) {
            cout << sales1[i] << ' ';
            if (i % 6 == 5) cout << endl;
        }
        cout << "Year = " << sales2.Year() << endl;
        cout << "Label = " << sales2.Label() << endl;
        for (i = 0; i <= 12; ++i) {
            cout << sales2[i] << ' ';
            if (i % 6 == 5) cout << endl;
        }
        cout << "End of try block 1.\n";
    } catch (Sales::bad_index &bad) {
        cout << bad.what();
        if (typeid(bad) == typeid(LabeledSales::nbad_index)) {
            cout << "Company: " << ((LabeledSales::nbad_index &)bad).label_val()
                << endl;
        }
        cout << "bad index: " << bad.bi_val() << endl;
    }
    cout << "\nNext try block:\n";
    try {
        sales2[2] = 37.5;
        sales1[20] = 23345;
        cout << "End of try block 2.\n";
    } catch (Sales::bad_index &bad) {
        cout << bad.what();
        if (typeid(bad) == typeid(LabeledSales::nbad_index)) {
            cout << "Company: " << ((LabeledSales::nbad_index &)bad).label_val()
                << endl;
        }
        cout << "bad index: " << bad.bi_val() << endl;
    }
    cout << "done\n";
    return 0;
}

第十六章课后习题答案

 

 复习题

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: ";
}

第十七章课后习题答案

复习题

 编程练习

1. 编写一个程序计算输入流中第一个$之前的字符数目,并将$留在输入流中。

main.cpp:

#include <iostream>
//#include <ios>  //used to get stream size
//#include <limits>  //used to get numeric limits

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

    cout << "Enter a string with a '$' in it: " << endl;
    int ct = 0;
    char ch;
    while ((ch = cin.get()) != '$') ct++;
    cin.putback(ch);  // put $ back to stream

    while (cin.get() != '\n') continue;
    // or use cin.ignore(std::numeric_limits<std::streamsize>::max(),// '\n');

    cout << "There are " << ct << " characters before $" << endl;

    return 0;
}

2. 编写一个程序,将键盘输入(直到模拟的文件尾)复制到通过命令行指定的文件中。

main.cpp:

#include <cstdlib>
#include <fstream>
#include <iostream>

using namespace std;

int main() {
    cout << "Please enter filename: ";
    char fname[30];
    cin >> fname;
    ofstream fout;
    fout.open(fname, std::ios_base::out);

    cout << "Please enter something: " << endl;
    char ch;
    while (cin.get(ch) && ch != EOF) fout << ch;

    fout.close();

    return 0;
}

:linux下键盘模拟输入文件结束符为Ctrl+D

3. 编写一个程序,将一个文件复制到另一个文件中。让程序通过命令行获取文件名。如果文件无法打开,程序将指出这一点。

main.cpp:

#include <cstdlib>
#include <fstream>
#include <iostream>

int main(int argc, char* argv[]) {
    using namespace std;

    if (argc < 3) {
        cerr << "Usage: " << argv[0] << " source-file target-file" << endl;
        exit(EXIT_FAILURE);
    }

    ifstream fin(argv[1], ios_base::in);
    if (!fin) {
        cerr << "Cant't open" << argv[1] << " for input" << endl;
        exit(EXIT_FAILURE);
    }

    ofstream fout(argv[2], ios_base::out);
    if (!fout) {
        cerr << "Can't open" << argv[2] << " for output" << endl;
        exit(EXIT_FAILURE);
    }

    char ch;
    while (fin.get(ch)) fout.put(ch);

    cout << "Content of " << argv[1] << " copied to " << argv[2] << "success."
        << endl;
    fin.close();
    fout.close();

    return 0;
}

4. 编写一个程序,它打开两个文本文件进行输入,打开一个文本文件进行输出。该程序将两个输入文件中对应的行拼接起来,并用空格分隔,然后将结果写入到输出文件中。如果一个文件比另一个短,则将较长文件中余下的几行直接复制到输出文件中。例如,假设第一个输入文件的内容如下:

eggs kites donuts
balloons hammers
stones

而第二个输入文件的内容如下:

zero lassitude
finance drama

则得到的文件的内容将如下:

eggs kites donuts zero lassitude
balloons hammers finance drama
stones

main.cpp:

#include <fstream>
#include <iostream>

int main() {
    using namespace std;
    char* file1 = "input1.txt";
    char* file2 = "input2.txt";
    char* file3 = "output.txt";
    ifstream fin1(file1, ios_base::in);
    ifstream fin2(file2, ios_base::in);
    ofstream fout1(file3, ios_base::out);
    if (!fin1.is_open() || !fin2.is_open()) {
        cerr << file1 << " or " << file2 << "open fail.";
        exit(EXIT_FAILURE);
    }
    char ch;
    if (!fout1.is_open()) {
        cerr << "Can't open " << file3 << endl;
        exit(EXIT_FAILURE);
    } else {
        while (!fin1.eof() || !fin2.eof()) {
            if (!fin1.eof()) {
                while (fin1.get(ch) && ch != '\n') fout1 << ch;
                fout1 << ' ';
            }
            if (!fin2.eof()) {
                while (fin2.get(ch) && ch != '\n') fout1 << ch;
            }
            fout1 << '\n';
        }
    }
    fin1.close();
    fin2.close();
    fout1.close();

    return 0;
}

5. Mat和Pat想邀请他们的朋友来参加派对,就像第16章中的编程练习8那样,但现在他们希望程序使用文件。他们请您编写一个完成下述任务的程序。

  • 从文本文件mat.dat中读取Mat朋友的姓名清单,其中每行为一个朋友。姓名将被存储在容器,然后按顺序显示出来。

  • 从文本文件pat.dat中读取Pat朋友的姓名清单,其中每行为一个朋友。姓名将被存储在容器中,然后按顺序显示出来。

  • 合并两个清单,删除重复的条目,并将结果保存在文件matnpat.dat中,其中每行为一个朋友。

main.cpp:

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

int main() {
    using namespace std;

    vector<string> mat;
    vector<string> pat;
    set<string> matnpat;

    ifstream fmat("mat.dat", ios_base::in), fpat("pat.dat", ios_base::in);
    if (!(fmat && fpat)) {
        cerr << "Failed to open input files" << endl;
        exit(EXIT_FAILURE);
    }

    ofstream fout("matnpat.at", ios_base::out);
    if (!fout) {
        cerr << "Failed to open output files" << endl;
        exit(EXIT_FAILURE);
    }

    string name;

    while (fmat >> name) mat.push_back(name);
    cout << "Mat's friends: ";
    for (auto it = mat.begin(); it != mat.end(); ++it) cout << *it << " ";
    cout << endl;

    while (fpat >> name) pat.push_back(name);
    cout << "Pat's friends: ";
    for (auto it = pat.begin(); it != pat.end(); ++it) cout << *it << " ";
    cout << endl;

    matnpat.insert(mat.begin(), mat.end());
    matnpat.insert(pat.begin(), pat.end());
    cout << "All friends: " << endl;
    for (auto& name : matnpat) cout << name << " ";
    cout << endl;

    return 0;
}

6. 考虑14章的编程练习5中的类定义。如果还没有完成这个练习,请现在就做,然后完成下面的任务。

编写一个程序,它使用标准C++ I/O、文件I/O以及14章的编程练习5中定义的employee、manager、fink和highfink类型的数据。该程序应包含程序清单17.17中的代码行,即允许用户将新数据添加到文件中。该程序首次被运行时,将要求用户输入数据,然后显示所有的数据,并将这些信息保存到一个文件中。当该程序再次被运行时,将首先读取并显示文件中的数据,然后让用户添加数据,并显示所有的数据。差别之一是,应通过一个指向employee类型的指针数组来处理数据。这样,指针可以指向employee对象,也可以指向从employee派生出来的其他三种对象中的任何一种。使数组较小有助于检查程序,例如,您可能将数组限定为最多包含10个元素:

const int MAX = 10; // no more than 10 objects
...
employee * pc[MAX];

为通过键盘输入,程序应使用一个菜单,让用户选择要创建的对象类型。菜单将使用一个switch,以便使用new来创建指定类型的对象,并将它的地址赋给pc数组中的一个指针。然后该对象可以使用虚函数setall( )来提示用户输入相应的数据:

pc[i]->setall(); // invokes function corresponding to type of object

为将数据保存到文件中,应设计一个虚函数writeall( ):

for (i = 0; i < index; i++)
pc[i]->writeall(fout);// fout ofstream connected to output file
 

注意】:

对于这个练习,应使用文本I/O,而不是二进制I/O(遗憾的是,虚对象包含指向虚函数指针表的指针,而write( )将把这种信息复制到文件中。使用read( )读取文件的内容,以填充对象时,函数指针值将为乱码,这将扰乱虚函数的行为)。可使用换行符将字段分隔开,这样在输入时将很容易识别各个字段。也可以使用二进制I/O,但不能将对象作为一个整体写入,而应该提供分别对每个类成员应用write( )和read( )的类方法。这样,程序将只把所需的数据保存到文件中。

比较难处理的部分是使用文件恢复数据。问题在于:程序如何才能知道接下来要恢复的项目是employee对象、manager对象、fink对象还是highfink对象?一种方法是,在对象的数据写入文件时,在数据前面加上一个指示对象类型的整数。这样,在文件输入时,程序便可以读取该整数,并使用switch语句创建一个适当的对象来接收数据:

enum classkind{Employee, Manager, Fink, Highfink}; // in class header
...
    int classtype;
while((fin >> classtype).get(ch)){ // newline separates int from data
    switch(classtype) {
        case Employee : pc[i] = new employee;
            : break;

然后便可以使用指针调用虚函数getall( )来读取信息:

pc[i++]->getall();

emp.h:

#ifndef EMP_H_
#define EMP_H_

#include <iostream>
#include <string>
#include <fstream>

enum classkind {Employee, Manager, Fink, HighFink};

class abstr_emp
{
private:
    std::string fname;
    std::string lname;
    std::string job;
public:
    abstr_emp();
    abstr_emp(const std::string & fn, const std::string & ln,
              const std::string & j);
    virtual void ShowAll() const;
    virtual void SetAll();
    virtual std::ofstream & WriteAll(std::ofstream & of) const;
    friend std::ostream & operator<<(std::ostream & os, const abstr_emp & e);
    virtual ~abstr_emp() = 0;
};

class employee : public abstr_emp
{
public:
    employee();
    employee(const std::string & fn, const std::string & ln,
             const std::string & j);
    virtual void ShowAll() const;
    virtual void SetAll();
    std::ofstream & WriteAll(std::ofstream & fout) const;
};

class manager : virtual public abstr_emp
{
private:
    int inchargeof;
protected:
    int InChargeOf() const { return inchargeof; }
    int & InChargeOf() { return inchargeof; }
public:
    manager();
    manager(const std::string & fn, const std::string & ln,
            const std::string & j, int ico = 0);
    manager(const abstr_emp & e, int ico);
    manager(const manager & m);
    virtual void ShowAll() const;
    virtual void SetAll();
    std::ofstream & WriteAll(std::ofstream & fout) const;
};

class fink : virtual public abstr_emp
{
private:
    std::string reportsto;
protected:
    const std::string ReportsTo() const { return reportsto; }
    std::string & ReportsTo() { return reportsto; }
public:
    fink();
    fink(const std::string & fn, const std::string ln,
         const std::string & j, const std::string rpo);
    fink(const abstr_emp & e, const std::string & rpo);
    fink(const fink & f);
    virtual void ShowAll() const;
    virtual void SetAll();
    std::ofstream & WriteAll(std::ofstream & fout) const;
};

class highfink : public manager, public fink
{
public:
    highfink();
    highfink(const std::string & fn, const std::string & ln,
             const std::string & j, const std::string & rpo,
             int ico);
    highfink(const abstr_emp & e, const std::string rpo, int ico);
    highfink(const fink & f, int ico);
    highfink(const manager & m, const std::string rpo);
    highfink(const highfink & h);
    virtual void ShowAll() const;
    virtual void SetAll();
    std::ofstream & WriteAll(std::ofstream & fout) const;
};

#endif

emp.cpp:

#include "emp.h"

/*******************
 * abstr_emp methods
 *******************/
abstr_emp::abstr_emp()
{
    fname = "none";
    lname = "none";
    job = "none";
}

abstr_emp::abstr_emp(const std::string & fn, const std::string & ln,
                     const std::string & j) : fname(fn), lname(ln), job(j)
{
}

abstr_emp::~abstr_emp() {}

void abstr_emp::ShowAll() const
{
    std::cout << "firstname: " << fname << std::endl;
    std::cout << "lastname: " << lname << std::endl;
    std::cout << "job: " << job << std::endl;
}

void abstr_emp::SetAll()
{
    std::cout << "Enter firstname: ";
    std::getline(std::cin, fname);
    std::cout << "Enter lastname: ";
    std::getline(std::cin, lname);
    std::cout << "Enter job: ";
    std::getline(std::cin, job);
}

std::ofstream & abstr_emp::WriteAll(std::ofstream & fout) const
{
    fout << fname << " " << lname << " " << job;
    return fout;
}

// friend functions
std::ostream & operator<<(std::ostream & os, const abstr_emp & e)
{
    os << e.lname << " " << e.fname << ", " << e.job;
    return os;
}

/******************
 * employee methods
 ******************/
employee::employee() {}

employee::employee(const std::string & fn, const std::string & ln,
                   const std::string & j)
    : abstr_emp(fn, ln, j)
{
}

void employee::ShowAll() const
{
    abstr_emp::ShowAll();
}

void employee::SetAll()
{
    abstr_emp::SetAll();
}

std::ofstream & employee::WriteAll(std::ofstream & fout) const
{
    fout << Employee << " ";
    abstr_emp::WriteAll(fout);
    return fout;
}


/*****************
 * manager methods
 *****************/
manager::manager()
{
    inchargeof = 0;
}


manager::manager(const std::string & fn, const std::string & ln,
                 const std::string & j, int ico)
    : abstr_emp(fn, ln, j), inchargeof(ico)
{
}

manager::manager(const abstr_emp & e, int ico)
    : abstr_emp(e)
{
    inchargeof = ico;
}

manager::manager(const manager & m)
    : abstr_emp(m)
{
    inchargeof = m.inchargeof;
}

void manager::ShowAll() const
{
    abstr_emp::ShowAll();
    std::cout << "Inchargeof: " << inchargeof << std::endl;
}

void manager::SetAll()
{
    abstr_emp::SetAll();
    std::cout << "Enter inchargeof: ";
    std::cin >> inchargeof;
    std::cin.get();
}

std::ofstream & manager::WriteAll(std::ofstream & fout) const
{
    fout << Manager << " ";
    abstr_emp::WriteAll(fout);
    fout << " " << inchargeof;
    return fout;
}

/**************
 * fink methods
 **************/
fink::fink()
{
    reportsto = "none";
}

fink::fink(const std::string & fn, const std::string ln,
           const std::string & j, const std::string rpo)
    : abstr_emp(fn, ln, j), reportsto(rpo)
{
}

fink::fink(const abstr_emp & e, const std::string & rpo)
    : abstr_emp(e), reportsto(rpo)
{
}

fink::fink(const fink & f)
    : abstr_emp(f)
{
    reportsto = f.reportsto;
}

void fink::ShowAll() const
{
    abstr_emp::ShowAll();
    std::cout << "Reportsto: " << reportsto << std::endl;
}

void fink::SetAll()
{
    abstr_emp::SetAll();
    std::cout << "Enter reportsto: ";
    std::getline(std::cin, reportsto);
}

std::ofstream & fink::WriteAll(std::ofstream & fout) const
{
    fout << Fink << " ";
    abstr_emp::WriteAll(fout);
    fout << " " << reportsto;
    return fout;
}

/******************
 * highfink methods
 ******************/
highfink::highfink() {}

highfink::highfink(const std::string & fn, const std::string & ln,
                   const std::string & j, const std::string & rpo,
                   int ico)
    : abstr_emp(fn, ln, j), manager(fn, ln, j, ico), fink(fn, ln, j, rpo)
{
}

highfink::highfink(const abstr_emp & e, const std::string rpo, int ico)
    : abstr_emp(e), manager(e, ico), fink(e, rpo)
{
}

highfink::highfink(const fink & f, int ico)
    : abstr_emp(f), fink(f), manager((const abstr_emp &)f, ico)  // ????
{
}

highfink::highfink(const manager & m, const std::string rpo)
    : abstr_emp(m), manager(m), fink((const abstr_emp &)m, rpo)
{
}

highfink::highfink(const highfink & h)
    : abstr_emp(h), manager(h), fink(h)
{
}

void highfink::ShowAll() const
{
    abstr_emp::ShowAll();
    std::cout << "Inchargeof: " << manager::InChargeOf() << std::endl;
    std::cout << "Reportsto: " << fink::ReportsTo() << std::endl;
}

void highfink::SetAll()
{
    abstr_emp::SetAll();
    std::cout << "Enter reportsto: ";
    std::getline(std::cin, fink::ReportsTo());
    std::cout << "Enter Inchargeof: ";
    std::cin >> manager::InChargeOf();
    std::cin.get();
}

std::ofstream & highfink::WriteAll(std::ofstream & fout) const
{
    fout << HighFink << " ";
    abstr_emp::WriteAll(fout);
    fout << " " << manager::InChargeOf() << " " << fink::ReportsTo();
    return fout;
}

main.cpp:

#include <fstream>
#include <iostream>

#include "emp.h"

using namespace std;

inline void show_line(int n);
void show_menu();
inline void eatline();

const int MAX = 10;

int main() {
    abstr_emp* pc[MAX];
    int ct = 0;  // number counter

    string fname, lname, job, reportsto;
    int inchargeof;

    ifstream fin("out.txt", ios_base::in);
    if (fin.good()) {  // read from file
        int kind;
        while (fin >> kind) {
            switch (kind) {
                case Employee:
                    fin >> fname;
                    fin >> lname;
                    fin >> job;
                    pc[ct] = new employee(fname, lname, job);
                    break;
                case Manager:
                    fin >> fname;
                    fin >> lname;
                    fin >> job;
                    fin >> inchargeof;
                    pc[ct] = new manager(fname, lname, job, inchargeof);
                    break;
                case Fink:
                    fin >> fname;
                    fin >> lname;
                    fin >> job;
                    fin >> reportsto;
                    pc[ct] = new fink(fname, lname, job, reportsto);
                    break;
                case HighFink:
                    fin >> fname;
                    fin >> lname;
                    fin >> job;
                    fin >> reportsto;
                    fin >> inchargeof;
                    pc[ct] = new highfink(fname, lname, job, reportsto, inchargeof);
            }
            ct++;
        }

        // show content in file
        cout << "content in out.txt" << endl;
        for (int i = 0; i < ct; ++i) pc[i]->ShowAll();
    }

    // fill the array
    show_menu();
    char choice;
    abstr_emp* p;
    while (cin >> choice && choice != 'q' && ct < MAX) {
        eatline();
        switch (choice) {
            case 'e':  // employee
                p = new employee;
                p->SetAll();
                pc[ct] = p;
                break;
            case 'm':  // manager
                p = new manager;
                p->SetAll();
                pc[ct] = p;
                break;
            case 'f':  // fink
                p = new fink;
                p->SetAll();
                pc[ct] = p;
                break;
            case 'h':  // highfink
                p = new highfink;
                p->SetAll();
                pc[ct] = p;
                break;
        }
        ct++;
        show_menu();
    }

    // show all input
    for (int i = 0; i < ct; ++i) pc[i]->ShowAll();

    // write to files
    ofstream fout("out.txt", ios_base::out);
    for (int i = 0; i < ct; ++i) {
        pc[i]->WriteAll(fout);
        fout << endl;
    }
    fout.close();

    cout << "content in array are written to out.txt" << endl;

    // free memories
    for (int i = 0; i < ct; ++i) delete pc[i];

    return 0;
}

void show_menu() {
    ios_base::fmtflags old_fmt = cout.setf(ios_base::left, ios_base::adjustfield);

    show_line(35);
    cout.width(20);
    cout << "e. employee";
    cout << "m. manager" << endl;
    cout.width(20);
    cout << "f. fink";
    cout << "h. highfink" << endl;
    cout << "q. quit" << endl;
    show_line(35);
    cout << "Select a type: " << endl;

    cout.setf(old_fmt);
}

inline void show_line(int n) {
    cout.fill('-');
    cout.width(n);
    cout << "-" << endl;
    cout.fill(' ');
}

inline void eatline() {
    while (cin.get() != '\n') continue;
}

7. 下面是某个程序的部分代码。该程序将键盘输入读取到一个由string对象组成的vector中,将字符串内容(而不是string对象)存储到一个文件中,然后该文件的内容复制到另一个由string对象组成的vector中。

int main()
{
    using namespace std;
    vector<string> vostr;
    string temp;
    // acquire strings
    cout << "Enter strings (empty line to quit):\n";
    while (getline(cin,temp) && temp[0] != '\0')
        vostr.push_back(temp);
    cout << "Here is your input.\n";
    for_each(vostr.begin(), vostr.end(), ShowStr);
    // store in a file
    ofstream fout("strings.dat", ios_base::out | ios_base::binary);
    for_each(vostr.begin(), vostr.end(), Store(fout));
    fout.close();
    // recover file contents
    vector<string> vistr;
    ifstream fin("strings.dat", ios_base::in | ios_base::binary);
    if (!fin.is_open())
    {
        cerr << "Could not open file for input.\n";
        exit(EXIT_FAILURE);
    }
    GetStrs(fin, vistr);
    cout << "\nHere are the strings read from the file:\n";
    for_each(vistr.begin(), vistr.end(), ShowStr);
    return 0;
}

该程序以二进制格式打开文件,并想使用read( )和write( )来完成I/O。余下的工作如下所述。

  • 编写函数void ShowStr(const string &),它显示一个string对象,并在显示完后换行。

  • 编写函数符Store,它将字符串信息写入到文件中。Store的构造函数应接受一个指定ifstream对象的参数,而重载的operator( )(const string &)应指出要写入到文件中的字符串。一种可行的计划是,首先将字符串的长度写入到文件中,然后将字符串的内容写入到文件中。例如,如果len存储了字符串的长度,可以这样做:

os.write((char *)&len, sizeof(std::size_t)); // store length
os.write(s.data(), len); // store characters

成员函数data( )返回一个指针,该指针指向一个其中存储了字符串中字符的数组。它类似于成员函数c_str( ),只是后者在数组末尾加上了一个空字符。

  • 编写函数GetStrs( ),它根据文件恢复信息。该函数可以使用read( )来获得字符串的长度,然后使用一个循环从文件中读取相应数量的字符,并将它们附加到一个原来为空的临时string末尾。由于string的数据是私有的,因此必须使用string类的方法来将数据存储到string对象中,而不能直接存储。

main.cpp:

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

using namespace std;

class Store {
    private:
    ostream& os;

    public:
    Store(ostream& o) : os(o){};
    void operator()(const string& s) {
        size_t len = s.length();

        // store string length
        os.write((char*)&len, sizeof(std::size_t));
        // store string data
        os.write(s.data(), len);
    }
};
inline void ShowStr(const std::string& s) { cout << s << endl; };
void GetStrs(std::ifstream& fin, std::vector<std::string>& vistr);

int main() {
    using namespace std;
    vector<string> vostr;
    string temp;

    // acquire strings
    cout << "Enter strings (empty line to quit):\n";
    while (getline(cin, temp) && temp[0] != '\0') vostr.push_back(temp);
    cout << "Here is your input.\n";
    for_each(vostr.begin(), vostr.end(), ShowStr);

    // store in a file
    ofstream fout("strings.dat", ios_base::out | ios_base::binary);
    for_each(vostr.begin(), vostr.end(), Store(fout));
    fout.close();

    // recover file contents
    vector<string> vistr;
    ifstream fin("strings.dat", ios_base::in | ios_base::binary);
    if (!fin.is_open()) {
        cerr << "Could not open file for input.\n";
        exit(EXIT_FAILURE);
    }
    GetStrs(fin, vistr);
    cout << "\nHere are the strings read from the file:\n";
    for_each(vistr.begin(), vistr.end(), ShowStr);
    return 0;
}

void GetStrs(std::ifstream& fin, std::vector<std::string>& vistr) {
    size_t len;  // string length
    while (fin.read((char*)&len, sizeof(std::size_t))) {
        string str;
        char ch;
        for (int i = 0; i < len; ++i) {
            fin.read(&ch, sizeof(char));
            str.push_back(ch);
        }

        // put string to vector
        vistr.push_back(str);
    }
}

第十八章课后习题答案

复习题

1. 使用用大括号括起的初始化列表语法重写下述代码。重写后的代码不应使用数组ar:

class Z200
{
    private:
    int j;
    char ch;
    double z;
    public:
    Z200(int jv, char chv, zv) : j(jv), ch(chv), z(zv) {}
    ...
};
double x = 8.8;
std::string s = "What a bracing effect!";
int k(99);
Z200 zip(200,'Z',0.675);
std::vector<int> ai(5);
int ar[5] = {3, 9, 4, 7, 1};
for (auto pt = ai.begin(), int i = 0; pt != ai.end(); ++pt, ++i)
    *pt = ai[i];

重写后代码:

class Z200
{
    private:
    int j;
    char ch;
    double z;
    public:
    Z200(int jv, char chv, zv) : j(jv), ch(chv), z(zv) {}
    ...
};
double x{8.8}; // = {8.8}
std::string s{"What a bracing effect!"};
int k{99};
Z200 zip{200,'Z',0.675};
std::vector<int> ai{3, 9, 4, 7, 1};

2.2. 在下述简短的程序中,哪些函数调用不对?为什么?对于合法的函数调用,指出其引用参数指向的是什么。

#include <iostream>
using namespace std;
double up(double x) { return 2.0* x;}
void r1(const double &rx) {cout << rx << endl;}
void r2(double &rx) {cout << rx << endl;}
void r3(double &&rx) {cout << rx << endl;}
int main()
{
    double w = 10.0;
    r1(w);
    r1(w+1);
    r1(up(w));
    r2(w);
    r2(w+1);
    r2(up(w));
    r3(w);
    r3(w+1);
    r3(up(w));
    return 0;
}
  • r1(w); ----合法,形参rx指向w

  • r1(w+1);----合法,形参rx指向一个临时变量,这个变量被初始化为w+1

  • r1(up(w));---合法,形参rx指向一个临时变量,这个变量被初始化为up(w)的返回值。

  • r2(w);---合法,形参rx指向w

  • r2(w+1); ---非法,因为w+1是一个右值。

  • r2(up(w));---非法,因为up(w)的返回值是一个右值。

  • r3(w);---非法,因为右值引用不能指向左值(如w)。

  • r3(w+1);---合法,rx指向表达式w+1的临时拷贝。

  • r3(up(w));---合法,rx指向up(w)的临时返回值。

一般而言,将左值传递给const左值引用参数的时候,参数将被初始化为左值。将右值传递给函数时,const左值引用参数将指向右值的临时拷贝。将左值传递给非const左值引用参数时,参数将被初始化为左值;但非const左值形参不能接受右值实参。

3. a. 下述简短的程序显示什么?为什么?

#include <iostream>
using namespace std;
double up(double x) { return 2.0 * x; }
void r1(const double &rx) { cout << "const double & rx\n"; }
void r1(double &rx) { cout << "double & rx\n"; }
int main() {
    double w = 10.0;
    r1(w);
    r1(w + 1);
    r1(up(w));
    return 0;
}

b. 下述简短的程序显示什么?为什么?

#include <iostream>
using namespace std;
double up(double x) { return 2.0 * x; }
void r1(double &rx) { cout << "double & rx\n"; }
void r1(double &&rx) { cout << "double && rx\n"; }
int main() {
    double w = 10.0;
    r1(w);
    r1(w + 1);
    r1(up(w));
    return 0;
}

c. 下述简短的程序显示什么?为什么?

#include <iostream>
using namespace std;
double up(double x) { return 2.0 * x; }
void r1(const double &rx) { cout << "const double & rx\n"; }
void r1(double &&rx) { cout << "double && rx\n"; }
int main() {
    double w = 10.0;
    r1(w);
    r1(w + 1);
    r1(up(w));
    return 0;
}

a.

double & rx
const double & rx
const double & rx

const左值引用与左值实参匹配,因此r1(w);调用void r1(double &rx)。另外两个实参均为右值,const左值引用可以指向他们的拷贝。【将右值传递给函数时,const左值引用参数将指向右值的临时拷贝。】。

b.

double & rx
double && rx
double && rx

左值引用与左值实参w匹配,而右值引用与两个右值实参匹配。

c.

const double & rx
double && rx
double && rx

const左值引用与左值实参w匹配,而右值引用与两个右值实参匹配。

总之,非const左值形参与左值实参匹配,非cosnt右值形参与右值实参匹配;const左值形参可以与左值或右值实参匹配。如果可供选择的话,编译器优先选择前两种方式。

4. 哪些成员函数是特殊的成员函数?它们特殊的原因是什么?

特殊成员函数:默认构造函数、复制构造函数、移动构造函数、析构函数、复制赋值运算符和移动赋值运算符。这些函数之所以特殊,是因为编译器将根据情况自动提供它们的默认版本。

5. 假设Fizzle类只有如下所示的数据成员:

class Fizzle
{
    private:
    double bubbles[4000];
    ...
};

为什么不适合给这个类定义移动构造函数?要让这个类适合定义移动构造函数,应如何修改存储4000个double值的方式?

移动构造函数是在转让数据所有权可行的时候是合适的。但对于标准数组没有转让所有权的机制,因此不适合给该类定义移动构造函数。如果Fizzle使用指针和动态内存分配来存储这4000个double值,即可以将数据的地址赋给新指针,以转让其所有权,则适合给Fizzle定义移动构造函数。

6. 修改下述简短的程序,使其使用lambda表达式而不是f1( )。请不要修改show2( )。

#include <iostream>
template<typename T>
void show2(double x, T& fp) {std::cout << x << " -> " << fp(x) << '\n';}
double f1(double x) { return 1.8*x + 32;}
int main()
{
    show2(18.0, f1);
    return 0;
}

修改后:

#include <iostream>
template <typename T>
void show2(double x, T& fp) { std::cout << x << " -> " << fp(x) << '\n';}
//void show2(double x, T fp) {std::cout << x << " -> " << fp(x) << '\n';}
int main() {
    auto f2 = [](double x) { return 1.8 * x + 32; };
    show2(18.0, f2);
    //show2(18.0, [](double x){return 1.8*x + 32;});
    return 0;
}

7. 修改下述简短而丑陋的程序,使其使用lambda表达式而不是函数符Adder。请不要修改sum( )。

#include <iostream>
#include <array>
const int Size = 5;
template<typename T>
void sum(std::array<double,Size> a, T& fp);
class Adder
{
    double tot;
    public:
    Adder(double q = 0) : tot(q) {}
    void operator()(double w) { tot +=w;}
    double tot_v () const {return tot;};
};
int main()
{
    double total = 0.0;
    Adder ad(total);
    std::array<double, Size> temp_c = {32.1, 34.3, 37.8, 35.2, 34.7};
    sum(temp_c,ad);
    total = ad.tot_v();
    std::cout << "total: " << ad.tot_v() << '\n';
    return 0;
}
template<typename T>
void sum(std::array<double,Size> a, T& fp)
{
    for(auto pt = a.begin(); pt != a.end(); ++pt)
    {
        fp(*pt);
    }
}

修改后:

#include <array>
#include <iostream>
const int Size = 5;
template <typename T>
void sum(std::array<double, Size> a, T& fp);

int main() {
    double total = 0.0;
    std::array<double, Size> temp_c = {32.1, 34.3, 37.8, 35.2, 34.7};
    auto f = [&total](double x) { total += x; };
    sum(temp_c, f);
    std::cout << "total: " << total << '\n';
    return 0;
}
template <typename T>
void sum(std::array<double, Size> a, T& fp) {
    for (auto pt = a.begin(); pt != a.end(); ++pt) {
        fp(*pt);
    }
}

编程练习

1. 下面是一个简短程序的一部分:

int main()
{
    using namespace std;
    // list of double deduced from list contents
    auto q = average_list({15.4, 10.7, 9.0});
    cout << q << endl;
    // list of int deduced from list contents
    cout << average_list({20, 30, 19, 17, 45, 38} ) << endl;
    // forced list of double
    auto ad = average_list<double>({'A', 70, 65.33});
    cout << ad << endl;
    return 0;
}

请提供函数average_list( ),让该程序变得完整。它应该是一个模板函数,其中的类型参数指定了用作函数参数的initilize_list模板的类型以及函数的返回类型。

修改后:

#include <algorithm>
#include <initializer_list>
#include <iostream>
using namespace std;

template <typename T>
T average_list(initializer_list<T> l) {
    T sum = 0;
    if (l.size() == 0) return 0;
    for_each(l.begin(), l.end(), [&sum](T t) { sum += t; });
    return sum / l.size();
}

int main() {
    using namespace std;
    // list of double deduced from list contents
    auto q = average_list({15.4, 10.7, 9.0});
    cout << q << endl;
    // list of int deduced from list contents
    cout << average_list({20, 30, 19, 17, 45, 38}) << endl;
    // forced list of double
    auto ad = average_list<double>({'A', 70, 65.33});
    cout << ad << endl;
    return 0;
}

2. 下面是类Cpmv的声明:

class Cpmv
{
    public:
    struct Info
    {
        std::string qcode;
        std::string zcode;
    };
    private:
    Info *pi;
    public:
    Cpmv();
    Cpmv(std::string q, std::string z);
    Cpmv(const Cpmv & cp);
    Cpmv(Cpmv && mv);
    ~Cpmv();
    Cpmv & operator=(const Cpmv & cp);
    Cpmv & operator=(Cpmv && mv);
    Cpmv operator+(const Cpmv & obj) const;
    void Display() const;
};

函数operator+ ( )应创建一个对象,其成员qcode和zcode有操作数的相应成员拼接而成。请提供为移动构造函数和移动赋值运算符实现移动语义的代码。编写一个使用所有这些方法的程序。为方便测试,让各个方法都显示特定的内容,以便知道它们被调用。

代码如下:

#include <iostream>

using namespace std;

class Cpmv {
    public:
    struct Info {
        std::string qcode;
        std::string zcode;
    };

    private:
    Info *pi;

    public:
    Cpmv();
    Cpmv(std::string q, std::string z);
    Cpmv(const Cpmv &cp);
    Cpmv(Cpmv &&mv);
    ~Cpmv();
    Cpmv &operator=(const Cpmv &cp);
    Cpmv &operator=(Cpmv &&mv);
    Cpmv operator+(const Cpmv &obj) const;
    void Display() const;
};

int main() {
    Cpmv c1;
    Cpmv c2("abc", "123");
    Cpmv c3(c2);
    c1 = c2;
    c1.Display();

    Cpmv c4(move(c1));
    c4.Display();

    Cpmv c5;
    c5 = move(c2);
    c5.Display();

    return 0;
}

Cpmv::Cpmv() {
    pi = new Info;
    pi->qcode = "";
    pi->zcode = "";
    cout << "Cpmv() called.\n";
}

Cpmv::Cpmv(std::string q, std::string z) {
    pi = new Info;
    pi->qcode = q;
    pi->zcode = z;
    cout << "Cpmv(std::string q, std::string z) called.\n";
}

Cpmv::Cpmv(const Cpmv &cp) {
    pi = new Info;
    pi->qcode = cp.pi->qcode;
    pi->zcode = cp.pi->zcode;
    cout << "Cpmv(const Cpmv &cp) called.\n";
}

Cpmv::Cpmv(Cpmv &&mv) {
    pi = mv.pi;
    mv.pi = nullptr;
    cout << "Cpmv(Cpmv &&mv) called.\n";
}

Cpmv::~Cpmv() {
    delete pi;
    cout << "~Cpmv()  called.\n";
}

Cpmv &Cpmv::operator=(const Cpmv &cp) {
    cout << "Cpmv &operator=(const Cpmv &cp) called.\n";
    if (this == &cp) {
        return *this;
    }
    delete pi;
    pi = new Info;
    pi->qcode = cp.pi->qcode;
    pi->zcode = cp.pi->zcode;
    return *this;
}

Cpmv &Cpmv::operator=(Cpmv &&mv) {
    cout << "Cpmv &operator=(Cpmv &&mv) called.\n";
    if (this == &mv) {
        return *this;
    }
    delete pi;
    pi = mv.pi;
    mv.pi = nullptr;
    return *this;
}

Cpmv Cpmv::operator+(const Cpmv &obj) const {
    cout << "Cpmv operator+(const Cpmv &obj) called.\n";
    Cpmv cv;
    cv.pi->qcode = this->pi->qcode + obj.pi->qcode;
    cv.pi->zcode = this->pi->zcode + obj.pi->zcode;
    return cv;
}

void Cpmv::Display() const {
    cout << "The qcode is " << this->pi->qcode << endl;
    cout << "The zcode is " << this->pi->zcode << endl;
}

3. 编写并测试可变参数模板函数sum_value( ),它接受任意长度的参数列表(其中包含数值,但可以是任何类型),并以long double的方式返回这些数值的和。

main.cpp:

#include <iostream>
#include <string>

// definition for 1 parameter
template <typename T>
long double sum_value(const T& value) {
  return value;
}

// definition for 2 or more parameters
template <typename T, typename... Args>
long double sum_value(const T& value, const Args&... args) {
  return value + sum_value(args...);
}

int main() {
  int n = 14;
  double x = 2.71828;
  std::cout << sum_value(n, x, 'a') << std::endl;
  return 0;
}

4. 使用lambda重新编写程序清单16.15。具体地说,使用一个有名称的lambda替换函数outint( ),并将函数符替换为两个匿名lambda表达式。

main.cpp:

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

auto outint_l = [](int n) { std::cout << n << " "; };

int main() {
    using std::cout;
    using std::endl;
    using std::list;
    int vals[10] = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
    list<int> yadayada(vals, vals + 10);  // range constructor
    list<int> etcetera(vals, vals + 10);
    // C++11 can use the following instead
    // list<int> yadayada = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
    // list<int> etcetera {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
    cout << "Original lists:\n";
    for_each(yadayada.begin(), yadayada.end(), outint_l);
    cout << endl;
    for_each(etcetera.begin(), etcetera.end(), outint_l);
    cout << endl;
    yadayada.remove_if(
        [](int n) { return n > 100; });  // use a named function object
    etcetera.remove_if(
        [](int n) { return n > 200; });  // construct a function object
    cout << "Trimmed lists:\n";
    for_each(yadayada.begin(), yadayada.end(), outint_l);
    cout << endl;
    for_each(etcetera.begin(), etcetera.end(), outint_l);
    cout << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值