转载公众号: 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. 对于下面的类声明:
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] = ↦
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] = ↦
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
中的soy
和sugar
都是私有的,Ribs
只能通过Sauce
的构造函数创建它们。
4. throw
和return
之间的区别何在?
假设函数f1( )调用函数f2( )。f2( )中的返回语句导致程序执行在函数f1( )中调用函数f2( )后面的一条语句。throw语句导致程序沿函数调用的当前序列回溯,直到找到直接或间接包含对f2( )的调用的try语句块为止。它可能在f1( )中、调用f1( )的函数中或其他函数中。找到这样的try语句块后,将执行下一个匹配的catch语句块,而不是函数调用后的语句。
5. 假设有一个从异常基类派生来的异常类层次结构,则应按什么样的顺序放置catch块?
应按从子孙到祖先的顺序排列catch语句块。
6. 对于本章定义的Grand、Superb
和Magnificent
类,假设pg
为Grand *
指针,并将其中某个类的对象的地址赋给了它,而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. 对Tv
和Remote
类做如下修改:
-
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)
,T
为int
,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>
对象vi
和list<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
按其指向的对象中的书名排序。重复上述过程,创建按rating
和price
排序的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;
}