答案目录

第7章 类

C++ 中保留了C语言的 struct 关键字,并且加以扩充。在C语言中,struct 只能包含成员变量,不能包含成员函数。

而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。

C++中的 struct 和 class 基本是通用的,唯有几个细节不同:

  1. 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。(最本质的区别)
  2. class 继承默认是 private 继承,而 struct 继承默认是 public 继承(《C++继承与派生》一章会讲解继承)
  3. class 可以使用模板,而 struct 不能。

练习7.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include<string>
#include<vector>
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
{
std::cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}

练习7.2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.4

1
2
3
4
class Person {
std::string name;
std::string address;
};

练习7.5

要使用const,因为这些函数并不改变它调用的对象的内容

1
2
3
4
5
6
7
class Person {
public:
std::string name;
std::string addr;
std::string getName() const { return name; }
std::string getAddr() const { return addr; }
};

练习7.6,7.7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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;
}

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

因为print函数不会改变对象的值,但是read函数则会改变对象内容

练习7.9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
int main() {
return 0;
}

练习7.10

是否成功读入data1,data2

练习7.11

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
};

练习7.12

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
};

练习7.13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sales_data total(cin);
if (cin)
{
Sales_data trans(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;
}

练习7.14

1
Sales_data() : bookNo(""), units_sold(0) , revenue(0){ }

练习7.15

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
};

练习7.16

一个类对访问说明符出现的次数和位置并没有严格的限定。构造函数和接口函数定义在public之后,而数据成员和部分成员函数定义在private后面

练习7.17

class和struct唯一的区别就是默认访问权限不同,class默认访问权限为private,而struct则是public

练习7.18

封装就是定义一系列的接口,对用户隐藏实现细节,用户在使用时只需要调用接口就可以

练习7.19

Person类的构造函数和获取信息等函数应该设置为public,成员数据设置为private

因为构造函数和获取信息的函数需要在类外进行调用,而成员数据可以封装成接口,不需要暴露给用户

练习7.20

友元是类提供给非成员函数访问类内私有成员的一种机制

优势是:让类外函数也可以像类内成员一样方便的访问私有成员

缺点是:破坏了类的封装,写法较麻烦,必须在类内类外都进行声明

练习7.21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
};

练习7.22

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
};

练习7.23

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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

只有内置类型和string类型可以依赖于操作的默认版本

练习7.26

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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:
inline double avg_price() const;

private:
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};

inline
double Sales_data::avg_price() const
{
return units_sold ? revenue/units_sold : 0;
}

// declarations for nonmember parts of the Sales_data interface.
std::istream &read(std::istream &is, Sales_data &item);
std::ostream &print(std::ostream &os, const Sales_data &item);
Sales_data add(const Sales_data &lhs, const Sales_data &rhs);

练习7.27

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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]; }
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 height = 0, width = 0;
std::string 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;
}

int main()
{
Screen myscreen(5,5,'x');
myscreen.move(4, 0).set('#').display(cout);
cout << "\n";
myscreen.display(cout);
cout << "\n";
}

练习7.28

若函数返回类型变为Screen,则返回的是对象的副本,函数的操作只能添加于对象的副本上,对象的本身并没有改变。

此题中,myScreen本身并不会被三个函数所改变,所以不会输出“#”

练习7.29

1
2
3
4
5
6
7
// 有&
xxxxxxxxxxxxxxxxxxxx#xxxx
xxxxxxxxxxxxxxxxxxxx#xxxx
// 没有&
xxxxxxxxxxxxxxxxxxxx#xxxx
xxxxxxxxxxxxxxxxxxxxxxxxx

练习7.30

优点:

1、使程序意图明确,更易读;

2、可以使形参名和要赋值的成员名相同。

如:std::string& setName(const string& name) { this->name = name; }

缺点:有些场景下比较多余

std::string const& getName() const { return this->name; }

练习7.31

1
2
3
4
5
6
7
8
9
class Y;

class X {
Y* y = nullptr;
};

class Y {
X x;
};

练习7.32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <vector>
#include <string>
#include <iostream>

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; // 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]; }
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 height = 0, width = 0;
std::string contents;
};

inline void Window_mgr::clear(ScreenIndex i)
{
if (i >= screens.size()) return; // judge for out_of_range.
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

error: unknown type name ‘pos’

pos在类中声明定义,在外部使用时需要声明作用域

1
2
3
4
Screen::pos Screen::size() const
{
return height*width;
}

练习7.34

将会提示pos是不知道的类型。

练习7.35

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef string Type;
Type initVal();
class Exercise {
public:
typedef double Type;
Type setVal(Type); // double
Type initVal(); // double
private:
int val;
};
Type Exercise::setVal(Type parm) { // 返回的Type是string类型,形参类型是double类型
val = parm + initVal; // initVal调用的类中的
return val;
}
// 会报错Type Exercise::setVal(Type parm)匹配不到类中的函数,因为返回值与类中不一致。改为:
// initVal函数只声明未定义,也会报错。
Exercise::Type Exercise::setVal(Type parm){}

练习7.36

1
2
3
4
5
struct X {
X (int i, int j):base(i), rem(base % j) {}
int rem, base; // 改为int base, rem;
};
// 用一个成员来初始化另一个成员,没有考虑顺序问题

练习7.37

1
2
3
4
5
6
7
Sales_data first_item(cin);   // 使用了 Sales_data(std::istream &is) ; 数据成员值依赖输入

int main() {
Sales_data next; // 使用了Sales_data(std::string s = ""); bookNo = "", cnt = 0, revenue = 0.0
Sales_data last("9-999-99999-9"); // 使用了 Sales_data(std::string s = ""); bookNo = "9-999-99999-9", cnt = 0, revenue = 0.0
}

练习7.38

1
Sales_data(std::istream &is = std::cin) { read(is, *this); }

练习7.39

不合法,如果都使用默认值,不提供实参,则编译器就不知道该调用哪个构造函数了

练习7.40

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Date
{
public:
Date(int y, int m, int d) : year(y), month(m), day(d) { }
void setYear(int y);
void setMonth(int m);
void setDay(int d);
int getYear();
int getMonth();
int getDay();
private:
int year;
int month;
int day;
};

练习7.41

1
2
3
4
5
6
7
8
9
10
11
int main()
{
cout << "----------- 1. default: " << endl;
Sales_data s1();
cout << "----------- 2. init bookNo" << endl;
Sales_data s2("999");
cout << "----------- 3.use cin init" << endl;
Sales_data s3(cin);
cout << "----------- 4. use three parameter init: " << endl;
Sales_data s4("s01-999", 2, 59.8);
}

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
----------- 1. default:
Sales_data(const std::string &s, unsigned n, double r)
default
----------- 2. init bookNo
Sales_data(const std::string &s, unsigned n, double r)
Sales_data(const std::string &s)
----------- 3.use cin init
Sales_data(const std::string &s, unsigned n, double r)
default
iso-99 2 59.2
Sales_data(std::istream& is)
----------- 4. use three parameter init:
Sales_data(const std::string &s, unsigned n, double r)

练习7.43

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <vector> 

class NoDefault {
public:
NoDefault(int i) { }
};

class C {
public:
C() : def(0) { } // define the constructor of C.
private:
NoDefault def;
};

int main()
{
C c;

std::vector<C> vec(10);
return 0;
}

练习7.44

非法,因为vector的10个元素没有被初始化,因此需要默认初始化,而NoDefault类型没有提供默认构造函数

练习7.45

合法,因为C类提供了默认构造函数

练习 7.46

以上论断都不正确:

  1. 类可以不提供构造函数,编译器会提供一个默认构造函数。
  2. 默认构造函数为没有初始化列表(而不是参数列表为空)的对象提供默认初始值,为成员提供默认值的构造函数也称为默认构造函数。
  3. 类应该提供默认构造函数。
  4. 只有当类没有定义任何构造函数的时候,编译器才会定义默认构造函数。

练习7.47

Sales_data类的构造函数应该是explicit的

优点:保证用户能按照类设计者的初衷进行初始化

缺点:当只有一个参数时,要进行初始化再使用,没有隐式转换的写法简洁

练习7.48

1
2
3
string null_isbn("9-99-9999-9");
Sales_data item1(null_isbn); // 用string类型的null_isbn直接初始化item1.
string item2("9-99-9999-9"); // 用字符串初始化item2.

都不会有任何问题,如若没有Salesdata在前,则结果会不一样,因为皆显示地声明了属于Salesdata类

练习7.49

(a)Sales_data &combine(Sales_data); // 正常初始化,将s转化成Sales_data类型。

(b)Sales_data &combine(Sales_data&);

//报错:error: invalid initialization of non-const reference of type ‘Sales_data&’ from an rvalue of type ‘Sales_data’,string不能转化为Sales_data类型的引用

©Sales_data &combine(const Sales_data&) const;

//报错:error: assignment of member ‘Sales_data::units_sold’ in read-only object,声明最后的const会禁止函数对值做出改变。

练习7.50

1
2
explicit Person (std::istream& is) { readPerson(is, *this); }
// 只接受一个参数的构造函数应该是explicit

练习7.51

引用github上的答案

比如这样的函数:

1
int getSize(const std::vector<int>&);

如果 vector 没有将其单参数构造函数定义为 explicit,我们可以使用如下函数:

1
getSize(34);	

What is this mean? It’s very confused.

但是 std::string 是不同的。 通常我们使用 std::string 来替换 const char *(C 语言),所以当我们调用这样的函数时:

1
2
void setYourName(std::string); // declaration.
setYourName("pezy"); // just fine.

it is very natural.

练习7.52

1
2
3
4
5
6
7
8
Sales_data item = {"978-059035", 15, 29.98};
// 要使用这种初始化方式,要求类必须是聚合类。因此Sales_data类需要改成如下形式:
struct Sales_data
{
std::string bookNo;
unsigned int unit_sold;
double revenue;
};

练习7.53

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Debug
{
public:
constexpr Debug(bool b = true) : hw(b), io(b), other(b) { }
constexpr Debug(bool h, bool i, bool o) : hw(h), io(i), other(o) {}
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; // 硬件错误
bool io; // io错误
bool other; // 其它错误
};

练习7.54

在c++11中声明函数是constexpr必须满足以下条件:

  1. 返回值和参数必须是Literal类型

  2. 函数体必须只包含一个return语句

  3. 函数提可以包含其他的语句,但是这些语句不能在运行期起作用

  4. 函数可以不返回常量,但是在调用的时候实参必须传入常量表达式

    因此,如果按照c++11的标准,set_开头的成员函数不能被声明为constexpr。

    报错信息:error: assignment of member ‘Debug::hw’ in read-only object

    error: invalid return type ‘void’ of constexpr function ‘constexpr void Debug::set_hw(bool) const’

    但c++14好像取消了一些限制,因此c++14编译不报错。

练习7.55

数据成员都是字面值类型的聚合类是字面值常量类。 但Data类的数据成员不一定是字面值类型,使用变量或表达式也可以进行初始化。

练习7.56

类的静态成员与类本身直接相关,而不是与类的各个对象关联。

优点:每个对象都不需要单独存储静态成员变量,一旦静态成员改变了,则每个对象都可以使用新的值。

区别:类的静态成员属于类本身,在类加载时就会分配内存,可以通过类名直接进行访问。

普通成员属于类的对象,只有在类对象产生时才会分配内存。只能通过对象去访问。

练习7.57

1
2
3
4
5
6
7
8
9
10
11
12
class Account
{
public:
void calculate() { amount += amount * interestRate; }
static double rate() { return interestRate; }
static void rate(double);
private:
string owner;
double amount;
static double interestRate;
static double initRate();
};

练习7.58

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// example.h
class Example
{
public:
static double rate = 6.5; // 错误,静态成员类内初始化应该是一个const表达式。
// 改为 static constexpr double rete = 6.5;
static const int vecSize = 20;
static vector<double> vec(vecSize); // vector不需要在类内就定义大小
// 改为static vector<double> vec;
}
// examplec.cpp
#include "example.h"
double Example::rate;
vector<double> Example::vec;