前情提要: CppNote2
类型转换 类对象与其他类型的转换:
由其他类型向自定义类型转换,一般称为隐式转换
由自定义类型向其他类型转换
由自定义类型向其他类型转换是由类型转换函数 完成的,这是一个特殊的成员函数。形式如下:
由自定义类型向其他类型转换的例子:
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 ; }
嵌套类 一个类可以写在另一个类中,即嵌套类 ,又称内部类 。
简单例子 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 (); }
设计模式 Pimpl 通过一个私有的成员指针,隐藏指针所指向类的内部实现。该设计模式有以下优点:
提高编译速度
信息隐藏
减小编译依赖,以小代价平滑升级库文件
接口与实现解耦
移动语义友好
1 2 3 4 5 6 7 8 9 10 11 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 #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 ; }
内部类加静态数据成员形式 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 ; }
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 ; }
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 ; }
在之前的代码中,若处于多线程环境,可能会创建不止一个实例,违背单例模式的初衷。因此可以采用 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 ; }
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 ; }
上面的代码用饿汉模式或是饱汉模式都 OK.
注意 :上面代码中的<pthread.h>
库只能在 linux 下使用。
string 的底层实现 原理 三种基本方式:
Eager Copy(深拷贝)
COW(写时复制)
SSO(Short String Optimization 短字符串优化)
COW:只是进行读操作时,浅拷贝;如果需要写操作,再深拷贝。
写时复制的体现(注意:以下图片是在较旧的环境 下):
需要写操作,进行深拷贝:
注意在较新环境下并不使用写时复制,而是使用 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 ; }
通常,一个程序里的字符串大都很短小,而在64位机器上,一个 char* 指针就占用了8个字节,所以 SSO 就出现了。其思想是:发生拷贝时要需复制一个指针,但对小字符串来说,直接复制整个字符串比较划算。实现示意图如下:
更多细节参考:
实现写时复制 大致写出写时复制的逻辑。大体思路:
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; } 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]; } 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 ; }
值得一提的是,在使用下标运算符[]
时,写时复制要求在 读/写 两种不同的操作下有不同的行为,因此考虑自定义一个新类型,然后重载=
和<<
。
这份代码细节拉满,需要细细品味。
派生类对象的创建与销毁
继承方式
基类成员访问权限
在派生类中访问权限
派生类对象访问
公有继承
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 ; }
二、如果派生类没有显式定义构造函数而基类有显示定义构造函数,则基类必须拥有默认构造函数。
三、如果派生类有构造函数,基类有默认构造函数,则创建派生类的对象时,基类的默认构造函数会自动调用。如果想调用基类的有参构造函数,必须要在派生类构造函数的初始化列表中显示调用基类的有参构造函数。
四、如果派生类和基类都有构造函数,但基类没有默认的无参构造函数,即基类的构造函数均带有参数,则派生类的每一个构造函数必须在其初始化列表中显示地去调用基类的某个带参的构造函数。
- - - - - 创建END - - - - -
- - - - - 销毁 - - - - -
当派生类对象被删除时,派生类的析构函数被执行。析构函数同样不能继承,因此,在执行派生类析构函数时,基类析构函数会被自动调用。执行顺序是先执行派生类的析构函数,再执行基类的析构函数。
当考虑对象成员时,继承机制下析构函数的调用顺序:
先调用派生类的析构函数
再调用派生类中对象成员的析构函数
最后调用普通基类的析构函数
- - - - - 销毁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 ; }
多继承下,基类构造函数的执行顺序与其在派生类构造函数初始化列表中的顺序无关,而与基类被继承的顺序有关。
成员名冲突的二义性 解决多继承下函数名字冲突的方法:
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 ; }
菱形继承的二义性 多基派生中,如果在多条继承路径上有一个共同的基类,如下图所示,不难看出,在D类对象中,会有来自两条不同路径的共同基类(类A)的双重拷贝。
菱形继承会导致数据成员的存储二义性,解决方法:使 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 #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 ; }
下面展示解决方法:
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 大小出现变化是由于虚基指针 。
或者也可以:
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 ; }
关于虚基指针的图示(转自一颗程序媛0915想上岸 ):
若为多继承时,内存布局如下:
虚继承的内存布局如下:
来看另一种情况:
若为多继承时,内存布局如下:
虚继承的内存布局如下: 第一个vbptr 存放3个数据,因为指针合并,向距离作用域最近的指针内层合并
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 ; }
基类与派生类间的转换 “类型适应”指类型间的关系。“A类适应B类”,意味着 A 类对象能直接用于需要 B 类对象的场合,在这种意义下,派生类适应基类 ,派生类对象适应基类对象,派生类对象的指针和引用也适应基类对象的指针和引用。例如:
可以把派生类的对象赋值给基类的对象
可以把基类的引用绑定到派生类的对象
可以声明基类的指针指向派生类的对象 (向上转型)
派生类对象间的复制控制 基类的拷贝构造函数和 operator= 运算符函数不能被继承,因此需要注意:
如果用户定义了基类的拷贝构造函数,而没有定义派生类的 拷贝构造函数,那么在用一个派生类对象初始化新的派生类对象时,两对象间的派生类部分执行缺省 的行为,而两对象间的基类部分执行用户定义的 基类拷贝构造函数。
如果用户重载了基类的赋值运算符函数,而没有重载派生类的 赋值运算符函数,那么在用一个派生类对象给另一个已经存在的派生类对象赋值时,两对象间的派生类部分执行缺省 的赋值行为,而两对象间的基类部分执行用户定义的 重载赋值函数。
如果用户定义了 派生类的拷贝构造函数或者重载了 派生类的对象赋值运算符=,则在用已有派生类对象初始化新的派生类对象时,或者在派生类对象间赋值时,将会执行用户定义的派生类的拷贝构造函数或者重载赋值函数,而不会再自动调用基类的 拷贝构造函数和基类的重载对象赋值运算符,这时,通常需要用户在派生类的拷贝构造函数或者派生类的赋值函数中显式调用基类的 拷贝构造或赋值运算符函数。
多态 概念、分类 多态:对于同一指令,不同对象产生不同行为。
多态的分类:
多态的其他细节例子参考 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 的大小是虚函数指针的体现。
虚函数的原理、条件 虚函数的实现原理:
虚函数机制的激活条件:
基类定义虚函数
派生类重写该虚函数
创建派生类的对象
用基类的指针指向(引用绑定)派生类的对象
使用基类的指针(引用)调用该虚函数
不能设置为虚函数的函数:
普通函数(自由函数、全局函数)
内联成员函数
静态成员函数
友元函数(分情况)
若该友元函数本身是一个普通函数,则不能被设置为虚函数
若该友元函数本身是另外一个类的成员函数,则可以被设置为虚函数
构造函数
虚函数的访问 一、指针访问
即上例。
二、引用访问
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 ; }
三、对象访问
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 ; }
注意,这里并没有体现出多态。
和普通函数一样,虚函数一样可以通过对象名来调用,此时编译器采用的是静态联编 。
四、成员函数中访问
例子不难理解,耐心看:
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 ; }
五、构造函数和析构函数中访问
它们所调用的虚函数是自己类中定义的函数,如果在自己的类中没有实现该函数,则调用的是基类中的虚函数。
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 ; }
抽象类 参见 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 ; }
在上面的代码中,~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 ; }
另一种方法是,使用虚析构函数:
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 ; }
重载、隐藏、覆盖 重载:在同一个作用域中,函数的名字相同,但是参数列表不一样(包括参数的个数、参数类型、参数顺序)
重定义(重写、覆盖):发生在基类与派生类中,必须是虚函数 ,函数名字相同,参数列表也相同。
隐藏:发生在基类与派生类中,派生中的函数与基类中的函数名字相同 。(至于是不是虚函数,至于参数列表是不是一样的没有关系),派生类的数据成员也可以隐藏基类中的同名数据成员。
看一个例子:
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 (1 ); d.Base::print (); 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 ; }
上面代码可以看出,对于普通单继承,虚表只有一张,位于只读段。
作业-词频统计 之前的作业,再用 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 (); pb->b (); pb->c (); pb->d (); cout << endl; C* pc = &c; pc->a (); pc->A::b (); pc->B::b (); pc->c (); pc->d (); } int main () { test (); 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 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; };
注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 : 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 : 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++ 中,如果继承链上存在虚继承的基类,则最底层的子类要负责完成该虚基类部分成员的构造。 即我们需要显式调用虚基类的构造函数来完成初始化,如果不显式调用 ,则编译器会调用虚基类的缺省构造函数 ,不管初始化列表中次序如何,对虚基类构造函数的调用总是先于普通基类的构造函数。如果虚基类中没有定义 的缺省构造函数,则会编译错误 。因为如果不这样做,虚基类部分会在存在的多个继承链上被多次初始化。 很多时候,对于继承链上的中间类,我们也会在其构造函数中显式调用虚基类的构造函数,因为一旦有人要创建这些中间类的对象,我们要保证它们能够得到正确的初始化。
对于虚继承的派生类对象的析构,析构函数的调用顺序为:
先调用派生类的析构函数
然后调用派生类中成员对象的析构函数
再调用普通基类的析构函数
最后调用虚基类的析构函数
效率分析:
使用 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>
格式。
一些方法论 如何研究一个陌生的第三方代码
若只要求达到使用标准,善用搜索。
找源代码。
查找头文件、实现文件、测试文件。
在测试文件中找 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) { XMLText* myTitleNode = p -> FirstChildElement ("title" ) -> FirstChild () -> ToText (); string mytitle = myTitleNode -> Value (); 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, "" ); 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 ; }
输出文件截图如下:
从效果上看,这份代码就是把文字部分扒了下来。
作业-抽象类 要求 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 ( ) Circle (double 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)
③ 成员方法
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 ; }
移动语义 引入。来看下面的例子:
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 ) { 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) { delete [] _pstr; _pstr = nullptr ; _pstr = new char [strlen (rhs._pstr) + 1 ](); strcpy (_pstr, rhs._pstr); } 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; String s3 = "world" ; cout << "s3 = " << s3 << endl; } int main (int argc, char **argv) { test (); return 0 ; }
注意第 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;
由此,写出如下函数:
1 2 3 4 5 6 7 8 9 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 String& operator =(String&& rhs) { cout << "String& operator=(String &&)" << endl; delete [] _pstr; _pstr = nullptr ; _pstr = rhs._pstr; rhs._pstr = nullptr ; return *this ; }
注意到上面代码中没有防止自复制的逻辑,这是否必要呢?
目前来看,似乎没有必要。
但是考虑如下代码:
1 2 3 4 5 cout << "000000" << endl; s4 = std::move (s4); cout << "s4 = " << s4 << endl; cout << "11111" << endl;
输出结果:
以上输出结果需要联系移动赋值运算符函数、输出函数来分析。
可见,移动赋值运算符函数仍然需要改进:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 String &operator =(String &&rhs) { cout << "String &operator=(String &&)" << endl; if (this != &rhs) { delete [] _pstr; _pstr = nullptr ; _pstr = rhs._pstr; rhs._pstr = nullptr ; } return *this ; }
进一步研究一下 std::move() 函数:
1 2 3 4 5 6 7 8 9 10 11 12 s4 = std::move (s4); cout << "s4 = " << s4 << endl; cout << "11111" << endl; std::move (s1); cout << "s1 = " << s1 << endl; cout << "2222" << endl;
输出结果:
此时 s1 里面的内容还没有转走(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 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 ) { 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) { delete [] _pstr; _pstr = nullptr ; _pstr = new char [strlen (rhs._pstr) + 1 ](); strcpy (_pstr, rhs._pstr); } return *this ; } String (String &&rhs) :_pstr(rhs._pstr) { cout << "String(string &&)" << endl; rhs._pstr = nullptr ; } String &operator =(String &&rhs) { cout << "String &operator=(String &&)" << endl; if (this != &rhs) { delete [] _pstr; _pstr = nullptr ; _pstr = rhs._pstr; rhs._pstr = nullptr ; } 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; String s3 = "world" ; cout << "s3 = " << s3 << endl; cout << endl; String s4 ("wangdao" ) ; cout << "s4 = " << s4 << endl; cout << endl; s4 = String ("wuhan" ); cout << "s4 = " << s4 << endl; cout << endl; cout << "000000" << endl; 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 ; }
总结:
左值、右值、左值引用、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 () { SafeFile sf (fopen("test.txt" , "a+" )) ; string msg = "delishashijiediyikeai\n" ; sf.write (msg); return 0 ; }
RAII 特征:
在构造函数中获取资源或者托管资源
在析构函数中释放资源
提供若干访问资源的方法
一般不允许复制或赋值(将拷贝构造函数或者赋值运算符函数删除即可)
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 (){ std::cout << "~point()" << std::endl; } 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) { 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 ; }
在上面的代码中,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; cout << endl; auto_ptr<int > ap2 = ap; cout << "*ap2 = " << *ap2 << endl; cout << "*ap = " << *ap << endl; } int main () { test (); return 0 ; }
出现错误的原因是:在执行拷贝操作的时候,会将 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 (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 > up3 (new int (34 )) ; cout << endl; vector<unique_ptr<int >> vec; vec.push_back (std::move (up)); vec.push_back (unique_ptr <int >(new int (30 ))); } int main () { test (); return 0 ; }
需要构建右值的时候,可以使用如下两种方法:
直接显示调用构造函数,创建临时对象
使用std::move函数将左值转换为右值。
需要构建左值的时候,可以使用如下两种方法:
可以使用构造函数创建对象,创建有名对象。Point pt(1, 2);
可以使用右值引用将右值转换为左值,Point &&rref = Point(1, 2)
shared_ptr std::shared_ptr
是一个引用计数 智能指针,用于共享对象的所有权
引进了一个计数器shared_count,用来表示当前有多少个智能指针对象共享指针指向的内存块
析构函数中不是直接释放指针对应的内存块,如果shared_count大于0则不释放内存只是将引用计数减1,只有计数等于0时释放内存
复制构造与赋值操作符只是提供一般意义上的复制功能,并且将引用计数加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; 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; 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 ; }
再来看是否可以作为容器的元素:
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 (){ std::cout << "~point()" << std::endl; } 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 ; }
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; 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 ; }
以上代码存在内容泄露,原因:
解决方法:
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; 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 ; }
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 (){ std::cout << "~point()" << std::endl; } point (const point& rhs):ix (rhs.ix), iy (rhs.iy){ std::cout << "拷贝构造函数" << std::endl; } private : int ix; int iy; }; void test () { weak_ptr<point> wp2; { shared_ptr<point> sp (new point(1 ,2 )) ; wp2 = sp; 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 ; }
删除器
对于一些特殊的情况,不能依赖默认的删除器:
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 ()); } 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 (){ std::cout << "~point()" << std::endl; } 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) ; } void test2 () { unique_ptr<point> up (new point(1 ,2 )) ; unique_ptr<point> up2 (new point(3 ,4 )) ; up.reset (up2.get ()); } void test3 () { point* pt = new point (1 ,2 ); shared_ptr<point> sp (pt) ; shared_ptr<point> sp2 (pt) ; } void test4 () { shared_ptr<point> sp (new point(1 ,2 )) ; shared_ptr<point> sp2 (new point(3 ,4 )) ; sp.reset (sp2.get ()); } 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 (){ std::cout << "~point()" << std::endl; } point (const point& rhs):ix (rhs.ix), iy (rhs.iy){ std::cout << "拷贝构造函数" << std::endl; } 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 ; }
上面代码问题的解决方法:
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 (){ std::cout << "~point()" << std::endl; } point (const point& rhs):ix (rhs.ix), iy (rhs.iy){ std::cout << "拷贝构造函数" << std::endl; } 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 ; }
模板 模板(Template),是一种通用的描述机制。模板允许使用通用类型 来定义函数或类等,在使用时,通用类型可被具体的类型,如int、double甚至是用户自定义的类型来代替。模板引入一种全新的编程思维方式,称为“泛型编程 ”或“通用编程”。
模板的引入使函数定义摆脱了类型的束缚,代码更为高效灵活。C++ 中,通过下述形式定义一个模板:
或:
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.h
中#include "add.cc"
。
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;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 ; }
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 ; }
成员函数模板 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 (){ std::cout << "~point()" << std::endl; } 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 ; }
可变模板参数 可变模板参数(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 ... 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 ... 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 (); display (1 , 2 , 3 ); display (1 , "hello" , 3.3 , s1); print (); print (1 , 2 ); print (1 , 2.2 , s1, "asdf" , 'c' ); 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 #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 ; }
类模板 理解了函数模板的应用,类模板的提出似乎是水到渠成的。
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> class Stack { private : T sz[num]; int point; public : Stack () { point=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 ; } template <class T ,int num>bool Stack<T,num>::isFull (){ return point==num; } template <class T ,int num>bool Stack<T,num>::push (const T& obt){ if (isFull ()) return false ; else { sz[point]=obt; point++; return true ; } } template <class T ,int num>bool Stack<T,num>::pop (T &obt){ if (isEmpty ()) return false ; else { point--; obt = sz[point]; return true ; } } int main () { Stack<int , 10 > st; cout << "开始时st是否为空? " << st.isEmpty () << endl; st.push (5 ); cout << "此时st是否为空? " << st.isEmpty () << endl; for (int i = 1 ; i < 10 ; i++) { st.push (i); } cout << "此时st是否已满?" << st.isFull () << endl; int rec = 0 ; while (st.pop (rec)) cout << rec << " " ; cout << endl; return 0 ; }
模板的嵌套:
模板的嵌套可以理解成在另一个模板里定义一个模板。以模板(类,或者函数)作为另一个模板(类,或者函数)的成员,也称成员模板 。
成员模版不能声明为 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 { public : template <class R > class Inside { private : R r; public : Inside (R x) { r=x; } void disp () {cout << "Inside: " << r << endl;} }; Outside (T x) : t (x) {} void disp () { cout<<"Outside:" ; t.disp (); } private : Inside<T> t; }; int main () { Outside<int >::Inside<double > obin (3.5 ) ; obin.disp (); Outside<int > obout (2 ) ; obout.disp (); getchar (); return 0 ; }
嵌套越多越麻烦:
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) { }
vector、deque、list
这部分知识早有接触,因此只是杂乱地记一些东西。
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); } void test2 () { std::deque<int > number = {1 , 3 , 5 , 7 , 9 }; display (number); number.push_back (11 ); display (number); number.pop_back (); display (number); } void test3 () { std::list<int > number = {1 , 3 , 5 , 7 , 9 }; display (number); number.push_back (11 ); display (number); number.pop_back (); display (number); } int main () { test3 (); return 0 ; }
vector 源码阅读 vector 的继承图:
vector底层原理图:
类型萃取(提取、过滤):
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; 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函数有范围检查,所以更加安全一些:
如何获取 vector 的第一个元素的地址:
1 2 3 4 5 6 &number; &*number; &*number.begin (); &number[0 ]; int * pdata = number.data ();
deque 源码阅读 deque 的继承图:
deque 的原理图:
杂项 容器的清空:
list 的排序相关:
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 ; }
针对于自定义类型用法(定义小于号):
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 ; }
作业-文本查询再探 在之前的基础上,支持逻辑运算与、或、非,进行文本查询。
很恶心的作业,让你充分理解为什么说 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) { auto & lines = wm[word]; if (!lines) lines.reset (new std::set<line_no>); 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 { static std::shared_ptr<std::set<line_no>> noData (new std::set<line_no>); 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; virtual ~Query_base () = default ; private : virtual QueryResult eval (const TextQuery&) const = 0 ; virtual std::string rep () const = 0 ; }; 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&); 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 ; 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 ("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 (){ std::cout << "~point()" << std::endl; } 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; } return _pInstance; } private : class AutoRelease { public : AutoRelease () { cout << "AutoRelease()" << endl; } ~AutoRelease () { cout << "~AutoRelease()" << endl; if (_pInstance) { delete _pInstance; _pInstance = nullptr ; } } }; Singleton () { cout << "Singleton()" << endl; } ~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;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 ; }
再次封装 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 #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 ; }
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 #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() #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__); } #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 #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 ; } } }
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 { 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' ; }
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 ; }
对于自定义类型:
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){ } 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; }; 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 ); } }; } 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 ; }
对于上面代码实现的功能而言,也可以使用函数对象(仿函数)的方法:
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){ } 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 ); } }; 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 ; }
也可以这样:
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 ()); } }; } 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 ; }
还可以这样(闲的):
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 ; }
对于 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 ; }
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