C++ Primer 中文版(第 5 版)练习解答合集
自己写的解答,如有错误之处,烦请在评论区指正!
1
#include <iostream>
#include <string>
using namespace std;
struct Sales_data {
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main() {
Sales_data total;
if (cin >> total.bookNo >> total.units_sold >> total.revenue) {
Sales_data trans;
while (cin >> trans.bookNo >> trans.units_sold >> trans.revenue) {
if (total.bookNo == trans.bookNo) {
total.units_sold += trans.units_sold;
total.revenue += trans.revenue;
} else {
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
total = trans;
}
}
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
} else {
cerr << "No data?!" << endl;
return -1;
}
return 0;
}
2
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
Sales_data& combine(const Sales_data& rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
std::string isbn() { return bookNo; }
};
#endif // SALES_DATA_H
3
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main() {
Sales_data total;
if (cin >> total.bookNo >> total.units_sold >> total.revenue) {
Sales_data trans;
while (cin >> trans.bookNo >> trans.units_sold >> trans.revenue) {
if (total.isbn() == trans.isbn()) {
total.combine(trans);
} else {
cout << total.isbn() << " " << total.units_sold << " " << total.revenue << endl;
total = trans;
}
}
cout << total.isbn() << " " << total.units_sold << " " << total.revenue << endl;
} else {
cerr << "No data?!" << endl;
}
return 0;
}
4
#ifndef PERSON_H
#define PERSON_H
#include <string>
struct Person {
string name;
string address;
};
#endif // PERSON_H
5
#ifndef PERSON_H
#define PERSON_H
#include <string>
struct Person {
string m_name;
string m_address;
string name() const { return m_name; }
string address() const { return m_address; }
};
#endif // PERSON_H
应该是 const 的,因为这些函数不需要修改 this 所指的对象。
6
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <iostream>
#include <string>
using std::istream;
using std::ostream;
using std::endl;
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
Sales_data& combine(const Sales_data& rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
std::string isbn() const { return bookNo; }
double avg_price() const { return revenue / static_cast<double>(units_sold); }
};
Sales_data add(const Sales_data& lhs, const Sales_data& rhs) {
Sales_data ret = lhs;
return ret.combine(rhs);
}
istream& read(istream& is, Sales_data& item) {
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) {
os << "isbn: " << item.isbn() << endl
<< "units sold: " << item.units_sold << endl
<< "revenue: " << item.revenue << endl
<< "average price: " << item.avg_price() << endl;
return os;
}
#endif // SALES_DATA_H
7
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main() {
Sales_data total;
if (read(cin, total)) {
Sales_data trans;
while (read(cin, trans)) {
if (total.isbn() == trans.isbn()) {
total.combine(trans);
} else {
print(cout, total) << endl;
total = trans;
}
}
print(cout, total) << endl;
} else {
cerr << "No data?!" << endl;
}
return 0;
}
8
因为 read 函数需要修改通过引用修改实参,而 print 函数不需要修改实参。
9
#ifndef PERSON_H
#define PERSON_H
#include <string>
#include <iostream>
using std::string;
using std::istream;
using std::ostream;
using std::endl;
struct Person {
string m_name;
string m_address;
string name() const { return m_name; }
string address() const { return m_address; }
};
istream& read(istream& is, Person& person) {
is >> person.m_name >> person.m_address;
}
ostream& print(ostream& os, const Person& person) {
os << "name: " << person.name() << endl
<< "address: " << person.address() << endl;
}
#endif // PERSON_H
10
先读取 data1 的数据,再读取 data2 的数据。如果成功读取了两个对象的数据则条件为真,否则假。
11
Sales_data.h
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <iostream>
#include <string>
using std::istream;
using std::ostream;
using std::endl;
using std::string;
struct Sales_data {
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
Sales_data() = default;
Sales_data(const string& s) : bookNo(s) { }
Sales_data(const string& s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(p*n) { }
Sales_data(istream& is);
Sales_data& combine(const Sales_data& rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
std::string isbn() const { return bookNo; }
double avg_price() const { return revenue / static_cast<double>(units_sold); }
};
Sales_data add(const Sales_data& lhs, const Sales_data& rhs) {
Sales_data ret = lhs;
return ret.combine(rhs);
}
istream& read(istream& is, Sales_data& item) {
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) {
os << "isbn: " << item.isbn() << endl
<< "units sold: " << item.units_sold << endl
<< "revenue: " << item.revenue << endl
<< "average price: " << item.avg_price() << endl;
return os;
}
Sales_data::Sales_data(istream& is) {
read(is, *this);
}
#endif // SALES_DATA_H
0711.cpp
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main() {
Sales_data sa;
Sales_data sb("001");
Sales_data sc("007", 10, 10);
Sales_data sd(cin);
print(cout, sa);
print(cout, sb);
print(cout, sc);
print(cout, sd);
return 0;
}
12
类内的构造函数如下:
Sales_data(istream& is) {
double price = 0;
is >> bookNo >> units_sold >> price;
revenue = price * units_sold;
}
13
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main() {
Sales_data total(cin);
if (cin) {
Sales_data trans(cin);
if (cin) {
do {
if (total.isbn() == trans.isbn())
total.combine(trans);
else {
print(cout, total) << endl;
total = trans;
}
} while (read(cin, trans));
}
print(cout, total) << endl;
} else {
cerr << "No data?!" << endl;
}
return 0;
}
14
Sales_data() : bookNo(""), units_sold(0), revenue(0) { }
15
#ifndef PERSON_H
#define PERSON_H
#include <string>
#include <iostream>
using std::string;
using std::istream;
using std::ostream;
using std::endl;
struct Person {
string m_name;
string m_address;
Person() = default;
Person(const string& name, const string& address) :
m_name(name), m_address(address) { }
Person(const string& name) :
m_name(name) { }
string name() const { return m_name; }
string address() const { return m_address; }
};
istream& read(istream& is, Person& person) {
is >> person.m_name >> person.m_address;
}
ostream& print(ostream& os, const Person& person) {
os << "name: " << person.name() << endl
<< "address: " << person.address() << endl;
}
#endif // PERSON_H
16
访问说明符次数和位置都没有限定,作用范围到下一个访问说明符或者类的定义结束为止。
接口,例如构造函数和部分成员函数放在 public 后面,数据成员和实现部分的函数放在 private 后面。
17
class 内成员默认 private,struct 成员默认 private
18
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。
封装有如下好处(P242):
- 确保用户代码不会无意间破坏封装对象的生态;
- 被封装的类的具体细节可以随时改变,无须调整用户级别的代码。
19
class Person {
private:
string m_name;
string m_address;
public:
Person() = default;
Person(const string& name, const string& address) :
m_name(name), m_address(address) { }
Person(const string& name) :
m_name(name) { }
string name() const { return m_name; }
string address() const { return m_address; }
};
原因见第 16 题。
20
友元什么时候有用:当其他类或函数需要获得该类的访问权限的时候(突破private的限制)
利:提升程序效率。不适用友元就必须使用接口。
弊:破坏了封装性和隐藏性。
21
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <iostream>
#include <string>
using std::istream;
using std::ostream;
using std::endl;
using std::string;
class Sales_data {
friend Sales_data add(const Sales_data& lhs, const Sales_data& rhs);
friend istream& read(istream& is, Sales_data& item);
friend ostream& print(ostream& os, const Sales_data& item);
private:
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
public:
Sales_data() = default;
Sales_data(const string& s) : bookNo(s) { }
Sales_data(const string& s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(p*n) { }
Sales_data(istream& is) {
double price = 0;
is >> bookNo >> units_sold >> price;
revenue = price * units_sold;
}
Sales_data& combine(const Sales_data& rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
std::string isbn() const { return bookNo; }
double avg_price() const { return revenue / static_cast<double>(units_sold); }
};
Sales_data add(const Sales_data& lhs, const Sales_data& rhs) {
Sales_data ret = lhs;
return ret.combine(rhs);
}
istream& read(istream& is, Sales_data& item) {
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) {
os << "isbn: " << item.isbn() << endl
<< "units sold: " << item.units_sold << endl
<< "revenue: " << item.revenue << endl
<< "average price: " << item.avg_price() << endl;
return os;
}
#endif // SALES_DATA_H
22
#ifndef PERSON_H
#define PERSON_H
#include <string>
#include <iostream>
using std::string;
using std::istream;
using std::ostream;
using std::endl;
class Person {
friend istream& read(istream& is, Person& person);
friend ostream& print(ostream& os, const Person& person);
private:
string m_name;
string m_address;
public:
Person() = default;
Person(const string& name, const string& address) :
m_name(name), m_address(address) { }
Person(const string& name) :
m_name(name) { }
string name() const { return m_name; }
string address() const { return m_address; }
};
istream& read(istream& is, Person& person) {
is >> person.m_name >> person.m_address;
}
ostream& print(ostream& os, const Person& person) {
os << "name: " << person.name() << endl
<< "address: " << person.address() << endl;
}
#endif // PERSON_H
23
#ifndef SCREEN_H
#define SCREEN_H
#include <string>
class Screen {
public:
typedef std::string::size_type pos;
Screen() = default;
Screen(pos ht, pos wd) :
height(ht), width(wd), contents(ht * wd, ' ') { }
Screen(pos ht, pos wd, char c) :
height(ht), width(wd), contents(ht * wd, c) { }
char get() const { return contents[cursor]; }
char get(pos ht, pos wd) const {
return contents[ht * wd];
}
Screen& move(pos r, pos c) {
cursor = r * width + c;
}
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
#endif // SCREEN_H
24
Screen() = default;
Screen(pos ht, pos wd) :
height(ht), width(wd), contents(ht * wd, ' ') { }
Screen(pos ht, pos wd, char c) :
height(ht), width(wd), contents(ht * wd, c) { }
25
能。因为 Screen 的所有数据成员都不是指针或引用,所有的自带类型以及 string 类型都支持操作的默认版本。
26
在函数头前面加 inline 即可。
27
// Screen.h
#ifndef SCREEN_H
#define SCREEN_H
#include <iostream>
#include <string>
using std::ostream;
using std::cout;
using std::string;
class Screen {
public:
typedef string::size_type pos;
Screen() = default;
Screen(pos ht, pos wd) :
height(ht), width(wd), contents(ht * wd, ' ') { }
Screen(pos ht, pos wd, char c) :
height(ht), width(wd), contents(ht * wd, c) { }
char get() const { return contents[cursor]; }
char get(pos ht, pos wd) const {
return contents[ht * wd];
}
Screen& set(char ch) {
do_set(cursor, ch);
return *this;
}
Screen& set(pos r, pos c, char ch) {
do_set(r * width + c, ch);
return *this;
}
Screen& move(pos r, pos c) {
cursor = r * width + c;
return *this;
}
Screen& display(ostream& os) {
do_display(os);
return *this;
}
const Screen& display(ostream& os) const {
do_display(os);
return *this;
}
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
void do_set(pos p, char c) {
contents[p] = c;
}
void do_display(ostream& os) const {
cout << contents;
}
};
#endif // SCREEN_H
// 0727.cpp
#include <iostream>
#include "Screen.h"
using namespace std;
int main() {
Screen myScreen(5, 5, 'X');
myScreen.move(4, 0).set('#').display(cout);
cout << "\n";
myScreen.display(cout);
cout << "\n";
return 0;
}
28
myScreen.move(4, 0).set('#').display(cout);
对上面这样的写法,除了第一个 move 是有效操作,后续的 set 和 display 都是对临时变量的操作,没有起到实际的效果。
29
推测正确
30
Screen& set(char cursor) {
do_set(this->cursor, cursor);
return *this;
}
优点:
- 对于上面这种情况,故意让成员函数的参数名和数据成员的名字重复,那么就有必要通过 this 指针来显示地指出是形参还是数据成员。
- 可以把对象作为一个整体返回
缺点:
- 可能造成代码冗余
31
class Y;
class X {
Y* ptrY;
};
class Y {
X itemX;
};
int main() {
return 0;
}
32
// Screen.h
#ifndef SCREEN_H
#define SCREEN_H
#include <iostream>
#include <string>
#include <vector>
using std::ostream;
using std::cout;
using std::string;
class Window_mgr;
class Screen {
friend Window_mgr;
public:
typedef string::size_type pos;
Screen() = default;
Screen(pos ht, pos wd) :
height(ht), width(wd), contents(ht * wd, ' ') { }
Screen(pos ht, pos wd, char c) :
height(ht), width(wd), contents(ht * wd, c) { }
char get() const { return contents[cursor]; }
char get(pos ht, pos wd) const {
return contents[ht * wd];
}
Screen& set(char ch) {
do_set(cursor, ch);
return *this;
}
Screen& set(pos r, pos c, char ch) {
do_set(r * width + c, ch);
return *this;
}
Screen& move(pos r, pos c) {
cursor = r * width + c;
return *this;
}
Screen& display(ostream& os) {
do_display(os);
return *this;
}
const Screen& display(ostream& os) const {
do_display(os);
return *this;
}
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
void do_set(pos p, char c) {
contents[p] = c;
}
void do_display(ostream& os) const {
cout << contents;
}
};
class Window_mgr {
public:
using ScreenIndex = std::vector<Screen>::size_type;
Window_mgr(ScreenIndex screensCnt, char ch) {
screens = std::vector<Screen>(screensCnt, Screen(24, 80, ch));
}
void clear(ScreenIndex i) {
Screen &s = screens[i];
s.contents = string(s.height * s.width, ' ');
}
void display(std::ostream& os) {
for (auto s : screens) {
s.display(os);
std::cout << std::endl;
}
}
private:
std::vector<Screen> screens{Screen(24, 80, ' ')};
};
#endif // SCREEN_H
// 0732.cpp
#include <iostream>
#include "Screen.h"
int main() {
Window_mgr window(5, '!');
window.display(std::cout);
window.clear(1);
window.display(std::cout);
return 0;
}
33
Screen::pos Screen::size() const {
return height * width;
}
34
Screen 类中所有用到 pos 类型的地方会报错,显示还未被定义。
35
typedef string Type;
Type initVal();
class Exercise {
public:
typedef double Type;
Type setVal(Type);
Type initVal();
private:
int val;
};
Type Exercise::setVal(Type parm) {
val = parm + initVal();
return val;
}
36
struct X {
X (int i, int j) : rem(i % j), base(j) { }
int rem, base;
}
原先的代码在 base 还没有初始化的时候就用 base 去初始化 rem。
注意初始化顺序和在类中定义的顺序一样,和在初始化列表中的顺序无关。
37
变量名 | 使用的构造函数 | bookNo | units_sold | revenue |
---|---|---|---|---|
first_item | Sales_data(istream& is); | 根据输入决定 | 根据输入决定 | 根据输入决定 |
next | Sales_data() = default; | “” | 0 | 0 |
last | Sales_data(const string& s) : bookNo(s) { } | “9-999-99999-9” | 0 | 0 |
38
std::istream& myClass(std::istream& is = std::cin);
39
不合法。因为如果构造函数的参数列表中,所有参数都有默认参数,那么就定义了默认构造函数。Sales_data sa;
这样试图调用默认构造函数的时候,编译器会出现二义性错误,即无法决定使用哪个默认构造函数。
40
using std::string
class Employee {
public:
Employee(string na, string jo, unsigned ag, unsigned sa, string ad) :
name(na), job(jo), age(ag), salary(sa), address(ad) { }
Employee(string na) : name(na) { }
private:
string name;
string job;
unsigned age;
unsigned salary;
string address;
};
41
Sales_data(const string& s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(p*n) { }
Sales_data() : Sales_data("", 0, 0) { }
Sales_data(const string& s) : Sales_data(s, 0, 0) { }
42
Employee(string na, string jo, unsigned ag, unsigned sa, string ad) :
name(na), job(jo), age(ag), salary(sa), address(ad) { }
Employee(string na) : Employee(na, "", 0, 0, "") { }
43
class NoDefault {
public:
NoDefault(int input) :
num(input) { }
private:
int num;
};
class C {
public:
C() : noDefault(NoDefault(0)){ }
private:
NoDefault noDefault;
};
int main() {
C c;
return 0;
}
44
不合法。这里显式地调用了 vector 的值初始化,会试图调用 NoDefault 类的默认构造函数,但是 NoDefault 类没有默认构造函数。
45
合法。因为 C 有默认构造函数。
46
(a)错误。不提供任何构造函数的话,编译器会生成合成的默认构造函数。
(b)错误。参数列表非空,但所有参数都有默认值,也相当于定义了默认构造函数。
(c)错误。默认构造函数可以被其他构造函数委托,这种情况下默认构造函数是有意义的。
(d)错误。如果定义了其他构造函数,编译器就不会自动生成。
47
是或者不是都行。取决于是否允许 string 类型到 Sales_data 类型的隐式转换。
声明成 explicit 的优点:
- 允许了 string 类型到 Sales_data 类型的隐式转换,提供便利
缺点:
- 该构造函数只能用于直接初始化,不能用于拷贝形式的初始化。
48
构造函数是不是 explicit 都一样:
- 第一句将字符串字面值用于 null_isbn 的直接初始化
- 第二句用 string 类型的 null_isbn 直接初始化了 item1
- 第三局将字符串字面值隐式转换成 string 类型,并用于 item2 的直接初始化
49
(a)正确。s 被隐式转换成 Sales_data 类型,combine 函数达到预期效果
(b)编译错误。s 被隐式转换后生成的是临时对象,只有把参数定义成 const Sales_data& 这样的常量引用才能绑定到临时对象上。
(c)编译错误。const 成员函数无法修改 this 所指的对象。而 combine 函数需要修改 this 所指的对象。
50
Person(const string& name) :
m_name(name) { }
首先,只有这一个构造函数的参数数量是 1
我觉得暂时没有必要声明成 explicit,因为暂时不需要抑制从 const string& 到 Person 的隐式转换。
51
vector 需要将其单参数的构造函数定义成 explicit,因为从数字到 vector 对象的隐式转换是不符合逻辑,没有意义的。
string 不是,因为 const char* 到 string 的隐式转换很合理,也很常用。
52
首先判断第 64 页的 Sales_data 类是不是聚合类,答案是否,因为部分类内元素有类内初始值。所以不能用聚合类的语法来初始化数据成员。
53
略。书P268
54
不能。因为 constexpr 函数的可执行语句有且只能有一条:return 语句。
55
不是。string 类型不是字面值类型。
56
静态成员是指声明语句之前带有关键字 static 的类成员,静态成员不是任意单独对象的组成部分,而是由该类的全体对象所共享。
静态成员的优点包括:作用域位于类的范围之内,避免与其他类的成员或者全局作用域的名字冲突;可以是私有成员;通过阅读程序可以非常容易地看出静态成员与特定类关联,使得程序的含义清晰明了。
静态成员与普通成员的区别主要体现在:普通成员与类的对象关联,是某个具体对象的组成部分;而静态成员不从属于任何具体的对象,它由该类的所有对象共享。另外,静态成员可以作为默认实参,而普通数据成员不能作为默认实参;静态成员可以是不完全类型(指针成员、引用成员也可以)。
57
略
58
原始代码:
// example.h
class Example {
public:
// 一般的静态成员不能在类内初始化
static double rate = 6.5;
static const int vecSize = 20;
// 一般的静态成员不能在类内初始化
// 类内初始化不能用圆括号
static std::vector<double> vec(vecSize);
};
// example.C
#include "example.h"
// 类外定义静态数据成员需要初始化
double Example::rate;
// 类外定义静态数据成员需要初始化
vector<double> Example::vec;
修改后代码:
// example.h
class Example {
public:
static double rate;
static const int vecSize = 20;
static vector<double> vec;
};
// example.C
#include "example.h"
double Example::rate = 6.5;
vector<double> Example::vec(vecSize);