前情提要: CppNote2

类型转换

类对象与其他类型的转换:

  • 由其他类型向自定义类型转换,一般称为隐式转换
  • 由自定义类型向其他类型转换

由自定义类型向其他类型转换是由类型转换函数完成的,这是一个特殊的成员函数。形式如下:

1
2
3
4
operator 目标类型()
{
// ...
}

由自定义类型向其他类型转换的例子:

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
92
#include <iostream>

using std::cout;
using std::endl;

class Complex {
public:
friend std::ostream& operator << (std::ostream& os, const Complex& rhs);

Complex(double dreal = 0, double dimag = 0)
:_dreal(dreal), _dimag(dimag) {
cout << "Complex(double = 0, double = 0)" << endl;
}

~Complex() {
cout << "~Complex()" << endl;
}

private:
double _dreal;
double _dimag;
};

std::ostream& operator << (std::ostream& os, const Complex& rhs) {
os << rhs._dreal << " + " << rhs._dimag << "i";
return os;
}

class point {
public:
friend std::ostream& operator << (std::ostream& os, const point& rhs);

point(int x=0, int y=0):ix(x),iy(y) {
cout << "point(int = 0, int = 0)" << endl;
}

~point() {
cout << "~point()" << endl;
}

point(const point& rhs):ix(rhs.ix), iy(rhs.iy) {
cout << "point(const point&)" << endl;
}

operator int() {
return ix + iy;
}

operator Complex() {
return Complex(ix, iy);
}

private:
int ix;
int iy;
};

std::ostream& operator << (std::ostream& os, const point& rhs){
os << rhs.ix << " " << rhs.iy << endl;
return os;
}


void test() {
point pt4(4, 5);
cout << "pt4 = " << pt4 << endl;

int ix = pt4;
cout << "ix = " << ix << endl;

double dx = pt4;
cout << "dx = " << dx << endl;

Complex cx = pt4;
cout << "cx = " << cx << endl;
}

int main() {
test();
return 0;
}
/*
point(int = 0, int = 0)
pt4 = 4 5

ix = 9
dx = 9
Complex(double = 0, double = 0)
cx = 4 + 5i
~Complex()
~point()
*/

嵌套类

一个类可以写在另一个类中,即嵌套类,又称内部类

简单例子

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
#include <iostream>

using std::cout;
using std::endl;

class Line {
public:
Line(int x1, int y1, int x2, int y2)
:_pt1(x1, y1), _pt2(x2, y2) {
cout << "Line(int, int, int, int)" << endl;
}

~Line() {
cout << "~Line()" << endl;
}

void print() const {
_pt1.print();
cout << " ---> ";
_pt2.print();
cout << endl;
}

private:
class point {
public:
point(int x=0, int y=0):ix(x),iy(y) {
cout << "point(int = 0, int = 0)" << endl;
}

void print() const {
std::cout << ix << " "
<< iy ;
}

~point() {
cout << "~point()" << endl;
}

private:
int ix;
int iy;
};

private:
point _pt1;
point _pt2;
};

int main() {
Line line(1, 2, 3, 4);
cout << "line = ";
line.print();
}
/*
point(int = 0, int = 0)
point(int = 0, int = 0)
Line(int, int, int, int)
line = 1 2 ---> 3 4
~Line()
~point()
~point()
*/

设计模式 Pimpl

通过一个私有的成员指针,隐藏指针所指向类的内部实现。该设计模式有以下优点:

  • 提高编译速度
  • 信息隐藏
  • 减小编译依赖,以小代价平滑升级库文件
  • 接口与实现解耦
  • 移动语义友好
1
2
3
4
5
6
7
8
9
10
11
//Line.h
class Line {
public:
Line(int,int,int,int);
~Line();
void printLine() const;

private:
class LineImpl; //类的前向声明
LineImple* _pimpl;
};
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
//Line.cc
#include "Line.h"

class Line::LineImpl {
public:
LineImpl(int x1, int y1, int x2, int y2);
void printLineImpl() const;

private:
class Point {
public:
Point(int x = 0, int y = 0): _x(x), _y(y) {}
void print() const;

private:
int _x;
int _y;
};

Point _pt1;
Point _pt2;
};

Line::LineImpl::LineImpl(int x1, int y1, int x2, int y2)
: _pt1(x1, y1) ,_pt2(x2, y2) {}

void Line::LineImpl::printLineImpl() const {
_pt1.print();
cout << " ---> ";
_pt2.print();
cout << endl;
}

Line::Line(int x1, int y1, int x2, int y2)
: _pimpl(new LineImpl(x1, y1, x2, y2)) {}

Line::~Line() {
delete _pimpl;
}

void Line::printLine() const {
pimpl->printLineImpl();
}

内存泄露的检测

安装内存泄露检测工具:

1
sudo apt install valgrind

写一段有泄露的代码:

1
2
3
4
int main() {
int* pInt = new int(10);
return 0;
}

在 terminal 中使用工具:

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
wanko@wanko:~/mycode$ g++ testtmp.cc -o test -g
wanko@wanko:~/mycode$ ls
a.out exam_log4cpp example_reids guoba.py testtmp
dict example_mysql for.sh test testtmp.cc
wanko@wanko:~/mycode$ valgrind --tool=memcheck --leak-check=full ./test
==84862== Memcheck, a memory error detector
==84862== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==84862== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==84862== Command: ./test
==84862==
==84862==
==84862== HEAP SUMMARY:
==84862== in use at exit: 4 bytes in 1 blocks
==84862== total heap usage: 2 allocs, 1 frees, 72,708 bytes allocated
==84862==
==84862== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==84862== at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==84862== by 0x10915E: main (testtmp.cc:2)
==84862==
==84862== LEAK SUMMARY:
==84862== definitely lost: 4 bytes in 1 blocks
==84862== indirectly lost: 0 bytes in 0 blocks
==84862== possibly lost: 0 bytes in 0 blocks
==84862== still reachable: 0 bytes in 0 blocks
==84862== suppressed: 0 bytes in 0 blocks
==84862==
==84862== For lists of detected and suppressed errors, rerun with: -s
==84862== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

再写一段没有泄露的代码:

1
2
3
4
5
int main() {
int* pInt = new int(10);
delete pInt;
return 0;
}

则工具输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
wanko@wanko:~/mycode$ g++ tmptest.cc -o test -g
wanko@wanko:~/mycode$ ls
dict example_mysql for.sh test
exam_log4cpp example_reids guoba.py tmptest.cc
wanko@wanko:~/mycode$ valgrind --tool=memcheck --leak-check=full ./test
==90126== Memcheck, a memory error detector
==90126== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==90126== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==90126== Command: ./test
==90126==
==90126==
==90126== HEAP SUMMARY:
==90126== in use at exit: 0 bytes in 0 blocks
==90126== total heap usage: 2 allocs, 2 frees, 72,708 bytes allocated
==90126==
==90126== All heap blocks were freed -- no leaks are possible
==90126==
==90126== For lists of detected and suppressed errors, rerun with: -s
==90126== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

单例模式的自动释放

使用友元形式进行设计

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
#include <iostream>

using std::cout;
using std::endl;

class A{
friend class AutoRelease;
public:
static A* myCreateObj(){
if(_ptmp == nullptr){
_ptmp = new A();
}
return _ptmp;
}

private:
~A(){
cout << "~A()" << endl;
}

A(){
cout << "A()" << endl;
}
static A* _ptmp;
};

A* A::_ptmp = nullptr;

class AutoRelease {
public:
AutoRelease() {
cout << "AutoRelease()" << endl;
}

~AutoRelease() {
cout << "~AutoRelease()" << endl;
if(A::_ptmp == nullptr) return;
delete A::_ptmp;
A::_ptmp = nullptr;
}
};

int main(){
A* ps1 = A::myCreateObj();
cout << "ps1 = " << ps1 << endl;

AutoRelease ar;
return 0;
}
/*
A()
ps1 = 0x563e22a51eb0
AutoRelease()
~AutoRelease()
~A()
*/

内部类加静态数据成员形式

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
#include <iostream>

using std::cout;
using std::endl;

class A{
public:
static A* myCreateObj(){
if(_ptmp == nullptr){
_ptmp = new A();
}
return _ptmp;
}

private:
class AutoRelease {
public:
AutoRelease() {
cout << "AutoRelease()" << endl;
}

~AutoRelease() {
cout << "~AutoRelease()" << endl;
if(_ptmp == nullptr) return;
delete _ptmp;
_ptmp = nullptr;
}
};

~A(){
cout << "~A()" << endl;
}

A(){
cout << "A()" << endl;
}

static A* _ptmp;
static AutoRelease _ar;
};

A* A::_ptmp = nullptr;
A::AutoRelease A::_ar;

int main(){
A* ps1 = A::myCreateObj();
cout << "ps1 = " << ps1 << endl;

return 0;
}
/*
AutoRelease()
A()
ps1 = 0x5572fd4e72c0
~AutoRelease()
~A()
*/

atexit方式

首先了解一下atexit()函数:

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

using std::cout;
using std::endl;

void func() {
cout << "void func()" << endl;
}

int main() {
cout << "begin main..." << endl;
atexit(func);
cout << "end main..." << endl;
return 0;
}
/*
begin main...
end main...
void func()
*/

atexit()函数会注册给定函数类型(参数是 void,返回类型是 void),注册的函数在进程正常结束的时候被调用。函数注册几次就会执行几次。

那么可以考虑使用该函数进行单例模式的自动释放:

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
#include <iostream>
#include <stdlib.h>

using std::cout;
using std::endl;

class A{
public:
static A* myCreateObj(){
if(_ptmp == nullptr){
_ptmp = new A();
atexit(destroy);
}
return _ptmp;
}

static void destroy(){
if(_ptmp == nullptr) return;
delete _ptmp;
_ptmp = nullptr;
}

private:
~A(){
cout << "~A()" << endl;
}

A(){
cout << "A()" << endl;
}
static A* _ptmp;
};

A* A::_ptmp = nullptr;

int main(){
A* ps1 = A::myCreateObj();
cout << "ps1 = " << ps1 << endl;

return 0;
}
/*
A()
ps1 = 0x563474c77eb0
~A()
*/

在之前的代码中,若处于多线程环境,可能会创建不止一个实例,违背单例模式的初衷。因此可以采用 atexit + 饿汉模式:

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
#include <iostream>
#include <stdlib.h>

using std::cout;
using std::endl;

class A{
public:
static A* myCreateObj(){
if(_ptmp == nullptr){
_ptmp = new A();
atexit(destroy);
}
return _ptmp;
}

static void destroy(){
cout << "void destory()" << endl;
if(_ptmp == nullptr) return;
delete _ptmp;
_ptmp = nullptr;
}

private:
~A(){
cout << "~A()" << endl;
}

A(){
cout << "A()" << endl;
}
static A* _ptmp;
};

A* A::_ptmp = myCreateObj(); // 饿汉模式

int main(){
A* ps1 = A::myCreateObj();
cout << "ps1 = " << ps1 << endl;

return 0;
}
/*
A()
ps1 = 0x560be12b7eb0
void destory()
~A()
*/

pthread_once 形式

pthread_once函数:在多线程编程环境下,由pthread_once()指定的函数执行且仅执行一次。

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
#include <iostream>
#include <pthread.h>
#include <stdlib.h>

using std::cout;
using std::endl;

class A{
public:
static A* myCreateObj(){
pthread_once(&_once, init);
return _ptmp;
}

static void init() {
_ptmp = new A();
atexit(destroy);
}

static void destroy(){
cout << "void destory()" << endl;
if(_ptmp == nullptr) return;
delete _ptmp;
_ptmp = nullptr;
}

private:
~A(){
cout << "~A()" << endl;
}

A(){
cout << "A()" << endl;
}
static A* _ptmp;
static pthread_once_t _once;
};

A* A::_ptmp = myCreateObj(); // 饿汉模式
pthread_once_t A::_once = PTHREAD_ONCE_INIT;

int main(){
A* ps1 = A::myCreateObj();
cout << "ps1 = " << ps1 << endl;

return 0;
}
/*
wanko@wanko:~/mycode$ g++ tmptest.cc -lpthread
wanko@wanko:~/mycode$ ./a.out
A()
ps1 = 0x5578fad56eb0
void destory()
~A()
*/

上面的代码用饿汉模式或是饱汉模式都 OK.

注意:上面代码中的<pthread.h>库只能在 linux 下使用。

string 的底层实现

原理

三种基本方式:

  • Eager Copy(深拷贝)
  • COW(写时复制)
  • SSO(Short String Optimization 短字符串优化)

66-1.png

COW:只是进行读操作时,浅拷贝;如果需要写操作,再深拷贝。

写时复制的体现(注意:以下图片是在较旧的环境下):

66-2.png

需要写操作,进行深拷贝:

66-3

注意在较新环境下并不使用写时复制,而是使用 sso .
当字符串的长度小于 16 字节时,放在栈上。否则放在堆上

注意以下代码输出结果的地址前几位:

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
#include <iostream>
#include <string>

using std::cout;
using std::endl;
using std::string;

void test() {
int a = 10;
int* pInt = new int(20);

string s1 = "hello";
string s2 = "helloworldwanko";
string s3 = "Welcome to Uptown Berloberg, Hooker.";

cout << "s1 = " << s1 << endl;
cout << "s2 = " << s2 << endl;
cout << "s3 = " << s3 << endl;
printf("s1' address = %p\n", s1.c_str());
printf("s2' address = %p\n", s2.c_str());
printf("s3' address = %p\n", s3.c_str());

printf("&a = %p\n", &a);
printf("&pInt = %p\n", &pInt);
printf("pInt = %p\n", pInt);

delete pInt;
pInt = nullptr;
}

int main() {
test();
return 0;
}
/*
s1 = hello
s2 = helloworldwanko
s3 = Welcome to Uptown Berloberg, Hooker.
s1' address = 0x7ffe48ba02b0
s2' address = 0x7ffe48ba02d0
s3' address = 0x558f62abaed0
&a = 0x7ffe48ba0294
&pInt = 0x7ffe48ba0298
pInt = 0x558f62abaeb0
*/

通常,一个程序里的字符串大都很短小,而在64位机器上,一个 char* 指针就占用了8个字节,所以 SSO 就出现了。其思想是:发生拷贝时要需复制一个指针,但对小字符串来说,直接复制整个字符串比较划算。实现示意图如下:

66-4.png

更多细节参考:

实现写时复制

大致写出写时复制的逻辑。大体思路:

66-5.png

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#include <iostream>
#include <string.h>

using std::cout;
using std::endl;
using std::ostream;

class String{
friend ostream& operator << (ostream& os, const String& rhs);
public:
String():_pstr(new char[5]()+4) {
cout << "String()" << endl;
initRefCount();
}

String(const char* pstr)
:_pstr(new char[strlen(pstr)+5]()+4) {
cout << "String(const char*)" << endl;
strcpy(_pstr, pstr);
initRefCount();
}

String(const String& rhs):_pstr(rhs._pstr) {
cout << "String(const String&)" << endl;
increseRefCount();
}

String& operator = (const String& rhs) {
cout << "String& operator = (const String&)" << endl;
if(this == &rhs) return *this;

decreseRefCount();
if(0 == getRefCount()) {
delete [] (_pstr-4);
}

_pstr = rhs._pstr; // 浅拷贝
increseRefCount();

return *this;
}

int getRefCount() const {
return *(int*)(_pstr-4);
}

const char* c_str() const {
return _pstr;
}

private:
class CharProxy {
public:
CharProxy(String& self, size_t idx)
: _self(self), _idx(idx) {}

char& operator = (const char& ch);

friend ostream& operator << (ostream& os, const CharProxy& rhs);

private:
String& _self;
size_t _idx;
};

public:
CharProxy operator [] (size_t idx) {
return CharProxy(*this, idx);
}

~String() {
cout << "~String()" << endl;
decreseRefCount();
if(0 == getRefCount()) {
delete [] (_pstr-4);
}
}

// 双重友元
friend ostream& operator << (ostream& os, const String::CharProxy& rhs);

private:
void initRefCount() {
*(int*)(_pstr-4) = 1; // 引用计数
}

void increseRefCount() {
++ *(int*)(_pstr-4);
}

void decreseRefCount() {
-- *(int*)(_pstr-4);
}

size_t size() const {
return strlen(_pstr);
}

char* _pstr;
};

ostream& operator << (ostream& os, const String& rhs) {
if(rhs._pstr) {
os << rhs._pstr;
}
return os;
}

// input
char& String::CharProxy::operator = (const char& ch) {
if(_idx >= _self.size()) {
static char charNull = '\0';
return charNull;
}
if(_self.getRefCount() > 1) {
char* tmp = new char[_self.size()+5]()+4;
strcpy(tmp, _self._pstr);
_self.decreseRefCount();

_self._pstr = tmp;
_self.initRefCount();
}
_self._pstr[_idx] = ch; // 进行写操作
return _self._pstr[_idx];
}

// output
ostream& operator << (ostream& os, const String::CharProxy& rhs) {
os << rhs._self._pstr[rhs._idx];
return os;
}

int main() {
String s1("hello");
cout << "s1 = " << s1 << endl;
cout << "s1.getRefCount = " << s1.getRefCount() << endl;
printf("s1' address = %p\n", s1.c_str());

cout << endl;
String s2 = s1;
cout << "s1 = " << s1 << endl;
cout << "s2 = " << s2 << endl;
cout << "s1.getRefCount = " << s1.getRefCount() << endl;
cout << "s2.getRefCount = " << s2.getRefCount() << endl;
printf("s1' address = %p\n", s1.c_str());
printf("s2' address = %p\n", s2.c_str());

cout << endl;
String s3("world");
cout << "s3 = " << s3 << endl;
cout << "s3.getRefCount = " << s3.getRefCount() << endl;
printf("s3' address = %p\n", s3.c_str());

cout << endl;
s3 = s1;
cout << "s1 = " << s1 << endl;
cout << "s2 = " << s2 << endl;
cout << "s3 = " << s3 << endl;
cout << "s1.getRefCount = " << s1.getRefCount() << endl;
cout << "s2.getRefCount = " << s2.getRefCount() << endl;
cout << "s3.getRefCount = " << s3.getRefCount() << endl;
printf("s1' address = %p\n", s1.c_str());
printf("s2' address = %p\n", s2.c_str());
printf("s3' address = %p\n", s3.c_str());

cout << endl << "对 s3[0] 执行写操作" << endl;
s3[0] = 'H';
cout << "s1 = " << s1 << endl;
cout << "s2 = " << s2 << endl;
cout << "s3 = " << s3 << endl;
cout << "s1.getRefCount = " << s1.getRefCount() << endl;
cout << "s2.getRefCount = " << s2.getRefCount() << endl;
cout << "s3.getRefCount = " << s3.getRefCount() << endl;
printf("s1' address = %p\n", s1.c_str());
printf("s2' address = %p\n", s2.c_str());
printf("s3' address = %p\n", s3.c_str());

cout << endl << "对 s1[0] 执行读操作" << endl;
cout << "s1[0] = " << s1[0] << endl;
cout << "s1 = " << s1 << endl;
cout << "s2 = " << s2 << endl;
cout << "s3 = " << s3 << endl;
cout << "s1.getRefCount = " << s1.getRefCount() << endl;
cout << "s2.getRefCount = " << s2.getRefCount() << endl;
cout << "s3.getRefCount = " << s3.getRefCount() << endl;
printf("s1' address = %p\n", s1.c_str());
printf("s2' address = %p\n", s2.c_str());
printf("s3' address = %p\n", s3.c_str());
return 0;
}
/*
String(const char*)
s1 = hello
s1.getRefCount = 1
s1' address = 0x558bc073aeb4

String(const String&)
s1 = hello
s2 = hello
s1.getRefCount = 2
s2.getRefCount = 2
s1' address = 0x558bc073aeb4
s2' address = 0x558bc073aeb4

String(const char*)
s3 = world
s3.getRefCount = 1
s3' address = 0x558bc073b2e4

String& operator = (const String&)
s1 = hello
s2 = hello
s3 = hello
s1.getRefCount = 3
s2.getRefCount = 3
s3.getRefCount = 3
s1' address = 0x558bc073aeb4
s2' address = 0x558bc073aeb4
s3' address = 0x558bc073aeb4

对 s3[0] 执行写操作
s1 = hello
s2 = hello
s3 = Hello
s1.getRefCount = 2
s2.getRefCount = 2
s3.getRefCount = 1
s1' address = 0x558bc073aeb4
s2' address = 0x558bc073aeb4
s3' address = 0x558bc073b2e4

对 s1[0] 执行读操作
s1[0] = h
s1 = hello
s2 = hello
s3 = Hello
s1.getRefCount = 2
s2.getRefCount = 2
s3.getRefCount = 1
s1' address = 0x558bc073aeb4
s2' address = 0x558bc073aeb4
s3' address = 0x558bc073b2e4
~String()
~String()
~String()
*/

值得一提的是,在使用下标运算符[]时,写时复制要求在 读/写 两种不同的操作下有不同的行为,因此考虑自定义一个新类型,然后重载=<<

这份代码细节拉满,需要细细品味。

派生类对象的创建与销毁

继承方式 基类成员访问权限 在派生类中访问权限 派生类对象访问
公有继承 public
protected
private
public
protected
不可直接访问
可直接访问
不可直接访问
不可直接访问
保护继承 public
protected
private
protected
protected
不可直接访问
不可直接访问
私有继承 public
protected
private
private
private
不可直接访问
不可直接访问
- - - - - 创建 - - - - -

一、若派生类显式定义构造函数,而基类没有显示定义构造函数,则创建派生类对象时,派生类相应的构造函数会被自动调用,且自动调用基类缺省的无参构造函数:

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
#include <iostream>

using std::cout;
using std::endl;

class Base {
public:
Base() : _base(0) {
cout << "Base()" << endl;
}

~Base() {
cout << "~Base()" << endl;
}

private:
int _base;
};

class Derived : public Base {
public:
Derived(int derived) : _derived(derived) {
cout << "Derived(int)" << endl;
}

~Derived() {
cout << "~Derived()" << endl;
}

private:
int _derived;
};

int main() {
Derived d1(10);
return 0;
}
/*
Base()
Derived(int)
~Derived()
~Base()
*/

二、如果派生类没有显式定义构造函数而基类有显示定义构造函数,则基类必须拥有默认构造函数。

三、如果派生类有构造函数,基类有默认构造函数,则创建派生类的对象时,基类的默认构造函数会自动调用。如果想调用基类的有参构造函数,必须要在派生类构造函数的初始化列表中显示调用基类的有参构造函数。

四、如果派生类和基类都有构造函数,但基类没有默认的无参构造函数,即基类的构造函数均带有参数,则派生类的每一个构造函数必须在其初始化列表中显示地去调用基类的某个带参的构造函数。

- - - - - 创建END - - - - -


- - - - - 销毁 - - - - -

当派生类对象被删除时,派生类的析构函数被执行。析构函数同样不能继承,因此,在执行派生类析构函数时,基类析构函数会被自动调用。执行顺序是先执行派生类的析构函数,再执行基类的析构函数。

当考虑对象成员时,继承机制下析构函数的调用顺序:

  1. 先调用派生类的析构函数
  2. 再调用派生类中对象成员的析构函数
  3. 最后调用普通基类的析构函数
- - - - - 销毁END - - - - -

多继承

引入

看一个例子:

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
#include <iostream>

using std::cout;
using std::endl;

class A {
public:
A() {
cout << "A()" << endl;
}

void show() const {
cout << "void A::show() const" << endl;
}

~A() {
cout << "~A()" << endl;
}
};

class B {
public:
B() {
cout << "B()" << endl;
}

void display() const {
cout << "void B::display() const" << endl;
}

~B() {
cout << "~B()" << endl;
}
};

class C {
public:
C() {
cout << "C()" << endl;
}

void print() const {
cout << "void C::print() const" << endl;
}

~C() {
cout << "~C()" << endl;
}
};

class D: public A, public B, public C {
public:
D() {
cout << "D()" << endl;
}

~D() {
cout << "~D()" << endl;
}
};

int main() {
D d;
d.show();
d.display();
d.print();
return 0;
}
/*
A()
B()
C()
D()
void A::show() const
void B::display() const
void C::print() const
~D()
~C()
~B()
~A()
*/

多继承下,基类构造函数的执行顺序与其在派生类构造函数初始化列表中的顺序无关,而与基类被继承的顺序有关。

成员名冲突的二义性

解决多继承下函数名字冲突的方法:

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
#include <iostream>

using std::cout;
using std::endl;

class A {
public:
A() {
cout << "A()" << endl;
}

void print() const {
cout << "void A::print() const" << endl;
}

~A() {
cout << "~A()" << endl;
}
};

class B {
public:
B() {
cout << "B()" << endl;
}

void print() const {
cout << "void B::print() const" << endl;
}

~B() {
cout << "~B()" << endl;
}
};

class C {
public:
C() {
cout << "C()" << endl;
}

void print() const {
cout << "void C::print() const" << endl;
}

~C() {
cout << "~C()" << endl;
}
};

class D: public A, public B, public C {
public:
D() {
cout << "D()" << endl;
}

~D() {
cout << "~D()" << endl;
}
};

int main() {
D d;
d.A::print();
d.B::print();
d.C::print();
return 0;
}
/*
A()
B()
C()
D()
void A::print() const
void B::print() const
void C::print() const
~D()
~C()
~B()
~A()
*/

菱形继承的二义性

多基派生中,如果在多条继承路径上有一个共同的基类,如下图所示,不难看出,在D类对象中,会有来自两条不同路径的共同基类(类A)的双重拷贝。

66-6.png

菱形继承会导致数据成员的存储二义性,解决方法:使 B 和 C 虚拟继承 A .

1
2
3
class B: virtual public A {

};

下面展示问题:

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
// dummy code
#include <iostream>

using std::cout;
using std::endl;

class A {
public:
A(long lx = 0):_lx(lx) {
cout << "A(long = 0)" << endl;
}

void print() const {
cout << "lx = " << _lx << endl;
}

void setVal(long value) {
_lx = value;
}

~A() {
cout << "~A()" << endl;
}

private:
long _lx;
};

class B: public A {

};

class C: public A {

};

class D: public B, public C {

};

int main() {
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl;
cout << "sizeof(C) = " << sizeof(C) << endl;
cout << "sizeof(D) = " << sizeof(D) << endl;

D d;
d.B::print();

d.C::setVal(23);
d.B::print();

d.C::print();

return 0;
}
/*
sizeof(A) = 8
sizeof(B) = 8
sizeof(C) = 8
sizeof(D) = 16
A(long = 0)
A(long = 0)
lx = 0
lx = 0
lx = 23
~A()
~A()
*/

下面展示解决方法:

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
#include <iostream>

using std::cout;
using std::endl;

class A {
public:
A(long lx = 0):_lx(lx) {
cout << "A(long = 0)" << endl;
}

void print() const {
cout << "lx = " << _lx << endl;
}

void setVal(long value) {
_lx = value;
}

~A() {
cout << "~A()" << endl;
}

private:
long _lx;
};

class B: virtual public A {

};

class C: virtual public A {

};

class D: public B, public C {

};

int main() {
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl;
cout << "sizeof(C) = " << sizeof(C) << endl;
cout << "sizeof(D) = " << sizeof(D) << endl;

D d;
d.B::print();

d.C::setVal(23);
d.B::print();

d.C::print();

return 0;
}
/*
sizeof(A) = 8
sizeof(B) = 16
sizeof(C) = 16
sizeof(D) = 24
A(long = 0)
lx = 0
lx = 23
lx = 23
~A()
*/

上面的代码中 sizeof 大小出现变化是由于虚基指针

或者也可以:

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
#include <iostream>

using std::cout;
using std::endl;

class A {
public:
A(long lx = 0):_lx(lx) {
cout << "A(long = 0)" << endl;
}

void print() const {
cout << "lx = " << _lx << endl;
}

void setVal(long value) {
_lx = value;
}

~A() {
cout << "~A()" << endl;
}

private:
long _lx;
};

class B: virtual public A {

};

class C: virtual public A {

};

class D: public B, public C {

};

int main() {
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl;
cout << "sizeof(C) = " << sizeof(C) << endl;
cout << "sizeof(D) = " << sizeof(D) << endl;

D d;
d.print();

d.setVal(23);
d.print();

return 0;
}
/*
sizeof(A) = 8
sizeof(B) = 16
sizeof(C) = 16
sizeof(D) = 24
A(long = 0)
lx = 0
lx = 23
~A()
*/

关于虚基指针的图示(转自一颗程序媛0915想上岸):

66-6dot1.png

若为多继承时,内存布局如下:

66-6dot2.png

虚继承的内存布局如下:

66-6dot3.png

来看另一种情况:

66-6dot6.png

若为多继承时,内存布局如下:

66-6dot7.png

虚继承的内存布局如下:
第一个vbptr 存放3个数据,因为指针合并,向距离作用域最近的指针内层合并

66-6dot8.png

pair

杂项知识点。

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

using std::cout;
using std::endl;
using std::pair;
using std::string;

void test() {
pair<int, string> aka = {1, "hello"};
cout << aka.first << " " << aka.second << endl;
}

int main() {
test();
return 0;
}
/*
1 hello
*/

基类与派生类间的转换

“类型适应”指类型间的关系。“A类适应B类”,意味着 A 类对象能直接用于需要 B 类对象的场合,在这种意义下,派生类适应基类,派生类对象适应基类对象,派生类对象的指针和引用也适应基类对象的指针和引用。例如:

  • 可以把派生类的对象赋值给基类的对象
  • 可以把基类的引用绑定到派生类的对象
  • 可以声明基类的指针指向派生类的对象 (向上转型)

派生类对象间的复制控制

基类的拷贝构造函数和 operator= 运算符函数不能被继承,因此需要注意:

  1. 如果用户定义了基类的拷贝构造函数,而没有定义派生类的拷贝构造函数,那么在用一个派生类对象初始化新的派生类对象时,两对象间的派生类部分执行缺省的行为,而两对象间的基类部分执行用户定义的基类拷贝构造函数。
  2. 如果用户重载了基类的赋值运算符函数,而没有重载派生类的赋值运算符函数,那么在用一个派生类对象给另一个已经存在的派生类对象赋值时,两对象间的派生类部分执行缺省的赋值行为,而两对象间的基类部分执行用户定义的重载赋值函数。
  3. 如果用户定义了派生类的拷贝构造函数或者重载了派生类的对象赋值运算符=,则在用已有派生类对象初始化新的派生类对象时,或者在派生类对象间赋值时,将会执行用户定义的派生类的拷贝构造函数或者重载赋值函数,而不会再自动调用基类的拷贝构造函数和基类的重载对象赋值运算符,这时,通常需要用户在派生类的拷贝构造函数或者派生类的赋值函数中显式调用基类的拷贝构造或赋值运算符函数。

多态

概念、分类

多态:对于同一指令,不同对象产生不同行为。

多态的分类:

  • 静态多态
    • 例如:函数重载、运算符重载、模板
    • 发生在编译时
  • 动态多态
    • 例如:虚函数
    • 发生在运行时

多态的其他细节例子参考 CppNote .

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
#include <iostream>

using std::cout;
using std::endl;

class Base {
public:
Base(double base = 0.0): _base(base) {
cout << "Base(double = 0.0)" << endl;
}

virtual void print() const {
cout << "Base::_base = " << _base << endl;
}

~Base() {
cout << "~Base()" << endl;
}

private:
double _base;
};

class Derived: public Base {
public:
Derived(double base = 0.0, double derived = 0.0)
: Base(base), _derived(derived) {
cout << "Derived(double = 0.0, double = 0.0)" << endl;
}

void print() const override {
cout << "Derived::_derived = " << _derived << endl;
}

~Derived() {
cout << "~Derived()" << endl;
}

private:
double _derived;
};

void func(Base* pb) {
pb->print();
}

int main() {
cout << "sizeof(Base) = " << sizeof(Base) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;

cout << endl;
Base base(11.11);
Derived derived(22.22, 33.33);

func(&base);
func(&derived);

return 0;
}
/*
sizeof(Base) = 16
sizeof(Derived) = 24

Base(double = 0.0)
Base(double = 0.0)
Derived(double = 0.0, double = 0.0)
Base::_base = 11.11
Derived::_derived = 33.33
~Derived()
~Base()
~Base()
*/

在上面的代码中,sizeof 的大小是虚函数指针的体现。

虚函数的原理、条件

虚函数的实现原理:

66-7.png

虚函数机制的激活条件:

  1. 基类定义虚函数
  2. 派生类重写该虚函数
  3. 创建派生类的对象
  4. 用基类的指针指向(引用绑定)派生类的对象
  5. 使用基类的指针(引用)调用该虚函数

不能设置为虚函数的函数:

  • 普通函数(自由函数、全局函数)
  • 内联成员函数
  • 静态成员函数
  • 友元函数(分情况)
    • 若该友元函数本身是一个普通函数,则不能被设置为虚函数
    • 若该友元函数本身是另外一个类的成员函数,则可以被设置为虚函数
  • 构造函数

虚函数的访问

一、指针访问

即上例。

二、引用访问

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
#include <iostream>

using std::cout;
using std::endl;

class Base {
public:
Base(double base = 0.0): _base(base) {
cout << "Base(double = 0.0)" << endl;
}

virtual void print() const {
cout << "Base::_base = " << _base << endl;
}

~Base() {
cout << "~Base()" << endl;
}

private:
double _base;
};

class Derived: public Base {
public:
Derived(double base = 0.0, double derived = 0.0)
: Base(base), _derived(derived) {
cout << "Derived(double = 0.0, double = 0.0)" << endl;
}

void print() const override {
cout << "Derived::_derived = " << _derived << endl;
}

~Derived() {
cout << "~Derived()" << endl;
}

private:
double _derived;
};

void func(Base& pb) {
pb.print();
}

int main() {
cout << "sizeof(Base) = " << sizeof(Base) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;

cout << endl;
Base base(11.11);
Derived derived(22.22, 33.33);

func(base);
func(derived);

return 0;
}
/*
sizeof(Base) = 16
sizeof(Derived) = 24

Base(double = 0.0)
Base(double = 0.0)
Derived(double = 0.0, double = 0.0)
Base::_base = 11.11
Derived::_derived = 33.33
~Derived()
~Base()
~Base()
*/

三、对象访问

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
#include <iostream>

using std::cout;
using std::endl;

class Base {
public:
Base(double base = 0.0): _base(base) {
cout << "Base(double = 0.0)" << endl;
}

virtual void print() const {
cout << "Base::_base = " << _base << endl;
}

~Base() {
cout << "~Base()" << endl;
}

private:
double _base;
};

class Derived: public Base {
public:
Derived(double base = 0.0, double derived = 0.0)
: Base(base), _derived(derived) {
cout << "Derived(double = 0.0, double = 0.0)" << endl;
}

void print() const override {
cout << "Derived::_derived = " << _derived << endl;
}

~Derived() {
cout << "~Derived()" << endl;
}

private:
double _derived;
};

int main() {
cout << "sizeof(Base) = " << sizeof(Base) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;

cout << endl;
Base base(11.11);
Derived derived(22.22, 33.33);

base.print();
derived.print();

return 0;
}
/*
sizeof(Base) = 16
sizeof(Derived) = 24

Base(double = 0.0)
Base(double = 0.0)
Derived(double = 0.0, double = 0.0)
Base::_base = 11.11
Derived::_derived = 33.33
~Derived()
~Base()
~Base()
*/

注意,这里并没有体现出多态。

和普通函数一样,虚函数一样可以通过对象名来调用,此时编译器采用的是静态联编

四、成员函数中访问

例子不难理解,耐心看:

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
#include <iostream>

using std::cout;
using std::endl;

class Base {
public:
Base(double base = 0.0): _base(base) {
cout << "Base(double = 0.0)" << endl;
}

virtual void print() const {
cout << "Base::_base = " << _base << endl;
}

void func1() {
print();
}

void func2() {
Base::print();
}

~Base() {
cout << "~Base()" << endl;
}

private:
double _base;
};

class Derived: public Base {
public:
Derived(double base = 0.0, double derived = 0.0)
: Base(base), _derived(derived) {
cout << "Derived(double = 0.0, double = 0.0)" << endl;
}

void print() const override {
cout << "Derived::_derived = " << _derived << endl;
}

~Derived() {
cout << "~Derived()" << endl;
}

private:
double _derived;
};

int main() {
cout << "sizeof(Base) = " << sizeof(Base) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;

cout << endl;
Base base(11.11);
Derived derived(22.22, 33.33);

Base* pbase = &base;
pbase->func1();
pbase->func2();

cout << endl;
Base* pbase2 = &derived;
pbase2->func1();
pbase2->func2();

return 0;
}
/*
sizeof(Base) = 16
sizeof(Derived) = 24

Base(double = 0.0)
Base(double = 0.0)
Derived(double = 0.0, double = 0.0)
Base::_base = 11.11
Base::_base = 11.11

Derived::_derived = 33.33
Base::_base = 22.22
~Derived()
~Base()
~Base()
*/

五、构造函数和析构函数中访问

它们所调用的虚函数是自己类中定义的函数,如果在自己的类中没有实现该函数,则调用的是基类中的虚函数。

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
92
93
94
95
96
97
98
99
100
101
#include <iostream>

using std::cout;
using std::endl;

class Grandpa
{
public:
Grandpa()
{
cout << "Grandpa()" << endl;
}

~Grandpa()
{
cout << "~Grandpa()" << endl;
}

virtual void func1()
{
cout << "Grandpa::func1()" << endl;
}

virtual void func2()
{
cout << "Grandpa::func2()" << endl;
}
};

class Father
: public Grandpa
{
public:
Father()
{
cout << "Father()" << endl;
func1();
}

~Father()
{
cout << "~Father()" << endl;
func2();
}

virtual void func1()
{
cout << "Father::func1()" << endl;
}

virtual void func2()
{
cout << "Father::func2()" << endl;
}
};

class Son
: public Father
{
public:
Son()
{
cout << "Son()" << endl;
}

~Son()
{
cout << "~Son()" << endl;
}

virtual void func1()
{
cout << "Son::func1()" << endl;
}

virtual void func2()
{
cout << "Son::func2()" << endl;
}
};

void test()
{
Son son;
}

int main()
{
test();
return 0;
}
/*
Grandpa()
Father()
Father::func1()
Son()
~Son()
~Father()
Father::func2()
~Grandpa()
*/

抽象类

参见 CppNote .

构造函数用protected修饰的类,也称为抽象类。

虚析构函数

虽然构造函数不能被定义成虚函数,但析构函数可以定义为虚函数,一般来说,如果类中定义了虚函数,析构函数也应被定义为虚析构函数,尤其是类内有申请的动态内存,需要清理和释放的时候。

来看一个例子:

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
#include <iostream>
#include <string.h>

using std::cout;
using std::endl;

class Base {
public:
Base(const char* pbase)
:_pbase(new char[strlen(pbase)+1]())
{
cout << "Base(const char*)" << endl;
strcpy(_pbase, pbase);
}

virtual void print() const {
if(_pbase) {
cout << "Base::_pbase = " << _pbase << endl;
}
}

~Base() {
cout << "~Base()" << endl;
if(_pbase) {
delete [] _pbase;
_pbase = nullptr;
}
}

private:
char* _pbase;
};

class Derived: public Base {
public:
Derived(const char* pbase, const char* pderived)
:Base(pbase), _pderived(new char[strlen(pderived)+1]())
{
cout << "Derived(const char*, const char*)" << endl;
strcpy(_pderived, pderived);
}

void print() const override {
if(_pderived) {
cout << "Derived::_pderived = " << _pderived << endl;
}
}

~Derived() {
cout << "~Derived()" << endl;
if(_pderived) {
delete [] _pderived;
_pderived = nullptr;
}
}

private:
char* _pderived;
};

int main() {
Base* pbase = new Derived("hello", "world");
pbase->print();

delete pbase;
pbase = nullptr;
return 0;
}
/*
Base(const char*)
Derived(const char*, const char*)
Derived::_pderived = world
~Base()
*/

在上面的代码中,~Derived()没有执行,内存泄露。

可以采用的不优雅的方法是:

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
#include <iostream>
#include <string.h>

using std::cout;
using std::endl;

class Base {
public:
Base(const char* pbase)
:_pbase(new char[strlen(pbase)+1]())
{
cout << "Base(const char*)" << endl;
strcpy(_pbase, pbase);
}

virtual void print() const {
if(_pbase) {
cout << "Base::_pbase = " << _pbase << endl;
}
}

~Base() {
cout << "~Base()" << endl;
if(_pbase) {
delete [] _pbase;
_pbase = nullptr;
}
}

private:
char* _pbase;
};

class Derived: public Base {
public:
Derived(const char* pbase, const char* pderived)
:Base(pbase), _pderived(new char[strlen(pderived)+1]())
{
cout << "Derived(const char*, const char*)" << endl;
strcpy(_pderived, pderived);
}

void print() const override {
if(_pderived) {
cout << "Derived::_pderived = " << _pderived << endl;
}
}

~Derived() {
cout << "~Derived()" << endl;
if(_pderived) {
delete [] _pderived;
_pderived = nullptr;
}
}

private:
char* _pderived;
};

int main() {
Base* pbase = new Derived("hello", "world");
pbase->print();

delete dynamic_cast<Derived*>(pbase);
pbase = nullptr;
return 0;
}
/*
Base(const char*)
Derived(const char*, const char*)
Derived::_pderived = world
~Derived()
~Base()
*/

另一种方法是,使用虚析构函数:

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
#include <iostream>
#include <string.h>

using std::cout;
using std::endl;

class Base {
public:
Base(const char* pbase)
:_pbase(new char[strlen(pbase)+1]())
{
cout << "Base(const char*)" << endl;
strcpy(_pbase, pbase);
}

virtual void print() const {
if(_pbase) {
cout << "Base::_pbase = " << _pbase << endl;
}
}

virtual ~Base() {
cout << "~Base()" << endl;
if(_pbase) {
delete [] _pbase;
_pbase = nullptr;
}
}

private:
char* _pbase;
};

class Derived: public Base {
public:
Derived(const char* pbase, const char* pderived)
:Base(pbase), _pderived(new char[strlen(pderived)+1]())
{
cout << "Derived(const char*, const char*)" << endl;
strcpy(_pderived, pderived);
}

void print() const override {
if(_pderived) {
cout << "Derived::_pderived = " << _pderived << endl;
}
}

~Derived() {
cout << "~Derived()" << endl;
if(_pderived) {
delete [] _pderived;
_pderived = nullptr;
}
}

private:
char* _pderived;
};

int main() {
Base* pbase = new Derived("hello", "world");
pbase->print();

delete pbase; // 体现多态
pbase = nullptr;
return 0;
}
/*
Base(const char*)
Derived(const char*, const char*)
Derived::_pderived = world
~Derived()
~Base()
*/

重载、隐藏、覆盖

重载:在同一个作用域中,函数的名字相同,但是参数列表不一样(包括参数的个数、参数类型、参数顺序)

重定义(重写、覆盖):发生在基类与派生类中,必须是虚函数,函数名字相同,参数列表也相同。

隐藏:发生在基类与派生类中,派生中的函数与基类中的函数名字相同。(至于是不是虚函数,至于参数列表是不是一样的没有关系),派生类的数据成员也可以隐藏基类中的同名数据成员。

看一个例子:

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
#include <iostream>

using std::cout;
using std::endl;

class Base {
public:
Base(double mem = 0.0)
: _member(mem)
{
cout << "Base(double = 0.0)" << endl;
}

void print() {
cout << "Base::_member = " << _member << endl;
}

~Base() {
cout << "~Base()" << endl;
}

private:
double _member;
};

class Derived: public Base {
public:
Derived(double mem = 0.0):Base(mem){
cout << "Derived(double = 0.0)" << endl;
}

void print(int x) {
cout << "Derived::x = " << x << endl;
}

~Derived() {
cout << "~Derived()" << endl;
}
};

int main() {
Derived d(11.11);
// d.print(); // 不ok
d.print(1); // ok
d.Base::print(); // ok
return 0;
}

虚表存在性的验证

参见 66-7.png

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <iostream>

using std::cout;
using std::endl;

class Base {
public:
Base(long base = 0):_base(base) {
cout << "Base(long = 0)" << endl;
}

~Base() {
cout << "~Base()" << endl;
}

virtual void f() {
cout << "void Base::f()" << endl;
}

virtual void g() {
cout << "void Base::g()" << endl;
}

virtual void h() {
cout << "void Base::h()" << endl;
}

private:
long _base;
};

class Derived:public Base {
public:
Derived(long base = 0, long derived = 0)
:Base(base), _derived(derived) {
cout << "Derived(long = 0, long = 0)" << endl;
}

~Derived() {
cout << "~Derived()" << endl;
}

void f() override {
cout << "void Derived::f()" << endl;
}

void g() override {
cout << "void Derived::g()" << endl;
}

void h() override {
cout << "void Derived::h()" << endl;
}

private:
long _derived;
};

void test() {
Derived myDerived(10, 20);
printf("对象 myDerived 的地址:%p\n", &myDerived);
printf("对象 myDerived 的地址:%p\n", (long*)&myDerived);
printf("虚表的地址:%p\n",(long*)*(long*)&myDerived);
printf("第一个虚函数的入口地址:%p\n",(long*)*(long*)*(long*)&myDerived);

typedef void (*pFunc) (void); // 函数指针
pFunc pf = nullptr;

cout << endl;
pf = (pFunc)*((long*)*(long*)&myDerived);
pf(); // 调用
printf("第一个虚函数的入口地址:%p\n",pf);

cout << endl;
pf = (pFunc)*((long*)*(long*)&myDerived + 1);
pf(); // 调用
printf("第二个虚函数的入口地址:%p\n",pf);

cout << endl;
pf = (pFunc)*((long*)*(long*)&myDerived + 2);
pf(); // 调用
printf("第三个虚函数的入口地址:%p\n",pf);


cout << endl;
Derived myDerived2(100, 200);
printf("对象 myDerived2 的地址:%p\n", &myDerived2);
printf("对象 myDerived2 的地址:%p\n", (long*)&myDerived2);
printf("虚表的地址:%p\n",(long*)*(long*)&myDerived2);
printf("第一个虚函数的入口地址:%p\n",(long*)*(long*)*(long*)&myDerived2);
}

int main() {
test();
return 0;
}
/*
Base(long = 0)
Derived(long = 0, long = 0)
对象 myDerived 的地址:0x7ffe729314c0
对象 myDerived 的地址:0x7ffe729314c0
虚表的地址:0x55da8aeb5d00
第一个虚函数的入口地址:0x55da8aeb3778

void Derived::f()
第一个虚函数的入口地址:0x55da8aeb3778

void Derived::g()
第二个虚函数的入口地址:0x55da8aeb37b6

void Derived::h()
第三个虚函数的入口地址:0x55da8aeb37f4

Base(long = 0)
Derived(long = 0, long = 0)
对象 myDerived2 的地址:0x7ffe729314e0
对象 myDerived2 的地址:0x7ffe729314e0
虚表的地址:0x55da8aeb5d00
第一个虚函数的入口地址:0x55da8aeb3778
~Derived()
~Base()
~Derived()
~Base()
*/

上面代码可以看出,对于普通单继承,虚表只有一张,位于只读段。

作业-词频统计

之前的作业,再用 map 实现。

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
#include <iostream>
#include <time.h>
#include <sstream>
#include <fstream>
#include <map>

using std::endl;
using std::cout;
using std::cerr;
using std::ifstream;
using std::ofstream;
using std::istringstream;
using std::string;
using std::map;

class Dictionary {
public:
Dictionary() = default;

void readFile(const string& ifileName) {
ifstream ifs(ifileName);
if(!ifs.good()) {
cerr << "file " << ifileName << "open failed." << endl;
return;
}

string line;
while(getline(ifs, line)) {
string word;
istringstream iss(line);
while(iss >> word) {
string new_word = process_word(word);
updmap(new_word);
}
}

ifs.close();
}

void store(const string& ofileName) {
ofstream ofs(ofileName);
if(!ofs.good()) {
cerr << "file " << ofileName << "open failed." << endl;
return;
}

auto it = word_cnt.cbegin();
for(; it != word_cnt.cend(); it++) {
ofs << it->first << " " << it->second << endl;
}
ofs.close();
}

private:
map<string,unsigned long> word_cnt;

string process_word(const string& word) {
for(int i = 0; i < word.size(); i++) {
if(!isalpha(word[i])) return string();
}
return word;
}

void updmap(const string& tobeInsert) {
if(tobeInsert == string()) return;
word_cnt[tobeInsert] ++;
}
};

int main() {
Dictionary myDic;
cout << "before reading..." << endl;
time_t beg = time(NULL);
myDic.readFile("The_Holy_Bible.txt");
time_t end = time(NULL);
cout << "time : " << (end - beg) << "s" << endl;
cout << "after reading..." << endl;
myDic.store("dictMap.dat");
return 0;
}

作业-文本查询

该程序将读取用户指定的任意文本文件【当前目录下的china_daily.txt】,然后允许用户从该文件中查找单词。查询的结果是该单词出现的次数,并列出每次出现所在的行。如果某单词在同一行中多次出现,程序将只显示该行一次。行号按升序显示。

要求:
a、它必须允许用户指明要处理的文件名字。
b、程序将存储该文件的内容,以便输出每个单词所在的原始行。
c、它必须将每一行分解为各个单词,并记录每个单词所在的所有行。在输出行号时,应保证以升序输出,并且不重复。
d、对特定单词的查询将返回出现该单词的所有行的行号。
e、输出某单词所在的行文本时,程序必须能根据给定的行号从输入文件中获取相应的行。

文本内容:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
1.Shenzhen-HK stock link 'likely in second quarter': UBS

The Chinese mainland will likely announce the launch of the much-anticipated
stock trading link between the Shenzhen and Hong Kong exchanges in the second
quarter of the year, further opening the mainland's stock market to overseas
investors, a senior analyst at UBS Securities said on Wednesday.

If the link is launched this year, it would mean that about 70 percent of market
capitalization of the A shares will be available to overseas investors who already
gained access to the mainland market through the existing Shanghai-Hong Kong
Stock Connect, said Gao Ting, chief China equities strategist at UBS Securities.

The link will broaden investors' stock selection in the mainland as the Shenzhen
market, unlike Shanghai, is home to many high-tech, innovative companies and
private firms with high growth potential, Gao said.

"The trading link will increase the investment opportunities for overseas investors
in sectors including healthcare, consumption, material and information technology,"
he said at a news conference in Beijing.

Premier Li Keqiang said in March that the central government will seek to launch the
Shenzhen-Hong Kong Stock Connect this year.

Bloomberg reported earlier citing people familiar with the matter that the launch
of the trading link may be announced before July. It quoted a spokesman for the
Hong Kong Exchanges & Clearing Ltd as saying that a preparatory period of three to
four months will be needed following the announcement of regulatory approval.

While overseas investors can already invest in the mainland's stock market under the
Qualified Foreign Institutional Investors program, the launch of the Shenzhen-Hong
Kong Stock Connect still holds symbolic significance as it will signal a further
opening of the capital market, analysts said.

Zhao Wenli, an analyst at China Merchants Securities (HK) Co Ltd, said in a
research note that good quality and reasonably priced small and mid-cap stocks
under the trading link will attract greater attention from foreign investors.



2.Going abroad? Don't forget your instant noodles

Chinese travelers like to take instant noodles with them while travelling abroad,
according to a report by Hong Kong Economic Times.
And the favorite items travelers like to bring with them in their luggage differed
greatly depending on their nationalities, according to a survey conducted by travel
website Lowcost Holidays, which sampled 7,500 travelers from 29 countries and regions.

Most of the items were edible, partially because travelers want a taste of home
wherever they go, such as kimchi for 55 percent of South Koreans, cheese for 53
percent of French, coffee for 64 percent of Italians, tea bags for 48 percent of
British, and miso soup (a very popular soup) for 56 percent of Japanese.

Others choices were somewhat surprising. Sixty-six percent of Belgians brought bath
towels for scrubbing their body, 32 percent of Americans brought toilet paper,
37 percent of Russians packed cards, and 30 percent of Swiss, their Swiss knives.


3.Popular eatery's duck tastes flight online

Quanjude, China's iconic restaurant chain for original Peking roast duck with a
history since 1864, has embraced the nation's "Internet Plus" strategy, with a new
online platform that features duck rolls and about 30 other dishes.
"Internet Plus" has sparked integration of the Internet with traditional industries,
and the food and beverage business has been no exception. In the catering and
dining industry, the past year has witnessed a speedy market increase in online
ordering, delivery and payment.

"Chinese people say life is all about clothing, eating, housing and traffic.
The Internet has changed all the other three industries before it started to change
dining and catering industry very recently, and Quanjude has been adapting into the new environment to make most out of it," says Xu Jia, chief accounting officer
with Quanjude and chairman of a new joint-venture company that is pursuing the online takeout and e-commerce market.

Recently, the State-owned restaurant chain announced in Beijing that it established
the company, Yage Technology Inc, in October 2015 with Chongqing Kuangcao Technology
Inc, an online-service company based in Chongqing.

"We believe with our time-honored brand image, experienced artisan cooking skills,
detail-oriented service and superb supply chain, we will succeed in this new sector,
" Xu says, "because even in the era of the Internet, what matters most in the
industry is still the food and service."

After more than a year's research and development, the new company has developed patented
techniques to make high-quality Peking roast duck rolls available for takeout diners,
testing the product in a six-month pilot project in Chongqing.

Under the brand name Xiaoyage, literally "little duck brother", the online-ordered
duck rolls are made in Quanjude restaurants, with the same recipe and ingredients
as rolls served in Quanjude restaurants.

At home or work, diners can reheat the duck rolls to 65 C in six minutes, using
a special bag that generates steam when water is poured on it.

The takeout duck rolls taste almost the same as those served in a Quanjude restaurant,
according to Yang Aixiang, general manager with Yage Technology.

The package costs 200 yuan ($31) each, and apart from the duck dish, there are
also more than 30 signature dishes of Quanjude available through online ordering
channels, such as mustard duck feet and spicy sliced duck wings. All can be ordered
through the official WeChat account (xiaoyage222) and the Baidu Takeaway platform.

Authorities in several municipalities have posed new regulations on speed and route
limits for delivery vehicles, including Beijing, the first and most important market for Quanjude Takeout and e-commerce.

Yang Xun, a publicist with Baidu Takeout, which handles delivery service of
Quanjude's takeout delicacies, says all their deliverymen will obey laws and
regulations to ensure best service for diners, including adjusting routes to
avoid barred roads for delivery vehicles.

我的代码:

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
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <set>

using std::cout;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::string;
using std::stringstream;
using std::vector;
using std::map;
using std::set;

class TextQuery {
public:
TextQuery() = default;

void readFile(const string& filename) {
ifstream ifs(filename);
if(!ifs.good()) {
std::cerr << "file " << filename << "open filed." << endl;
return;
}

string line;
int idx = 0;
while(getline(ifs, line)) {
idx++;
_lines.push_back(line);
stringstream ssm(line);
string word;
while(ssm >> word) {
_word2Numbers[word].insert(idx);
_dict[word]++;
}
}

ifs.close();
}

void query(const string& word) {
ofstream ofs("output.dat");
ofs << "the word '" << word << "' occurs " << _dict[word] << " times." << endl;
auto it = _word2Numbers[word].cbegin();
for(; it != _word2Numbers[word].cend(); it++) {
ofs << "(line " << *it << ") " << _lines[(*it)-1] << endl;
}
ofs.close();
}

private:
vector<string> _lines;
map<string, set<int> > _word2Numbers;
map<string, int> _dict;
};

int main(int argc, char* argv[]) {
TextQuery tq;
tq.readFile("china_daily.txt");
cout << "input the word you want to query: ";
string queryWord;
std::cin >> queryWord;
tq.query(queryWord);
return 0;
}

输出文件:

1
2
3
4
5
6
7
8
9
10
11
12
the word 'for' occurs 14 times.
(line 17) "The trading link will increase the investment opportunities for overseas investors
(line 25) of the trading link may be announced before July. It quoted a spokesman for the
(line 49) wherever they go, such as kimchi for 55 percent of South Koreans, cheese for 53
(line 50) percent of French, coffee for 64 percent of Italians, tea bags for 48 percent of
(line 51) British, and miso soup (a very popular soup) for 56 percent of Japanese.
(line 54) towels for scrubbing their body, 32 percent of Americans brought toilet paper,
(line 60) Quanjude, China's iconic restaurant chain for original Peking roast duck with a
(line 83) techniques to make high-quality Peking roast duck rolls available for takeout diners,
(line 102) limits for delivery vehicles, including Beijing, the first and most important market for Quanjude Takeout and e-commerce.
(line 106) regulations to ensure best service for diners, including adjusting routes to
(line 107) avoid barred roads for delivery vehicles.

可以用自带的记事本等软件,查找单词,验证结果的正确性。

注意,这里按照要求,必须是整个单词匹配,而不需要返回仅含有子串的单词。

多基派生的二义性

来看一个例子:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <iostream>

using std::cout;
using std::endl;

class A {
public:
virtual void a() {
cout << "virtual void A::a()" << endl;
}

virtual void b() {
cout << "virtual void A::b()" << endl;
}

virtual void c() {
cout << "virtual void A::c()" << endl;
}
};

class B {
public:
virtual void a() {
cout << "virtual void B::a()" << endl;
}

virtual void b() {
cout << "virtual void B::b()" << endl;
}

void c() {
cout << "void B::c()" << endl;
}

void d() {
cout << "void B::d()" << endl;
}
};

class C: public A, public B {
public:
virtual void a() {
cout << "virtual void C::a()" << endl;
}

void c() {
cout << "void C::c()" << endl;
}

void d() {
cout << "void C::d()" << endl;
}
};

void test() {
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl;
cout << "sizeof(C) = " << sizeof(C) << endl << endl;

C c;
cout << endl;
A* pa = &c;
pa->a();
pa->b();
pa->c();

cout << endl;
B* pb = &c;
pb->a(); // C::a()
pb->b(); // B::b()
pb->c(); // B::c()
pb->d(); // B::d()

cout << endl;
C* pc = &c;
pc->a();
// pc->b(); 二义性
pc->A::b();
pc->B::b();
pc->c();
pc->d(); // 隐藏
}

int main() {
test();
return 0;
}
/*
sizeof(A) = 8
sizeof(B) = 8
sizeof(C) = 16


virtual void C::a()
virtual void A::b()
void C::c()

virtual void C::a()
virtual void B::b()
void B::c()
void B::d()

virtual void C::a()
virtual void A::b()
virtual void B::b()
void C::c()
void C::d()
*/

原理:

66-8.png

多态之内存布局

注,参考代码:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
class Base1
{
public:
Base1()
: _iBase1(10)
{
}

virtual void f()
{
cout << "Base1::f()" << endl;
}

virtual void g()
{
cout << "Base1::g()" << endl;
}

virtual void h()
{
cout << "Base1::h()" << endl;
}

private:
int _iBase1;
};

class Base2
{
public:
Base2()
: _iBase2(100)
{
}

virtual void f()
{
cout << "Base2::f()" << endl;
}

virtual void g()
{
cout << "Base2::g()" << endl;
}

virtual void h()
{
cout << "Base2::h()" << endl;
}

private:
int _iBase2;
};

class Base3
{
public:
Base3()
: _iBase3(1000)
{
}

virtual void f()
{
cout << "Base3::f()" << endl;
}

virtual void g()
{
cout << "Base3::g()" << endl;
}

virtual void h()
{
cout << "Base3::h()" << endl;
}

private:
int _iBase3;
};

class Derived
: virtual public Base1,
public Base2,
public Base3
{
public:
Derived()
: _iDerived(10000)
{
}

void f()
{
cout << "Derived::f()" << endl;
}

virtual void g1()
{
cout << "Derived::g1()" << endl;
}

private:
int _iDerived;
};
// 23d1

注2,参考代码:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
class B
{
public:
B(): _ib(10), _cb('B'){}

virtual void f()
{
cout << "B::f()" << endl;
}

virtual void Bf()
{
cout << "B::Bf()" << endl;
}

private:
int _ib;
char _cb;
};

class B1
: /*virtual*/ public B
{
public:
B1(): _ib1(100), _cb1('1'){}

virtual void f()
{
cout << "B1::f()" << endl;
}

virtual void f1()
{
cout << "B1::f1()" << endl;
}

virtual void Bf1()
{
cout << "B1::Bf1()" << endl;
}

private:
int _ib1;
char _cb1;
};

class B2
: /*virtual*/ public B
{
public:
B2(): _ib2(1000), _cb2('2'){}

virtual void f()
{
cout << "B2::f()" << endl;
}

virtual
void f2()
{
cout << "B2::f2()" << endl;
}

virtual
void Bf2()
{
cout << "B2::Bf2()" << endl;
}

private:
int _ib2;
char _cb2;
};

class D
: public B1,
public B2
{
public :
D(): _id(10000), _cd('3'){}

virtual void f()
{
cout << "D::f()" << endl;
}

virtual void f1()
{
cout << "D::f1()" << endl;
}

virtual void f2()
{
cout << "D::f2()" << endl;
}

virtual void Df()
{
cout << "D::Df()" << endl;
}

private:
int _id;
char _cd;
};

在 C++ 中,如果继承链上存在虚继承的基类,则最底层的子类要负责完成该虚基类部分成员的构造。
即我们需要显式调用虚基类的构造函数来完成初始化,如果不显式调用,则编译器会调用虚基类的缺省构造函数,不管初始化列表中次序如何,对虚基类构造函数的调用总是先于普通基类的构造函数。如果虚基类中没有定义的缺省构造函数,则会编译错误因为如果不这样做,虚基类部分会在存在的多个继承链上被多次初始化。很多时候,对于继承链上的中间类,我们也会在其构造函数中显式调用虚基类的构造函数,因为一旦有人要创建这些中间类的对象,我们要保证它们能够得到正确的初始化。

对于虚继承的派生类对象的析构,析构函数的调用顺序为:

  • 先调用派生类的析构函数
  • 然后调用派生类中成员对象的析构函数
  • 再调用普通基类的析构函数
  • 最后调用虚基类的析构函数

效率分析:

66-9.png

作业-解析RSS文件

使用 tinyXml2 解析 RSS 文件,并生成一个网页库pagelib.dat

tinyXml2 -- https://github.com/leethomason/tinyxml2
rss      -- https://coolshell.cn/feed
         -- http://www.runoob.com/rss/rss-tutorial.html

思路分析

使用正则表达式进行过滤。

rss 文件其实就是一个树形结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<rss>
<channel>
<item>
<title> </title>
<link> </link>
<description> </description>
<content> </content>
</item>

<item>
<title> </title>
<link> </link>
<description> </description>
<content> </content>
</item>
<item>
</item>
</channel>
</rss>

参考接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct RssItem
{
string title;
string link;
string description;
string content;
};

class RssReader
{
public:
RssReader();
void parseRss();//解析
void dump(const string & filename);//输出
private:
vector<RssItem> _rss;
};

要求:最后生成一个pagelib.txt, 其格式:

<doc>
    <docid>1</docid>
    <title>...</title>
    <link>...</link>
    <description>...</description>
    <content>...</content>
</doc>
<doc>
    <docid>2</docid>
    <title>...</title>
    <link>...</link>
    <description>...</description>
    <content>...</content>
</doc>
<doc>
  ...
</doc>

RSS文件解析作业思路:
xml —> rss —> tinyxml2(使用该库对rss文件进行解析) —> boost::regex/std::regex (使用正则表达式去除html标签)

提示:
首先去读 coolshell.xml 文件,因为是一个 rss 文件,而我们需要找到 rss 的 channel 节点下面的 item 节点的 title 节点、link 节点中间的文本,至于这些文本可以使用 tinyxml2 这个第三方库进行解析,所以这里需要看看 timyxml2 如何解析第三方库(可以看看 timyxml2 的源码),解析完成一个 item 后,可以将其存到 vector 中(也可以将这些原始信息经过后面正则表达式去除标签后再存起来),然后再去读第二个 item(其实就是一个循环操作),其实第二个 item 是第一个 item 的兄弟节点(可以使用 tinyxml2 里面的函数进行跳转到第二个 item),这样就可以解析 coolshell.xml 文档了。
接着,因为 description 信息中包含 html 的标签,所以需要去除这样的 html 标签,如<p></p>,这个可以使用正则表达式,也就是 std::regex 进行去除,这个可以在 cppreference 中进行查找使用方法。
最后就是遍历 vector,讲读取到的信息存到另外一个文件,格式需要自己定义,使用我们自定义的<doc> </doc>格式。

一些方法论

如何研究一个陌生的第三方代码

  1. 若只要求达到使用标准,善用搜索。
  2. 找源代码。
  3. 查找头文件、实现文件、测试文件。
    66-10.png
  4. 在测试文件中找 main 函数。

youtube 备份视频:初步写码的一些方法论

代码实现

github 地址:https://github.com/leethomason/tinyxml2

官方文档:https://leethomason.github.io/tinyxml2/

首先看下我写的代码:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include "tinyxml2.h"
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <regex>

using std::string;
using std::vector;
using std::cerr;
using std::endl;
using std::ofstream;
using namespace tinyxml2;

struct RssItem
{
string title;
string link;
string description;
string content;
};

class RssReader
{
public:
RssReader() = default;

void Let_Me_See_See(const char* _dir) {
XMLDocument doc;
doc.LoadFile(_dir);
if(doc.ErrorID()) {
cerr << "load file failed." << endl;
return;
}

XMLElement* myItemElem = doc.FirstChildElement("rss")
-> FirstChildElement("channel")
-> FirstChildElement("item");

if(myItemElem == nullptr) {
cerr << "this xml file is invalid." << endl;
return;
}

processItem(myItemElem);

while(myItemElem->NextSiblingElement("item")) {
myItemElem = myItemElem -> NextSiblingElement("item");
processItem(myItemElem);
}
}

void dump(const string& fileName) {
ofstream ofs(fileName);
if(!ofs) {
cerr << "file " << fileName << "open failed." << endl;
return;
}

auto it = _rss.cbegin();
unsigned int cnt = 0;
for(; it != _rss.cend(); it++) {
cnt ++;
ofs << "<doc>" << endl
<< " <docid>" << cnt << "</docid>" << endl
<< " <title>" << it->title << "</title>" << endl
<< " <link>" << it->link << "</link>" << endl
<< " <description>" << it->description << "</description>" << endl
<< " <content>" << it->content << "</content>" << endl
<< "</doc>" << endl;
}
ofs.close();
}

private:
void processItem(XMLElement* p) {
// get title
XMLText* myTitleNode = p -> FirstChildElement("title")
-> FirstChild() -> ToText();
string mytitle = myTitleNode -> Value();

// get link
XMLText* myLinkNode = p -> FirstChildElement("link")
-> FirstChild() -> ToText();
string mylink = myLinkNode -> Value();

XMLText* myDescriptionNode = p -> FirstChildElement("description")
-> FirstChild() -> ToText();
string mydescription = myDescriptionNode -> Value();
std::regex reg("<[^>]+>");
mydescription = regex_replace(mydescription, reg, "");

// get content
XMLText* myContNode = p -> FirstChildElement("content:encoded")
-> FirstChild() -> ToText();
string mycontent = myContNode -> Value();
mycontent = regex_replace(mycontent, reg, "");

RssItem tmp = (RssItem){mytitle, mylink, mydescription, mycontent};
_rss.push_back(tmp);
}

vector<RssItem> _rss;
};

int main() {
RssReader akashi;
akashi.Let_Me_See_See("./feed.xml");
akashi.dump("pagelib.dat");
return 0;
}

输出文件截图如下:

66-11.png

从效果上看,这份代码就是把文字部分扒了下来。

作业-抽象类

要求

1、编写一个抽象类 Figure,该类拥有:
① 1个成员变量,存放图形的名字(是否该设计成private/protected?)
② 2个纯虚函数

1
2
3
virtual double getArea( )=0
virtual string getName( )=0
virtual void show()=0 //打印图形的相关信息

2、编写一个圆类 Circle,让其继承自 Figure 类,该类拥有:

① 1个成员变量,存放圆的半径;(是否该设计成private/protected?)
② 2个构造方法

1
2
Circle( )              // 将半径设为0           
Circle(double r) //创建Circle对象时将半径初始化为r

③ 3个成员方法

1
2
3
4
5
6
double getRadius( )           //获取圆的半径
double getPerimeter( ) //获取圆的周长
virtual double getArea( ) //获取圆的面积
virtual string getName( ) //获取圆的名字

void show( ) //将圆的半径、周长、面积输出到屏幕

3、编写一个圆柱体类 Cylinder,它继承于上面的 Circle 类,还拥有:
① 1个成员变量,圆柱体的高;
② 构造方法

1
Cylinder (double r, double  h) //创建Circle对象时将半径初始化为r         

③ 成员方法

1
2
3
4
5
覆盖Circle的getArea( )   //获取圆柱体的表面积 
覆盖Circle的getName( ) //获取图形的名字
double getHeight( ) //获取圆柱体的高
double getVolume( ) //获取圆柱体的体积
void show() //将圆柱体的高、表面积、体积输出到屏幕

4、编写测试用例,在实现的过程中,体会动态多态的用法。
① 创建类的对象,分别设置圆的半径、圆柱体的高
② 计算并分别显示圆半径、圆面积、圆周长,
③ 计算并分别显式圆柱体的高、表面积、体积。

代码

这个要求有点奇怪,不能完全满足,比如那个圆柱的 getName() 我就没有重写。

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <iostream>
#include <string>
#include <math.h>
#define PI acos(-1)

using std::cout;
using std::endl;
using std::string;

class Figure {
public:
Figure() = default;

virtual double getArea() = 0;
virtual string getName() = 0;
virtual void show()=0;

protected:
string _name;
};

class Circle: public Figure {
public:
Circle(): _radius(0) {
_name = "Circle";
}
Circle(double r): _radius(r) {
_name = "Circle";
}

double getArea() override {
return PI*_radius*_radius;
}

string getName() override {
return _name;
}

double getRadius() const {
return _radius;
}

double getPerimeter() const {
return 2*PI*_radius;
}

void show() override {
cout << "name = " << _name << endl
<< "radius = " << _radius << endl
<< "perimeter = " << getPerimeter() << endl
<< "area = " << getArea() << endl << endl;
}

protected:
double _radius;
};

class Cylinder: public Circle {
public:
Cylinder(double r, double h)
:Circle(r), _height(h) {_name = "Cylinder";}

double getArea() override {
double sum1 = Circle::getArea();
double sum2 = Circle::getPerimeter() * _height;
return sum1*2 + sum2;
}

double getHeight() const {
return _height;
}

double getVolume(){
return Circle::getArea() * _height;
}

void show() override {
cout << "name = " << _name << endl
<< "height = " << _height << endl
<< "area = " << getArea() << endl
<< "volume = " << getVolume() << endl << endl;
}

protected:
double _height;
};

int main() {
Circle o1(1.0);
o1.show();

Circle o2(2.0);
o2.show();

Cylinder cy1(1.0, 1.0);
cy1.show();

Cylinder cy2(2.0, 2.0);
cy2.show();

return 0;
}
/*
name = Circle
radius = 1
perimeter = 6.28319
area = 3.14159

name = Circle
radius = 2
perimeter = 12.5664
area = 12.5664

name = Cylinder
height = 1
area = 12.5664
volume = 3.14159

name = Cylinder
height = 2
area = 50.2655
volume = 25.1327

*/

移动语义

引入。来看下面的例子:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <string.h>
#include <iostream>

using std::cout;
using std::endl;

class String
{
public:
String()
: _pstr(nullptr)
/* : _pstr(new char[1]()) */
{
cout << "String()" << endl;
}

String(const char *pstr)
: _pstr(new char[strlen(pstr) + 1]())
{
cout << "String(const char *)" << endl;
strcpy(_pstr, pstr);
}

String(const String &rhs)
: _pstr(new char[strlen(rhs._pstr) + 1]())
{
cout << "String(const String &)" << endl;
strcpy(_pstr, rhs._pstr);
}

String &operator=(const String &rhs)
{
cout << "String &operator=(const String &)" << endl;
if(this != &rhs)//1、自复制
{
delete [] _pstr;//2、释放左操作数
_pstr = nullptr;

//3、深拷贝
_pstr = new char[strlen(rhs._pstr) + 1]();
strcpy(_pstr, rhs._pstr);
}

//4、返回*this
return *this;
}

~String()
{
cout << "~String()" << endl;
if(_pstr)
{
delete [] _pstr;
_pstr = nullptr;
}
}

friend std::ostream &operator<<(std::ostream &os, const String &rhs);
private:
char *_pstr;
};

std::ostream &operator<<(std::ostream &os, const String &rhs)
{
if(rhs._pstr)
{
os << rhs._pstr;
}
return os;
}

void test()
{
String s1("hello");
cout << "s1 = " << s1 << endl;

cout << endl;
String s2 = s1;
cout << "s1 = " << s1 << endl;
cout << "s2 = " << s2 << endl;

cout << endl;
// C++ C C风格转换为C++风格
// 过渡
String s3 = "world";//String("world"),临时对象/匿名对象,
cout << "s3 = " << s3 << endl;

/* &"world";//文字常量区,左值 */
/* String("world");//右值 */
}

int main(int argc, char **argv)
{
test();
return 0;
}
/*
String(const char *)
s1 = hello

String(const String &)
s1 = hello
s2 = hello

String(const char *)
s3 = world
~String()
~String()
~String()
*/

注意第 85 行,编译器并没有调用复制构造函数String(const String&)来复制这个临时对象到 s3 中。相反,它直接将临时对象的内容移动到 s3 中,这是因为在这种情况下编译器会执行一种叫做”复制省略”(copy elision)的优化。这个优化允许编译器在一定条件下避免创建临时对象,直接将临时对象的内容移到目标对象中,这样可以提高效率。

【存疑】然而按照教学演示,可以使用编译选项避免这个优化。在这种情况下,第 85 行会调用两个构造函数:String(const char*)String(const String&)。即在没有编译器优化的情况下,隐式转换之后会调用拷贝构造函数。在我的环境中即使开了编译选项也不会如此,可能是新的 g++ 版本并不支持这样做。

如何区分一个变量是否是右值?

在 C++11 之前是不能识别右值的,C++11 之后新增语法可以识别右值。

右值引用可以识别、绑定到右值,但不能识别、绑定到左值:

1
2
int &&rref = 10;   //右值引用
int &&rref2 = a; //error, a 是左值

由此,写出如下函数:

1
2
3
4
5
6
7
8
9
//移动构造函数优先于拷贝构造函数执行的(优先级)
//移动构造函数
//String s3 = String("world");
String(String &&rhs)
:_pstr(rhs._pstr)
{
cout << "String(String &&)" << endl;
rhs._pstr = nullptr;
}

解读:我们直接进行了一个浅拷贝。临时变量销毁时,为了防止此时实际意义上已经归 this 所有的空间被销毁,需要将 rhs._pstr 置空。

考虑如下代码:

1
2
s4 = String("wuhan");
cout << "s4 = " << s4 << endl;

类似地,我们可以写出如下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//移动赋值运算符函数优先于赋值运算符函数执行的(优先级)
//移动赋值运算符函数(移动赋值函数)
//s4 = String("wuhan")
String& operator=(String&& rhs)
{
cout << "String& operator=(String &&)" << endl;

delete [] _pstr; //释放左操作数,防止内存泄露
_pstr = nullptr;

_pstr = rhs._pstr; //浅拷贝
rhs._pstr = nullptr;

return *this; //返回*this
}

注意到上面代码中没有防止自复制的逻辑,这是否必要呢?

1
2
//左右操作数是两个不一样对象
/* String("wuhan") = String("wuhan"); */

目前来看,似乎没有必要。

但是考虑如下代码:

1
2
3
4
5
cout << "000000" << endl;
//std::move可以将左值转换为右值
s4 = std::move(s4);
cout << "s4 = " << s4 << endl;
cout << "11111" << endl;

输出结果:

66-12.png

以上输出结果需要联系移动赋值运算符函数、输出函数来分析。

可见,移动赋值运算符函数仍然需要改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//移动赋值运算符函数优先于赋值运算符函数执行的(优先级)
//移动赋值运算符函数(移动赋值函数)
//s4 = String("wuhan")
//s4 = std::move(s4)
//s4 = std::move(s5)
String &operator=(String &&rhs)
{
cout << "String &operator=(String &&)" << endl;
if(this != &rhs)//1、自移动
{
delete [] _pstr;//2、释放左操作数
_pstr = nullptr;

_pstr = rhs._pstr;//3、浅拷贝
rhs._pstr = nullptr;
}

return *this;//4、返回*this
}

进一步研究一下 std::move() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
//std::move可以将左值转换为右值,实质上没有做任何移动,只是
//在底层做了强制转换static_cast<T &&>(lvalue)
//如果以后不想使用某个左值,可以使用std::move将其转换为
//右值,以后就不再使用了
s4 = std::move(s4);
cout << "s4 = " << s4 << endl;
cout << "11111" << endl;

std::move(s1);

cout << "s1 = " << s1 << endl;
cout << "2222" << endl;

输出结果:

66-13.png

此时 s1 里面的内容还没有转走(std::move()没有做移动操作)。

而如果走到移动赋值运算符函数中,内容才会被转走:

66-14.png

该例完整代码:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include <string.h>
#include <iostream>

using std::cout;
using std::endl;

class String
{
public:
String()
: _pstr(nullptr)
/* : _pstr(new char[1]()) */
{
cout << "String()" << endl;
}

String(const char *pstr)
: _pstr(new char[strlen(pstr) + 1]())
{
cout << "String(const char *)" << endl;
strcpy(_pstr, pstr);
}

String(const String &rhs)
: _pstr(new char[strlen(rhs._pstr) + 1]())
{
cout << "String(const String &)" << endl;
strcpy(_pstr, rhs._pstr);
}

String &operator=(const String &rhs)
{
cout << "String &operator=(const String &)" << endl;
if(this != &rhs)//1、自复制
{
delete [] _pstr;//2、释放左操作数
_pstr = nullptr;

//3、深拷贝
_pstr = new char[strlen(rhs._pstr) + 1]();
strcpy(_pstr, rhs._pstr);
}

//4、返回*this
return *this;
}

//移动构造函数优先于拷贝构造函数执行的(优先级)
//移动构造函数
//String s3 = String("world");
String(String &&rhs)
:_pstr(rhs._pstr)
{
cout << "String(string &&)" << endl;
rhs._pstr = nullptr;
}

//移动赋值运算符函数优先于赋值运算符函数执行的(优先级)
//移动赋值运算符函数(移动赋值函数)
//s4 = String("wuhan")
//s4 = std::move(s4)
//s4 = std::move(s5)
String &operator=(String &&rhs)
{
cout << "String &operator=(String &&)" << endl;
if(this != &rhs)//1、自移动
{
delete [] _pstr;//2、释放左操作数
_pstr = nullptr;

_pstr = rhs._pstr;//3、浅拷贝
rhs._pstr = nullptr;
}

return *this;//4、返回*this
}

~String()
{
cout << "~String()" << endl;
if(_pstr)
{
delete [] _pstr;
_pstr = nullptr;
}
}

friend std::ostream &operator<<(std::ostream &os, const String &rhs);
private:
char *_pstr;
};

std::ostream &operator<<(std::ostream &os, const String &rhs)
{
if(rhs._pstr)
{
os << rhs._pstr;
}
return os;
}

void test()
{
String s1("hello");
cout << "s1 = " << s1 << endl;

cout << endl;
String s2 = s1;
cout << "s1 = " << s1 << endl;
cout << "s2 = " << s2 << endl;

cout << endl;
// C++ C C风格转换为C++风格
// 过渡
String s3 = "world";//String("world"),临时对象/匿名对象,
cout << "s3 = " << s3 << endl;

/* &"world";//文字常量区,左值 */
/* String("world");//右值 */

cout << endl;
String s4("wangdao");
cout << "s4 = " << s4 << endl;

cout << endl;
s4 = String("wuhan");
cout << "s4 = " << s4 << endl;

//左右操作数是两个不一样对象
/* String("wuhan") = String("wuhan"); */
cout << endl;
cout << "000000" << endl;
//std::move可以将左值转换为右值,实质上没有做任何移动,只是
//在底层做了强制转换static_cast<T &&>(lvalue)
//如果以后不想使用某个左值,可以使用std::move将其转换为
//右值,以后就不再使用了
s4 = std::move(s4);
cout << "s4 = " << s4 << endl;
cout << "11111" << endl;

s2 = std::move(s1);

cout << "s1 = " << s1 << endl;
cout << "2222" << endl;

}

int main(int argc, char **argv)
{
test();
return 0;
}

拷贝构造函数赋值运算符函数称为具有复制控制语义的函数

移动构造函数移动赋值运算符函数称为具有移动语义的函数

最后来看一个细节:

1
2
3
4
5
int main() {
int&& rref = 10; // 右值引用
&rref; // 右值引用在此处是左值
return 0;
}

右值引用作为函数返回类型的时候,是右值:

1
2
3
4
5
6
7
8
9
10
11
int&& func() {
return 10;
}

int main() {
&func();
return 0;
}
/*
error: taking address of rvalue [-fpermissive]
*/

总结:

  • 左值、右值、左值引用、const左值引用、右值引用。
  • 区分左值与右值的是能不能取地址。
  • 左值引用:可以绑定到左值,但是不能绑定到右值。
  • const左值引用:既可以绑定到左值也可以绑定到右值。(正因如此,才将拷贝构造函数写成const左值引用)
  • 右值引用:可以绑定到右值,但是不能绑定到左值。(正因如此,才能有移动语义的函数)

资源管理

资源管理是 C 的难点:

1
2
3
4
5
6
7
8
9
10
void UseFile(char const* fn)
{
FILE* f = fopen(fn, “r”); // 获取资源
…… // 使用资源
if (!g()) { fclose(f); return; }
// ...
if (!h()) { fclose(f); return; }
// ...
fclose(f); // 释放资源
}

困难在于:

  • 用于释放资源的代码需要在不同的位置重复书写多次。
  • 如果再加入异常处理,fclose(f)情况会变得更加复杂。

RAII(Resource Acquisition Is Initialization) 是一种由 C++创造者 Bjarne Stroustrup 提出的, 利用栈对象生命周期管理程序资源(包括内存、文件句柄、锁等)的技术。

使用 RAII 时,一般在资源获得的同时构造对象, 在对象生存期间,资源一直保持有效;对象析构时,资源被释放。

关键:要保证资源的释放顺序与获取顺序严格相反。

看一个简单例子:

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
#include <iostream>
#include <string>

using std::endl;
using std::cout;
using std::string;

class SafeFile {
public:
SafeFile(FILE* fp): _fp(fp){
cout << "SafeFile(FILE*)" << endl;
}

void write(const string& msg) {
fwrite(msg.c_str(), 1, msg.size(), _fp);
}

~SafeFile() {
cout << "~SafeFile()" << endl;
if(_fp) {
fclose(_fp);
cout << "fclose(_fp)" << endl;
}
}

private:
FILE* _fp;
};

int main() {
// sf 是栈对象
SafeFile sf(fopen("test.txt", "a+"));

string msg = "delishashijiediyikeai\n";
sf.write(msg);

return 0;
}
/*
wanko@wanko:~/mycode$ g++ tmptest.cc
wanko@wanko:~/mycode$ ./a.out
SafeFile(FILE*)
~SafeFile()
fclose(_fp)
wanko@wanko:~/mycode$ cat test.txt
delishashijiediyikeai

*/

RAII 特征:

  1. 在构造函数中获取资源或者托管资源
  2. 在析构函数中释放资源
  3. 提供若干访问资源的方法
  4. 一般不允许复制或赋值(将拷贝构造函数或者赋值运算符函数删除即可)

RAII 的本质是用栈对象的生命周期来管理资源,因为栈对象在离开作用域时,会自动调用析构函数。

对象语义:不能进行复制或赋值。(在流的代码中,都是不能复制或赋值的)
值语义:可以进行复制或赋值。

实现 RAII 的逻辑:

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
#include <iostream>

using std::cout;
using std::endl;

class point{
public:
explicit point(int x=0, int y=0):ix(x),iy(y){
std::cout << "构造函数调用" << std::endl;
}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point(){ //析构函数
// destructor cannot have any parameters
std::cout << "~point()" << std::endl; // for test
}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy){
std::cout << "拷贝构造函数" << std::endl;
}
private:
int ix;
int iy;
};

template<typename T>
class RAII {
public:
// 在构造函数中初始化资源
RAII(T* data): _data(data) {
cout << "RAII(T*)" << endl;
}

// 在析构函数中释放资源
~RAII() {
cout << "~RAII()" << endl;
if(_data) {
// 假如指针是 new 出来的
delete _data;
_data = nullptr;
}
}

// 提供若干访问资源的方法
T* operator -> () {
return _data;
}

T& operator * () {
return *_data;
}

T* get() const {
return _data;
}

void reset(T* data) {
if(_data) {
delete _data;
_data = nullptr;
}
_data = data;
}

// 不允许复制或赋值
RAII(const RAII& rhs) = delete;
RAII& operator = (const RAII& rhs) = delete;

private:
T* _data;
};

int main() {
RAII<point> pt(new point(1, 2));
pt->print();
pt.operator->()->print();
return 0;
}
/*
构造函数调用
RAII(T*)
1 2
1 2
~RAII()
~point()
*/

在上面的代码中,pt 本身不是指针,但具备指针的功能,也不用操心空间的回收,由此引出智能指针的概念。

智能指针

智能指针(Smart Pointer)

  • 是存储指向动态分配(堆)对象的指针的类
  • 在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象

C++11 提供了以下几种智能指针,位于头文件<memory>,它们都是类模板

  • std::auto_ptr (复制/赋值)
  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

auto_ptr

来看下面关于 auto_ptr 的例子:

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 <iostream>
#include <memory>

using std::cout;
using std::endl;
using std::auto_ptr;

void test() {
int* pInt = new int(10);
auto_ptr<int> ap(pInt);

cout << "*pInt = " << *pInt << endl;
cout << "*ap = " << *ap << endl;

// delete pInt;
// pInt = nullptr;
// 不需要,因为被 auto_ptr 托管了

cout << endl;
auto_ptr<int> ap2 = ap;
cout << "*ap2 = " << *ap2 << endl;
cout << "*ap = " << *ap << endl; // error

}

int main() {
test();
return 0;
}
/*
// some warning
*pInt = 10
*ap = 10

*ap2 = 10
Segmentation fault (core dumped)
*/

出现错误的原因是:在执行拷贝操作的时候,会将 ap 托管的资源交给 ap2 ,然后将 ap 的数据成员置空。

更进一步地,我们看一下 auto_ptr 源码的逻辑(并非完全一致,为方便理解作了少量修改):

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
class auto_ptr
{
public:
//auto_ptr<int> ap2 = ap;
//_Tp = int
//auto_ptr &__a = ap;
auto_ptr(auto_ptr& __a)
: _M_ptr(__a.release())
{

}

_Tp* release()
{
_Tp* __tmp = _M_ptr;
_M_ptr = nullptr;
return __tmp;
}

_Tp& operator*() const
{
return *_M_ptr;
}

private:
_Tp* _M_ptr;
};

即表面上执行了拷贝操作,但底层已经发生了所有权的转移。auto_ptr在设计上存在缺陷。

unique_ptr

std::unique_ptr是一个独享所有权的智能指针,它提供了一种严格语义上的所有权,包括:

  • 拥有它所指向的对象
  • 无法进行复制、赋值操作
  • 保存指向某个对象的指针,当它本身被删除释放的时候,会使用给定的删除器释放它指向的对象
  • 具有移动(std::move)语义,可做为容器元素
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
#include <iostream>
#include <memory>
#include <vector>

using std::cout;
using std::endl;
using std::unique_ptr;
using std::vector;

void test() {
unique_ptr<int> up(new int(10));
cout << "*up = " << *up << endl;

cout << endl;
// unique_ptr<int> up2 = up; // 在语法层面报错

unique_ptr<int> up3(new int(34));
// up3 = up; // error

cout << endl;
vector<unique_ptr<int>> vec;
// unique_ptr 据有移动语义(有移动构造函数和移动赋值函数)
// 所以可以作为容器的元素
vec.push_back(std::move(up));
vec.push_back(unique_ptr<int>(new int(30)));
}

int main() {
test();
return 0;
}

需要构建右值的时候,可以使用如下两种方法:

  1. 直接显示调用构造函数,创建临时对象
  2. 使用std::move函数将左值转换为右值。

需要构建左值的时候,可以使用如下两种方法:

  1. 可以使用构造函数创建对象,创建有名对象。Point pt(1, 2);
  2. 可以使用右值引用将右值转换为左值,Point &&rref = Point(1, 2)

shared_ptr

std::shared_ptr是一个引用计数智能指针,用于共享对象的所有权

  1. 引进了一个计数器shared_count,用来表示当前有多少个智能指针对象共享指针指向的内存块
  2. 析构函数中不是直接释放指针对应的内存块,如果shared_count大于0则不释放内存只是将引用计数减1,只有计数等于0时释放内存
  3. 复制构造与赋值操作符只是提供一般意义上的复制功能,并且将引用计数加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
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
#include <iostream>
#include <memory>
#include <vector>

using std::cout;
using std::endl;
using std::shared_ptr;
using std::vector;

void test() {
shared_ptr<int> sp(new int(10));
cout << "*sp = " << *sp << endl;
cout << "sp.get() = " << sp.get() << endl;
cout << "sp.use_count() = " << sp.use_count() << endl;

cout << endl;
shared_ptr<int> sp2 = sp; // ok
cout << "*sp = " << *sp << endl;
cout << "*sp2 = " << *sp2 << endl;
cout << "sp.get() = " << sp.get() << endl;
cout << "sp2.get() = " << sp2.get() << endl;
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "sp2.use_count() = " << sp2.use_count() << endl;

cout << endl;
shared_ptr<int> sp3(new int(34));
cout << "*sp3 = " << *sp3 << endl;
cout << "sp3.get() = " << sp3.get() << endl;
cout << "sp3.use_count() = " << sp3.use_count() << endl;

cout << endl;
sp3 = sp; // 赋值,ok
cout << "*sp = " << *sp << endl;
cout << "*sp2 = " << *sp2 << endl;
cout << "*sp3 = " << *sp3 << endl;
cout << "sp.get() = " << sp.get() << endl;
cout << "sp2.get() = " << sp2.get() << endl;
cout << "sp3.get() = " << sp3.get() << endl;
cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "sp2.use_count() = " << sp2.use_count() << endl;
cout << "sp3.use_count() = " << sp3.use_count() << endl;
}

int main() {
test();
return 0;
}
/*
*sp = 10
sp.get() = 0x5c03fdecaeb0
sp.use_count() = 1

*sp = 10
*sp2 = 10
sp.get() = 0x5c03fdecaeb0
sp2.get() = 0x5c03fdecaeb0
sp.use_count() = 2
sp2.use_count() = 2

*sp3 = 34
sp3.get() = 0x5c03fdecb300
sp3.use_count() = 1

*sp = 10
*sp2 = 10
*sp3 = 10
sp.get() = 0x5c03fdecaeb0
sp2.get() = 0x5c03fdecaeb0
sp3.get() = 0x5c03fdecaeb0
sp.use_count() = 3
sp2.use_count() = 3
sp3.use_count() = 3
*/

再来看是否可以作为容器的元素:

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
#include <iostream>
#include <memory>
#include <vector>

using std::cout;
using std::endl;
using std::shared_ptr;
using std::vector;

class point{
public:
explicit point(int x=0, int y=0):ix(x),iy(y){
std::cout << "构造函数调用" << std::endl;
}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point(){ //析构函数
// destructor cannot have any parameters
std::cout << "~point()" << std::endl; // for test
}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy){
std::cout << "拷贝构造函数" << std::endl;
}
private:
int ix;
int iy;
};

void test() {
shared_ptr<point> sp4(new point(1,2));
vector<shared_ptr<point>> vec;
vec.push_back(std::move(sp4));
vec.push_back(shared_ptr<point>(new point(3,4)));

vec.push_back(sp4);
}

int main() {
test();
return 0;
}
/*
构造函数调用
构造函数调用
~point()
~point()
*/

shared_ptr 存在的问题:循环引用。

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
#include <iostream>
#include <memory>

class Parent;
class Child;

typedef std::shared_ptr<Parent> parent_ptr;
typedef std::shared_ptr<Child> child_ptr;

class Child
{
public:
Child() { std::cout << "Child..." << std::endl; }
~Child() { std::cout << "~Child..." << std::endl; }
parent_ptr parent_;
};

class Parent
{
public:
Parent() { std::cout << "Parent..." << std::endl; }
~Parent() { std::cout << "~Parent..." << std::endl; }
child_ptr child_;
};

int main(void)
{
parent_ptr parent(new Parent);
child_ptr child(new Child);

std::cout << "parent.use_count() = " << parent.use_count() << std::endl;
std::cout << "child.use_count() = " << child.use_count() << std::endl;

parent->child_ = child;
//parent.operator->()->child_ = child;
child->parent_ = parent;


std::cout << "parent.use_count() = " << parent.use_count() << std::endl;
std::cout << "child.use_count() = " << child.use_count() << std::endl;

return 0;
}
/*
Parent...
Child...
parent.use_count() = 1
child.use_count() = 1
parent.use_count() = 2
child.use_count() = 2
*/

以上代码存在内容泄露,原因:

66-15.png

66-16.png

解决方法:

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
#include <iostream>
#include <memory>

class Parent;
class Child;

typedef std::shared_ptr<Parent> parent_ptr;
typedef std::shared_ptr<Child> child_ptr;

class Child
{
public:
Child() { std::cout << "Child..." << std::endl; }
~Child() { std::cout << "~Child..." << std::endl; }
std::weak_ptr<Parent> parent_; // 不会使引用计数++
};

class Parent
{
public:
Parent() { std::cout << "Parent..." << std::endl; }
~Parent() { std::cout << "~Parent..." << std::endl; }
child_ptr child_;
};

int main(void)
{
parent_ptr parent(new Parent);
child_ptr child(new Child);

std::cout << "parent.use_count() = " << parent.use_count() << std::endl;
std::cout << "child.use_count() = " << child.use_count() << std::endl;

parent->child_ = child;
//parent.operator->()->child_ = child;
child->parent_ = parent;


std::cout << "parent.use_count() = " << parent.use_count() << std::endl;
std::cout << "child.use_count() = " << child.use_count() << std::endl;

return 0;
}
/*
Parent...
Child...
parent.use_count() = 1
child.use_count() = 1
parent.use_count() = 1
child.use_count() = 2
~Parent...
~Child...
*/

weak_ptr

特点:

  • std::shared_ptr 是强引用智能指针
  • std::weak_ptr 是弱引用智能指针
  • 强引用,只要有一个引用存在,对象就不能被释放
  • 弱引用,并不增加对象的引用计数,但它知道对象是否存在。
    • 如果存在,提升为shared_ptr成功;否则,提升失败
    • 通过weak_ptr访问对象的成员的时候,要提升为shared_ptr
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 <memory>
#include <iostream>

using std::cout;
using std::endl;
using std::shared_ptr;
using std::weak_ptr;

class point{
public:
explicit point(int x=0, int y=0):ix(x),iy(y){
std::cout << "构造函数调用" << std::endl;
}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point(){ //析构函数
// destructor cannot have any parameters
std::cout << "~point()" << std::endl; // for test
}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy){
std::cout << "拷贝构造函数" << std::endl;
}
private:
int ix;
int iy;
};

void test() {
// weak_ptr<point> wp(new point(1, 2)); // 不 ok
weak_ptr<point> wp2; //可以创建空对象

{
shared_ptr<point> sp(new point(1,2));
wp2 = sp; // ok

cout << "sp.use_count() = " << sp.use_count() << endl;
cout << "wp2.use_count() = " << wp2.use_count() << endl;

cout << "wp2.expired() = " << wp2.expired() << endl;

cout << endl;
shared_ptr<point> sp2 = wp2.lock();
if(sp2) {
cout << "提升成功" << endl;
cout << "*sp2 = ";
sp2->print();
cout << endl;
}
else {
cout << "提升失败,托管资源已销毁" << endl;
}
}

cout << "wp2.use_count() = " << wp2.use_count() << endl;
cout << "wp2.expired() = " << wp2.expired() << endl;


cout << endl;
shared_ptr<point> sp2 = wp2.lock();
if(sp2) {
cout << "提升成功" << endl;
cout << "*sp2 = ";
sp2->print();
cout << endl;
}
else {
cout << "提升失败,托管资源已销毁" << endl;
}
}

int main() {
test();
return 0;
}
/*
构造函数调用
sp.use_count() = 1
wp2.use_count() = 1
wp2.expired() = 0

提升成功
*sp2 = 1 2

~point()
wp2.use_count() = 0
wp2.expired() = 1

提升失败,托管资源已销毁
*/

删除器

66-17.png

对于一些特殊的情况,不能依赖默认的删除器:

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
#include <iostream>
#include <memory>
#include <string>

using std::cout;
using std::endl;
using std::string;
using std::unique_ptr;
using std::shared_ptr;

struct FileCloser {
void operator() (FILE* fp) const {
if(fp) {
fclose(fp);
cout << "fclose(fp)" << endl;
}
}
};

void test() {
string msg = "jiarenmenshuidonga.\n";
unique_ptr<FILE, FileCloser> up(fopen("test.txt", "a+"));
fwrite(msg.c_str(), 1, msg.size(), up.get());
// fclose(up.get());
}

void test2() {
string msg = "yudaoyigeikun.\n";
shared_ptr<FILE> sp(fopen("test.txt", "a+"), FileCloser());
fwrite(msg.c_str(), 1, msg.size(), sp.get());
}

int main() {
test2();
return 0;
}

智能指针的误用

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 <iostream>
#include <memory>

using std::cout;
using std::endl;
using std::unique_ptr;
using std::shared_ptr;

class point{
public:
explicit point(int x=0, int y=0):ix(x),iy(y){
std::cout << "构造函数调用" << std::endl;
}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point(){ //析构函数
// destructor cannot have any parameters
std::cout << "~point()" << std::endl; // for test
}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy){
std::cout << "拷贝构造函数" << std::endl;
}
private:
int ix;
int iy;
};

void test() {
// 使用了不同的智能指针托管了同一个裸指针
point* pt = new point(1,2);
unique_ptr<point> up(pt);
unique_ptr<point> up2(pt);
/*
构造函数调用
~point()
~point()
free(): double free detected in tcache 2
Aborted (core dumped)
*/
}

void test2() {
// 用不同的智能指针托管了同一块空间
unique_ptr<point> up(new point(1,2));
unique_ptr<point> up2(new point(3,4));
up.reset(up2.get());
/*
构造函数调用
构造函数调用
~point()
~point()
~point()
free(): double free detected in tcache 2
Aborted (core dumped)
*/
}

void test3() {
point* pt = new point(1,2);
shared_ptr<point> sp(pt);
shared_ptr<point> sp2(pt);
/*
构造函数调用
~point()
~point()
free(): double free detected in tcache 2
Aborted (core dumped)
*/
}

void test4() {
shared_ptr<point> sp(new point(1,2));
shared_ptr<point> sp2(new point(3,4));
sp.reset(sp2.get());
/*
构造函数调用
构造函数调用
~point()
~point()
~point()
free(): double free detected in tcache 2
Aborted (core dumped)
*/
}

int main() {
test4();
return 0;
}

再有一例:

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
#include <iostream>
#include <memory>

using std::cout;
using std::endl;
using std::unique_ptr;
using std::shared_ptr;

class point{
public:
explicit point(int x=0, int y=0):ix(x),iy(y){
std::cout << "构造函数调用" << std::endl;
}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point(){ //析构函数
// destructor cannot have any parameters
std::cout << "~point()" << std::endl; // for test
}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy){
std::cout << "拷贝构造函数" << std::endl;
}

// pt1->addPoint(pt2)
point* addPoint(point* pt) {
this->ix += pt->ix;
this->iy += pt->iy;
return this;
}

private:
int ix;
int iy;
};

void test5() {
shared_ptr<point> sp(new point(1,2));
cout << "*sp = " ;
sp->print();
cout << endl;

shared_ptr<point> sp2(new point(3,4));
cout << "*sp2 = " ;
sp2->print();
cout << endl;

shared_ptr<point> sp3(sp->addPoint(sp2.get()));
// 用裸指针进行的初始化!
cout << "*sp3 = " ;
sp3->print();
cout << endl;
}

int main() {
test5();
return 0;
}
/*
构造函数调用
*sp = 1 2

构造函数调用
*sp2 = 3 4

*sp3 = 4 6

~point()
~point()
~point()
free(): double free detected in tcache 2
Aborted (core dumped)
*/

上面代码问题的解决方法:

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
#include <iostream>
#include <memory>

using std::cout;
using std::endl;
using std::shared_ptr;

class point:
public std::enable_shared_from_this<point> {
public:
explicit point(int x=0, int y=0):ix(x),iy(y){
std::cout << "构造函数调用" << std::endl;
}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point(){ //析构函数
// destructor cannot have any parameters
std::cout << "~point()" << std::endl; // for test
}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy){
std::cout << "拷贝构造函数" << std::endl;
}

// pt1->addPoint(pt2)
shared_ptr<point> addPoint(point* pt) {
this->ix += pt->ix;
this->iy += pt->iy;
return shared_from_this();
}

private:
int ix;
int iy;
};

void test5() {
shared_ptr<point> sp(new point(1,2));
cout << "*sp = " ;
sp->print();
cout << endl;

shared_ptr<point> sp2(new point(3,4));
cout << "*sp2 = " ;
sp2->print();
cout << endl;

shared_ptr<point> sp3(sp->addPoint(sp2.get()));
cout << "*sp3 = " ;
sp3->print();
cout << endl;
}

int main() {
test5();
return 0;
}
/*
构造函数调用
*sp = 1 2

构造函数调用
*sp2 = 3 4

*sp3 = 4 6

~point()
~point()
*/

模板

模板(Template),是一种通用的描述机制。模板允许使用通用类型来定义函数或类等,在使用时,通用类型可被具体的类型,如int、double甚至是用户自定义的类型来代替。模板引入一种全新的编程思维方式,称为“泛型编程”或“通用编程”。

模板的引入使函数定义摆脱了类型的束缚,代码更为高效灵活。C++ 中,通过下述形式定义一个模板:

1
template <class T, ...>

或:

1
template<typename T, ...>

早期模板定义使用的是 class,关键字 typename 是后加入到标准中的,相比 class,typename 更容易体现“类型”的观点,虽然两个关键字在模板定义时是等价的,但从代码兼容的角度讲,使用 class 较好一些。

模板有函数模板类模板之分。通过参数实例化构造出具体的函数或类,称为模板函数或模板类。

函数模板

模板的形式:

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
#include <iostream>
#include <string>

using std::cout;
using std::endl;
using std::string;

// 函数模板
template <typename T>
T add(T x, T y) {
return x+y;
}

void test() {
int ia = 3, ib = 4;
double dx = 3.3, dy = 4.4;
string s1 = "hello ";
string s2 = "world";

// 显式实例化
cout << "add(ia, ib) = " << add<int>(ia, ib) << endl;

// 隐式实例化
cout << "add(dx, dy) = " << add(dx, dy) << endl;
cout << "add(s1, s2) = " << add(s1, s2) << endl;
}

int main() {
test();
return 0;
}
/*
add(ia, ib) = 7
add(dx, dy) = 7.7
add(s1, s2) = hello world
*/

原理:在编译时做了类型推导。

普通函数与函数模板可以同时存在。
普通函数优先于函数模板。
普通函数与函数模板可以形成重载。
函数模板与函数模板之间也可以形成重载。

对于模板而言,一般不能分成头文件与实现文件的形式,即不能将声明与实现分开。
如果非要分成头文件和实现文件,可以在头文件中包含实现文件,如在add.h#include "add.cc"

66-18.png

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

using std::cout;
using std::endl;

// 模板的参数类型
// 1. 类型参数,比如这里的 T
// 2. 非类型参数,都是整型(bool/char/int/size_t/...)
// 注意:排除了浮点数 float/double
template <typename T, short kMin>
T multiply(T x, T y) {
return x * y * kMin;
}

int main() {
int a = 10, b = 20;
cout << "multiply(a, b) = " << multiply<int, 100>(a, b) << endl;
return 0;
}
/*
multiply(a, b) = 20000
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

using std::cout;
using std::endl;

template <typename T = double , short kMin = 300>
T multiply(T x, T y) {
return x * y * kMin;
}

int main() {
int a = 10, b = 20;
cout << "multiply(a, b) = " << multiply<int, 100>(a, b) << endl;
cout << "multiply(a, b) = " << multiply(a, b) << endl;
return 0;
}
/*
multiply(a, b) = 20000
multiply(a, b) = 60000
*/

成员函数模板

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
#include <iostream>

using std::cout;
using std::endl;

class point{
public:
explicit point(int x=0, int y=0):ix(x),iy(y){
std::cout << "构造函数调用" << std::endl;
}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point(){ //析构函数
// destructor cannot have any parameters
std::cout << "~point()" << std::endl; // for test
}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy){
std::cout << "拷贝构造函数" << std::endl;
}

template <typename T = int>
T func() {
return (T)ix;
}

private:
int ix;
int iy;
};

void test() {
point pt(1, 2);
cout << "pt.func() = " << pt.func<double>() << endl;
cout << "pt.func() = " << pt.func<int>() << endl;
cout << "pt.func() = " << pt.func() << endl;
}

int main() {
test();
return 0;
}
/*
构造函数调用
pt.func() = 1
pt.func() = 1
pt.func() = 1
~point()
*/

可变模板参数

可变模板参数(variadic templates) 是 C++11 新增的特性,它对参数进行了高度泛化,能表示 0 到任意个任意类型的参数。

模板参数包(parameter pack),如:

1
template<typename… Args>  class tuple;

Args标识符的左侧使用了省略号,在 C++11 中Args被称为模板参数包,表示可以接受任意多个参数作为模板参数,编译器将多个模板参数打包成“单个”的模板参数包。

函数参数包,如:

1
template<typename…T> void f(T…args);

args 被称为函数参数包,表示函数可以接受多个任意类型的参数。

例子:

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

using std::cout;
using std::endl;

// template <typename T1, typename T2, typename T3>
// void display(T1 x, T2 y, T3 z) {
// // ...
// }
/* 这里的 Args,args 只是一个名字,可以换成别的 */
template <typename ... Args> // 模板参数包
void display(Args ... args) // 函数参数包
{

}

int main() {
return 0;
}

C++11 标准中,要求函数参数包必须唯一,且是函数的最后一个参数。

声明一个变量(或标识符)为可变参数时,省略号位于该变量的左侧。

使用参数包时,省略号位于参数名称的右侧,表示立即展开该参数,这个过程也被称为解包。

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
#include <iostream>
#include <string>

using std::cout;
using std::endl;
using std::string;

// template <typename T1, typename T2, typename T3>
// void display(T1 x, T2 y, T3 z) {
// // ...
// }
/* 这里的 Args,args 只是一个名字,可以换成别的 */
template <typename... Args> // 模板参数包
void display(Args... args) // 函数参数包
{
cout << "sizeof...(Args) = " << sizeof...(Args) << endl;
cout << "sizeof...(args) = " << sizeof...(args) << endl;
}

void print() { // 递归的出口
cout << endl;
}

template <typename T, typename... Args>
void print(T t, Args... args) {
cout << t << " ";
print(args...); // 递归
}

int main() {
string s1("hello");
display();

// template <typename T1, typename T2, typename T3>
// display(T1 x1, T2 x2, T3 x3)
// T1 = int, T2 = int, T3 = int
display(1, 2, 3);

// template <typename T1, typename T2, typename T3, typename T4>
// display(T1 x1, T2 x2, T3 x3, T4 x4)
// T1 = int, T2 = const char*, T3 = double/float, T4 = string
display(1, "hello", 3.3, s1);

print();
print(1, 2);
print(1, 2.2, s1, "asdf", 'c');

return 0;
}
/*
sizeof...(Args) = 0
sizeof...(args) = 0
sizeof...(Args) = 3
sizeof...(args) = 3
sizeof...(Args) = 4
sizeof...(args) = 4

1 2
1 2.2 hello asdf c
*/

来看一个不是很有用但是很神奇的例子:

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
#include <iostream>

using std::cout;
using std::endl;

int sum() {
return 0;
}

template <typename T, typename... Args>
int sum(T t, Args... args) {
return t + sum(args...);
}

void test() {
cout << "sum(1, 2, 3, 4, 5, 6) = "
<< sum(1, 2, 3, 4, 5, 6) << endl;
}

int main() {
test();
return 0;
}
/*
sum(1, 2, 3, 4, 5, 6) = 21
*/

类模板

理解了函数模板的应用,类模板的提出似乎是水到渠成的。

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
92
#include <iostream>
using namespace std;

template <class T, int num> //类型参数表
//或template <class T = int, int num = 10>
class Stack //Stack类定义
{
private:
T sz[num]; //存储空间,用数组表示
int point; //指针,表示存储位置(即元素个数)

public:
Stack() //构造函数
{
point=0; //初始位置为0,栈底
}

bool isEmpty(); //判断栈是否为空
bool isFull(); //判断栈是否已满
bool push(const T&); //将一个元素压入栈
bool pop(T&); //从栈中弹出一个元素
int &GetPos()
{
return point;
}
};

template<class T,int num> //参数列表不要求字字相同,但形式要相同
bool Stack<T,num>::isEmpty()
{
return point==0; //point为0,说明当前无元素
}

template<class T,int num>
bool Stack<T,num>::isFull()
{
return point==num; //point为num,说明数组已满
}

template<class T,int num>
bool Stack<T,num>::push(const T& obt)
{
if (isFull())
return false; //如果栈已满,压入不成功,返回false
else
{
sz[point]=obt; //将传入的元素存储在point指向的当前位置
point++; //point加1,向栈顶移动
return true; //压入成功,返回true
}
}

template<class T,int num>
bool Stack<T,num>::pop(T &obt)
{
if (isEmpty())
return false; //如果栈已空,无法弹出,返回false
else
{
point--; //point减1,向栈底移动,指向存储的最上面一个元素
obt = sz[point]; //将point指向的当前位置元素复制给传入参数
return true; //弹出成功,返回true
}
}

int main()
{
Stack<int, 10> st; //模版类, 模版参数为<class T, int num>
cout << "开始时st是否为空? " << st.isEmpty() << endl;

st.push(5); //压入元素5
cout << "此时st是否为空? " << st.isEmpty() << endl;

for (int i = 1; i < 10; i++)
{
st.push(i); //压入9个元素
}
cout << "此时st是否已满?" << st.isFull() << endl;

int rec = 0;
while (st.pop(rec))
cout << rec << " ";
cout << endl;

return 0;
}
/*
开始时st是否为空? 1
此时st是否为空? 0
此时st是否已满?1
9 8 7 6 5 4 3 2 1 5
*/

模板的嵌套:

  • 模板的嵌套可以理解成在另一个模板里定义一个模板。以模板(类,或者函数)作为另一个模板(类,或者函数)的成员,也称成员模板
  • 成员模版不能声明为 virtual.
    • 模板在编译时进行类型推导
    • 虚函数体现多态发生在运行时

来看一个嵌套模版类的模版类的例子:

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
#include <iostream>
using namespace std;

template<class T>
class Outside //外部Outside类定义
{
public:
template <class R>
class Inside //嵌套类模板定义
{
private:
R r;
public:
Inside(R x) //模板类的成员函数可以在定义时实现
{
r=x;
}
//void disp();
void disp() {cout << "Inside: " << r << endl;}
};

Outside(T x) : t(x) //Outside类的构造函数
{}

//void disp();
void disp()
{
cout<<"Outside:";
t.disp();
}

private:
Inside<T> t;
};

//template<class T>
//template<class R>
//void Outside<T>::Inside<R>::disp() //模板类的成员函数也可以在定义外实现
//{ //但必须是在所有类定义的外边,不能放在Outside内Inside外去实现.
// cout<<"Inside: "<<Outside<T>::Inside<R>::r<<endl;
//}

//template<class T>
//void Outside<T>::disp()
//{
// cout<<"Outside:";
// t.disp();
//}

int main()
{
Outside<int>::Inside<double> obin(3.5); //声明Inside类对象obin
obin.disp();

Outside<int> obout(2); //创建Outside类对象obout
obout.disp();

getchar();
return 0;
}
/*
Inside: 3.5
Outside:Inside: 2
*/

嵌套越多越麻烦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename T>
class OutSide {
public:
template <typename R>
class Inside {
public:
template <typename Q>
void print(Q x);
};
};

template <typename T>
template <typename R>
template <typename Q>
void OutSide<T>::Inside<R>::print(Q x) {
// some code...
}

vector、deque、list

66-19.png

这部分知识早有接触,因此只是杂乱地记一些东西。

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
#include <iostream>
#include <vector>
#include <list>
#include <deque>

using std::endl;
using std::cout;

template <typename Container>
void display(const Container& con) {
for(auto& elem : con) {
cout << elem << " ";
}
cout << endl;
}

void test() {
std::vector<int> number = {1, 3, 5, 7, 9};
display(number);
number.push_back(11);
display(number);
number.pop_back();
display(number);
/*
1 3 5 7 9
1 3 5 7 9 11
1 3 5 7 9
*/
}

void test2() {
std::deque<int> number = {1, 3, 5, 7, 9};
display(number);
number.push_back(11);
display(number);
number.pop_back();
display(number);
/*
1 3 5 7 9
1 3 5 7 9 11
1 3 5 7 9
*/
}

void test3() {
std::list<int> number = {1, 3, 5, 7, 9};
display(number);
number.push_back(11);
display(number);
number.pop_back();
display(number);
/*
1 3 5 7 9
1 3 5 7 9 11
1 3 5 7 9
*/
}

int main() {
test3();
return 0;
}

vector 源码阅读

vector 的继承图:

66-20.png

vector底层原理图:

66-21.png

类型萃取(提取、过滤):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename __Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp)>
class vector
{
typedef typename _Base::allocator_type allocator_type; //typename为了严格表明一个类型
public:
typedef _Tp value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
};

在 vector 中,operator[]at函数都具有随机访问的功能,但是at函数有范围检查,所以更加安全一些:

66-22.jpeg

如何获取 vector 的第一个元素的地址:

1
2
3
4
5
6
// number 是一个 vector 对象
&number; // error
&*number; // error
&*number.begin(); // ok
&number[0]; // ok
int* pdata = number.data(); // ok

deque 源码阅读

deque 的继承图:

66-23.png

deque 的原理图:

66-24.png

杂项

容器的清空:

66-25.png

list 的排序相关:

66-26.png

vector 的 insert 扩容机制:
对于 push_back 而言,每次插入的个数是一定的,那么按照两倍的方法进行扩容,是 OK 的。但是对于 insert 而言,每次插入元素的个数是不确定的,所以没有一个统一的标准进行扩容。

  • 令 capacity() = n, size() = m, 将要插入 t 个元素
  • t < n-m, 不扩容
  • n-m < t < m, 按照 2*m 进行扩容
  • t > n-m, m < t < n, 按照 t+m 进行扩容
  • t > n-m, t > n, 按照 t+m 进行扩容

(multi)set/map

同样是很杂的笔记。

一些魔咒:

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
#include <iostream>
#include <set>
#include <utility>

using std::cout;
using std::endl;
using std::set;
using std::pair;

template <typename Container>
void display(const Container& con)
{
for(auto &elem : con)
{
cout << elem << " ";
}
cout << endl;
}

void test() {
set<int> aa = {1, 2, 3, 4, 5, 6, 7, 87};

set<int>::iterator it = aa.find(7);
if(it == aa.end()) {
cout << "not exist." << endl;
}
else cout << "exist." << endl;

// 吟唱魔咒
pair<set<int>::iterator, bool> ret = aa.insert(8);
if(ret.second){
cout << "插入成功 " << *ret.first << endl;
}
else {
cout << "插入失败" << endl;
}
display(aa);
}

int main() {
test();
return 0;
}
/*
exist.
插入成功 8
1 2 3 4 5 6 7 8 87
*/

针对于自定义类型用法(定义小于号):

  • 模板的特化方式
  • 66-27.png
  • 重载运算符
  • 66-28.png
  • 函数对象(仿函数)
  • 66-29.png

multiset 的一些用法:

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
#include <iostream>
#include <set>

using std::cout;
using std::endl;
using std::multiset;

void show(const multiset<int>& a) {
for(auto& elem : a) {
cout << elem << " ";
}
cout << endl;
}

void test() {
multiset<int> number = {1, 1, 3, 3, 7, 5, 7, 100, 9, 5, 5, 5};
show(number);

std::pair<multiset<int>::iterator, multiset<int>::iterator> ret
= number.equal_range(5);
while(ret.first != ret.second) {
cout << *ret.first << " ";
++ ret.first;
}
cout << endl;
}

int main() {
test();
return 0;
}
/*
1 1 3 3 5 5 5 5 7 7 9 100
5 5 5 5
*/

作业-文本查询再探

在之前的基础上,支持逻辑运算与、或、非,进行文本查询。

很恶心的作业,让你充分理解为什么说 C++ Primer 编排垃圾。

书中没有完整的程序,代码也散落各处,有的代码甚至在几百页之前。

这个作业如果只是要完成功能的话,还是很容易想到转换成集合的运算的,只是我实在无法想象要怎么面向对象地写出七八个类来,因此直接看书中的代码了。

对于不会设计类的我来说,还是很有启发的。

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#include <set>
#include <vector>
#include <map>
#include <memory>
#include <algorithm>

class QueryResult;
class TextQuery {
public:
using line_no = std::vector<std::string>::size_type;
TextQuery(std::ifstream&);
QueryResult query(const std::string&) const;
private:
std::shared_ptr<std::vector<std::string>> file; // 输入文件
// 单词到行号集合的指针的映射
std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};

TextQuery::TextQuery(std::ifstream& is): file(new std::vector<std::string>) {
std::string text;
while(getline(is, text)) {
file->push_back(text);
int n = file->size() -1; // 当前行号
std::istringstream line(text);
std::string word;
while(line >> word) {
// 若单词不在 wm 中,以之为下标在 wm 中添加一项
auto& lines = wm[word]; // lines 是一个 shared_ptr
if(!lines) // 第一次遇到这个单词时,此指针为空
lines.reset(new std::set<line_no>); // 分配一个新 set
lines->insert(n);
}
}
}

class QueryResult {
friend std::ostream& print(std::ostream&, const QueryResult&);
public:
using line_no = TextQuery::line_no;
QueryResult(std::string s,
std::shared_ptr<std::set<line_no>> p,
std::shared_ptr<std::vector<std::string>> f):
sought(s), lines(p), file(f) {}

std::set<line_no>::iterator begin() const {
return lines->begin();
}

std::set<line_no>::iterator end() const {
return lines->end();
}

std::shared_ptr<std::vector<std::string>> get_file() const {
return file;
}

private:
std::string sought; // 查询单词
std::shared_ptr<std::set<line_no>> lines; // 出现的行号
std::shared_ptr<std::vector<std::string>> file; // 保存每行
};

QueryResult TextQuery::query(const std::string& sought) const {
// 若未找到 sought, 返回指向此 set 的指针
static std::shared_ptr<std::set<line_no>> noData(new std::set<line_no>);
// 使用 find() 而不是下标运算符来查找单词,避免将单词添加到 wm 中
auto loc = wm.find(sought);
if(loc == wm.end())
return QueryResult(sought, noData, file); // 未找到
else
return QueryResult(sought, loc->second, file);
}

std::ostream& print(std::ostream& os, const QueryResult& qr) {
os << qr.sought << " occurs "
<< qr.lines->size() << " time(s) "<< std::endl;

for(auto num : *qr.lines)
os << "(line " << num+1 << ") "
<< *(qr.file->begin() + num) << std::endl;

return os;
}

class Query_base {
friend class Query;
protected:
using line_no = TextQuery::line_no; // 用于 eval 函数
virtual ~Query_base() = default;
private:
// eval 返回与当前 Query 匹配的 QueryResult
virtual QueryResult eval(const TextQuery&) const = 0;
// rep 是表示查询的一个 string
virtual std::string rep() const = 0;
};

// 这是一个管理 Query_base 继承体系的接口类
class Query {
friend Query operator ~ (const Query&);
friend Query operator | (const Query&, const Query&);
friend Query operator & (const Query&, const Query&);
friend std::ostream& operator << (std::ostream& os, const Query& rhs);
public:
Query(const std::string&);

// 接口函数,调用对应的 Query_base 操作
QueryResult eval(const TextQuery& t) const {
return q->eval(t);
}
std::string rep() const { return q->rep(); }

private:
Query(std::shared_ptr<Query_base> query): q(query) {}
std::shared_ptr<Query_base> q;
};

std::ostream& operator << (std::ostream& os, const Query& query) {
return os << query.rep();
}

class WordQuery: public Query_base {
friend class Query; // Query 使用 WordQuery 的构造函数
private:
WordQuery(const std::string& s): query_word(s) {}

QueryResult eval(const TextQuery& t) const override {
return t.query(query_word);
}
std::string rep() const override {
return query_word;
}

std::string query_word; // 要查找的单词
};

inline Query::Query(const std::string& s): q(new WordQuery(s)) {}

class NotQuery: public Query_base {
friend Query operator ~ (const Query&);
private:
NotQuery(const Query& q): query(q) {}

QueryResult eval(const TextQuery&) const override;
std::string rep() const override {
return "~(" + query.rep() + ")";
}

Query query;
};

inline Query operator ~ (const Query& operand) {
return std::shared_ptr<Query_base>(new NotQuery(operand));
}

class BinaryQuery: public Query_base {
protected:
BinaryQuery(const Query& l, const Query& r, std::string s):
lhs(l), rhs(r), opSym(s) {}

std::string rep() const override {
return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")";
}

Query lhs, rhs;
std::string opSym; // 运算符的名字
};

class AndQuery: public BinaryQuery {
friend Query operator & (const Query&, const Query&);
AndQuery(const Query& left, const Query& right):
BinaryQuery(left, right, "&") {}

QueryResult eval(const TextQuery&) const override;
};

inline Query operator & (const Query& lhs, const Query& rhs) {
return std::shared_ptr<Query_base>(new AndQuery(lhs, rhs));
}

class OrQuery: public BinaryQuery {
friend Query operator | (const Query&, const Query&);
OrQuery(const Query& left, const Query& right):
BinaryQuery(left, right, "|") {}

QueryResult eval(const TextQuery&) const override;
};

inline Query operator | (const Query& lhs, const Query& rhs) {
return std::shared_ptr<Query_base>(new OrQuery(lhs, rhs));
}

QueryResult OrQuery::eval(const TextQuery& text) const {
auto right = rhs.eval(text), left = lhs.eval(text);
auto ret_lines =
std::make_shared<std::set<line_no>>(left.begin(), left.end());
ret_lines->insert(right.begin(), right.end());

return QueryResult(rep(), ret_lines, left.get_file());
}

QueryResult AndQuery::eval(const TextQuery& text) const {
auto left = lhs.eval(text), right = rhs.eval(text);
auto ret_lines = std::make_shared<std::set<line_no>> ();
set_intersection(left.begin(), left.end(),
right.begin(), right.end(),
inserter(*ret_lines, ret_lines->begin()));
return QueryResult(rep(), ret_lines, left.get_file());
}

QueryResult NotQuery::eval(const TextQuery& text) const {
auto result = query.eval(text);
auto ret_lines = std::make_shared<std::set<line_no>> ();
auto beg = result.begin(), end = result.end();
auto sz = result.get_file()->size();
for(size_t n = 0; n != sz; n++) {
if(beg == end || *beg != n)
ret_lines->insert(n);
else if(beg != end)
++beg;
}
return QueryResult(rep(), ret_lines, result.get_file());
}

int main()
{
std::ifstream infile("text.txt");
TextQuery file(infile);

// Query q = Query("Daddy");
// Query q = ~ Query("Alice");
// Query q = Query("hair") | Query("Alice");
// Query q = Query("hair") & Query("Alice");
Query q = Query("fiery") & Query("bird") | Query("wind");
const auto results = q.eval(file);
std::cout << "Executing Query for: " << q << std::endl;
print(std::cout, results) << std::endl;

infile.close();
return 0;
}

注意上面代码缺失了处理标点符号的逻辑,应此输出与答案不一致,但缺失部分都对的上。处理标点符号的逻辑我就不写了,应该不难

贴个测试用例:

1
2
3
4
5
6
7
8
9
10
Alice Emma has long flowing red hair. 
Her Daddy says when the wind blows
through her hair, it looks almost alive,
like a fiery bird in flight.
A beautiful fiery bird, he tells her,
magical but untamed.
"Daddy, shush, there is no such thing,"
she tells him, at the same time wanting
him to tell her more.
Shyly, she asks, "I mean, Daddy, is there?"

单例模式的模板形式

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
92
93
94
95
96
97
#include <iostream>

using std::endl;
using std::cout;

class point{
public:
explicit point(int x=0, int y=0):ix(x),iy(y){
std::cout << "构造函数调用" << std::endl;
}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point(){ //析构函数
// destructor cannot have any parameters
std::cout << "~point()" << std::endl; // for test
}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy){
std::cout << "拷贝构造函数" << std::endl;
}
private:
int ix;
int iy;
};

template <typename T>
class Singleton {
public:
template <typename... Args>
static T* getInstance(Args... args) {
if(_pInstance == nullptr) {
_pInstance = new T(args...);
_ar; // 为了在模板参数推导时创建 ar 对象
}
return _pInstance;
}

private:
class AutoRelease {
public:
AutoRelease() {
cout << "AutoRelease()" << endl;
}

~AutoRelease() {
cout << "~AutoRelease()" << endl;
if(_pInstance) {
delete _pInstance;
_pInstance = nullptr;
}
}
};

Singleton() {
cout << "Singleton()" << endl;
/* _ar; */
}

~Singleton() {
cout << "~Singleton()" << endl;
}

static T* _pInstance;
static AutoRelease _ar;
};

template <typename T>
T* Singleton<T>::_pInstance = nullptr;

template <typename T>
typename Singleton<T>::AutoRelease Singleton<T>::_ar;
// typename 表明是一个类型

void test() {
point* pt1 = Singleton<point>::getInstance(1, 2);
point* pt2 = Singleton<point>::getInstance(3, 4);
pt1->print();
pt2->print();
cout << "pt1 = " << pt1 << endl
<< "pt2 = " << pt2 << endl;
}

int main() {
test();
return 0;
}
/*
AutoRelease()
构造函数调用
1 2
1 2
pt1 = 0x5ff914de52c0
pt2 = 0x5ff914de52c0
~AutoRelease()
~point()
*/

再次封装 log4cpp

实现log4cpp的封装,使其可以像printf一样使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// testlogger.cc
#include "myLogger.h"
#include <string>

using std::string;
using namespace wd;

void test(){
int number = 10;
const char* pstr = "hello world";
printf("hello world %d%s\n", number,pstr);
LogError("hello world, %s %d\n", pstr, number);
}

int main(){
test();
return 0;
}
/*
hello world 10hello world
Mylogger()
2049-02-30 22:45:08,333 MyCat [ERROR] [testlogger.cc:test:11]hello world, hello world 10

*/
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
// myLogger.h
#ifndef _MYLOGGER_H_
#define _MYLOGGER_H_

#include <string>
#include <log4cpp/Category.hh>

using std::string;
using namespace log4cpp;

namespace wd
{
class Mylogger{
public:
enum Priority {
ERROR = 300,
WARN,
INFO,
DEBUG
};

static Mylogger* getInstance();
static void destory();

template <typename... Args>
void warn(const char* msg, Args... args){
_mycat.warn(msg, args...);
}

template <typename... Args>
void error(const char* msg, Args... args){
_mycat.error(msg, args...);
}

template <typename... Args>
void info(const char* msg, Args... args){
_mycat.info(msg, args...);
}

template <typename... Args>
void debug(const char* msg, Args... args){
_mycat.debug(msg, args...);
}

void warn(const char* msg);
void error(const char* msg);
void info(const char* msg);
void debug(const char* msg);

void setPriority(Priority p);

private:
Mylogger();
~Mylogger();

log4cpp::Category& _mycat;
static Mylogger* _pInstance;
};

#define prefix(msg) string("[") \
.append(__FILE__).append(":") \
.append(__FUNCTION__).append(":") \
.append(std::to_string(__LINE__)).append("]") \
.append(msg).c_str()

// ##__VA_ARGS__ 宏前面加上 ## 的作用在于,当可变参数个数变为零时,
// 这里的 ## 会去掉前面多余的 “,”
#define LogWarn(msg, ...) Mylogger::getInstance()->warn(prefix(msg), ##__VA_ARGS__);
#define LogError(msg, ...) Mylogger::getInstance()->error(prefix(msg), ##__VA_ARGS__);
#define LogInfo(msg, ...) Mylogger::getInstance()->info(prefix(msg), ##__VA_ARGS__);
#define LogDebug(msg, ...) Mylogger::getInstance()->debug(prefix(msg), ##__VA_ARGS__);

} // namespace wd

#endif
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
92
// myLogger.cc
#include "myLogger.h"
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/OstreamAppender.hh>
#include <log4cpp/FileAppender.hh>
#include <log4cpp/Priority.hh>

#include <iostream>
using std::cout;
using std::endl;

namespace wd
{
Mylogger* Mylogger::_pInstance = nullptr;

Mylogger* Mylogger::getInstance() {
if(nullptr == _pInstance) {
_pInstance = new Mylogger();
}
return _pInstance;
}

void Mylogger::destory() {
if(_pInstance) {
delete _pInstance;
_pInstance = nullptr;
}
}

Mylogger::Mylogger()
: _mycat(log4cpp::Category::getRoot().getInstance("MyCat"))
{
using namespace log4cpp;
cout << "Mylogger()" << endl;

PatternLayout* ppl1 = new PatternLayout();
ppl1->setConversionPattern("%d %c [%p] %m%n");
PatternLayout* ppl2 = new PatternLayout();
ppl2->setConversionPattern("%d %c [%p] %m%n");

// 日志的目的地
OstreamAppender* poa = new OstreamAppender("OSA", &cout);
poa->setLayout(ppl1);
FileAppender* pfa = new FileAppender("FA", "zy.txt");
pfa->setLayout(ppl2);

//日志的过滤器
_mycat.setPriority(Priority::DEBUG);

// 目的地
_mycat.addAppender(poa);
_mycat.addAppender(pfa);
}

Mylogger::~Mylogger(){
cout << "~Mylogger()" << endl;
Category::shutdown();
}

void Mylogger::warn(const char* msg){
_mycat.warn(msg);
}
void Mylogger::error(const char* msg){
_mycat.error(msg);
}

void Mylogger::debug(const char* msg){
_mycat.debug(msg);
}
void Mylogger::info(const char* msg){
_mycat.info(msg);
}

void Mylogger::setPriority(Priority p) {
switch (p)
{
case WARN:
_mycat.setPriority(log4cpp::Priority::WARN);
break;
case ERROR:
_mycat.setPriority(log4cpp::Priority::ERROR);
break;
case INFO:
_mycat.setPriority(log4cpp::Priority::INFO);
break;
case DEBUG:
_mycat.setPriority(log4cpp::Priority::DEBUG);
break;
}
}

} // namespace wd

unordered_(multi)set/map

Unordered associative containers implement unsorted (hashed) data structures that can be quickly searched (O(1) average, O(n) worst-case complexity).

  • unordered_set: collection of unique keys, hashed by keys
  • unordered_map: collection of key-value pairs, hashed by keys, keys are unique
  • unordered_multiset: collection of keys, hashed by keys
  • unordered_multimap: collection of key-value pairs, hashed by keys

std::hash<Key>::operator()例子(来自 cppreference )

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
#include <cstdint>
#include <functional>
#include <iostream>
#include <string>

struct Employee
{
std::string name;
std::uint64_t ID;
};

namespace std
{
template <>
class hash<Employee>
{
public:
std::uint64_t operator()(const Employee& employee) const
{
// computes the hash of an employee using a variant
// of the Fowler-Noll-Vo hash function
constexpr std::uint64_t prime{0x100000001B3};
std::uint64_t result{0xcbf29ce484222325};

for (std::uint64_t i{}, ie = employee.name.size(); i != ie; ++i)
result = (result * prime) ^ employee.name[i];

return result ^ (employee.ID << 1);
}
};
}

int main()
{
Employee employee;
employee.name = "Zaphod Beeblebrox";
employee.ID = 42;

std::hash<Employee> hash_fn;
std::cout << hash_fn(employee) << '\n';
}
/*
12615575401975788567
*/

unordered_set 的模板形式:

1
2
3
4
5
6
template<
class Key,
class Hash = std::hash<Key>,
class KeyEqual = std::equal_to<Key>,
class Allocator = std::allocator<Key>
> class unordered_set;

unordered_set 特性:

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
#include <iostream>
#include <unordered_set>

using std::cout;
using std::endl;
using std::unordered_set;

template <typename Container>
void display(const Container& con)
{
for(auto &elem : con)
{
cout << elem << " ";
}
cout << endl;
}

void test() {
unordered_set<int> number = {1, 3, 7, 9, 5, 7, 5, 3, 2, 10};
display(number);
}

int main() {
test();
return 0;
}
/*
10 2 5 9 7 3 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
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
92
93
94
95
96
97
98
99
100
101
#include <iostream>
#include <cstdint>
#include <unordered_set>

using std::cout;
using std::endl;
using std::unordered_set;

class point{
friend std::ostream& operator << (std::ostream& os, const point& rhs);
public:
explicit point(int x=0, int y=0):ix(x),iy(y){
// std::cout << "point(int, int)" << std::endl;
}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point(){ //析构函数
// destructor cannot have any parameters
// std::cout << "~point()" << std::endl; // for test
}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy){
// std::cout << "point(const point&)" << std::endl;
}
int getX() const {
return ix;
}
int getY() const {
return iy;
}
private:
int ix;
int iy;
};

namespace std
{
template<>
class hash<point>
{
public:
std::uint64_t operator () (const point& rhs) const {
// 设计哈希函数
cout << "we've hashed here" << endl;
return (rhs.getX() << 1) ^ (rhs.getY() << 2);
}
};

} // namespace std

bool operator == (const point& lhs, const point& rhs) {
cout << "we've compared == here" << endl;
return (lhs.getX() == rhs.getX())
&& (lhs.getY() == rhs.getY());
}

std::ostream& operator << (std::ostream& os, const point& rhs) {
os << "(" << rhs.ix
<< ", " << rhs.iy << ")";
return os;
}

template <typename Container>
void display(const Container& con)
{
for(auto &elem : con)
{
cout << elem << " ";
}
cout << endl;
}

void test() {
unordered_set<point> number = {
point(4, 5),
point(1, 2),
point(-1, 2),
point(1, -2),
point(1, 2),
point(3, 2),
point(8, 10),
};
display(number);
}

int main() {
test();
return 0;
}
/*
we've hashed here
we've hashed here
we've hashed here
we've hashed here
we've hashed here
we've compared == here
we've hashed here
we've hashed here
(8, 10) (3, 2) (-1, 2) (1, -2) (1, 2) (4, 5)
*/

对于上面代码实现的功能而言,也可以使用函数对象(仿函数)的方法:

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
92
93
94
#include <iostream>
#include <cstdint>
#include <unordered_set>

using std::cout;
using std::endl;
using std::unordered_set;

class point{
friend std::ostream& operator << (std::ostream& os, const point& rhs);
public:
explicit point(int x=0, int y=0):ix(x),iy(y){
// std::cout << "point(int, int)" << std::endl;
}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point(){ //析构函数
// destructor cannot have any parameters
// std::cout << "~point()" << std::endl; // for test
}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy){
// std::cout << "point(const point&)" << std::endl;
}
int getX() const {
return ix;
}
int getY() const {
return iy;
}
private:
int ix;
int iy;
};

struct HashPoint {
std::uint64_t operator () (const point& rhs) const {
// 设计哈希函数
cout << "we've hashed here" << endl;
return (rhs.getX() << 1) ^ (rhs.getY() << 2);
}
};

bool operator == (const point& lhs, const point& rhs) {
cout << "we've compared == here" << endl;
return (lhs.getX() == rhs.getX())
&& (lhs.getY() == rhs.getY());
}

std::ostream& operator << (std::ostream& os, const point& rhs) {
os << "(" << rhs.ix
<< ", " << rhs.iy << ")";
return os;
}

template <typename Container>
void display(const Container& con)
{
for(auto &elem : con)
{
cout << elem << " ";
}
cout << endl;
}

void test() {
unordered_set<point, HashPoint> number = {
point(4, 5),
point(1, 2),
point(-1, 2),
point(1, -2),
point(1, 2),
point(3, 2),
point(8, 10),
};
display(number);
}

int main() {
test();
return 0;
}
/*
we've hashed here
we've hashed here
we've hashed here
we've hashed here
we've hashed here
we've compared == here
we've hashed here
we've hashed here
(8, 10) (3, 2) (-1, 2) (1, -2) (1, 2) (4, 5)
*/

也可以这样:

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
92
93
94
95
#include <iostream>
#include <cstdint>
#include <unordered_set>

using std::cout;
using std::endl;
using std::unordered_set;

class point{
friend std::ostream& operator << (std::ostream& os, const point& rhs);
public:
explicit point(int x=0, int y=0):ix(x),iy(y) {}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point() {}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy) {}
int getX() const {
return ix;
}
int getY() const {
return iy;
}
private:
int ix;
int iy;
};

struct HashPoint {
std::uint64_t operator () (const point& rhs) const {
// 设计哈希函数
cout << "we've hashed here" << endl;
return (rhs.getX() << 1) ^ (rhs.getY() << 2);
}
};

namespace std
{
template <>
struct equal_to<point>
{
bool operator () (const point& lhs, const point& rhs) const
{
cout << "kafaka." << endl;
return (lhs.getX() == rhs.getX())
&& (lhs.getY() == rhs.getY());
}
};
} // namespace std

std::ostream& operator << (std::ostream& os, const point& rhs) {
os << "(" << rhs.ix
<< ", " << rhs.iy << ")";
return os;
}

template <typename Container>
void display(const Container& con)
{
for(auto &elem : con)
{
cout << elem << " ";
}
cout << endl;
}

void test() {
unordered_set<point, HashPoint> number = {
point(4, 5),
point(1, 2),
point(-1, 2),
point(1, -2),
point(1, 2),
point(3, 2),
point(8, 10),
};
display(number);
}

int main() {
test();
return 0;
}
/*
we've hashed here
we've hashed here
we've hashed here
we've hashed here
we've hashed here
kafaka.
we've hashed here
we've hashed here
(8, 10) (3, 2) (-1, 2) (1, -2) (1, 2) (4, 5)
*/

还可以这样(闲的):

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 <iostream>
#include <cstdint>
#include <unordered_set>

using std::cout;
using std::endl;
using std::unordered_set;

class point{
friend std::ostream& operator << (std::ostream& os, const point& rhs);
public:
explicit point(int x=0, int y=0):ix(x),iy(y) {}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point() {}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy) {}
int getX() const {
return ix;
}
int getY() const {
return iy;
}
private:
int ix;
int iy;
};

struct HashPoint {
std::uint64_t operator () (const point& rhs) const {
// 设计哈希函数
cout << "we've hashed here" << endl;
return (rhs.getX() << 1) ^ (rhs.getY() << 2);
}
};

struct EqualToPoint
{
bool operator () (const point& lhs, const point& rhs) const
{
cout << "kafaka2." << endl;
return (lhs.getX() == rhs.getX())
&& (lhs.getY() == rhs.getY());
}
};

std::ostream& operator << (std::ostream& os, const point& rhs) {
os << "(" << rhs.ix
<< ", " << rhs.iy << ")";
return os;
}

template <typename Container>
void display(const Container& con)
{
for(auto &elem : con)
{
cout << elem << " ";
}
cout << endl;
}

void test() {
unordered_set<point, HashPoint, EqualToPoint> number = {
point(4, 5),
point(1, 2),
point(-1, 2),
point(1, -2),
point(1, 2),
point(3, 2),
point(8, 10),
};
display(number);
}

int main() {
test();
return 0;
}
/*
we've hashed here
we've hashed here
we've hashed here
we've hashed here
we've hashed here
kafaka.
we've hashed here
we've hashed here
(8, 10) (3, 2) (-1, 2) (1, -2) (1, 2) (4, 5)
*/

对于 unordered_multiset 的情况:

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 <iostream>
#include <cstdint>
#include <unordered_set>

using std::cout;
using std::endl;
using std::unordered_multiset;

class point{
friend std::ostream& operator << (std::ostream& os, const point& rhs);
public:
explicit point(int x=0, int y=0):ix(x),iy(y) {}
void print(){
std::cout << ix << " "
<< iy << std::endl;
}
~point() {}
point(const point& rhs):ix(rhs.ix), iy(rhs.iy) {}
int getX() const {
return ix;
}
int getY() const {
return iy;
}
private:
int ix;
int iy;
};

struct HashPoint {
std::uint64_t operator () (const point& rhs) const {
// 设计哈希函数
cout << "we've hashed here" << endl;
return (rhs.getX() << 1) ^ (rhs.getY() << 2);
}
};

struct EqualToPoint
{
bool operator () (const point& lhs, const point& rhs) const
{
cout << "kafaka2." << endl;
return (lhs.getX() == rhs.getX())
&& (lhs.getY() == rhs.getY());
}
};

std::ostream& operator << (std::ostream& os, const point& rhs) {
os << "(" << rhs.ix
<< ", " << rhs.iy << ")";
return os;
}

template <typename Container>
void display(const Container& con)
{
for(auto &elem : con)
{
cout << elem << " ";
}
cout << endl;
}

void test() {
unordered_multiset<point, HashPoint, EqualToPoint> number = {
point(4, 5),
point(1, 2),
point(-1, 2),
point(1, -2),
point(1, 2),
point(3, 2),
point(8, 10),
};
display(number);
}

int main() {
test();
return 0;
}
/*
we've hashed here
we've hashed here
we've hashed here
we've hashed here
we've hashed here
kafaka2.
we've hashed here
we've hashed here
(-1, 2) (1, -2) (1, 2) (1, 2) (8, 10) (3, 2) (4, 5)
*/

unordered_map 和 unordered_multimap 大体上和上面思路相同。

针对 string 而言,已经实现了 hash ,所以就不需要再进行 hash 函数的设计。

priority_queue

1
2
3
4
5
template<
class T,
class Container = std::vector<T>,
class Compare = std::less<typename Container::value_type> // 萃取
> class priority_queue;

这部分内容可以参考之前的笔记,或者参考:
https://en.cppreference.com/w/cpp/container/priority_queue