- 新增现代 C++ 教程的 Preface 章节,包括英文和中文版本 - 添加 C++ Primer 练习代码 - 新增 Learn C++ 教程的 C++ 开发简介章节 - 添加头文件解析文档 - 更新 mkdocs.yml,包含新教程的目录结构 - 修改项目设置,使用 Python 3.10环境
1370 lines
32 KiB
Markdown
1370 lines
32 KiB
Markdown
---
|
||
sort: 7
|
||
---
|
||
|
||
|
||
# 类
|
||
|
||
## 练习7.1
|
||
|
||
使用2.6.1节定义的`Sales_data`类为1.6节的交易处理程序编写一个新版本。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <iostream>
|
||
#include <string>
|
||
using std::cin; using std::cout; using std::endl; using std::string;
|
||
|
||
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
|
||
{
|
||
std::cerr << "No data?!" << std::endl;
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
## 练习7.2
|
||
|
||
曾在2.6.2节的练习中编写了一个`Sales_data`类,请向这个类添加`combine`函数和`isbn`成员。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
|
||
struct Sales_data {
|
||
std::string isbn() const { return bookNo; };
|
||
Sales_data& combine(const Sales_data&);
|
||
|
||
std::string bookNo;
|
||
unsigned units_sold = 0;
|
||
double revenue = 0.0;
|
||
};
|
||
|
||
Sales_data& Sales_data::combine(const Sales_data& rhs)
|
||
{
|
||
units_sold += rhs.units_sold;
|
||
revenue += rhs.revenue;
|
||
return *this;
|
||
}
|
||
```
|
||
|
||
## 练习7.3
|
||
|
||
修改7.1.1节的交易处理程序,令其使用这些成员。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <iostream>
|
||
using std::cin; using std::cout; using std::endl;
|
||
|
||
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.bookNo << " " << total.units_sold << " " << total.revenue << endl;
|
||
total = trans;
|
||
}
|
||
}
|
||
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
|
||
}
|
||
else
|
||
{
|
||
std::cerr << "No data?!" << std::endl;
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
## 练习7.4
|
||
|
||
编写一个名为`Person`的类,使其表示人员的姓名和地址。使用`string`对象存放这些元素,接下来的练习将不断充实这个类的其他特征。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
|
||
class Person {
|
||
std::string name;
|
||
std::string address;
|
||
};
|
||
```
|
||
|
||
## 练习7.5
|
||
|
||
在你的`Person`类中提供一些操作使其能够返回姓名和地址。
|
||
这些函数是否应该是`const`的呢?解释原因。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
|
||
class Person
|
||
{
|
||
std::string name;
|
||
std::string address;
|
||
public:
|
||
auto get_name() const -> std::string const& { return name; }
|
||
auto get_addr() const -> std::string const& { return address; }
|
||
};
|
||
```
|
||
应该是`const`的。因为常量的`Person`对象也需要使用这些函数操作。
|
||
|
||
## 练习7.6
|
||
|
||
对于函数`add`、`read`和`print`,定义你自己的版本。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
#include <iostream>
|
||
|
||
struct Sales_data {
|
||
std::string const& isbn() const { return bookNo; };
|
||
Sales_data& combine(const Sales_data&);
|
||
|
||
std::string bookNo;
|
||
unsigned units_sold = 0;
|
||
double revenue = 0.0;
|
||
};
|
||
|
||
// member functions.
|
||
Sales_data& Sales_data::combine(const Sales_data& rhs)
|
||
{
|
||
units_sold += rhs.units_sold;
|
||
revenue += rhs.revenue;
|
||
return *this;
|
||
}
|
||
|
||
// nonmember functions
|
||
std::istream &read(std::istream &is, Sales_data &item)
|
||
{
|
||
double price = 0;
|
||
is >> item.bookNo >> item.units_sold >> price;
|
||
item.revenue = price * item.units_sold;
|
||
return is;
|
||
}
|
||
|
||
std::ostream &print(std::ostream &os, const Sales_data &item)
|
||
{
|
||
os << item.isbn() << " " << item.units_sold << " " << item.revenue;
|
||
return os;
|
||
}
|
||
|
||
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
|
||
{
|
||
Sales_data sum = lhs;
|
||
sum.combine(rhs);
|
||
return sum;
|
||
}
|
||
```
|
||
|
||
## 练习7.7
|
||
|
||
使用这些新函数重写7.1.2节练习中的程序。
|
||
|
||
```cpp
|
||
int main()
|
||
{
|
||
Sales_data total;
|
||
if (read(std::cin, total))
|
||
{
|
||
Sales_data trans;
|
||
while (read(std::cin, trans)) {
|
||
if (total.isbn() == trans.isbn())
|
||
total.combine(trans);
|
||
else {
|
||
print(std::cout, total) << std::endl;
|
||
total = trans;
|
||
}
|
||
}
|
||
print(std::cout, total) << std::endl;
|
||
}
|
||
else
|
||
{
|
||
std::cerr << "No data?!" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
## 练习7.8
|
||
|
||
为什么`read`函数将其`Sales_data`参数定义成普通的引用,而`print`函数将其参数定义成常量引用?
|
||
|
||
解:
|
||
|
||
因为`read`函数会改变对象的内容,而`print`函数不会。
|
||
|
||
## 练习7.9
|
||
|
||
对于7.1.2节练习中代码,添加读取和打印`Person`对象的操作。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
#include <iostream>
|
||
|
||
struct Person
|
||
{
|
||
std::string const& getName() const { return name; }
|
||
std::string const& getAddress() const { return address; }
|
||
|
||
std::string name;
|
||
std::string address;
|
||
};
|
||
|
||
std::istream &read(std::istream &is, Person &person)
|
||
{
|
||
return is >> person.name >> person.address;
|
||
}
|
||
|
||
std::ostream &print(std::ostream &os, const Person &person)
|
||
{
|
||
return os << person.name << " " << person.address;
|
||
}
|
||
```
|
||
|
||
## 练习7.10
|
||
|
||
在下面这条`if`语句中,条件部分的作用是什么?
|
||
|
||
```cpp
|
||
if (read(read(cin, data1), data2)) //等价read(std::cin, data1);read(std::cin, data2);
|
||
```
|
||
|
||
解:
|
||
|
||
`read`函数的返回值是`istream`对象,
|
||
`if`语句中条件部分的作用是从输入流中读取数据给两个`data`对象。
|
||
|
||
## 练习7.11 :
|
||
|
||
在你的`Sales_data`类中添加构造函数,
|
||
然后编写一段程序令其用到每个构造函数。
|
||
|
||
解:
|
||
|
||
头文件:
|
||
|
||
```cpp
|
||
#include <string>
|
||
#include <iostream>
|
||
|
||
struct Sales_data {
|
||
Sales_data() = default;
|
||
Sales_data(const std::string &s):bookNo(s) { }
|
||
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p){ }
|
||
Sales_data(std::istream &is);
|
||
|
||
std::string isbn() const { return bookNo; };
|
||
Sales_data& combine(const Sales_data&);
|
||
|
||
std::string bookNo;
|
||
unsigned units_sold = 0;
|
||
double revenue = 0.0;
|
||
};
|
||
|
||
// nonmember functions
|
||
std::istream &read(std::istream &is, Sales_data &item)
|
||
{
|
||
double price = 0;
|
||
is >> item.bookNo >> item.units_sold >> price;
|
||
item.revenue = price * item.units_sold;
|
||
return is;
|
||
}
|
||
|
||
std::ostream &print(std::ostream &os, const Sales_data &item)
|
||
{
|
||
os << item.isbn() << " " << item.units_sold << " " << item.revenue;
|
||
return os;
|
||
}
|
||
|
||
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
|
||
{
|
||
Sales_data sum = lhs;
|
||
sum.combine(rhs);
|
||
return sum;
|
||
}
|
||
|
||
// member functions.
|
||
Sales_data::Sales_data(std::istream &is)
|
||
{
|
||
read(is, *this);
|
||
}
|
||
|
||
Sales_data& Sales_data::combine(const Sales_data& rhs)
|
||
{
|
||
units_sold += rhs.units_sold;
|
||
revenue += rhs.revenue;
|
||
return *this;
|
||
}
|
||
```
|
||
|
||
主函数:
|
||
|
||
```cpp
|
||
int main()
|
||
{
|
||
Sales_data item1;
|
||
print(std::cout, item1) << std::endl;
|
||
|
||
Sales_data item2("0-201-78345-X");
|
||
print(std::cout, item2) << std::endl;
|
||
|
||
Sales_data item3("0-201-78345-X", 3, 20.00);
|
||
print(std::cout, item3) << std::endl;
|
||
|
||
Sales_data item4(std::cin);
|
||
print(std::cout, item4) << std::endl;
|
||
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
## 练习7.12
|
||
|
||
把只接受一个`istream`作为参数的构造函数移到类的内部。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
#include <iostream>
|
||
|
||
struct Sales_data;
|
||
std::istream &read(std::istream&, Sales_data&);
|
||
|
||
struct Sales_data {
|
||
Sales_data() = default;
|
||
Sales_data(const std::string &s):bookNo(s) { }
|
||
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p){ }
|
||
Sales_data(std::istream &is) { read(is, *this); }
|
||
|
||
std::string isbn() const { return bookNo; };
|
||
Sales_data& combine(const Sales_data&);
|
||
|
||
std::string bookNo;
|
||
unsigned units_sold = 0;
|
||
double revenue = 0.0;
|
||
};
|
||
|
||
// member functions.
|
||
Sales_data& Sales_data::combine(const Sales_data& rhs)
|
||
{
|
||
units_sold += rhs.units_sold;
|
||
revenue += rhs.revenue;
|
||
return *this;
|
||
}
|
||
|
||
// nonmember functions
|
||
std::istream &read(std::istream &is, Sales_data &item)
|
||
{
|
||
double price = 0;
|
||
is >> item.bookNo >> item.units_sold >> price;
|
||
item.revenue = price * item.units_sold;
|
||
return is;
|
||
}
|
||
|
||
std::ostream &print(std::ostream &os, const Sales_data &item)
|
||
{
|
||
os << item.isbn() << " " << item.units_sold << " " << item.revenue;
|
||
return os;
|
||
}
|
||
|
||
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
|
||
{
|
||
Sales_data sum = lhs;
|
||
sum.combine(rhs);
|
||
return sum;
|
||
}
|
||
```
|
||
|
||
## 练习7.13
|
||
使用`istream`构造函数重写第229页的程序。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
int main()
|
||
{
|
||
Sales_data total(std::cin);
|
||
if (!total.isbn().empty())
|
||
{
|
||
std::istream &is = std::cin;
|
||
while (is) {
|
||
Sales_data trans(is);
|
||
if (!is) break;
|
||
if (total.isbn() == trans.isbn())
|
||
total.combine(trans);
|
||
else {
|
||
print(std::cout, total) << std::endl;
|
||
total = trans;
|
||
}
|
||
}
|
||
print(std::cout, total) << std::endl;
|
||
}
|
||
else
|
||
{
|
||
std::cerr << "No data?!" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
## 练习7.14
|
||
编写一个构造函数,令其用我们提供的类内初始值显式地初始化成员。
|
||
|
||
```cpp
|
||
Sales_data() : units_sold(0) , revenue(0) { }
|
||
```
|
||
|
||
## 练习7.15
|
||
为你的`Person`类添加正确的构造函数。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
#include <iostream>
|
||
|
||
struct Person;
|
||
std::istream &read(std::istream&, Person&);
|
||
|
||
struct Person
|
||
{
|
||
Person() = default;
|
||
Person(const std::string& sname, const std::string& saddr) :name(sname), address(saddr) {}
|
||
Person(std::istream &is) { read(is, *this); }
|
||
|
||
std::string getName() const { return name; }
|
||
std::string getAddress() const { return address; }
|
||
|
||
std::string name;
|
||
std::string address;
|
||
};
|
||
|
||
std::istream &read(std::istream &is, Person &person)
|
||
{
|
||
is >> person.name >> person.address;
|
||
return is;
|
||
}
|
||
|
||
std::ostream &print(std::ostream &os, const Person &person)
|
||
{
|
||
os << person.name << " " << person.address;
|
||
return os;
|
||
}
|
||
```
|
||
|
||
## 练习7.16
|
||
在类的定义中对于访问说明符出现的位置和次数有限定吗?
|
||
如果有,是什么?什么样的成员应该定义在`public`说明符之后?
|
||
什么样的成员应该定义在`private`说明符之后?
|
||
|
||
解:
|
||
|
||
在类的定义中对于访问说明符出现的位置和次数**没有限定**。
|
||
|
||
每个访问说明符指定了接下来的成员的访问级别,其有效范围直到出现下一个访问说明符或者达到类的结尾处为止。
|
||
|
||
如果某个成员能够在整个程序内都被访问,那么它应该定义为`public`;
|
||
如果某个成员只能在类内部访问,那么它应该定义为`private`。
|
||
|
||
## 练习7.17
|
||
使用`class`和`struct`时有区别吗?如果有,是什么?
|
||
|
||
解:
|
||
|
||
`class`和`struct`的唯一区别是默认的访问级别不同。
|
||
|
||
## 练习7.18
|
||
封装是何含义?它有什么用处?
|
||
|
||
解:
|
||
|
||
将类内部分成员设置为外部不可见,而提供部分接口给外面,这样的行为叫做封装。
|
||
|
||
用处:
|
||
|
||
- 1.确保用户的代码不会无意间破坏封装对象的状态。
|
||
- 2.被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。
|
||
|
||
## 练习7.19
|
||
在你的`Person`类中,你将把哪些成员声明成`public`的?
|
||
哪些声明成`private`的?
|
||
解释你这样做的原因。
|
||
|
||
构造函数、`getName()`、`getAddress()`函数将设为`public`。
|
||
`name`和 `address` 将设为`private`。
|
||
函数是暴露给外部的接口,因此要设为`public`;
|
||
而数据则应该隐藏让外部不可见。
|
||
|
||
## 练习7.20
|
||
友元在什么时候有用?请分别举出使用友元的利弊。
|
||
|
||
解:
|
||
|
||
当其他类或者函数想要访问当前类的私有变量时,这个时候应该用友元。
|
||
|
||
利:
|
||
|
||
与当前类有关的接口函数能直接访问类的私有变量。
|
||
|
||
弊:
|
||
|
||
牺牲了封装性与可维护性。
|
||
|
||
## 练习7.21
|
||
修改你的`Sales_data`类使其隐藏实现的细节。
|
||
你之前编写的关于`Sales_data`操作的程序应该继续使用,借助类的新定义重新编译该程序,确保其正常工作。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
#include <iostream>
|
||
|
||
class Sales_data {
|
||
friend std::istream &read(std::istream &is, Sales_data &item);
|
||
friend std::ostream &print(std::ostream &os, const Sales_data &item);
|
||
friend Sales_data add(const Sales_data &lhs, const Sales_data &rhs);
|
||
|
||
public:
|
||
Sales_data() = default;
|
||
Sales_data(const std::string &s):bookNo(s) { }
|
||
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p){ }
|
||
Sales_data(std::istream &is) { read(is, *this); }
|
||
|
||
std::string isbn() const { return bookNo; };
|
||
Sales_data& combine(const Sales_data&);
|
||
|
||
private:
|
||
std::string bookNo;
|
||
unsigned units_sold = 0;
|
||
double revenue = 0.0;
|
||
};
|
||
|
||
// member functions.
|
||
Sales_data& Sales_data::combine(const Sales_data& rhs)
|
||
{
|
||
units_sold += rhs.units_sold;
|
||
revenue += rhs.revenue;
|
||
return *this;
|
||
}
|
||
|
||
// friend functions
|
||
std::istream &read(std::istream &is, Sales_data &item)
|
||
{
|
||
double price = 0;
|
||
is >> item.bookNo >> item.units_sold >> price;
|
||
item.revenue = price * item.units_sold;
|
||
return is;
|
||
}
|
||
|
||
std::ostream &print(std::ostream &os, const Sales_data &item)
|
||
{
|
||
os << item.isbn() << " " << item.units_sold << " " << item.revenue;
|
||
return os;
|
||
}
|
||
|
||
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
|
||
{
|
||
Sales_data sum = lhs;
|
||
sum.combine(rhs);
|
||
return sum;
|
||
}
|
||
```
|
||
|
||
## 练习7.22
|
||
修改你的`Person`类使其隐藏实现的细节。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
#include <iostream>
|
||
|
||
class Person {
|
||
friend std::istream &read(std::istream &is, Person &person);
|
||
friend std::ostream &print(std::ostream &os, const Person &person);
|
||
|
||
public:
|
||
Person() = default;
|
||
Person(const std::string sname, const std::string saddr):name(sname), address(saddr){ }
|
||
Person(std::istream &is){ read(is, *this); }
|
||
|
||
std::string getName() const { return name; }
|
||
std::string getAddress() const { return address; }
|
||
private:
|
||
std::string name;
|
||
std::string address;
|
||
};
|
||
|
||
std::istream &read(std::istream &is, Person &person)
|
||
{
|
||
is >> person.name >> person.address;
|
||
return is;
|
||
}
|
||
|
||
std::ostream &print(std::ostream &os, const Person &person)
|
||
{
|
||
os << person.name << " " << person.address;
|
||
return os;
|
||
}
|
||
```
|
||
|
||
## 练习7.23
|
||
编写你自己的`Screen`类型。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
|
||
class Screen {
|
||
public:
|
||
using pos = std::string::size_type;
|
||
|
||
Screen() = default;
|
||
Screen(pos ht, pos wd, char c):height(ht), width(wd), contents(ht*wd, c){ }
|
||
|
||
char get() const { return contents[cursor]; }
|
||
char get(pos r, pos c) const { return contents[r*width+c]; }
|
||
|
||
private:
|
||
pos cursor = 0;
|
||
pos height = 0, width = 0;
|
||
std::string contents;
|
||
};
|
||
```
|
||
|
||
# 练习7.24
|
||
给你的`Screen`类添加三个构造函数:一个默认构造函数;另一个构造函数接受宽和高的值,然后将`contents`初始化成给定数量的空白;第三个构造函数接受宽和高的值以及一个字符,该字符作为初始化后屏幕的内容。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <string>
|
||
|
||
class Screen {
|
||
public:
|
||
using pos = std::string::size_type;
|
||
|
||
Screen() = default; // 1
|
||
Screen(pos ht, pos wd):height(ht), width(wd), contents(ht*wd, ' '){ } // 2
|
||
Screen(pos ht, pos wd, char c):height(ht), width(wd), contents(ht*wd, c){ } // 3
|
||
|
||
char get() const { return contents[cursor]; }
|
||
char get(pos r, pos c) const { return contents[r*width+c]; }
|
||
|
||
private:
|
||
pos cursor = 0;
|
||
pos height = 0, width = 0;
|
||
std::string contents;
|
||
};
|
||
```
|
||
|
||
## 练习7.25
|
||
`Screen`能安全地依赖于拷贝和赋值操作的默认版本吗?
|
||
如果能,为什么?如果不能?为什么?
|
||
|
||
解:
|
||
|
||
能。 `Screen`的成员只有内置类型和`string`,因此能安全地依赖于拷贝和赋值操作的默认版本。
|
||
|
||
管理动态内存的类则不能依赖于拷贝和赋值操作的默认版本,而且也应该尽量使用`string`和`vector`来避免动态管理内存的复杂性。
|
||
|
||
## 练习7.26
|
||
将`Sales_data::avg_price`定义成内联函数。
|
||
|
||
解:
|
||
|
||
在头文件中加入:
|
||
|
||
```cpp
|
||
inline double Sales_data::avg_price() const
|
||
{
|
||
return units_sold ? revenue/units_sold : 0;
|
||
}
|
||
```
|
||
|
||
## 练习7.27
|
||
给你自己的`Screen`类添加`move`、`set` 和`display`函数,通过执行下面的代码检验你的类是否正确。
|
||
|
||
```cpp
|
||
Screen myScreen(5, 5, 'X');
|
||
myScreen.move(4, 0).set('#').display(cout);
|
||
cout << "\n";
|
||
myScreen.display(cout);
|
||
cout << "\n";
|
||
```
|
||
|
||
解:
|
||
|
||
增加代码:
|
||
|
||
```cpp
|
||
#include <string>
|
||
#include <iostream>
|
||
|
||
class Screen {
|
||
public:
|
||
... ...
|
||
|
||
inline Screen& move(pos r, pos c);
|
||
inline Screen& set(char c);
|
||
inline Screen& set(pos r, pos c, char ch);
|
||
|
||
const Screen& display(std::ostream &os) const { do_display(os); return *this; }
|
||
Screen& display(std::ostream &os) { do_display(os); return *this; }
|
||
|
||
private:
|
||
void do_display(std::ostream &os) const { os << contents; }
|
||
|
||
... ...
|
||
};
|
||
|
||
inline Screen& Screen::move(pos r, pos c)
|
||
{
|
||
cursor = r*width + c;
|
||
return *this;
|
||
}
|
||
|
||
inline Screen& Screen::set(char c)
|
||
{
|
||
contents[cursor] = c;
|
||
return *this;
|
||
}
|
||
|
||
inline Screen& Screen::set(pos r, pos c, char ch)
|
||
{
|
||
contents[r*width+c] = ch;
|
||
return *this;
|
||
}
|
||
```
|
||
|
||
测试代码:
|
||
|
||
```cpp
|
||
int main()
|
||
{
|
||
Screen myScreen(5, 5, 'X');
|
||
myScreen.move(4, 0).set('#').display(std::cout);
|
||
std::cout << "\n";
|
||
myScreen.display(std::cout);
|
||
std::cout << "\n";
|
||
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
## 练习7.28
|
||
如果`move`、`set`和`display`函数的返回类型不是`Screen&` 而是`Screen`,则在上一个练习中将会发生什么?
|
||
|
||
解:
|
||
|
||
如果返回类型是`Screen`,那么`move`返回的是`*this`的一个副本,因此`set`函数只能改变临时副本而不能改变`myScreen`的值。
|
||
|
||
## 练习7.29
|
||
修改你的`Screen`类,令`move`、`set`和`display`函数返回`Screen`并检查程序的运行结果,在上一个练习中你的推测正确吗?
|
||
|
||
解:
|
||
|
||
推测正确。
|
||
|
||
```
|
||
#with '&'
|
||
XXXXXXXXXXXXXXXXXXXX#XXXX
|
||
XXXXXXXXXXXXXXXXXXXX#XXXX
|
||
^
|
||
# without '&'
|
||
XXXXXXXXXXXXXXXXXXXX#XXXX
|
||
XXXXXXXXXXXXXXXXXXXXXXXXX
|
||
^
|
||
```
|
||
|
||
## 练习7.30
|
||
通过`this`指针使用成员的做法虽然合法,但是有点多余。讨论显示使用指针访问成员的优缺点。
|
||
|
||
解:
|
||
|
||
优点:
|
||
|
||
程序的意图更明确
|
||
|
||
函数的参数可以与成员同名,如
|
||
|
||
```cpp
|
||
void setAddr(const std::string &addr) { this->addr = addr; }
|
||
```
|
||
|
||
缺点:
|
||
|
||
有时候显得有点多余,如
|
||
```cpp
|
||
std::string getAddr() const { return this->addr; }
|
||
```
|
||
|
||
## 练习7.31
|
||
定义一对类`X`和`Y`,其中`X`包含一个指向`Y`的指针,而`Y`包含一个类型为`X`的对象。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
class Y;
|
||
|
||
class X{
|
||
Y* y = nullptr;
|
||
};
|
||
|
||
class Y{
|
||
X x;
|
||
};
|
||
```
|
||
|
||
## 练习7.32
|
||
定义你自己的`Screen`和`Window_mgr`,其中`clear`是`Window_mgr`的成员,是`Screen`的友元。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
#include <vector>
|
||
#include <iostream>
|
||
#include <string>
|
||
|
||
class Screen;
|
||
|
||
class Window_mgr
|
||
{
|
||
public:
|
||
using ScreenIndex = std::vector<Screen>::size_type;
|
||
inline void clear(ScreenIndex);
|
||
|
||
private:
|
||
std::vector<Screen> screens;
|
||
};
|
||
|
||
class Screen
|
||
{
|
||
friend void Window_mgr::clear(ScreenIndex);
|
||
|
||
public:
|
||
using pos = std::string::size_type;
|
||
|
||
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 r, pos c) const { return contents[r*width + c]; }
|
||
inline Screen& move(pos r, pos c);
|
||
inline Screen& set(char c);
|
||
inline Screen& set(pos r, pos c, char ch);
|
||
|
||
const Screen& display(std::ostream& os) const { do_display(os); return *this; }
|
||
Screen& display(std::ostream& os) { do_display(os); return *this; }
|
||
|
||
private:
|
||
void do_display(std::ostream &os) const { os << contents; }
|
||
|
||
private:
|
||
pos cursor = 0;
|
||
pos width = 0, height = 0;
|
||
std::string contents;
|
||
};
|
||
|
||
|
||
inline void Window_mgr::clear(ScreenIndex i)
|
||
{
|
||
Screen& s = screens[i];
|
||
s.contents = std::string(s.height*s.width,' ');
|
||
}
|
||
|
||
inline Screen& Screen::move(pos r, pos c)
|
||
{
|
||
cursor = r*width + c;
|
||
return *this;
|
||
}
|
||
|
||
inline Screen& Screen::set(char c)
|
||
{
|
||
contents[cursor] = c;
|
||
return *this;
|
||
}
|
||
|
||
inline Screen& Screen::set(pos r, pos c, char ch)
|
||
{
|
||
contents[r*width + c] = ch;
|
||
return *this;
|
||
}
|
||
```
|
||
|
||
## 练习7.33
|
||
如果我们给`Screen`添加一个如下所示的`size`成员将发生什么情况?如果出现了问题,请尝试修改它。
|
||
|
||
```cpp
|
||
pos Screen::size() const
|
||
{
|
||
return height * width;
|
||
}
|
||
```
|
||
|
||
解:
|
||
|
||
纠正:错误为 error: extra qualification 'Screen::' on member 'size' [-fpermissive]
|
||
则应该去掉Screen::,改为
|
||
```cpp
|
||
pos size() const{
|
||
return height * width;
|
||
}
|
||
```
|
||
## 练习7.34
|
||
如果我们把第256页`Screen`类的`pos`的`typedef`放在类的最后一行会发生什么情况?
|
||
|
||
解:
|
||
|
||
在 dummy_fcn(pos height) 函数中会出现 未定义的标识符pos。
|
||
|
||
类型名的定义通常出现在类的开始处,这样就能确保所有使用该类型的成员都出现在类名的定义之后。
|
||
|
||
## 练习7.35
|
||
解释下面代码的含义,说明其中的`Type`和`initVal`分别使用了哪个定义。如果代码存在错误,尝试修改它。
|
||
|
||
```cpp
|
||
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;
|
||
}
|
||
```
|
||
解:
|
||
|
||
书上255页中说:
|
||
|
||
```
|
||
然而在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字。
|
||
```
|
||
|
||
因此重复定义`Type`是错误的行为。
|
||
|
||
虽然重复定义类型名字是错误的行为,但是编译器并不为此负责。所以我们要人为地遵守一些原则,在这里有一些讨论。
|
||
|
||
## 练习7.36
|
||
下面的初始值是错误的,请找出问题所在并尝试修改它。
|
||
|
||
```cpp
|
||
struct X {
|
||
X (int i, int j): base(i), rem(base % j) {}
|
||
int rem, base;
|
||
};
|
||
```
|
||
|
||
解:
|
||
|
||
应该改为:
|
||
|
||
```cpp
|
||
struct X {
|
||
X (int i, int j): base(i), rem(base % j) {}
|
||
int base, rem;
|
||
};
|
||
```
|
||
|
||
## 练习7.37
|
||
使用本节提供的`Sales_data`类,确定初始化下面的变量时分别使用了哪个构造函数,然后罗列出每个对象所有的数据成员的值。
|
||
|
||
解:
|
||
```cpp
|
||
Sales_data first_item(cin); // 使用 Sales_data(std::istream &is) ; 各成员值从输入流中读取
|
||
int main() {
|
||
// 使用默认构造函数 bookNo = "", cnt = 0, revenue = 0.0
|
||
Sales_data next;
|
||
|
||
// 使用 Sales_data(std::string s = ""); bookNo = "9-999-99999-9", cnt = 0, revenue = 0.0
|
||
Sales_data last("9-999-99999-9");
|
||
}
|
||
```
|
||
|
||
## 练习7.38
|
||
有些情况下我们希望提供`cin`作为接受`istream&`参数的构造函数的默认实参,请声明这样的构造函数。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
Sales_data(std::istream &is = std::cin) { read(is, *this); }
|
||
```
|
||
|
||
## 练习7.39
|
||
如果接受`string`的构造函数和接受`istream&`的构造函数都使用默认实参,这种行为合法吗?如果不,为什么?
|
||
|
||
解:
|
||
|
||
不合法。当你调用`Sales_data()`构造函数时,无法区分是哪个重载。
|
||
|
||
## 练习7.40
|
||
从下面的抽象概念中选择一个(或者你自己指定一个),思考这样的类需要哪些数据成员,提供一组合理的构造函数并阐明这样做的原因。
|
||
|
||
```
|
||
(a) Book
|
||
(b) Data
|
||
(c) Employee
|
||
(d) Vehicle
|
||
(e) Object
|
||
(f) Tree
|
||
```
|
||
|
||
解:
|
||
|
||
(a) Book.
|
||
|
||
```cpp
|
||
class Book
|
||
{
|
||
public:
|
||
Book(unsigned isbn, std::string const& name, std::string const& author, std::string const& pubdate)
|
||
:isbn_(isbn), name_(name), author_(author), pubdate_(pubdate)
|
||
{ }
|
||
|
||
explicit Book(std::istream &in)
|
||
{
|
||
in >> isbn_ >> name_ >> author_ >> pubdate_;
|
||
}
|
||
|
||
private:
|
||
unsigned isbn_;
|
||
std::string name_;
|
||
std::string author_;
|
||
std::string pubdate_;
|
||
};
|
||
```
|
||
|
||
## 练习7.41
|
||
使用委托构造函数重新编写你的`Sales_data`类,给每个构造函数体添加一条语句,令其一旦执行就打印一条信息。用各种可能的方式分别创建`Sales_data`对象,认真研究每次输出的信息直到你确实理解了委托构造函数的执行顺序。
|
||
|
||
解:
|
||
|
||
- [头文件](https://github.com/applenob/Cpp_Primer_Practice/tree/master/cpp_source/ch07/ex_7_41.h)
|
||
- [源文件](https://github.com/applenob/Cpp_Primer_Practice/tree/master/cpp_source/ch07/ex_7_41.cpp)
|
||
- [主函数](https://github.com/applenob/Cpp_Primer_Practice/tree/master/cpp_source/ch07/ex_7_41_main.cpp)
|
||
|
||
总结:使用委托构造函数,调用顺序是:
|
||
- 1.实际的构造函数的函数体。
|
||
- 2.委托构造函数的函数体。
|
||
|
||
## 练习7.42
|
||
对于你在练习7.40中编写的类,确定哪些构造函数可以使用委托。如果可以的话,编写委托构造函数。如果不可以,从抽象概念列表中重新选择一个你认为可以使用委托构造函数的,为挑选出的这个概念编写类定义。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
class Book
|
||
{
|
||
public:
|
||
Book(unsigned isbn, std::string const& name, std::string const& author, std::string const& pubdate)
|
||
:isbn_(isbn), name_(name), author_(author), pubdate_(pubdate)
|
||
{ }
|
||
|
||
Book(unsigned isbn) : Book(isbn, "", "", "") {}
|
||
|
||
explicit Book(std::istream &in)
|
||
{
|
||
in >> isbn_ >> name_ >> author_ >> pubdate_;
|
||
}
|
||
|
||
private:
|
||
unsigned isbn_;
|
||
std::string name_;
|
||
std::string author_;
|
||
std::string pubdate_;
|
||
};
|
||
```
|
||
|
||
## 练习7.43
|
||
假定有一个名为`NoDefault`的类,它有一个接受`int`的构造函数,但是没有默认构造函数。定义类`C`,`C`有一个 `NoDefault`类型的成员,定义`C`的默认构造函数。
|
||
```cpp
|
||
class NoDefault {
|
||
public:
|
||
NoDefault(int i) { }
|
||
};
|
||
|
||
class C {
|
||
public:
|
||
C() : def(0) { }
|
||
private:
|
||
NoDefault def;
|
||
};
|
||
```
|
||
|
||
## 练习7.44
|
||
下面这条声明合法吗?如果不,为什么?
|
||
|
||
```cpp
|
||
vector<NoDefault> vec(10);//vec初始化有10个元素
|
||
```
|
||
|
||
解:
|
||
|
||
不合法。因为`NoDefault`没有默认构造函数。
|
||
|
||
## 练习7.45
|
||
如果在上一个练习中定义的vector的元素类型是C,则声明合法吗?为什么?
|
||
|
||
合法。因为`C`有默认构造函数。
|
||
|
||
## 练习7.46
|
||
下面哪些论断是不正确的?为什么?
|
||
|
||
- (a) 一个类必须至少提供一个构造函数。
|
||
- (b) 默认构造函数是参数列表为空的构造函数。
|
||
- (c) 如果对于类来说不存在有意义的默认值,则类不应该提供默认构造函数。
|
||
- (d) 如果类没有定义默认构造函数,则编译器将为其生成一个并把每个数据成员初始化成相应类型的默认值。
|
||
|
||
解:
|
||
|
||
- (a) 不正确。如果我们的类没有显式地定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数,并称之为合成的默认构造函数。
|
||
- (b) 不完全正确。为每个参数都提供了默认值的构造函数也是默认构造函数。
|
||
- (c) 不正确。哪怕没有意义的值也需要初始化。
|
||
- (d) 不正确。只有当一个类没有定义**任何构造函数**的时候,编译器才会生成一个默认构造函数。
|
||
|
||
## 练习7.47
|
||
说明接受一个`string`参数的`Sales_data`构造函数是否应该是`explicit`的,并解释这样做的优缺点。
|
||
|
||
解:
|
||
|
||
是否需要从`string`到`Sales_data`的转换依赖于我们对用户使用该转换的看法。在此例中,这种转换可能是对的。`null_book`中的`string`可能表示了一个不存在的`ISBN`编号。
|
||
|
||
优点:
|
||
|
||
可以抑制构造函数定义的隐式转换
|
||
|
||
缺点:
|
||
|
||
为了转换要显式地使用构造函数
|
||
|
||
## 练习7.48
|
||
假定`Sales_data`的构造函数不是`explicit`的,则下述定义将执行什么样的操作?
|
||
|
||
解:
|
||
|
||
```cpp
|
||
string null_isbn("9-999-9999-9");
|
||
Sales_data item1(null_isbn);
|
||
Sales_data item2("9-999-99999-9");
|
||
```
|
||
这些定义和是不是`explicit`的无关。
|
||
|
||
## 练习7.49
|
||
对于`combine`函数的三种不同声明,当我们调用`i.combine(s)`时分别发生什么情况?其中`i`是一个`Sales_data`,而` s`是一个`string`对象。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
(a) Sales_data &combine(Sales_data); // ok
|
||
(b) Sales_data &combine(Sales_data&); // error C2664: 无法将参数 1 从“std::string”转换为“Sales_data &” 因为隐式转换只有一次
|
||
(c) Sales_data &combine(const Sales_data&) const; // 该成员函数是const 的,意味着不能改变对象。而 combine函数的本意就是要改变对象
|
||
```
|
||
|
||
## 练习7.50
|
||
确定在你的`Person`类中是否有一些构造函数应该是`explicit` 的。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
explicit Person(std::istream &is){ read(is, *this); }
|
||
```
|
||
|
||
## 练习7.51
|
||
`vector`将其单参数的构造函数定义成`explicit`的,而`string`则不是,你觉得原因何在?
|
||
|
||
假如我们有一个这样的函数:
|
||
```cpp
|
||
int getSize(const std::vector<int>&);
|
||
```
|
||
如果`vector`没有将单参数构造函数定义成`explicit`的,我们就可以这样调用:
|
||
|
||
```cpp
|
||
getSize(34);
|
||
```
|
||
很明显这样调用会让人困惑,函数实际上会初始化一个拥有34个元素的`vecto`r的临时量,然后返回34。但是这样没有任何意义。而`string`则不同,`string`的单参数构造函数的参数是`const char *`,因此凡是在需要用到`string`的地方都可以用` const char *`来代替(字面值就是`const char *`)。如:
|
||
|
||
```cpp
|
||
void print(std::string);
|
||
print("hello world");
|
||
```
|
||
|
||
## 练习7.52
|
||
使用2.6.1节的 `Sales_data` 类,解释下面的初始化过程。如果存在问题,尝试修改它。
|
||
```cpp
|
||
Sales_data item = {"987-0590353403", 25, 15.99};
|
||
```
|
||
|
||
解:
|
||
|
||
`Sales_data` 类不是聚合类,应该修改成如下:
|
||
```cpp
|
||
struct Sales_data {
|
||
std::string bookNo;
|
||
unsigned units_sold;
|
||
double revenue;
|
||
};
|
||
```
|
||
|
||
## 练习7.53
|
||
定义你自己的`Debug`。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
class Debug {
|
||
public:
|
||
constexpr Debug(bool b = true) : hw(b), io(b), other(b) { }
|
||
constexpr Debug(bool h, bool i, bool o) : hw(r), io(i), other(0) { }
|
||
|
||
constexpr bool any() { return hw || io || other; }
|
||
void set_hw(bool b) { hw = b; }
|
||
void set_io(bool b) { io = b; }
|
||
void set_other(bool b) { other = b; }
|
||
|
||
private:
|
||
bool hw; // runtime error
|
||
bool io; // I/O error
|
||
bool other; // the others
|
||
};
|
||
```
|
||
|
||
## 练习7.54
|
||
`Debug`中以 `set_` 开头的成员应该被声明成`constexpr` 吗?如果不,为什么?
|
||
|
||
解:
|
||
|
||
不能。`constexpr`函数必须包含一个返回语句。
|
||
|
||
## 练习7.55
|
||
7.5.5节的`Data`类是字面值常量类吗?请解释原因。
|
||
|
||
解:
|
||
|
||
不是。因为`std::string`不是字面值类型。
|
||
|
||
## 练习7.56
|
||
什么是类的静态成员?它有何优点?静态成员与普通成员有何区别?
|
||
|
||
解:
|
||
|
||
与类本身相关,而不是与类的各个对象相关的成员是静态成员。静态成员能用于某些场景,而普通成员不能。
|
||
|
||
## 练习7.57
|
||
编写你自己的`Account`类。
|
||
|
||
解:
|
||
|
||
```cpp
|
||
class Account {
|
||
public:
|
||
void calculate() { amount += amount * interestRate; }
|
||
static double rate() { return interestRate; }
|
||
static void rate(double newRate) { interestRate = newRate; }
|
||
|
||
private:
|
||
std::string owner;
|
||
double amount;
|
||
static double interestRate;
|
||
static constexpr double todayRate = 42.42;
|
||
static double initRate() { return todayRate; }
|
||
};
|
||
|
||
double Account::interestRate = initRate();
|
||
```
|
||
|
||
## 练习7.58
|
||
下面的静态数据成员的声明和定义有错误吗?请解释原因。
|
||
|
||
```cpp
|
||
//example.h
|
||
class Example {
|
||
public:
|
||
static double rate = 6.5;
|
||
static const int vecSize = 20;
|
||
static vector<double> vec(vecSize);
|
||
};
|
||
|
||
//example.c
|
||
#include "example.h"
|
||
double Example::rate;
|
||
vector<double> Example::vec;
|
||
```
|
||
|
||
解:
|
||
|
||
`rate`应该是一个**常量表达式**。而类内只能初始化整型类型的静态常量,所以不能在类内初始化`vec`。修改后如下:
|
||
|
||
```cpp
|
||
// example.h
|
||
class Example {
|
||
public:
|
||
static constexpr double rate = 6.5;
|
||
static const int vecSize = 20;
|
||
static vector<double> vec;
|
||
};
|
||
|
||
// example.C
|
||
#include "example.h"
|
||
constexpr double Example::rate;
|
||
vector<double> Example::vec(Example::vecSize);
|
||
``` |