5.7-day07-C++纯虚函数/动态绑定/虚析构函数/标准异常/MFC模板
十二、纯虚函数、抽象类、纯抽象类
形如:
virtrual 返回类型 成员函数名 (形参表) = 0;
的虚函数被称为纯虚函数。
一个包含了纯虚函数类称为抽象类,抽象类不能实例化为对象。
如果一个类继承自抽象类,但是并没有为其抽象基类中的全部纯虚函数提供覆盖,那么该子类就也是一个抽象类。
class A { // 纯抽象类
virtual void foo (void) = 0;
virtual void bar (void) = 0;
virtual void fun (void) = 0;
};
class B : public A { // 抽象类
void foo (void) { ... }
};
class C : public B { // 抽象类
void bar (void) { ... }
};
class D : public C { // 具体类
void fun (void) { ... }
};
除了构造和析构函数以外,所有的成员函数都是纯虚函数的类称为纯抽象类。
十三、动态绑定(后期绑定、运行时绑定)
1.虚函数表
class A {
public:
virtual void foo (void) { ... }
virtual void bar (void) { ... }
};
class B : public A {
public:
void foo (void) { ... }
};
A* pa = new A;
pa->foo (); // A::foo
pa->bar (); // A::bar
---------------------
A* pa = new B;
pa->foo (); // B::foo
pa->bar (); // A::bar
2.动态绑定
当编译器看到通过指向子类对象的基类指针或者引用子类对象的基类引用,调用基类中的虚函数时,并不急于生成函数调用代码,相反会在该函数调用出生成若干条指令,这些指令在程序的运行阶段被执行,完成如下动作:
1)根据指针或引用的目标对象找到相应虚函数表的指针;
2)根据虚函数表指针,找到虚函数的地址;
3)根据虚函数地址,指向虚函数代码。
由此可见,对虚函数的调用,只有运行阶段才能够确定,故谓之后期绑定或运行时绑定。
3.动态绑定对程序的性能会造成不利影响。如果不需要实现多态就不要使用虚函数。
十四、运行时类型信息(RTTI)
1.typeid操作符
A a;
typeid (a)返回typeinfo类型的对象的常引用。
typeinfo::name() - 以字符串的形式返回类型名称。
typeinfo::operator==() -类型一致
typeinfo::operator!=() -类型不一致
#include <typeinfo>
2.dynamic_cast
十五、虚析构函数
将基类的析构函数声明为虚函数,delete一个指向子类对象的基类指针,实际被执行的将是子类的析构函数,而子类的析构函数可以自动调用基类的析构函数,进而保证子类特有的资源,和基类子对象中的资源都能够得到释放,防止内存泄漏。
如果基类中存在虚函数,那么就有必要为其定义一个虚析构函数,即使该函数什么也不做。
思考:
虚函数可以内联吗?不可以
一个类的构造函数可以被定义为虚函数吗?不可以
一个类的静态成员函数可以被定义为虚函数吗?不可以
一个类的成员函数形式的操作符函数可以被定义为虚函数吗?可以
一个全局函数可以被定义为虚函数吗?不可以
class PDFParser {
public:
void parse (const char* file) {
// 解析出一行文字
on_text (...);
// 解析出一幅图片
on_image (...);
// 解析出一张图形
on_graph (...);
}
private:
virtual void on_text (...) = 0;
virtual void on_image (...) = 0;
virtual void on_graph (...) = 0;
};
class PDFRender : public PDFParser {
private:
void on_text (...) { ... }
void on_image (...) { ... }
void on_graph (...) { ... }
};
PDFRender render (...);
render.parse ("test.pdf");
模板方法模式
MFC
第六课 异常和I/O流
一、为什么要有异常——WHY?
1.通过返回值表达错误
局部对象都能正确的析构
层层判断返回值,流程繁琐
2.通过setjmp/longjmp远程跳转
一步到位进入错误处理,流程简单
局部对象会失去被析构的机会
3.异常处理
局部对象都能正确的析构
一步到位进入错误处理,流程简单
二、异常的语法——WHAT?
1.异常的抛出
throw 异常对象;
异常对象可以是基本类型的变量,也可以是类类型的对象。
当程序执行错误分支时抛出异常。
2.异常的捕获
try {
可能抛出异常的语句块;
}
catch (异常类型1 异常对象1) {
处理异常类型1的语句块;
}
catch (异常类型2 异常对象2) {
处理异常类型2的语句块;
}
...
catch (...) {
处理其它类型异常的语句块;
}
异常处理的流程,始终沿着函数调用的逆序,依次执行右花括号,直到try的右花括号,保证所有的局部对象都能被正确地析构,然会根据异常对象的类型,匹配相应的catch分支,进行有针对性的错误处理。
三、异常处理的使用方法——HOW?
1.抛出基本类型的异常,用不同的值代表不同的错误。
2.抛出类类型的异常,用不同的类型表示不同的错误。
3.通过类类型的异常携带更多诊断信息。
4.忽略异常和继续抛出异常。
5.异常说明
在一个函数的形参表后面写如下语句:
...形参表) throw (异常类型1, 异常类型2, ...) { ... }
表示这个函数可以被捕获的异常。
throw () - 这个函数所抛出的任何异常均无法捕获。
没有异常说明 - 这个函数所抛出的任何异常均可捕获。
class A {
virtual void foo (void)
throw (int, double) { ... }
virtual void bar (void)
throw () { ... }
};
class B : public A {
void foo (void)
throw (int, char) { ... }
// ERROR
void bar (void) { ... } // ERROR
void bar (void)
throw () { ... }
};
6.使用标准异常
#include <stdexcept>
四、构造函数中的异常
构造函数可以抛出异常,而且有些时候还必须抛出异常,已通知调用者构造过程中所发生的错误。
如果在一个对象的构造过程中抛出了异常,那么这个对象就称为不完整对象。不完整对象的析构函数永远不会被指向,因此需要在throw之前,手动释放动态的资源。
五、析构函数中的异常
永远不要在析构函数中抛出异常。
class A {
public:
~A (void) {
//throw -1;
try {
sysfunc ();
}
catch (...) {}
}
};
try {
A a;
a.foo ();
}
catch (...) { ... }
通过try-catch拦截所有可能引发的异常。
consexp.cpp
57
1
#include <iostream>
2
#include <stdexcept>
3
#include <cstdio>
4
using namespace std;
5
class FileError : public exception {
6
private:
7
const char* what (void) const throw () {
8
return "文件访问失败!";
9
}
10
};
11
class B {
12
public:
13
B (void) {clvivi
14
cout << "B构造" << endl;
15
}
16
~B (void) {
17
cout << "B析构" << endl;
18
}
19
};
20
class C {
21
public:
22
C (void) {
23
cout << "C构造" << endl;
24
}
25
~C (void) {
26
cout << "C析构" << endl;
27
}
28
};
29
class A : public C {
30
public:
31
A (void) : m_b (new B) {
32
FILE* fp = fopen ("none", "r");
33
if (! fp) {
34
delete m_b;
35
throw FileError ();
36
}
37
// ...
38
fclose (fp);
39
}
40
~A (void) {
41
delete m_b;
42
}
43
private:
44
B* m_b;
45
//C m_c;
46
};
47
int main (void) {
48
try {
49
A a;
50
// ...
51
}
52
catch (exception& ex) {
53
cout << ex.what () << endl;
54
return -1;
55
}
56
return 0;
57
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230128588-1909541285.png)
引用是以抛出异常的方式表达引用的失败;
得到引用的报告错误;
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230133871-558041099.png)
异常时,系统会在一安全区域保存异常的副本,此区域持用直到catch引用完了为止,保证持久性。
引用接收更好,若值接收,会引发一次拷贝构造(不能保证深拷贝),用引用效率更高,避免拷贝构造发生的错误。
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230134560-169829782.png)
如果在一个对象的构造过程中抛出了异常,那么这个对象就称为不完整对象。不完整对象的析构函数永远不会被执行,因此需要在throw之前,手动释放动态的资源。
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230134829-1299784596.png)
dc.cpp
49
1
#include <iostream>
2
using namespace std;
3
class A { virtual void foo (void) {} };
4
class B : public A {};
5
class C : public B {};
6
class D {};
7
int main (void) {
8
B b;
9
A* pa = &b;
10
cout << pa << endl;
11
cout << "-------- dc --------" << endl;
12
// pa指向B对象,成功
13
B* pb = dynamic_cast<B*> (pa);
14
cout << pb << endl;
15
// pa没有指向C对象,失败,安全
16
C* pc = dynamic_cast<C*> (pa);
17
cout << pc << endl;
18
A& ra = b;
19
try {
20
C& rc = dynamic_cast<C&> (ra);
21
}
22
catch (exception& ex) {
23
cout << "类型转换失败:" << ex.what ()
24
<< endl;
25
// ...
26
}
27
// pa没有指向D对象,失败,安全
28
D* pd = dynamic_cast<D*> (pa);
29
cout << pd << endl;
30
cout << "-------- sc --------" << endl;
31
// B是A的子类,成功
32
pb = static_cast<B*> (pa);
33
cout << pb << endl;
34
// C是A的孙子类,成功,危险!
35
pc = static_cast<C*> (pa);
36
cout << pc << endl;
37
// D不是A的后裔,失败,安全
38
//pd = static_cast<D*> (pa);
39
//cout << pd << endl;
40
cout << "-------- rc --------" << endl;
41
// 无论在编译期还是在运行期都不做检查,危险!
42
pb = reinterpret_cast<B*> (pa);
43
cout << pb << endl;
44
pc = reinterpret_cast<C*> (pa);
45
cout << pc << endl;
46
pd = reinterpret_cast<D*> (pa);
47
cout << pd << endl;
48
return 0;
49
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230135508-452349957.png)
返回空指针0,转换失败,仅限于有多态性;
以返回空指针0的方式表达失败,进行安全
性检查。
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230135846-150236135.png)
dynamic_cast 动态类型转换,具有多态性的父子之间
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230136104-118439201.png)
子类的指针任何时候都可以转换为基类的指针;
基类类型的指针通过static_cast<> ();的方式转换为
子类类型的指针。
exp1.cpp
23
1
#include <iostream>
2
#include <cstdio>
3
#include <cstdlib>
4
using namespace std;
5
void foo (void) {
6
FILE* fp = fopen ("none", "r");
7
if (! fp)
8
throw "打开文件失败!";
9
void* pv = malloc (0xFFFFFFFF);
10
if (! pv)
11
throw "内存分配失败!";
12
// ...
13
}
14
int main (void) {
15
try {
16
foo ();
17
}
18
catch (const char* ex) {
19
cout << ex << endl;
20
return -1;
21
}
22
return 0;
23
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230136581-1773627755.png)
void *pv = malloc(0xFFFFFFFF);
分配出一块很大的内存;
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230136966-1595644698.png)
fopen ---> #include <cstdio>
malloc--> #include <cstdlib>
exp2.cpp
34
1
#include <iostream>
2
#include <cstdio>
3
#include <cstdlib>
4
using namespace std;
5
class Error {};
6
class FileError : public Error {};
7
class MemError : public Error {};
8
void foo (void) {
9
FILE* fp = fopen ("none", "r");
10
if (! fp)
11
throw FileError ();
12
void* pv = malloc (0xFFFFFFFF);
13
if (! pv)
14
throw MemError ();
15
// ...
16
}
17
int main (void) {
18
try {
19
foo ();
20
}
21
catch (FileError& ex) {
22
cout << "打开文件失败!" << endl;
23
return -1;
24
}
25
catch (MemError& ex) {
26
cout << "内存分配失败!" << endl;
27
return -1;
28
}
29
catch (Error& ex) {
30
cout << "一般性错误!" << endl;
31
return -1;
32
}
33
return 0;
34
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230137647-849717626.png)
throw FileError (); 局部变量,作用域结束时销毁;
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230137863-114917269.png)
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230138065-2142662564.png)
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230138285-1359774732.png)
try {
可能抛出异常的语句块;
}
catch (异常类型1 异常对象1) {
处理异常类型1的语句块;
}
catch (异常类型2 异常对象2) {
处理异常类型2的语句块;
}
...
catch (...) {
处理其它类型异常的语句块;
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230138518-123089033.png)
exp3.cpp
46
1
#include <iostream>
2
#include <cstdio>
3
#include <cstdlib>
4
using namespace std;
5
class Error {
6
public:
7
virtual void print (void) const = 0;
8
};
9
class FileError : public Error {
10
public:
11
FileError (const string& file, int line) :
12
m_file (file), m_line (line) {}
13
void print (void) const: {
14
cout << "在" << m_file << "文件的第"
15
<< m_line << "行,发生了文件错误!"
16
<< endl;
17
}
18
private:
19
string m_file;
20
int m_line;
21
};
22
class MemError : public Error {
23
public:
24
void print (void) const {
25
cout << "内存不够啦!!!" << endl;
26
}
27
};
28
void foo (void) {
29
FILE* fp = fopen ("none", "r");
30
if (! fp)
31
throw FileError (__FILE__, __LINE__);
32
void* pv = malloc (0xFFFFFFFF);
33
if (! pv)
34
throw MemError ();
35
// ...
36
}
37
int main (void) {
38
try {
39
foo ();
40
}
41
catch (Error& ex) {
42
ex.print ();
43
return -1;
44
}
45
return 0;
46
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230139110-850496292.png)
在文件的第31行发生错误,携带更多的信息让异常更有意义。 因为FileError是一个类。
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230139313-2060180292.png)
Error & ex
采用引用;
异常系统会在一安全区域保存异常的副本,此区域持用
直到catch引用完为止。
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230139512-99908958.png)
分配出一块很大很大的内存;
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230139756-2049463516.png)
virtual 返回类型 成员函数名 (形参表) = 0
的虚函数被称为纯虚函数。
一个包含了纯虚函数的类称为抽象类,抽象类不能实例化为对象。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数
exp4.cpp
24
1
#include <iostream>
2
using namespace std;
3
void foo (void) {
4
throw 10;
5
}
6
void bar (void) {
7
try {
8
foo ();
9
}
10
catch (int& ex) {
11
--ex;
12
throw; // 继续抛出
13
}
14
// ...
15
}
16
int main (void) {
17
try {
18
bar ();
19
}
20
catch (int& ex) {
21
cout << ex << endl; // 9
22
}
23
return 0;
24
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230140074-1825143537.png)
继续抛出:一个函数捕获到异常以后,处理一部分工作。
其余的继续往上层抛出,让上层函数进行后续处理。
把catch捕获的异常处理一下,扔给下一个,
原来安全区的10的副本 --ex;
变为9
exp5.cpp
22
1
#include <iostream>
2
using namespace std;
3
void foo (void) throw (int, double, const char*) {
4
//throw 1;
5
//throw 3.14;
6
throw "Hello, Exception !";
7
}
8
int main (void) {
9
try {
10
foo ();
11
}
12
catch (int ex) {
13
cout << ex << endl;
14
}
15
catch (double ex) {
16
cout << ex << endl;
17
}
18
catch (const char* ex) {
19
cout << ex << endl;
20
}
21
return 0;
22
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230140453-599590264.png)
void foo (void)throw(int,double,constchar*)
所抛出的异常需要在这些范围之内;
不能抛出比基类更多的异常;
experr.cpp
54
1
#include <iostream>
2
#include <cstdio>
3
using namespace std;
4
class A {
5
public:
6
A (void) {
7
cout << "A构造" << endl;
8
}
9
~A (void) {
10
cout << "A析构" << endl;
11
}
12
};
13
void func3 (void) {
14
A a;
15
FILE* fp = fopen ("none", "r");
16
if (! fp) {
17
cout << "throw前" << endl;
18
throw -1;
19
cout << "throw后" << endl;
20
}
21
cout << "文件打开成功!" << endl;
22
// ...
23
fclose (fp);
24
}
25
void func2 (void) {
26
A a;
27
cout << "func3()前" << endl;
28
func3 ();
29
cout << "func3()后" << endl;
30
// ...
31
}
32
void func1 (void) {
33
A a;
34
cout << "func2()前" << endl;
35
func2 ();
36
cout << "func2()后" << endl;
37
// ...
38
}
39
int main (void) {
40
try {
41
cout << "func1()前" << endl;
42
func1 ();
43
cout << "func1()后" << endl;
44
}
45
catch (int ex) {
46
if (ex == -1) {
47
cout << "执行失败!改天再见!" << endl;
48
return -1;
49
}
50
}
51
// ...
52
cout << "执行成功!恭喜恭喜!" << endl;
53
return 0;
54
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230140976-1204112768.png)
jmperr.cpp
42
1
#include <iostream>
2
#include <cstdio>
3
#include <csetjmp>
4
using namespace std;
5
jmp_buf g_env;
6
class A {
7
public:
8
A (void) {
9
cout << "A构造" << endl;
10
}
11
~A (void) {
12
cout << "A析构" << endl;
13
}
14
};
15
void func3 (void) {
16
A a;
17
FILE* fp = fopen ("none", "r");
18
if (! fp)
19
longjmp (g_env, -1);
20
// ...
21
fclose (fp);
22
}
23
void func2 (void) {
24
A a;
25
func3 ();
26
// ...
27
}
28
void func1 (void) {
29
A a;
30
func2 ();
31
// ...
32
}
33
int main (void) {
34
if (setjmp (g_env) == -1) {
35
cout << "执行失败!改天再见!" << endl;
36
return -1;
37
}
38
func1 ();
39
// ...
40
cout << "执行成功!恭喜恭喜!" << endl;
41
return 0;
42
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230141380-1086242997.png)
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230141595-1827110813.png)
longjmp (g_env, -1);
远程跳转;
setjmp (g_env, -1);
setjmp 调一次,返回两次;
longjmp从一个函数调的,从另一个函数出去;
longjmp的第2个参数为返回值,实现远程跳转;
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230141837-1792862900.png)
reterr.cpp
32
1
#include <iostream>
2
#include <cstdio>
3
using namespace std;
4
int func3 (void) {
5
FILE* fp = fopen ("none", "r");
6
if (! fp)
7
return -1;
8
// ...
9
fclose (fp);
10
return 0;
11
}
12
int func2 (void) {
13
if (func3 () == -1)
14
return -1;
15
// ...
16
return 0;
17
}
18
int func1 (void) {
19
if (func2 () == -1)
20
return -1;
21
// ...
22
return 0;
23
}
24
int main (void) {
25
if (func1 () == -1) {
26
cout << "执行失败!改天再见!" << endl;
27
return -1;
28
}
29
// ...
30
cout << "执行成功!恭喜恭喜!" << endl;
31
return 0;
32
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230142234-1389068260.png)
shape.cpp
47
1
#include <iostream>
2
using namespace std;
3
class Shape {
4
public:
5
Shape (int x, int y) : m_x (x), m_y (y) {}
6
virtual void draw (void) = 0;
7
protected:
8
int m_x, m_y;
9
};
10
class Rect : public Shape {
11
public:
12
Rect (int x, int y, int w, int h) :
13
Shape (x, y), m_w (w), m_h (h) {}
14
void draw (void) {
15
cout << "矩形(" << m_x << ',' << m_y << ','
16
<< m_w << ',' << m_h << ')' << endl;
17
}
18
//int draw (void) const {}
19
private:
20
int m_w, m_h;
21
};
22
class Circle : public Shape {
23
public:
24
Circle (int x, int y, int r) :
25
Shape (x, y), m_r (r) {}
26
void draw (void) {
27
cout << "圆形(" << m_x << ',' << m_y << ','
28
<< m_r << ')' << endl;
29
}
30
private:
31
int m_r;
32
};
33
void render (Shape* shapes[]) {
34
for (size_t i = 0; shapes[i]; ++i)
35
shapes[i]->draw ();
36
}
37
int main (void) {
38
Shape* shapes[1024] = {};
39
shapes[0] = new Rect (1, 2, 3, 4);
40
shapes[1] = new Circle (5, 6, 7);
41
shapes[2] = new Circle (8, 9, 10);
42
shapes[3] = new Rect (11, 12, 13, 14);
43
shapes[4] = new Rect (15, 16, 17, 18);
44
render (shapes);
45
//Shape shape (1, 2);
46
return 0;
47
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230142785-213805758.png)
typeid.cpp
33
1
#include <iostream>
2
#include <typeinfo>
3
#include <cstring>
4
using namespace std;
5
class A {
6
public:
7
virtual void foo (void) {}
8
};
9
class B : public A {};
10
void print (A* pa) {
11
//if (! strcmp (typeid (*pa).name (), "1A"))
12
if (typeid (*pa) == typeid (A))
13
cout << "pa指向A对象!" << endl;
14
else
15
//if (! strcmp (typeid (*pa).name (), "1B"))
16
if (typeid (*pa) == typeid (B))
17
cout << "pa指向B对象!" << endl;
18
}
19
int main (void) {
20
cout << typeid (int).name () << endl;
21
cout << typeid (unsigned int).name () << endl;
22
cout << typeid (double[10]).name () << endl;
23
cout << typeid (char[3][4][5]).name () << endl;
24
char* (*p[5]) (int*, short*);
25
cout << typeid (p).name () << endl;
26
cout << typeid (const char* const* const).name (
27
) << endl;
28
cout << typeid (A).name () << endl;
29
A* pa = new B;
30
cout << typeid (*pa).name () << endl;
31
print (new A);
32
print (new B);
33
}
vd.cpp
25
1
#include <iostream>
2
using namespace std;
3
class A {
4
public:
5
A (void) {
6
cout << "A构造" << endl;
7
}
8
virtual ~A (void) {
9
cout << "A析构" << endl;
10
}
11
};
12
class B : public A {
13
public:
14
B (void) {
15
cout << "B构造" << endl;
16
}
17
~B (void) {
18
cout << "B析构" << endl;
19
}
20
};
21
int main (void) {
22
A* pa = new B;
23
delete pa;
24
return 0;
25
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230143377-2049010466.png)
vft.cpp
1
#include <iostream>
2
using namespace std;
3
class A {
4
public:
5
virtual void foo (void) {
6
cout << "A::foo()" << endl;
7
}
8
virtual void bar (void) {
9
cout << "A::bar()" << endl;
10
}
11
};
12
class B : public A {
13
public:
14
void foo (void) {
15
cout << "B::foo()" << endl;
16
}
17
};
18
int main (void) {
19
A a;
20
void (**vft) (void) = *(void (***) (void))&a;
21
cout << (void*)vft[0] << ' '
22
<< (void*)vft[1] << endl;
23
vft[0] ();
24
vft[1] ();
25
B b;
26
vft = *(void (***) (void))&b;
27
cout << (void*)vft[0] << ' '
28
<< (void*)vft[1] << endl;
29
vft[0] ();
30
vft[1] ();
31
return 0;
32
}
![](https://img2018.cnblogs.com/blog/1167156/201811/1167156-20181129230143840-1510885519.png)
函数的指针的指针---二级指针;
三级指针的目标---二级指针本身;
六、C++的I/O流库
C:fopen/fclose/fread/fwrite/fprintf/fscanf/fseek/ftell...
C++:对基本的I/O操作做了类的封装,其功能没有任何差别,用法和C的I/O流也非常近似。
sscanf
七、格式化I/O
<</>>
八、非格式化I/O
put/get
九、随机I/O
seekp/seekg
tellp/tellg
十、二进制I/O
read/write
K 0 - 255
A^K=B
B^K=A
PKI
HAS MD5
十一、格式控制
in a;
printf ("%d%x\n", a, a)
cout << a << endl;
流函数
流控制符
cout << hex << a;
cout << setw (10) << a;
十二、字符串流
class Student {
...
private:
string m_name;
int m_age;
};
Student s ("张飞", 25);
ofs.write (&s, sizeof (s));
初学
C++ Primer Plus
进阶
C++ Primer
Effective C++
More Effective C++
深入
C++程序设计语言,Bjarena Stroustrup,机械版
深度探索C++对象模型
休闲
C++语言99个常见错误
C++语言的设计与演化,Bjarena Stroustrup
-----------------
第七课
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">