Fork me on GitHub

类的基础知识点

Sales_data类

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<bits/stdc++.h>
#include<iterator>
using namespace std;

class Sales_data{
friend istream &read(istream &is , Sales_data &item); //友元函数,可以访问类的private成员
friend ostream &print(ostream &os , const Sales_data &item);
public:
string isbn() const {return bookNo;}; //指向常量的指针,这个const表示this指针所指向的对象是const,即不能改变 const Sales_data *const,体现在了第一个const上
Sales_data& combine(const Sales_data&);
//构造函数
Sales_data() = default; //强制要求编译器生成默认构造函数,不然合成的默认构造函数只有在没有定义构造函数时才生成
Sales_data(const string str): bookNo(str) {}; //构造函数初始值列表,是初始化const或引用类型成员的唯一方法
Sales_data(const string str , unsigned n , double p) :
bookNo(str) , units_sold(n) , revenue(p*n) {};
Sales_data(istream &);
private:
string bookNo; //如果没有构造函数,那么合成的构造函数就会对其进行默认初始化
unsigned units_sold = 0;
double revenue = 0.0;
double avg_price() const {return units_sold ? revenue / units_sold : 0;}
};

istream &read(istream &is , Sales_data &item){ //由于IO类属于不能拷贝的类型,所以用&来传递
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is; //返回值也必须是&类型,不然相当于拷贝
}

Sales_data::Sales_data(istream &is){ //构造函数可以定义在类的外面
read(is , *this);
}

Sales_data& Sales_data::combine(const Sales_data &rhs){
revenue += rhs.revenue;
units_sold += rhs.units_sold;
return *this; //返回的是Sales_data类的&类型,可以是左值返回,即返回值可以当成是左侧运算对象,不能是拷贝
}

ostream &print(ostream &os , const Sales_data &item){ //必须用&,不然是拷贝,那么就毫无意义
os << item.isbn() << ' ' << item.units_sold << ' '
<< item.revenue << ' ' << item.avg_price();
return os;
}

Sales_data add(const Sales_data &lhs , const Sales_data &rhs){
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}

int main()
{
//freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
//test(42);
Sales_data s("lzc" , 2 , 10);
read(cin , s);
print(cout , s) << endl;
Sales_data s1 = s;
print(cout , s1) << endl;
Sales_data s2 = add(s , s1);
print(cout , s2) << endl;
}

Screen类和Window_mgr类

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include<bits/stdc++.h>
#include<iterator>
using namespace std;


class Screen {
friend class Window_mgr; //友元类
//friend Window_mgr::clear(ScreenIndex); //可以把一个类的某个成员函数设为友元函数
public:
typedef string::size_type pos; //用来定义类型的成员必须先定义后使用,所以一般放在类开始的地方
//using pos = string::size_type; //等价定义
Screen() = default;
Screen(pos ht , pos wd = period , char c = '^'): height(ht) , width(wd) , contents(ht*wd , c){}; //使用默认实参,必须保证默认实参后边的实参都是有默认实参的,其中静态变量可以作为默认实参,而普通成员变量不能
explicit Screen(char c): Screen(4,2,c) {};
char get() const {return contents[cursor];}; //读取光标处的字符
inline char get(pos ht , pos wd) const; //内联函数
Screen &move(pos r , pos c);
Screen &set(char);
Screen &set(pos , pos , char);
Screen &display(ostream &os) {
do_display(os);
return *this;
}
const Screen &display(ostream &os) const {
do_display(os);
return *this;
}
private:
static int period; //静态成员存在于任何对象之外,即对象中不包含任何与静态成员有关的数据,被类的所有对象所共享
pos cursor = 0; //光标
pos height = 0 , width = 0;
string contents; //屏幕内容
mutable size_t access_ctr = 0; //可变数据成员
void do_display(ostream &os) const {
os << contents;
}
};

int Screen::period = 4; //静态成员必须在类的外部定义和初始化

class Window_mgr{
public:
using ScreenIndex = vector<Screen>::size_type;
void clear(ScreenIndex);
private:
vector<Screen> screens{Screen(24 , 80 , ' ')};
};

void Window_mgr::clear(ScreenIndex i){
Screen &s = screens[i];
s.contents = {s.height * s.width , ' '};
}

inline Screen &Screen::set(char c){
contents[cursor] = c;
return *this;
}

inline Screen &Screen::set(pos r , pos col , char ch){
contents[r*width + col] = ch;
return *this;
}

inline
Screen &Screen::move(pos r , pos c){
cursor = r * width;
cursor += c;
return *this;
}

char Screen::get(pos r , pos c) const {
pos row = r * width;
row += c;
return contents[row];
}

int main()
{
//freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);

//Screen s = Screen('^');
//Screen s = static_cast<Screen>('^');
Screen s = {4 , 2 , '*'};
s.move(3 , 0).set('$');
cout<<s.get()<<endl;
s.display(cout).set('&');
cout<<endl;
s.display(cout);
cout<<endl;
}

1. Screen(char c): Screen(4,2,c) {}; 的写法

这其实也是构造函数,是委托构造函数,Screen(4,2,c)是受委托的函数。执行时,受委托的构造函数的初始值列表和函数体先执行,然后在执行委托者的函数体。

2. explicit Screen(char c): Screen(4,2,c) {}; 中的explicit

explicit 可以抑制构造函数定义的隐式转换。

1
2
3
4
5
6
//如果去掉中的explicit,那么下面的代码是可以通过的
Screen s = '^';
//这是因为这样相当于编译器自动调用了Screen('^'),即进行了类类型转换,然后把一个临时的Screen对象赋给了s。
//但是类类型转换只能发生一步,比如,假定item是Sales_data的一个对象:
item.combine("123");
//这是不行的,我们需要把"123"先转成string,然后才能由string转成Sales_data

3. 为什么move,set,这些函数要左值返回?有什么好处?

左值返回意味着我们可以当做左侧运算对象。

1
2
3
// 如果不是左值返回,那么下面代码无法通过:
s.move(3 , 0).set('$');
//如果是左值返回,那么s.move(3 , 0)返回的是一个Screen对象的&,即还是返回了s,那么就可以调用set函数了。

4. 为什么display函数要定义两个?

因为display函数只是负责打印而没有修改的功能,所以我们希望display为const成员,所以this指针将是指向const的指针,所以display返回类型是const Screen &,注意const Screen &display(ostream &os) const 中const Screen &才是返回类型。

1
2
3
//如果display返回的是const Screen &,那么下边的代码无法通过编译:
s.display(cout).set('&');
//所以我们重载一个非const版本

5. 为什么要单独定义do_display这个函数?

这就是公共代码使用私有功能的函数

  • 避免重复相同的代码
  • 修改代码时更方便
  • 不会带来额外开销,类内隐式被声明为inline函数