C/C++程序员基础:标准C++概括与总结

来源:互联网 发布:淘宝刷访客软件 编辑:程序博客网 时间:2024/04/30 13:39
第一课  从C到C++
一、C++语言概述
C++之父:Bjarne Stroustrup
1979,Cpre,C with Class
1983,第一个C with Class编译器,C++命名
1985,CFront 1.0,《The C++ Programming Language》
1987,GNU C++
1990,Borland C++
1992,Microsoft C++
1998,ISO C++98
2003,修订,C++03
2011,ISO C++2011/C++0X
二、我的第一个C++程序
1.编译方式:
g++ hello.cpp
gcc hello.cpp -lstdc++
2.文件扩展名:
.cpp/.cc/.C/.cxx
3.头文件:
#include <iostream>
#include <cstdio>
#include <stdio.h>
4.输入输出:
用cin对象表示标准输入,用cout对象表示标准输出。
5.名字空间:
std是标准C++库中所有的函数、变量、类型、对象的名字空间。
三、名字空间
1.定义名字空间
namespace 名字空间名 {
  名字空间成员;
}
2.访问名字空间
1)通过作用域限定运算符“::”
2)名字空间指令
using namespace 名字空间名;
可以省去名字空间前缀,但是注意不要因此引入歧义错误。
3)名字空间声明
using 名字空间名::名字空间成员;
4)任何没有被置于某名字空间中的全局变量、函数、类型等均处于无名名字空间中。以如下方式访问之:
::标识符
四、C++的结构、联合和枚举
1.结构中可以定义函数,谓之成员函数,在成员函数中可以直接访问成员变量。
2.可以定义匿名联合变量。
3.枚举是一个独立的数据类型。
五、C++的布尔类型
bool的值域:true、false。
bool类型的变量可以接受任意类型的表达式,其值非零则为true,零则为false。
六、C++的运算符
&& - and
|| - or
^  - xor
{  - <%
}  - %>
七、C++的函数
1.重载:在同一个作用域中,函数名相同,形参表不同的函数之间构成重载关系。
C++编译器通过对函数名按照形参表做编码替换区分不同的重载版本。
如果希望C程序调用C++模块中的函数,需要禁止C++对函数名进行替换:
extern "C" {
  函数1 {...}
  函数2 {...}
  ...
}
一个函数指针究竟指向哪个重载版本由函数指针的类型(包括形参表)决定,而不由调用实参表决定。
2.缺省参数
如果某个参数具有缺省值,那么其右边的所有参数必须都有缺省值。
注意不要因为使用缺省参数导致重载歧义。
3.哑元
只有类型而没有变量名的形参。
4.内联
用函数的二进制代码模块替换函数调用语句,进而节省函数调用的时间开销,其代价是增加目标模块的体积。
建议内联关键字:inline,编译器会根据实际情况决定是否真的做内联。复杂函数、递归函数不内联。
八、C++的动态内存分配
九、C++的引用


第二天:
八、C++的动态内存分配
new运算符用于动态内存分配,delete运算符用于动态内存释放。
C语言:
int* p = (int*)malloc (sizeof (int));
*p = 100;
free (p);
C++语言:
int* p = new int (10);
*p = 100;
delete p;
int* pa = new int[10];
pa[0] = 10;
pa[1] = 20;
...
delete[] p;
九、C++的引用
1.引用就是别名。
int a = 10;
int& b = a; // b就是a的别名
b++;
cout << a << endl; // 11
2.将引用用于函数的参数,可以修改实参变量的值,可以减小函数调用的开销,避免虚实结合过程中对实参值的复制。
3.将函数的形参定义为常引用,可以在提高传参效率的同时,防止对实参的意外修改,而且还可以接受常量型的实参对象。
4.不要从函数中返回对局部变量的引用,因为该引用所引用的内存会在函数返回以后随着函数栈一起被释放。
5.引用与指针
1)引用的本质就是指针。
2)指针可以不做初始化,其目标可以在初始化以后随意改变(除非是指针常量),而引用必须做初始化,
且一旦初始化其所引用的目标不能再改变。
int a, b;
int* p; // 可以不初始化
p = &a; // p指向a
p = &b; // p指向b
int& r; // error,引用不能不初始化
int& r = a; // r引用a
r = b; // 将b的值赋给a
3)可以定义指针的指针(二级指针),但是不能定义引用的指针。
int a;
int* p = &a;
int** pp = &p; // ok
int& r = a;
int&* pr = &r; // error
4)可以定义指针的引用,但是不能定义引用的引用。
int a;
int* p = &a;
int*& rp = p; // ok
int& r = a;
int&& rr = r; // error
5)可以定义指针数组,但是不能定义引用数组,可以定义数组引用。
int a, b, c;
int* parr[]={&a,&b,&c}; // ok
int& rarr[]={a,b,c}; //error
int arr[] = {1,2,3};
int (&r)[3] = arr; // ok

6)和函数指针一样,也可以定义函数引用,其语法特征与指针完全相同。


第二课  类和对象(上)
一、类型转换运算符
1.静态类型转换:
目标类型标识符 = static_cast<目标类型> (源类型标识符);
int a;
void* pv = &a;
int* pn = static_cast<int*> (pv);
主要用于将void*转换为其它类型的指针。
2.动态类型转换:
目标类型标识符 = dynamic_cast<目标类型> (源类型标识符);
主要用于就有多态特性的父子类对象指针或引用之间的转换。
3.重解释类型转换:
目标类型标识符 = reinterpret_cast<目标类型> (源类型标识符);
int a;
double* p = reinterpret_cast<double*> (&a);
在不相关的指针或引用类型之间相互转换。
4.常类型转换:
目标类型标识符 = const_cast<目标类型> (源类型标识符);
去除一个指针或引用的常属性。
const int a = 10;
const int* p = &a;
*p = 20; // error
int* p2 = const_cast<int*> (p);
*p2 = 20; // ok
double* p3 = const_cast<double*> (p); // error
const int& r = a;
r = 30; // error
int& r2 = const_cast<int&> (r);
r2 = 30; // ok
二、什么是对象
万物皆对象。
学生对象
属性:年龄、姓名、学号
行为:学习、吃饭、娱乐
手机对象
属性:品牌、价格、分辨率、内存
行为:通话、短信、播放音频视频、游戏、应用
三、类和对象
类:对拥有相同属性和行为的对象进行归纳和抽象。
四、类的定义与使用
1.类的定义
class 类名 {
};
例如:
class Student {
};
2.成员变量:描述属性
class 类名 {
  类型 成员变量名;
};
例如:
class Student {
  string m_name;
  int m_age;
};
3.成员函数:描述行为
class 类名 {
  返回类型 成员函数名 (形参表) {
     函数体;
  }
};
例如:
class Student {
  string m_name;
  int m_age;
  void learn (string lesson) {
    ...
  }
  void eat (string food) {
    ...
  }
};
4.访问控制:
public:公有成员,谁都能访问。
private:私有成员,只有自己能访问。
protected:保护成员,只有自己和自己的子类能访问。
class Student {
public:
  string m_name;
  int m_age;
  void learn (string lesson) {
    ...
  }
  void eat (string food) {
    ...
  }
};
类的缺省访问控制为私有,而结构的缺省访问控制为公有。
五、构造函数
class 类名 {
  类名 (形参表) {
    构造函数体;
  }
};
构造函数没有返回类型,函数名与类名相同。构造函数无需亦无法直接调用,
在创建对象的过程中,构造函数自动被执行,完成对象的初始化。
1)构造函数的参数来自创建对象时所提供的构造实参表。
2)构造函数可以通过形参表的不同实现重载。在创建对象的过程中,
根据所提供构造实参表的不同,匹配到相适应的构造函数版本。
3)构造函数可以不带任何参数,这样的构造函数通常被称为无参构造函数或缺省构造函数。
请注意,无参构造函数或缺省构造函数未必一定无参。
4)如果一个类没有定义任何构造函数,系统就会自动提供一个无参构造函数,
在这个构造函数中完成基本的初始化工作。如果定义了构造函数,则系统不再提供构造函数。
5)构造函数的工作过程:
S1:创建并初始化基类部分;
S2:创建并初始化成员变量;
S3:执行构造函数中的代码。
6)初始化表
A.可以在定义构造函数的时候通过初始化表指示编译器对各个成员变量的初始化方式。
B.必须使用初始化表的场合:
a)类中含有缺少无参构造函数的类类型成员或者基类。
b)类中含有引用或者常量型的成员变量。
C.成员变量的初始化顺序依据其被声明的顺序,而非在初始化表中出现的顺序。
class Dumy {
public:
  Dumy (char* psz) : m_str (psz), m_len (/*m_str.length ()*/strlen (psz)) {}
  // ...
private:
  int m_len;
  string m_str;
};
以上类的定义有何问题?
六、声明与实现分离的类定义
1.将父类的声明放在.h文件中,而将类的实现放在.cpp文件中。
2.如果一个类的成员函数在类的声明部分进行定义,则缺省为内联函数,
如果成员函数在类声明的外部定义,则缺省为非内联。对于后者如果在其声明部分使用inline关键字,
则表明建议编译器对该函数做内联处理。
第三课  类和对象(下)
一、this指针
1.对于一个对象,在其所属类型的成员函数中,都有一个隐含的this指针,
该指针指向调用该函数的对象——调用对象。对于构造函数而言,其this指针就指向这个正在被构造的对象。
2.在构造函数中可以通过this指针区分同名的构造形参和成员变量。
3.通过返回*this,实现返回对调用对象自身的引用,满足级联式函数调用的需要。
4.通过将this指针做为函数的参数,实现不同对象间的交互。
二、常量型成员函数与常量型对象
1.常量型成员函数中的this指针是一个常量指针,以此防止对调用对象的意外修改。
2.被声明为mutable的成员变量可以在常量型成员函数中被修改。
3.常量型成员函数与非常量型成员函数可以构成重载关系,通过常量型对象(或指针、引用)
调用常量型成员函数,通过非常量型对象(或指针、引用)调用非常量型成员函数。
4.通过常量型对象(或指针、引用)只能调用常量型成员函数。通过非常量型对象(或指针、引用)
优先选择非常量型成员函数,如果没有非常量型成员函数,也可以调用常量型成员函数。
三、析构函数
1.当一个类类型对象被销毁的时候(局部变量离开作用域、动态创建堆对象被delete),析构函数被自动执行。
2.语法形式
class 类名 {
  ~类名 (void) {
    析构函数体;
  }
};
没有参数,不能重载。
3.如果在构造函数动态分配了资源,那么就需要自己定义析构函数,并在其中释放上述资源。
如果没有定义析构函数,那么系统就会提供一个缺省析构函数,但是缺省析构函数只负责释放成员变量,而不释放动态分配的资源。
4.析构函数的执行过程
S1:执行析构函数中的代码
S2:释放成员变量
S3: 释放基类部分
四、拷贝构造函数与拷贝赋值运算符
1.所谓拷贝构造就是以拷贝的方式进行构造。
Student s1 (...);
Student s2 (s1); // 拷贝构造
Student s2 = s1; // 拷贝构造
构造s1的副本s2
2.缺省方式的拷贝构造对于基本类型的成员变量,按字节复制,对于类类型的成员变量,执行相应类型的拷贝构造。
3.对于含有手动分配资源的对象缺省拷贝构造函数往往只能实现浅拷贝,为了获得基于深拷贝的完整副本,
常常需要自定义拷贝构造函数。
类名::类名 (const 类名& that) {...}
4.拷贝构造的时机
1)直接构造对象副本
Student s2 (s1);
Student s3 = s2;
2)以对象为参数调用函数
void foo (Student s) {
  ...
}
Student s;
foo (s);
3)从函数中返回对象
Student foo (void) {
  Student s (...);
  ...
  return s;
}
4)以对象的方式捕获异常
try {
  ...
}
catch (MyException ex) {
  ...
}
现代C++编译器倾向于对拷贝构造过程进行优化,基本原则就是尽可能地不做拷贝构造。
5.拷贝赋值
Student s1 ("张飞", 28);
Student s2 ("赵云", 25);
s2 = s1; // 拷贝赋值
s2.print (); // 张飞,28
6.缺省方式的拷贝赋值对于基本类型的成员变量,按字节复制,对于类类型的成员变量,执行相应类型的拷贝赋值。
7.对于含有手动分配资源的对象缺省拷贝赋值往往只能实现浅拷贝,为了获得基于深拷贝的完整副本,常常需要自定义拷贝赋值函数。
类名& 类名::operator= (const 类名& that) {...}
释放原内存
分配新内存
拷贝源内容
返回自引用
防止自赋值
五、类的静态成员
1.与属于对象的非静态成员不同,类的静态成员是属于类的,而不属于对象。
1)静态成员变量的定义和初始化,只能在类的外部进行,而不能在构造函数中进行。
2)静态成员变量被该类的多个对象实例所共享。
3)静态成员函数只能访问静态成员,而非静态成员函数既可以访问非静态成员,也可以访问静态成员。
4)访问静态成员,既可以通过类,也可以通过对象,但是最好用类。
5)静态成员和非静态成员一样也受类的作用域和访问控制的限制。
2.单例模式
1)通过构造函数私有化,禁止在类外部自由创建对象。
2)通过静态成员控制对象的创建。
3)通过引用计数控制对象的销毁。
六、类的成员指针
Student student (...);
string* pstr=&student.m_name;
pstr不是成员指针!
1.指向成员变量的指针
定义:成员变量的类型 类名::*指针变量名;
string Student::*pstr;
赋值:指针变量名=&类名::成员变量名;
pstr = &Student::m_name;
解引用:对象.*指针变量名、对象指针->*指针变量名
cout << student.*pstr;
cout << ps->*pstr;
2.指向成员函数的指针
定义:成员函数返回类型 (类名::*指针变量名) (形参表);
void (Student::*pfunc) (void);
赋值:指令变量名=&类名::成员函数名;
pfunc = &Student::who;
解引用:(对象.*指针变量名) (实参表)、(对象指针->*指针变量名) (实参表)
(s1.*pfunc) ();
(ps->*pfunc) ();
3.指向静态成员的指针与普通指针没有区别,不需要特殊的语法。
第四课  操作符重载
一、操作符与操作符函数
对于表达式L#R,如果L和R中至少有一个是类类型的对象,那么编译器就会将以上表达式处理为如下函数调用:
L.operator# (R); // 成员函数
operator# (L, R); // 全局函数
而该函数的返回值就是表达式的值。
二、双目操作符重载
复数:(3+2i) + (4+5i) = 7+7i
用复数类表达复数,可以通过重载加号运算符的方法,实现复数相加的运算规则。
三、输入输出操作符重载
Complex a (...);
cout << a;
// cout.operator<< (a);
// operator<< (cout, a);
cin >> a;
四、单目操作符重载
#O ==> O.operator# ()
   ==> operator# (O)
五、自增减操作符重载
前缀:Complex c (...);
++c ==> c.operator++ ()
表达式的值是自增减以后的值。
可以连用:++++c;
后缀:Complex c (...);
c++ ==> c.operator++ (0)
表达式的值是自增减以前的值。
不可连用:c++++; 编译错误
六、不能重载的操作符
:: - 作用域限定
.  - 成员访问
.* - 成员指针解引用
?: - 三目运算符
sizeof() - 获取字节数
typeid() - 获取类型信息
不能通过操作符重载发明新的操作符
Complex operator**(int x) {} // error !
操作符重载尽可能不要改变操作符的原始语义。
练习:定义3X3的矩阵类,实现如下操作符:
+/+=/*/*=/前后++/<<
第五课  继承与多态
一、下标操作符重载
int a[10] = {...};
a[5] = 100;
*(a+5) = 100;
------------------
Array a (...);
a[5] = 100; // a.operator[] (5) = 100;
cout << a[5] << endl; // cout << a.operator[] (5) << endl;
1.通常会分别针对下标运算的值作为左值和右值两种情况提供相对应的重载函数。形如:
元素类型& operator[] (size_t i) {...}
const 元素类型& operator[] (size_t i) const {...}
2.下标操作符多数情况下应用在具有连续存储特性的容器类型中。
二、函数操作符重载
返回类型 operator() (形参表) {...}
定义了函数操作符的类型所实例化的对象可以被当做函数使用,其参数表和返回值由上述operator()的参数表和返回值决定。
三、解引用和间接操作符重载
解引用:*
间接:->
智能指针
四、自定义类型转换和类型转换操作符重载
1.在目标类型中提供一个以源类型为参数的构造函数。
例如:
Integer::Integer (int data) {...}
int -> Integer
通过引入explicit关键字可以强制该类型转换为显式。
2.在源类型中提供一个以目标类型为函数名的类型转换运算符。
例如:
Integer::operator int (void) {...}
Integer -> int
五、继承的基本概念和语法
人                   - 基类
  姓名、年龄、吃饭
学生:是人           - 子类
  学号、学习
教师:是人           - 子类
  工资、讲课
     人       
    /  \     派生 |^
学生    教师      V| 继承
继承语法:
class 子类名 : 继承方式1 基类1, 继承方式2 基类2, ... {
};
class Person {...};
class Student : public Person {...};
class Teacher : public Person {...};
六、公有继承的基本特点
1.一个子类类型的对象在任何时候都可以作为一个基类类型的对象看待。
从一个子类类型对象的指针或引用到其基类类型的指针或引用不需要显示类型转换。
2.基类类型的指针或引用不能通过隐式类型转换得到子类类型的指针或引用(编译错误),
但是通过显式类型转换可以避免语法上的限制,由此带来的运行时风险要有程序设计者自己评估。
七、继承方式对访控属性的影响
三种继承方式:public、protected、private
当通过一个子类对象访问其从基类中继承的成员时,需要考虑继承方式对访控属性的影响。
防控属性:
访控限定符 访控属性 基类 子类 外部 友元
--------------------------------------
public     公有成员  OK   OK   OK   OK
protected  保护成员  OK   OK   NO   OK
private    私有成员  OK   NO   NO   OK
继承方式:
基类     在公有子 在保护子 在私有子
中的     类中变成 类中变成 类中变成
-----------------------------------
公有成员 公有成员 保护成员 私有成员
保护成员 保护成员 保护成员 私有成员
私有成员 私有成员 私有成员 私有成员
八、子类的构造函数与析构函数
1.子类没有定义构造函数,系统为子类提供一个无参构造函数,
该构造函数会调用基类的无参构造函数构造子类对象中的基类子对象。前提是,基类中要有无参构造函数。
2.若子类的构造函数中没有显式指明其基类部分如果被构造,
则系统会调用基类的无参构造函数构造子类对象中的基类子对象。
3.若子类的构造函数在其初始化表中以如下方式:
基类类名 (构造实参表)
指明了其基类的构造方式,则系统会根据重载匹配原则,
在基类中选择适当的构造函数构造子类对象中的基类子对象。
4.子类对象的构造顺序:
基类子对象->成员变量->子类的构造代码
5.任何时候子类的析构函数都会自动调用基类的析构函数析构子类对象中的基类子对象。
但是基类的析构函数不会自动调用子类的析构函数。因此delete一个指向子类对象的基类指针,可能引发内存泄露!!!
6.子类对象的析构顺序:
子类的析构代码->成员变量->基类子对象
九、子类的拷贝构造函数与拷贝赋值运算符函数
1.缺省方式拷贝构造和拷贝赋值会自动调用基类的拷贝构造和拷贝赋值,以保证子类对象副本中的基类子对象得到正确的复制。
2.自定义的拷贝构造和拷贝赋值需要显式地指明基类部分的拷贝构造和赋值方式,
否则子类对象副本中的基类部分或者以无参方式被构造,或者得不到赋值。
十、名字隐藏
子类中存在与基类中完全相同的标识符即构成隐藏。通过“基类名::”可以指明访问基类中被隐藏的标识符。
十一、私有继承与保护继承
公有继承:IsA
class Student : public Person
Student is a Person
class Cat : public Animal
Cat is a Animal
私有继承:实现继承,工具性的需要。
class DCT {
public:
  void codec (void) {...}
};
class JPEG : protected DCT {
public:
  void render (void) {
    ...
    codec ();
    ...
  }
};
class SJPEG : public JPEG {
public:
  void render (void) {
    ...
    codec ();
    ...
  }
};
Adobe:
十二、多重继承与钻石继承
1.名字冲突问题
用过类名+作用域限定
A
  foo
B
  foo
C : A, B
C c;
c.A::foo
c.B::foo
汇聚替代:在子类中提供对有冲突标识符的隐藏版本,在该隐藏版本中通过重载等机制,指明调用的是哪个基类中的。
2.钻石继承问题
钻石继承:子类继承自多个基类,而这些基类又源自一个共同的基类。
       A
      / \
     B   C
      \ /
       D
问题:公共基类子对象在每个中间子类中各有一个实例,因此通过不同继承路径访问公共基类子对象中的数据会有不一致的问题。
解决:通过虚继承,使公共基类子对象在最终子类对象中只有一份实例,而所有中间子类对象共享该实例。
虚继承:virtual、最终子类的构造函数可能需要显式指明公共基类子对象的构造方式。
十三、虚函数与多态
1.虚函数与多态的基本概念
       图形-位置、绘制
      /    \
  矩形      圆形
  长,宽    半径
  绘制      绘制
在基类中将一个成员函数声明为虚函数(在返回类型前面加上virtual关键字),
其子类中的同原型成员函数就也成为虚函数,并且对基类中的版本形成覆盖。
此时,通过一个指向子类对象的基类指针,或者引用子类对象的基类引用,调用该虚函数,
实际被调用的是子类中的覆盖版本。这种语法现象被称为多态。
虚函数——覆盖——多态。
2.虚函数覆盖的限制
1)基类中的成员函数必须被声明为虚函数,子类中的成员函数与基类中虚函数的函数名、形参表、常属性完全相同。
2)如果基类中虚函数的返回类型是基本类型,那么子类中覆盖版本的返回类型必须与基类版本完全相同。
3)如果基类中虚函数的返回类型是类类型的指针或引用,那么子类中覆盖版本的返回类型也可以是基类版本返回类型的子类。
4)子类中的覆盖版本不能比基类中的虚函数抛出更多的异常。
5)子类中覆盖版本的访控属性与基类无关。
3.注意严格区分重载、隐藏和覆盖。
class Visitor {
public:
  virtual bool visit (int credit); // A
  virtual bool visit (double cash); // B
};
class ValidVisitor : public Visitor {
public:
  bool visit (int credit); // C
};
class MyValidVisitor : public ValidVisitor {
public:
  bool visit (double cash); // D
};
class Dumy : public Visitor {
public:
  void visit (int credit); // E
};
B和A:重载
C和A:覆盖
C和B:隐藏
D和C:隐藏
D和B:覆盖
E和A:错误的覆盖
4.纯虚函数、抽象类与纯抽象类
纯虚函数:在基类中被定义为如下形式的成员函数:
virtual 返回类型 成员函数名 (形参表)=0;
被称为纯虚函数。
抽象类:至少包含一个纯虚函数的类。抽象类不能被实例化为对象。抽象类的子类如果没有对基类中所有的纯虚函数提供覆盖定义,那么该子类也是抽象类。
纯抽象类:完全由纯虚函数(除了构造和析构函数)组成的抽象类。
5.虚函数表与动态绑定
虚函数表是一个存放虚函数地址的函数指针数组。每个由包含虚函数的类所创建的对象中都有一个指向该虚函数表的指针。
当通过指向子类对象的基类指针,或者引用子类对象的基类引用调用基类中的虚函数时,
实际上是根据该指针或引用实际指向的对象中的虚函数表获取函数地址并调用的。
对于A的子类B:
A* pa = new B (...);
当前编译器看到
pa -> foo ();
并不直接生成函数调用代码,相反它会自动生成一些列操作指令,完成以下动作:
a)明确pa的类型及其所指向的对象;
b)从pa所指向的对象中提取虚函数表,并找到与foo()函数相对应的函数地址。
c)根据所找到的虚函数地址,调用之。
以上三步操作均在运行阶段完成,故谓之动态绑定。
6.多态的前提和限制
多态=虚函数+指针或引用
在基类的构造函数和析构函数中调用虚函数,没有多态性,实际调用的永远是基类版本。
十四、运行时类型信息——RTTI
1.动态类型转换:dynamic_cast
在运行期对指针实际指向的类型(应具有多态性)做检查,以返回NULL表示转换失败。
2.静态类型转换:static_cast
在转换源类型和目标类型之间只要有一个方向上可以做隐式转换,那么在另一个方向上就可以做静态转换。
以编译错误的形式表示转换失败。
3.重解释类型转换:reinterpret_cast
无论在编译期还是运行期均不做任何检查,使用时要十分慎重。
4.获取类型信息
typeid ()运算符返回值是一个typeinfo类型(声明在typeinfo的头文件中)的对象。其中包括一个name的成员函数,
通过该函数可以获得类型的字面名称。同时typeinfo对象还可以用于比较两个类型是否相同或不同。
typeid可以获取指针所指向对象的实际类型,前提是该类型应具有多态性。
十五、虚析构函数
1.将基类的析构函数定义为虚函数。delete一个指向子类对象的基类指针将导致子类类型的析构函数被调用,
该函数在释放完子类对象特有的资源之后,自动调用基类类型的析构函数,完整其基类部分的释放。
2.如果一个类中存在虚函数,往往意味着该类可以多态方式被使用,因此该类的析构函数就应该被定义为虚函数。
思考:
              定义为虚函数
全局函数      No
普通成员函数  Yes
静态成员函数  No
构造函数      No
析构函数      Yes
操作符函数
  成员        Yes
  友元        No
-----------------
class DocParser {
public:
  void parse (...) {
    ...
    onRect (...);
    ...
    onText (...);
    ...
    onImage (...);
  }
  virtual void onRect(...)=0;
  virtual void onText(...)=0;
  virtual void onImage(..)=0;
};
class DocRender : public DocParser {
  void onRect(...) {...};
  void onText(...) {...};
  void onImage(..) {...};
};
DocRender dr (...);
dr.parse ();


第六课  异常与I/O流
一、异常
1.错误处理与非本地控制转移
2.异常处理语法
3.异常处理流程
无异常:throw之后的语句执行,引发异常的语句之后的代码执行,catch块不执行。
有异常:throw之后的语句不执行,引发异常的语句之后的代码不执行,catch块执行。
4.异常处理用法
1)抛出基本类型异常,根据异常的值区分不同的异常。
2)抛出类类型的异常,根据异常的类型区分不同的异常。
3)使用异常说明语句。在函数的参数表和左大括号之间写:
throw (异常类型1, 异常类型2, ...)
A.如果没有异常说明,表示该函数可以抛出任何异常。
B.如果异常说明为throw (),表示该函数不抛出任何异常。
C.如果函数抛出了异常说明以外的异常,该异常不可被调用函数的catch子句捕获。
4)继续抛出异常。
5.构造函数中的异常
------------------
二、I/O流
5.构造函数中的异常
1)构造函数可以抛出异常,而且某些时候还必须抛出异常以表达构造过程中发生的错误(因为构造函数没有返回值)。
2)如果一个对象在其构造过程中发生了异常,该对象即被不完整构造,这样的对象其析构函数是不会被执行的。
3)对于不完整对象中的普通成员变量,构造函数的回退机制可以保证其被正确地析构,但是动态分配(new/malloc)的指针或引用型对象,必须在抛出异常之前手动(delete/free)进行释放,除非使用了智能指针。
6.析构函数中的异常
任何时候都不要在析构函数抛出异常。
析构函数中尽量捕获可能引发的异常。
二、I/O流
1.C++中的I/O流
应用程序  -用户开发
C++ I/O流 -istream/ostream类
C语言I/O流-fopen/fread库函数
I/O子系统 -系统调用:open/read
驱动程序  -寄存器、中断控制
I/O硬件   -机电器件
2.文本I/O
1)文本文件的格式化I/O

2)文件位置与随机读写

3)非格式化I/O

3.二进制I/O
C       C++   
stdout  cout
stdin   cin
stderr  cerr
4.格式控制
流函数:
cout.precision (10);
流控制符:
cout << setprecision (10) << ...
5.字符串流
0 0