寒假MOOC学习计划
寒假MOOC学习计划
课程网址: https://www.icourse163.org/course/0809CAU007-432001
选课理由
该课程为C++国家精品课程,好评如潮。
学习计划
每天学习一小节,两到三天完成一个章节。
学习笔记 02.06
面向对象程序设计方法(1):
-
面向对象(object-oriented)
-
数据分散管理策略与数据集中管理策略:
分别对应局部变量和全局变量的使用。
-
类的封装
通过设定访问权限封装类成员,将需要访问的成员开放,不需访问的成员隐藏,避免误访问。
– 公有权限(
public
)
– 私有权限(private
)
– 保护权限(protected
) -
类的接口
– 公有成员是封装后类对外的接口
– 一个类必须有公有成员,否则这个类无法使用
– 开放用户正常使用所必须的成员,隐藏不需被直接访问的成员 -
类的定义
– 声明数据成员的语法形式类似于定义变量,所不同的是声明数据成员不能初始化
– 编译器默认将直接定义在类声明部分大括号里的函数成员当做内联函数
– 类的默认访问权限为private
,每个类成员都有且只有一种访问权限 -
对象的定义与访问
类相当于是程序员自己定义的一种新的数据类型,可称为类类型。
定义对象和定义变量的语法形式基本相同。
–访问对象就是通过公有成员接口操作内存中的对象,实现特定的程序功能
–访问对象中的公有成员需使用.
运算符
–用对象指针访问则需要使用->
运算符
学习笔记 02.07
面向对象程序设计方法(2):
-
类与对象的编译原理
– 每个对象所占用的内存空间都等于类中全部数据成员所需内存空间的总和
– 多个同类对象共用同一个函数,内存中只需要保存一份函数代码
– 编译器会自动进行调整↓
-
构造函数
– 构造函数由计算机自动调用,程序员不能直接调用
– 构造函数没有返回值,定义时不能写函数类型,写void
也不行
– 构造函数通常是类外调用,其访问权限不能设为private
– 类如果未定义构造函数,编译器将添加默认构造函数,形式为类名(){}
– 类如果未定义拷贝构造函数,编译器将添加默认拷贝构造函数(数据成员一一对应拷贝) -
析构函数
– 析构函数由计算机自动调用,程序员不能直接调用
– 析构函数没有形参
– 析构函数没有返回值,定义时不能写函数类型,写void
也不行
– 一个类只能有一个析构函数
– 析构函数通常是类外调用,其访问权限不能设为private
– 类如果未定义析构函数,编译器将自动添加默认析构函数,形式为~类名(){}
-
拷贝构造函数中的深拷贝与浅拷贝
默认拷贝构造函数执行浅拷贝。
对于动态分配的数据成员,浅拷贝只拷贝指针,并没有再分配内存,而深拷贝则需要程序员编写拷贝构造函数再分配新的内存空间。 -
类与对象编程的主要内容
– 定义类。程序员在定义一个类的时候要考虑到5大要素,即数据成员、函数成员、各成员的访问权限、构造函数和析构函数
– 定义对象。将类当作一种自定义数据类型来定义变量,所定义的变量就称为对象
– 访问对象。访问对象就是通过接口(即公有成员)操作内存中的对象,实现特定的程序功能
学习笔记 02.08
面向对象程序设计方法(3):
-
对象数组
有多少个数组元素,就会调用多少次构造函数和析构函数。
-
对象作为函数的形参
– 值传递与常对象
– 引用传递与常引用
– 指针传递与指向常对象的指针 -
常数据成员
– 在类定义中声明常数据成员需使用关键字
const
进行限定,声明时不能初始化
– 为构造函数添加初始化列表是对常数据成员进行初始化的唯一途径
– 定义对象时必须初始化常数据成员 -
常函数成员
– 声明、定义常函数成员需在函数头后面加关键字
const
进行限定
– 常函数成员只能读类中的数据成员,不能赋值修改
– 常函数成员只能调用其它常函数成员
– 通过常对象只能调用其常函数成员
– 除形参个数、类型之外,还可以用关键字const
区分类中的重载函数 -
静态数据成员
– 在类定义中声明静态数据成员需使用关键字
static
进行限定,声明时不能初始化
– 必须在类声明的大括号外对静态成员进行定义及初始化,定义时不能再加关键字static
– 在类外其它函数中访问静态数据成员需以类名::静态数据成员名
的形式访问,或通过任何一个该类对象以对象名.静态数据成员名
的形式访问
– 类外访问受权限约束,只能访问公有的静态数据成员
– 静态数据成员是静态分配的,程序加载后立即分配内存,直到程序执行结束退出时才被释放
– 私有静态数据成员具有类作用域,公有静态数据成员具有文件作用域 -
静态函数成员
– 声明时使用关键字
static
进行限定,定义时不能再使用关键字static
– 静态函数成员只能访问类中的静态成员
– 在类外调用静态函数成员需以类名::静态函数成员名()
的形式调用,或通过任何一个该类对象以对象名.静态函数成员名()
的形式调用
– 类外调用受权限约束,只能调用公有的静态函数成员
– 静态函数成员不能再声明为常函数
– 静态函数成员不能引用this指针
– 静态函数成员不推荐声明为内联函数,因为编译器在编译时会调整内联函数,此时所访问的静态数据成员可能还未初始化,因此其数据是不可靠的,此时访问会导致程序的运行结果出错 -
友元函数与 友元类
– 在类声明部分使用
friend
关键字声明友元函数原型或友元类
– 声明语句可以放在大括号内的任意位置,该位置的访问权限无影响
– 友元函数是类外的其它函数,不是类的成员
– 友元函数可以在其函数体内访问该类对象的所有成员,不受权限约束
– 友元类的所有成员函数都是该类的友元函数
– 友元关系是单向的
– 友元关系不能传递
学习笔记 02.09
面向对象程序设计方法(4):
-
代码重用
结构化程序设计重用的是算法代码。
而面向对象程序设计,既重用算法代码,也重用数据代码。 -
类的组合与聚合
– 数据成员中包含对象成员的类称为组合类
– 数据成员中包含对象指针的类称为聚合类,聚合类是一种特殊形式的组合类
– 聚合类的对象成员是独立创建的,聚合类对象只包含指向对象成员的指针
– 聚合类对象可以共用对象成员
– 访问组合类对象中对象成员的下级成员,受多级访问权限控制
– 组合类对象构造时,先调用对象成员构造函数,再执行组合类构造函数的函数体
– 组合类对象析构时,先执行组合类析构函数的函数体,再调用对象成员析构函数
– 多个对象成员之间由声明顺序决定(先声明,先构造,后析构) -
类的继承与派生
– 派生类将继承基类中除构造函数、析构函数之外的所有数据成员和函数成员
– 继承后,派生类会对其基类成员按照继承方式进行再次封装
–public
(公有继承):
派生类对其基类成员不做封装,它们在派生类中的访问权限与基类中相同
–private
(私有继承):
派生类对其基类成员做全封装,它们在派生类中的访问权限都被改为private
–protected
(保护继承):
派生类对其基类成员做半封装,基类中的public
成员被继承到派生类后,其访问权限被改成protected
– 基类的private会被继承,但不能被派生类直接调用,要使用基类接口访问基类 继承类型 子类 public public public protected protected private 不能直接访问 public protected protected protected protected private 不能直接访问 public private private protected private private 不能直接访问 -
同名覆盖
派生类中定义与基类成员重名的新增成员,新增成员将覆盖基类成员。
同名覆盖后,被覆盖的基类成员仍然存在,只是被隐藏了。可以访问被覆盖的基类成员,其访问形式是基类名 :: 基类成员名
-
组合派生类的构造与析构
– 派生类对象先调用基类构造函数,再执行派生类构造函数的函数体
– 初始化基类成员和新增对象成员需通过初始化列表
– 组合派生类的构造函数依次初始化基类成员->新增对象成员->新增非对象成员
– 析构顺序与构造顺序相反
– 多个基类之间由声明顺序决定(先声明,先构造,后析构)
学习笔记 02.10
面向对象程序设计方法(5):
-
运算符重载
– 运算符函数可定义为类的函数成员或类外的友元函数
– 若运算符+
被重载为类的函数成员,则调用形式为c1.+(c2)
,其中,c1
是对象名,.
是成员运算符,+
是函数成员名,c2
是实参
– 若运算符+
被重载为类的友元函数,则调用形式为+(c1, c2)
,其中,+
是友元函数名,c1
和c2
是实参
– 五个不能重载的运算符是:
条件运算符?:
,sizeof
运算符,成员运算符.
,指针运算符*
,作用域运算符::
– 重载后,运算符的优先级和结合性不会改变
– 重载后,运算符的操作数个数不能改变
– 类如果未重载=
运算符,编译器将添加默认重载(数据成员一一对应拷贝,浅拷贝)– 具体用法:
-
Liskov替换准则
– 为了让基类对象及其派生类对象之间可以重用代码,C++语言制定了如下的类型兼容语法规则:
• 派生类的对象可以赋值给基类对象
• 派生类的对象可以初始化基类引用
• 派生类对象的地址可以赋值给基类的对象指针– 应用类型兼容语法规则有1个前提条件和1个使用限制:
• 前提条件:派生类必须公有继承基类。
• 使用限制:通过基类对象、引用或对象指针访问派生类对象,只能访问其基类成员 -
虚函数
– 只能在类声明部分声明虚函数,在类实现部分定义函数成员时不能使用
virtual
关键字
– 基类中声明的虚函数成员被继承到派生类后,自动成为派生类的虚函数成员
– 派生类可以重写基类虚函数成员,如果重写后的函数原型与基类虚函数成员完全一致,则该函数自动成为派生类的虚函数成员,无论声明时加不加virtual
关键字
– 静态函数、构造函数不能是虚函数
– 析构函数可以是虚函数– 演示:
-
多态性的实现
– 定义基类时将需要自动调用派生类新增的同名函数成员定义成虚函数
– 定义派生类时公有继承基类,并重写那些从基类继承来的虚函数成员
– 编写类族共用的程序代码时,定义基类引用或基类对象指针来访问该类族的对象 -
纯虚函数与抽象类
– 类定义中“只声明,未定义”的函数成员被称为纯虚函数
– 含有纯虚函数成员的类就是抽象类
– 抽象类不能实例化,但可以定义抽象类的引用、对象指针,所定义的引用、对象指针可以引用或指向其派生类的实例化对象– 抽象类可以作为基类定义派生类
• 纯虚函数成员只声明了函数原型,没有定义函数体代码。因此派生类继承纯虚函数成员时,只是继承其函数原型,即函数接口。派生类需要为纯虚函数成员编写函数体代码,称为实现纯虚函数成员
• 派生类如果实现了所有的纯虚函数成员,那么它就变成了一个普通的类,可以实例化– 抽象类可以用来统一类族接口和重用代码
-
多继承
派生类可以从多个基类继承,这就是多继承。多继承派生类存在比较复杂的成员重名问题,其具体表现形式有3种:
– 新增成员与基类成员重名
• 如果通过派生类的对象名、引用或对象指针访问派生类对象,则访问到的是新增成员,此时新增成员覆盖同名的基类成员(同名覆盖)
• 如果通过基类的对象名、引用或对象指针访问派生类对象,则访问到的是基类成员,此时派生类对象被当作基类对象使用(Liskov替换准则)
• 如果基类定义虚函数成员,派生类公有继承基类并重写虚函数,那么通过基类的引用或对象指针访问派生类对象所访问到的将是新增的虚函数成员。这就是调用对象中虚函数成员时所呈现出的多态性(对象多态性)– 多个基类之间的成员重名
如果多个基类之间有重名的成员,同时继承这些基类会造成派生类中基类成员之间的重名。访问重名的基类成员,需在成员名前加基类名::
– 同一基类被重复继承
多级派生时,从同一基类派生出多个派生类,这多个派生类再被多继承到同一个下级派生类时,该下级派生类将包含多份基类成员的拷贝,也就是同一基类被重复继承。此时,应当在派生类继承基类时,将基类声明为虚基类:class A1 : virtual public A
学习笔记 02.11
流类库与文件读写:
-
C++语言的输入与输出
– 提供输入数据的数据源称作输入数据流
– 输出数据时的目的地称作输出数据流
– 输入数据流和输出数据流统称为输入/输出流 -
输入/输出流中的数据缓冲区
-
流类库
以
ios
为基类的类族
– 通用输入/输出流类:提供通用的输入/输出(简称标准I/O)功能
– 文件输入/输出流类:提供文件输入/输出(简称文件I/O)功能
– 字符串输入/输出流类:提供字符串输入/输出(简称字符串I/O)功能
-
标准I/O
#include<iostream>
• 通用输入流类
istream
及其对象cin
– 使用提取运算符>>
– 使用函数成员width
设置下一输入项的最大字符个数(含字符串结束符)
– 使用函数成员get
每次从流缓冲区中读出1个字符• 通用输出流类
ostream
及其对象cout
– 使用插入运算符<<
– 使用函数成员put
每次输出1个字符到流缓冲区中
– 格式标记(Flag
)是基类ios
中定义的一组枚举常量,用于表示不同的输出格式。设置输出格式,某些格式需使用函数成员flags
,而另外一些格式需通过专门的函数成员,示例代码:
cout.flags(ios::hex); // 使用函数flags设置格式:以十六进制输出整数
– 格式操纵符(
Manipulator
)是流类库中定义的一组函数,这些函数被分散定义在不同的头文件中,示例代码:// 头文件:<iomanip>
inline long setiosflags(long _l); // 设置某个格式标记,例如:setiosflags(ios::hex)
inline long resetiosflags(long _l); // 将某个格式标记恢复到其默认值
inline int setw(int _w); // 设置输出位数,不足部分补空格。仅对下一个输出项有效
inline int setfill(int _m); // 设置填充字符。输出位数不足部分补填充字符
inline int setprecision(int _p); // 设置浮点数的输出精度(即小数位数)
cout << hex << setw(5) << 20; // 显示结果:凵凵凵14,其中“凵”表示空格
```
• 设定输出位数及填充字符
– 插入运算符 + 格式标记:cout.fill(‘#’); cout.width(6);
– 插入运算符 + 格式操纵符:cout << setfill(‘#’) << setw(8);
• 设定对齐方式
– 插入运算符 + 格式标记:`cout.flags(ios:: left);`
– 插入运算符 + 格式操纵符:`cout << setiosflags(ios::left);`
• 设定浮点数的输出格式
– 插入运算符 + 格式标记:
```
cout.flags(ios::fixed); // 定点表示法
cout.precision(2); // 保留2位小数
cout.flags(ios::scientific); //科学表示法
```
– 插入运算符 + 格式操纵符:
```
cout << setiosflags(ios::fixed); // 定点表示法
cout << setprecision(2); // 保留2位小数
cout << resetiosflags(ios::fixed); //取消定点格式
cout << setiosflags(ios::scientific); //科学表示法
```
-
文件I/O
#include<fstream>
fstream(const char *, int = ios::in); // 有参构造函数 void open(const char *, int =ios::in); // 打开文件 bool is_open() const; // 检查文件是否正确打开 void close(); // 关闭文件
• 文件输出流类ofstream及文件输出
– 【输出文本文件】
– 【输出二进制文件】
• 文件输入流类ifstream及文件输入
– 【输入文本文件】
– 【输入二进制文件】
• 检查输入文件状态
–eof()
检查文件是否已结束
–good()
检查文件是否已损坏• 文件打开模式
• 文件的随机读写
– 文件输入流对象包含一个读文件指针
– 文件输出流对象包含一个写文件指针istream& seekg(long bytes, ios::seek_dir origin); // 移动读文件指针 long tellg(); // 返回当前读文件指针的位置 ostream& seekp(long bytes, ios::seek_dir origin); // 移动写文件指针 long tellp(); // 返回当前写文件指针的位置
-
string
类• 字符串类string
– string类的函数成员
str.length(); // 函数length返回字符串长度
str.find("cd"); // 函数find查找子串"cd"的位置
str.substr(2, 4); // 函数substr取出1个子串,从下标为2的元素开始取4个字符
str.append("123"); // 函数append在串尾追加1个字符串
```
– string类重载的运算符
(字符串比较:若果字符相等,则比较下一个字符,如果全部相等,那个字符较长那个大)
-
字符串I/O
#include <sstream>
– 字符串I/O就是指在字符串对象和其它变量之间进行数据的输入/输出
– 字符串输入流类istringstream
– 字符串输出流类ostringstream
– 字符串输入/输出流类stringstream
-
基于
Unicode
编码的流类库基于Unicode编码的流类库以
wios
为基类,所有名称前加w
。
如预定义的宽字符流对象:
wistream wcin; // 宽字符的键盘对象wcin
wostream wcout; // 宽字符的显示器对象wcout
```
学习笔记 02.12
C++标准库(1):
-
函数模板的定义
– 函数模板定义中的前2行被称为是函数模板头
– 类型参数列表可定义一个或多个类型参数
– 类型参数以typename 类型参数名
或class 类型参数名
的形式定义,之间用,
隔开
– 类型参数将会作为一种新的数据类型出现函数模板定义中 -
函数模板的编译原理
函数模板是具有类型参数的函数。类型参数是表示数据类型的参数,可指代任意一种实际数据类型。编译器在编译到函数模板调用语句时,根据位置对应关系从实参数据类型推导出类型参数所指代的数据类型,然后按照函数模板自动生成一个该类型的函数定义代码。不同类型实参的函数模板调用语句将生成不同类型的重载函数。
– 函数模板将数据类型参数化,调用时会呈现出参数多态性
– 使用函数模板可以减少程序员的编码工作量,但所编译出的可执行代码不会减少,执行效率也不变 -
类模板的定义
– 类型参数列表可定义一个或多个类型参数
– 类型参数以typename 类型参数名
或class 类型参数名
的形式定义,之间用,
隔开
– 类型参数就像是一种新的数据类型,可以用它来定义数据成员,也可以用来定义函数成员
– 定义模板类的函数成员,如果在类内(即声明部分)定义,其语法形式与普通类的函数成员没有区别;如果在类外(即类实现部分)定义,则必须按照函数模板的语法形式来定义,并且要在函数名前加类名<类型参数名列表> ::
限定,类型参数名列表应列出类型参数列表中的所有类型参数名,多个参数名之间用,
隔开– 使用类模板定义对象时需要明确给出类型参数所指代的实际数据类型。
类模板名 <实际数据类型列表> 对象名1, 对象名2, ......
-
类模板的编译原理
类模板是具有类型参数的类。类型参数是表示数据类型的参数,可指代任意一种实际数据类型。编译器在编译到使用类模板定义对象语句时,将首先按照所给定的实际数据类型对类模板进行实例化,生成一个实例类。最终,编译器是使用实例类来定义所需要的对象。
– 使用
typedef
类型定义显式地实例化类模板typedef A<int> IntA;
– 使用类模板可以减少程序员的编码工作量,但所编译出的可执行代码不会减少,执行效率也不变 -
类模板的继承与派生
– 类模板可以被继承,派生出新类
– 以类模板为基类定义派生类时可以在派生时实例化
– 也可以继续定义派生类模板
-
C++标准库(Standard Template Library, STL)简介
– 10大类功能
– 输入/输出流类模板
学习笔记 02.13
C++标准库(2):
-
C++语言的异常处理机制
• 程序运行时,常见的异常情况有:
– 用户操作不当
– 输入文件不存在
– 非法访问内存单元
– 网络连接中断• 异常处理机制由2部分组成
– 发现异常。程序员应在可能出现异常的程序位置增加检查异常的代码
– 处理异常。发现异常后,程序需要改变算法流程,否则将产生运行时错误。• 运用异常处理机制,将同时涉及到主调函数与被调函数
– 被调函数负责发现异常。发现异常后应处理异常,并向上级主调函数报告异常
– 常规的报告方法是通过特定的函数返回值(即错误代码)来表示不同的异常情况
– 主调函数通过检查被调函数的返回值来检查调用时是否出现了异常,发现异常后也需要做相应的异常处理
– 如果是函数多级调用,主调函数还需要逐级向各自的上级函数报告异常• 涉及到主调函数与被调函数之间的异常处理机制被称为多级异常处理机制
– 发现异常
– 处理异常
– 报告异常• C++语言提供了try-catch异常处理机制,把业务逻辑和异常处理分开,提高了代码可读性
-
try-catch异常处理机制
• throw语句:
throw 异常表达式 ;
– 其功能是报告异常,该异常将被try-catch
语句捕获并做相应的异常处理
– 计算机执行该语句时,将抛出一个异常,并退出当前函数的执行
– 异常表达式的结果可以是基本数据类型、自定义数据类型或类类型
– 异常表达式的数据类型被用于区分不同类型的异常,结果的值被用于描述异常的详细信息
– 异常表达式可以是单个的常量、变量或对象
– 声明异常:void fun() throw (A, B, C); // 函数只能抛出A、B、C三类异常 void fun() throw (); // 函数不会抛出任何类型的异常 void fun(); // 函数可能会抛出任何类型的异常
• try-catch语句:
try { 受保护代码段 } catch ( 异常类型1 ) // 可选择是否声明参数名,用于子句使用 { 异常类型1的处理代码 } catch ( 异常类型2 ) { 异常类型2的处理代码 }
–
try
子句将代码段保护起来。受保护代码段在执行时将启用异常保护机制,捕获该代码段执行过程中的任何异常报告,包括下级被调函数所报告的异常
–catch
子句负责捕获并处理异常。每个catch
子句只负责一种类型的异常。异常类型就是抛出该异常的throw
语句中表达式结果的类型
– 若受保护代码段在执行过程中有异常,则根据异常类型依次匹配catch
子句,如果匹配上某个子句(异常被捕获),则执行所匹配子句的处理代码
– 每个异常最多只会有一个catch
子句的处理代码被执行,如果异常未被任何子句捕获,则自动逐级向上级函数继续报告该异常,直到被上级函数中的某个子句所捕获
– 假设某个异常,连最上级的主函数main
也未能捕获它,也就是说没有任何函数能够捕获并处理该异常,则自动使用默认方法来进行处理,通常是中止当前程序的执行,并提示简单的错误信息
–catch(...)
形式的子句可匹配并捕获任意类型的异常,其后的子句都将是无效的
–catch
子句可以再抛出异常:
catch( ... )
{
...... // 处理异常
throw ; // 将当前捕捉到的异常再次抛出
}
```
-
使用类描述异常
类类型的异常可以提供更多的异常信息和异常处理功能。可自己编写异常类,也可以使用C++标准库中的异常类
exception
: