刘胥影 liuxy@seu.edu.cn 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 liuxy@seu.edu.cn 东南大学计算机学院
考试安排 东南大学计算机学院 1/15/2019
最终成绩 = 平时成绩 + 考试成绩 30% 70% 东南大学计算机学院 1/15/2019
考试 (100分) 笔试 (60分) 机考 (40分) 半开卷考试:可带一本英文或中文教材入场 英文出题,中文作答 考试时间 考试地点 15日上午 9:00-11:00 教务处安排 机考 (40分) 笔试当天下午 计算机楼xx房间 机考的时间和地点安排会通知给教务员和班长 半开卷考试:可带一本英文或中文教材入场 英文出题,中文作答 东南大学计算机学院 1/15/2019
考试题型 笔试部分(共60分) 概念题 (共10分,5分X 2) 代码阅读题 (共30分,5分 X 6) 编程题 (共20分,共3题,5分 + 5分 + 10分) 以简答题形式回答 阅读代码,写出程序输出 进行综合性的编程,有难有易 东南大学计算机学院 1/15/2019
考试题型 机考部分(40分) 编程题(共40分,共3题,10分 + 15分 + 15分) 以完善程序的形式(根据部分程序代码和程序的输出, 给出类的实现或完善类的成员函数的实现)进行综合 性的编程,有难有易 答疑:6月9日周四上机时间 东南大学计算机学院 1/15/2019
关于作业 公布作业答案? 平时成绩在80分以下的同学 直接上交标准答案的同学! 6月9日周四上机时间 随机抽一道作业题进行测试 主动到我这儿说明情况 不主动,。。。。 东南大学计算机学院 1/15/2019
复 习 (考试重点) 东南大学计算机学院 1/15/2019
chap 09: Classes: A Deeper Look, Part 1 chap 11: Operator Overloading; String and Array Objects chap 12: Inheritance chap 13: Polymorphism chap 14: Templates chap 15: Stream Input/Output (基本没有要求内容) chap 16: Exception Handling chap 17: File Processing (要求基本读写,但是在教学要求范围内) 东南大学计算机学院 1/15/2019
chap9 类作用域 构造函数、析构函数,对象何时创建、何时销毁 构造函数和析构函数的调用顺序 东南大学计算机学院 1/15/2019
访问控制 句柄 public:使用该类型的所有代码,通过句柄 private:本类的其他类成员、友元类 protected:派生类 对象名:a.f() 引用:b.f(); 指针:(*p).f(); p->f(); 东南大学计算机学院 1/15/2019
对象的组成 仅包含数据成员 static成员不包含在内:类的所有对象共享static成员 东南大学计算机学院 1/15/2019
对象的大小 sizeof():计算对象的大小 每个语句分配的变量存储区至少为4个字节 class test1{ int num; //4B }; class test2{ char c; //ceil(1/4)*4B class test3{ char c, t; //4B class test4{ char s; //4 int num; //4 char c, t; //4 }; class test5{ static char s;//omit 4 8 8 12 4 东南大学计算机学院 1/15/2019
类作用域 类作用域内,类成员可以被其他成员访问 类作用域外,public成员通过对象的句柄访问 成员函数只能由该类的成员函数重载(类作用域) 东南大学计算机学院 1/15/2019
作用域屏蔽规则 访问被屏蔽的变量 函数作用域变量 屏蔽 全局作用域变量 函数作用域变量 屏蔽 类作用域变量 类作用域变量 屏蔽 全局作用域变量 访问被屏蔽的变量 全局变量:::a 类成员:this->a; A::a; 东南大学计算机学院 1/15/2019
File Scope Block Scope Class Scope 98 99 100 #include <iostream> using namespace std; int a = 0; class Test { public: Test (int a){ int b = 99; this->a = a; Test::a = a; this->b = b; Test::b = b; ::a = 100; } void displayMessage(){ cout << a << " " << b << " " << ::a; } private: int a, b; }; int main() { Test test(98); test.displayMessage(); return 0; How to Access File Scope Block Scope Class Scope 98 99 100
默认实参 默认构造函数 默认实参应在函数名第一次出现时设定(类定义中声明 时) 默认实参必须从右边开始 不指定实参即可调用的构造函数称为默认构造函数 不带参数的构造函数 或所有参数都有默认值的构造函数 每个类最多只有一个默认构造函数 东南大学计算机学院 1/15/2019
函数重载 具有相同的函数名,而形参表不同 const成员函数和非const成员函数之间可以有重载关系 要考虑默认参数的影响 有默认参数时判断函数重载是否合理的原则: 列举出所有函数的调用方式,看是否能唯一确定函数进行调用 东南大学计算机学院 1/15/2019
构造函数和析构函数的调用顺序 按类成员声明次序的逆序撤销每个非static成员 类类型成员:调用其析构函数 东南大学计算机学院 1/15/2019
9.4构造函数与析构函数的调用顺序(1) 一般而言,析构函数的调用顺序和其相应的构造函数的调用顺序相反,对象的存储类别和作用域能改变顺序 全局对象: 在任何函数(含main)执行前,构造; 在main程序结束时,析构. 局部对象: 自动对象:变量定义时,构造;块结束时,析构. 静态对象:首次定义时,构造;main程序结束时,析构. 全局和静态对象(均为静态存储类别)析构顺序恰好与构造顺序相反. 东南大学计算机学院 1/15/2019
1C 2C 3C 5C 6C 7C 7D 5D 4C 4D 2D 6D 3D 1D create main 7 6 全局 栈区 数据区 5 1/15/2019 Fig.13
类定义和类实现分离 类定义在h文件中 类实现在cpp文件中,要包含类定义文件 类实现时,不要忘记ClassName:: 对于类模板,定义和实现要放在同一文件中! 东南大学计算机学院 1/15/2019
其他 类定义后不要忘记分号; 不要忘记#endif 东南大学计算机学院 1/15/2019
chap10 static成员和const成员 组成的概念 友元(概念) 动态内存管理 Copy构造函数(注意参数) is-a关系 对象的创建顺序 构造函数和析构函数的调用顺序 友元(概念) 动态内存管理 基于指针的字符串 动态数组 Copy构造函数(注意参数) 东南大学计算机学院 1/15/2019
10.1 const对象和const成员(1) const关键字指定元素为常量,不可修改 const对象:对象的数据成员在生命期内不被改变 若为public数据成员 How? const Time noon(12,0,0) class A{ public: int x; int f(); private: int y; }; const A a; a.x=6;//语法错:表达式必须是可 //修改的左值 //a.f()是否会修改数据成员呢? const成员函数 哪些成员函数会改变数据? 哪些成员函数是对const对象“安全”的? 东南大学计算机学院 1/15/2019
10.1 const对象和const成员(2) const成员函数:不能修改本对象数据成员 告诉编译器它对const对象是“安全的” non-const成员函数被编译器视为将要修改数据成员 const对象只能调用const成员函数 const成员函数不能调用其它non-const成员函数 对象 成员函数 Access const √ non-const X 东南大学计算机学院 1/15/2019
10.1 const对象和const成员(5) 适用情况 重载:const成员函数可以进行non-const版本的重载 需要调用其他non-const成员函数的不能为const 构造函数和析构函数不能为const 不修改数据成员的任何成员函数都应该声明为const (最小权限原则) 重载:const成员函数可以进行non-const版本的重载 const对象调用const成员函数 non-const对象调用non-const成员函数 const对象生命期 构造 “常量性质”保持 析构 东南大学计算机学院 1/15/2019
10.1 const对象和const成员(7) const数据成员:一旦初始化,值不可修改 初始化:? 成员初始化表(只能进行初始化,不能赋值) class A{ public: A(); private: const int a; }; 如何将a初始化为1? 回顾 哪些数据类型只能在成员初始化表中进行初始化? 哪些数据类型不能在成员初始化表中初始化? A():a(1){} 东南大学计算机学院 1/15/2019
10.5 static类成员(3) A motivation example static数据成员描述类的属性,而非特定对象的属性 int name; int count; 学生 学生1 … … 学生2 学生n 学生总人数:count int name; static int count; int name; int count; 新学生 static数据成员描述类的属性,而非特定对象的属性 它独立于对象而存在 对象不包含static数据成员 东南大学计算机学院 1/15/2019
10.5 static类成员(5) 声明与定义 类内声明,类外定义、初始化 若类的实现和接口分离,则应在实现部分定义和初始化 否则“error LNK1169: 找到一个或多个多重定义的符号” //className.h class className{ public: void f(); static int total; }; //className.cpp int className::total; // 不用static关键字修饰 void className::f(){……} 不能缺少定义 否则:error LNK2001: 无法解析的外部符号 东南大学计算机学院 1/15/2019
10.5 static类成员(6) 当static成员需要被多个文件的代码访问时,只能在.h文 件中声明为static,不能在.cpp文件中声明为static //main.cpp class className{ public: static int total; }; int className::total; int main(){ … … } //className.cpp class className{ public: void f(); static int total; }; int className::total; //main.cpp int main(){ … … } No error include “className.cpp” error No error static关键字用于文件作用域的元素时,它只在该文件内可见 东南大学计算机学院 1/15/2019
10.5 static类成员(7) 初始化 至多只能被初始化一次 类外定义处初始化,不能在任何函数体中 (文件作用域) static const整型(int/char/bool)或枚举类型可在类内声明 时初始化 可以通过成员初始化表进行初始化吗?为什么? class className{ private: static const int a = 0; static const char b = ‘h’; static const bool c = false; static int total; static const double d; }; int className::total = 0; // 不用static关键字修饰 double className::d = 0.0; 东南大学计算机学院 1/15/2019
10.5 static类成员(8) 默认的初始化 访问方式 基本类型:0 成员对象:默认构造函数 public: 通过类名访问:A::x 通过对象访问:a.x private, protected:public成员函数/友元 class A{ public: static int x; }; A::x=1; A a; a.x=1; 1/15/2019
10.5 static类成员(13) 成员函数:static or non-static? 对象 成员函数 Access const √ 调用者 被调用者 static成员函数 static成员 √ non-static成员 X non-static成员函数 对象 成员函数 Access const √ non-const X 回顾 东南大学计算机学院 1/15/2019
10.5 static类成员(14) 特点 An example (Fig.10.21 ~ 10.23) 不具有this指针 不能声明为const An example (Fig.10.21 ~ 10.23) this指针指向对象,this指针和对象一一对应 在static成员函数中使用this指针是编译错误! const限定成员函数不能修改操作对象的内容 将static成员函数声明为const是编译错误! 东南大学计算机学院 1/15/2019
10.2组成(2) 组成(Composition):一个类可以将其他类的对象作为 自己的成员 提高软件复用性的一个普遍形式 Company -Name:string -registerDate:Date Employer -BirthDay:Date Employee -hireDay:Date Date -year:int -month:int -day:int 提高软件复用性的一个普遍形式 东南大学计算机学院 1/15/2019
10.2组成(3) has-a关系 程序文件组织(B has-a A) has-a 宿主对象 (Host Object) Employee -Name:string -BirthDay:Date Date -year:int -month:int -day:int has-a 宿主对象 (Host Object) 成员对象 (Member Object) A类声明 A.h A类定义 A.cpp #include “A.h” B类声明 B.h B类定义 B.cpp #include “B.h” 驱动程序 main.cpp 1/15/2019
10.2组成(4) 成员对象初始化:成员初始化表 不能通过构造函数在函数体内初始化! Date类没有定义Date &类型参数的构造函数 成员对象初始化:成员初始化表 不能通过构造函数在函数体内初始化! Date类没有定义Date &类型参数的构造函数 初始化使用了默认的拷贝构造函数 class E{ public: E(Date & x):d(x){}; E(Date & x){d=x};//error private: Date d; }; Date date(7,24,1985); E e(date); 东南大学计算机学院 1/15/2019
10.2组成(5) 成员对象初始化:默认构造函数 当没有显式通过成员初始化表进行初始化时,编译器自 动调用成员对象的默认构造函数进行初始化 若无默认构造函数,则必须使用成员初始化表 开始创建对象 成员初始化表 构造函数函数体 创建完毕 创建成员对象 初始化其他数据成员 东南大学计算机学院 1/15/2019
10.2组成(6) 没有默认的构造函数 正确 Which one is correct? #include <iostream> using namespace std; class Test{ public: Test(int a){ num = a; } private: int num; }; class Test2{ Test2(int a, int b) { num = b; }; Test t; int main() { Test2 test(10, 20); return 0; } #include <iostream> using namespace std; class Test{ public: Test(int a){ num = a; } private: int num; }; class Test2{ Test2(int a, int b): t(a) { num = b; }; Test t; int main() { Test2 test(10, 20); return 0; } (1) 没有默认的构造函数 (2) 正确
10.2组成(7) 正确,调用了默认的构造函数 正确,调用了默认的构造函数 Which one is correct? #include <iostream> using namespace std; class Test{ public: Test(int a=0){ num = a; } private: int num; }; class Test2{ Test2(int a, int b) { num = b; }; Test t; int main() { Test2 test(10, 20); return 0; } #include <iostream> using namespace std; class Test{ public: Test(){ num = 0; } private: int num; }; class Test2{ Test2(int a, int b) { num = b; }; Test t; int main() { Test2 test(10, 20); return 0; } (3) 正确,调用了默认的构造函数 (4) 正确,调用了默认的构造函数
10.2组成(9) 构造和析构的顺序 成员对象先于宿主对象构造 成员对象按在类中声明的顺序构造 成员对象后于宿主对象析构 birthDate和hireDate manager birthDate hireDate manager hireDate birthDate 东南大学计算机学院 1/15/2019
10.2组成(10) An example (Fig. 10.10~10.13) lastDayOff manager hire birth birth构造: Date object constructor for date 7/24/1949 Hire构造: Date object constructor for date 3/12/1988 birthDate缺省拷贝构造: 无输出 hireDate缺省拷贝构造: 无输出 manager构造: Employee object constructor: Bob Blue lastDayOff构造: Date object constructor for date 1/1/1994 lastDayOff析构: Date object destructor for date 1/1/1994 manager析构: Employee object destructor: Blue, Bob hireDate析构: Date object destructor for date 3/12/1988 birthDate析构: Date object destructor for date 7/24/1949 hire析构: Date object destructor for date 3/12/1988 birth析构: Date object destructor for date 7/24/1949 lastDayOff manager hireDate birthDate hire birth main栈区 东南大学计算机学院 1/15/2019
10.3友元(1) 友元(friend)在类外定义,能访问类的所有数据成员 通常在运算符重载中使用 声明和定义 友元函数(friend function) 友元类(friend class):所有成员函数都是友元函数 通常在运算符重载中使用 声明和定义 类内声明,类外定义 声明时位置无关,访问属性无关 友元函数不是类成员 class A{ friend void f(A &); friend class B; public: Test():x(0){}; private: int x; }; void f(A & a){ a.x=1; } class B{……}; 良好的编程习惯 东南大学计算机学院 1/15/2019
动态内存管理 动态内存管理:在运行时动态管理内存 作用范围 动态内存管理操作 变量存储在自由存储区:堆区(heap) 内置类型 11.4动态内存管理 动态内存管理 动态内存管理:在运行时动态管理内存 作用范围 内置类型 用户自定义类类型 动态内存管理操作 new:申请内存 delete:释放内存 变量生命期:从new开始,delete结束 变量存储在自由存储区:堆区(heap) A *p = new A; delete p; 东南大学计算机学院 1/15/2019
如果delete释放内存后,指针仍在内存中,将指针值置为0可使其不再指向自由区内存,可以避免虚悬指针 11.4动态内存管理 operator new/delete operator new 安全检测:抛出异常 为对象分配内存 调用构造函数 operator delete 调用析构函数 释放内存 A *p = new A(1,2); 调用malloc(sizeof(A)) 调用A::A(int,int)初始化 返回this指针 delete p; 调用A::~A() 调用free(p) 注意:指针本身没有被撤销 new和delete必须配对使用,避免内存泄漏! 如果delete释放内存后,指针仍在内存中,将指针值置为0可使其不再指向自由区内存,可以避免虚悬指针 东南大学计算机学院 1/15/2019
Copy构造函数 如果没有定义copy构造函数,则执行默认的逐个 成员赋值 但对于动态数组来说不行(只复制指针,造成内 存泄漏) 东南大学计算机学院 1/15/2019
Array::Array(Array arrayToCopy ) 29 Array::Array( const Array &arrayToCopy ) 30 : size( arrayToCopy.size ) 31 { 32 ptr = new int[ size ]; // create space for pointer-based array 33 34 for ( int i = 0; i < size; i++ ) 35 ptr[ i ] = arrayToCopy.ptr[ i ]; // copy into object } Array.cpp 拷贝构造函数的作用? 何时调用拷贝构造函数? 拷贝构造函数的参数类型为什么是const Array & ?可以是Array 吗? 通过建立一个现有对象的副本来初始化一个Array对象 默认的赋值运算符=不自动调用拷贝构造函数 需要创建对象副本时 按值传递参数时 (函数形参/函数返回值) 用对象的副本初始化另一对象时 const引用不需要拷贝const对象 不可以是Array,若为Array: Array::Array(Array arrayToCopy ) 需要创建实参的副本(拷贝),此时需要调用拷贝构造函数,即该函数本身,造成无穷递归!
29 Array::Array( const Array &arrayToCopy ) 30 : size( arrayToCopy.size ) 31 { 32 ptr = new int[ size ]; // create space for pointer-based array 33 34 for ( int i = 0; i < size; i++ ) 35 ptr[ i ] = arrayToCopy.ptr[ i ]; // copy into object } Array.cpp 4. 是否可以直接拷贝指针?ptr=&arrayToCopy.ptr 不可以。当传入的参数被析构时,ptr成为虚悬指针!
chap11 运算符重载函数的两种实现方式 一些特例 友元的使用 传值/传引用的区别 函数的级联调用(注意参数类型) 成员函数 全局函数 一些特例 <<和>>运算符必须以全局函数实现 。。。 友元的使用 传值/传引用的区别 函数的级联调用(注意参数类型) 运算符重载和模板的关系(用户自定义的类) 流运算符重载 东南大学计算机学院 1/15/2019
11.2两种运算符函数的比较(1) 两种运算符函数 必须作为成员函数重载的运算符 必须作为全局函数重载的运算符 成员运算符函数:必须是non-static成员函数 全局运算符函数:一般为友元 必须作为成员函数重载的运算符 ()、[]、-> 任何赋值运算符 必须作为全局函数重载的运算符 流插入操作符(<<)和流提取运算符(>>) 可交换的运算符(指操作对象类型不同) 其他运算符可以作为成员函数或全局函数进行重载 用法一样,实现不同 东南大学计算机学院 1/15/2019
11.2两种运算符函数的比较(2) 两种运算符函数的调用 成员运算符函数 一元运算符 二元运算符 全局运算符函数 + obj1 obj1 + obj2 obj1.operator+( ) obj1.operator+( obj2 ) + obj1 obj1 + obj2 operator+( obj1 ) operator+( obj1, obj2 ) 东南大学计算机学院 1/15/2019
11.2两种运算符函数的比较(5) 函数的参数传递类型(形参/返回值类型) 传递值:返回对象的副本,而非对象本身 传递引用:返回对象本身,而非对象的副本 重要知识点回顾 int main(){ int x = 0; set(x); cout << “x = “ << x; } void set ( int a){ a = 1; int main(){ int x = 0; set(x); cout << “x = “ << x; } void set ( int & a){ a = 1; x = 0 x = 1 东南大学计算机学院 1/15/2019
11.2两种运算符函数的比较(6) 函数的参数传递类型(形参/返回值类型) 传递值:返回对象的副本,而非对象本身 传递引用:返回对象本身,而非对象的副本 效率高,但由于能改变对象,容易出错 避免改变对象的方法:const对象引用 重要知识点回顾 bool operator!( A a ) const; 需拷贝对象,效率低 bool operator!( const A & a ) const; 不需拷贝对象,效率高 东南大学计算机学院 1/15/2019
11.2两种运算符函数的比较(7) 函数的参数传递类型(形参/返回值类型) 传递引用:返回对象本身,而非对象的副本 是函数级联调用的必要条件 重要知识点回顾 Time & Time::setHour(int h){ … … return *this; } Time & Time::setMinute(int m){ t.setHour(12).setMinute(10); t.setHour(12); t.setMinute(10); (11.3中有另一个例子) 东南大学计算机学院 1/15/2019
11.2两种运算符函数的比较(9) 返回引用:避免“虚悬引用”(dangling references) 虚悬引用:引用的对象不存在 从重载的流操作符返回的cin、cout等大部分stream对象 一般是全局的,返回引用一般会成功 避免返回自动变量和临时变量的引用 int & f(){ int a; return a; } int & f( int a ){ return a; } a是实参的副本,是临时变量 虚悬引用! 函数结束时,自动变量a被撤销 虚悬引用! 东南大学计算机学院 1/15/2019
const PhoneNumber & vs. PhoneNumber friend ostream & operator<<( ostream &, const PhoneNumber & ); cout << obj 此函数是不是成员函数? 友元函数不是类成员 重载方式:全局运算符函数 为何要声明为友元? 方便访问私有数据 函数返回值类型: ostream & 返回对象本身,可以级联调用 形参类型 ostream & vs. ostream const PhoneNumber & vs. PhoneNumber 传入cout对象本身,才能保证返回时是对象本身,才可以级联调用 传引用:不进行复制,节省时间和内存开销,效率高 限定为const:保证对象不被改变 1/15/2019 东南大学计算机学院
PhoneNumber & vs. PhoneNumber PhoneNumber & vs. const PhoneNumber & friend istream & operator>>( istream &, PhoneNumber & ); 形参类型 PhoneNumber & vs. PhoneNumber PhoneNumber & vs. const PhoneNumber & cin需要改变对象的数据成员,因此既不能传入对象的副本,又不能限定为const 1/15/2019 东南大学计算机学院
chap12&Chap13 类之间的继承关系(UML) protected成员 派生类构造函数和析构函数的顺序 多态性 基类的指针指向派生类对象 virtual函数 动态绑定和virtual函数 动态绑定:引用或指针,而不是对象名 virtual函数调用哪个函数? 抽象类和纯虚函数 抽象类定义:不能实例化对象,必须有一个纯虚函数 纯虚函数在基类中不能定义 东南大学计算机学院 1/15/2019
protected成员(3) 面向类用户的接口:public成员 面向派生类用户的“接口”:public成员和protected成员 12.2继承简介 protected成员(3) 可访问性 基类成员 本类成员 本类友元 类用户 派生类 Public成员 Protected成员 Private成员 private:宁与友(友元),不与子(派生类) 面向类用户的接口:public成员 面向派生类用户的“接口”:public成员和protected成员 东南大学计算机学院 1/15/2019
派生类对基类成员函数的继承 对于基类的 构造函数 析构函数 重载的赋值运算符 派生类不会继承它们,但是可以调用它们 12.4派生类中的构造和析构函数 派生类对基类成员函数的继承 对于基类的 构造函数 析构函数 重载的赋值运算符 派生类不会继承它们,但是可以调用它们 东南大学计算机学院 1/15/2019
在创建对象时,为对象分配内存和进行初始化 12.4派生类中的构造和析构函数 派生类中构造函数的调用(1) 构造函数的作用: 在创建对象时,为对象分配内存和进行初始化 析构函数的作用: (准备) 释放对象占用的内存 派生类对象的组成 基类的数据成员 自定义的数据成员 基类定义 基类的构造和析构函数 派生类定义 派生类的构造和析构函数 东南大学计算机学院 1/15/2019
派生类中构造函数的调用(2) 构造函数的调用顺序 带有成员初始化表的构造函数 12.4派生类中的构造和析构函数 函数调用顺序 基类A的构造函数 B b; 函数调用顺序 函数执行顺序 初始化表 函数体 派生类B的构造函数 派生类B 基类A 东南大学计算机学院 1/15/2019
派生类中构造函数的调用(2) 构造函数的调用方式 隐式调用:调用默认的构造函数 12.4派生类中的构造和析构函数 派生类中构造函数的调用(2) 构造函数的调用方式 隐式调用:调用默认的构造函数 显示调用: 在派生类的构造函数中,一般在成员初始化表中调用构 造函数,避免多次修改数据成员 东南大学计算机学院 1/15/2019
派生类中析构函数的调用 析构函数的调用顺序与构造函数相反 12.4派生类中的构造和析构函数 函数执行顺序 派生类C的析构函数 派生类C 派生类B的析构函数 基类A的析构函数 {C c;} 函数调用顺序 函数执行顺序 派生类C的析构函数 派生类B 基类A 派生类C 东南大学计算机学院 1/15/2019
和组成的构造和析构顺序进行对照学习 如何基类和派生类中都有成员对象呢? 函数调用顺序 派生类B的构造函数 派生类 派生类的 成员对象创建 基类A的构造函数 B b; 函数调用顺序 基类的成员 对象创建 初始化表 函数体 派生类B的构造函数 基类对象 创建 派生类的 成员对象创建 派生类 东南大学计算机学院 1/15/2019
什么是多态性?(5) 多态性 同样的消息在发送给不同的对象时会产生多种形式的结果 多态性指:不同对象对同一消息的响应是多态的 13.1多态性 关键概念 消息 对象类型 响应 Circle Rectangle Square draw 同样的消息在发送给不同的对象时会产生多种形式的结果 多态性指:不同对象对同一消息的响应是多态的 东南大学计算机学院 1/15/2019
继承中指针和对象的关系(2) 指针(引用)类型 对象类型 基类指针 基类对象 派生类指针 派生类对象 is-a 基类对象 派生类对象 13.1多态性 继承中指针和对象的关系(2) 指针(引用)类型 对象类型 基类指针 基类对象 派生类指针 派生类对象 基类对象 派生类对象 is-a 东南大学计算机学院 1/15/2019
virtual函数(1) virtual函数 virtual函数指示根据句柄指向的对象的类型调用与之相应的virtual函数版本 句柄函数 13.1多态性 virtual函数(1) virtual函数 关键概念 virtual函数指示根据句柄指向的对象的类型调用与之相应的virtual函数版本 句柄函数 对象数据 非virtual函数的调用 对象函数 virtual函数的调用 基类指针 派生类对象 A a; //基类 B b; //派生类 A *p = &b; //若f()为virtual函数 p->f(); //调用B::f() 东南大学计算机学院 1/15/2019
基类指针指向派生类对象 一般成员函数 virtual成员函数 指针函数 对象数据 对象函数 对象数据 不能产生多态性! 多态性的必要条件! 东南大学计算机学院 1/15/2019
chap14 函数模板 类模板 类型参数/非类型参数 模板与运算符重载之间的关系 重载函数模板 vector的使用 东南大学计算机学院 1/15/2019
函数模板 函数模板(function template) 是一个独立于类型的函数, 可作为一种方式,产生函数的特定类型版本 模板形参表(template parameter list) < >内为模板形参(template parameter) int compare (const T &v1, const T &v2) { if ( v1 < v2) return -1; if ( v1 > v2) return 1; return 0; } template < typename T > 注意不是一个单独的语句 关键字 T表示实际使用的类型 东南大学计算机学院 1/15/2019
定义类模板 定义类模板 在类定义部分: 将类定义为模板 在类实现部分: 成员函数为函数模板 泛型类:A<T> 在类定义部分: 将类定义为模板 在类实现部分: 成员函数为函数模板 template < typename T > class A { public: T getX(); void setX(const T &); private: T x; }; T A<T>:: getX(){ return x; } void A<T>:: setX(const T &a){ x = a; } 泛型类:A<T> 在类定义外实现成员函数时,需使用泛型类指定类作用域 模板的定义和实现必须在同一文件中! 东南大学计算机学院 1/15/2019
模板形参表 模板形参的类型 类型形参:表示类型 (任何内置类型或用户自定义类型) 非类型形参:表示常量表达式 关键字为typename或class,二者无区别 template < typename T > template < class T > template < typename T, class U > template < class T, size_t N> void array_init( T (&parm) [N] ){ for (size_t i = 0; i != N; ++i) parm[i] = 0; } int x[40]; doubel y[10]; array_init(x); //实例化array_init(int (&x) [40]) array_init(y); //实例化array_init(double (&y) [10]) 东南大学计算机学院 1/15/2019
非类型参数 使用非类型参数定义Stack类 template< typename T, int elements = 5> class Stack { public: Stack(); bool push( const T& ); bool pop( T& ); bool isEmpty() const { return top == -1; } bool isFull() const { return top == elements- 1; } private: int top; T stackPtr[elements]; }; 使用非类型参数定义Stack类 不再需要参数对数据成员size赋初值 数据成员size不再需要 使用静态数组,在编译时指定数组大小为非类型形参elements的实参指定的常量值 东南大学计算机学院 1/15/2019
非类型参数 template< typename T, int elements > Stack< T, elements >::Stack() : top( -1 ) // 只需对top进行初始化 { for (int i=0; i<elements; i++) stackPtr[i] = 0; } template< typename T, int elements > bool Stack< T, elements >::push( const T &pushValue ) { if ( !isFull() ) stackPtr[ ++top ] = pushValue; // place item on Stack return true; // push successful } // end if return false; // push unsuccessful } 东南大学计算机学院 1/15/2019
非类型参数 测试非类型参数定义的Stack类 template< typename T, int elements > bool Stack< T, elements >::pop( T &popValue ) { if ( !isEmpty() ) popValue = stackPtr[ top-- ]; // remove item from Stack return true; // pop successful } // end if return false; // pop unsuccessful } // end function template pop Stack<int, 3> intStack; //Stack<int> intStack; is also ok intStack.push(1); intStack.push(2); int value; intStack.pop(value); cout<<value<<endl; intStack.pop(value); cout<<value <<endl; 测试非类型参数定义的Stack类 东南大学计算机学院 1/15/2019
使用函数模板 例子: printArray (Fig. 14.1) template< typename T > void printArray( const T *array, int count ){ for ( int i = 0; i < count; i++ ) cout << array[ i ] << " "; cout << endl; } 如果T是用户自定义的类? 该类必须有重载的流插入重载函数 const int aCount = 5; // size of array a const int bCount = 7; // size of array b const int cCount = 6; // size of array c int a[ aCount ] = { 1, 2, 3, 4, 5 }; double b[ bCount ] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7 }; char c[ cCount ] = "HELLO"; // 6th position for null printArray( a, aCount ); printArray( b, bCount ); printArray( c, cCount ); 如果模板用到了函数或运算符,则当以用户自定义类型作为模板实参时,这些函数和运算符必须被该用户自定义类所重载
重载函数模板 重载函数模板 函数模板和函数模板之间的重载: 函数名相同,参数不同 函数模板和非模板函数之间的重载: 函数名相同,参数不同 函数模板和函数模板之间的重载: 函数名相同,参数不同 函数模板和非模板函数之间的重载: 函数名相同,参数不同 template < typename T> T f(const T&); template < typename T> T f(const T&, int); template < typename T> T f(const T&); int f(const int &); 东南大学计算机学院 1/15/2019
重载函数模板 重载函数的匹配过程 寻找匹配的函数模板 找出和调用函数的函数名相匹配的函数模板 寻找匹配的函数模板 找出和调用函数的函数名相匹配的函数模板 实例化函数模板 根据调用函数的参数类型对函数模板实例化 寻找匹配的一般函数 找出最佳匹配 普通函数优先匹配:若一个普通函数和一个函数模板 实例的匹配程度相同,则调用普通函数 若存在多个匹配程度相同的函数,则编译错误 若无匹配,则编译错 东南大学计算机学院 1/15/2019
chap16 异常处理的基本步骤(throw-try-catch) 异常处理继承层次中使用多态性处理异常 重抛出异常 东南大学计算机学院 1/15/2019
异常处理 异常处理的步骤 抛出异常 侦测异常 处理异常 特别注意:抛出点必须在可能出错的语句之前 异常处理模式 :throw 抛出点 (throw point) :try :catch 异常处理模式 try { } 每个try块后面至少立即跟着一个catch处理器 可能出现异常的语句块 throw 可能出错的语句; 特定类型的异常; 定义异常类,描述问题, 在throw和catch间传递信息 继承标准库异常类 自定义 catch(特定类型的异常) { 处理异常; } 异常参数列表 catch(异常类类型 异常参数) 东南大学计算机学院 1/15/2019
异常处理 抛出多个异常 异常处理模式 每个try块后面至少立即跟着一个catch处理器 可能出现异常的语句块 { } catch(异常1) 处理异常1; 可能出现异常的语句块 throw 异常1; … … throw 异常2; catch(异常2) 处理异常2; 抛出多个异常 每个try块后面至少立即跟着一个catch处理器 catch处理器会匹配异常的类型 与抛出的异常类型相同 是抛出的异常的基类 东南大学计算机学院 1/15/2019
异常处理 抛出多个异常 异常处理模式 每个try块后面至少立即跟着一个catch处理器 可能出现异常的语句块 { } catch(异常1) 处理异常1; 可能出现异常的语句块 throw 异常1; … … throw 异常2; catch(异常2) 处理异常2; 抛出多个异常 每个try块后面至少立即跟着一个catch处理器 catch处理器会匹配异常的类型 与抛出的异常类型相同 是抛出的异常的基类 东南大学计算机学院 1/15/2019
异常处理 异常处理的终止模式 try语句块 catch处理器 throw 异常1 if … … throw 异常2 异常? 异常类型 true false … … throw 异常2 异常? 异常类型 catch(异常1){…} catch(异常2){…} 异常处理的终止模式 … … try { throw 异常1; throw 异常2; } catch(异常1) 处理异常1; catch(异常2) 处理异常2;
异常匹配 异常匹配规则 基类的指针可以指向派生类对象 可以利用多态性来处理异常 抛出异常 的类型 catch处理器中 异常参数的类型 类型相同 抛出异常 的类型 catch处理器中 异常参数的类型 继承 基类的指针可以指向派生类对象 可以利用多态性来处理异常 东南大学计算机学院 1/15/2019
异常匹配 异常与继承 派生类的处理代码必须在基类的之前 bool flag = true; try { if(flag) throw runtime_error("runtime error"); else throw logic_error("logic error"); } catch(exception &e) { cout<<"Exception Handle: "<<e.what()<<endl; } catch(runtime_error &e) //不会执行 { cout<<"Runtime_error Handle: "<<e.what()<<endl; } 东南大学计算机学院 1/15/2019
重新抛出异常 调用函数 try { } 抛出的是原来的异常对象,而不是catch的形参 被调用函数 可能出现异常的语句块 特定类型的异常; throw 可能出错的语句; 特定类型的异常; runtime_error() catch(特定类型的异常) { 处理异常的语句1; } exception &e throw; runtime_error() catch(特定类型的异常) { } 处理异常的语句2; 1/15/2019
栈展开 栈展开(Stack Unwinding) 当被调用函数中抛出异常,但没有用try-catch将其捕获,则展开函数调用栈,沿函数执行顺序在调用函数内寻找try-catch块捕获该异常 void f(){ throw runtime_error(“runtime error"); } } int main(){ try{ f(); } catch( exception &e ) { cout<<e.what()<<endl; } 函数f() 函数g() 函数k() 捕 获 异 常 顺 序 1/15/2019
栈展开 throw在try内 中断函数执行,判断throw在catch内 查找当前try对应的catch:不匹配 退出当前函数f() void f(){ try { throw 异常1; } catch(异常2) {… …} 语句1; int main(){ f(); catch(异常1) catch(其他异常){……} 语句2; 中断函数执行,判断throw在catch内 查找当前try对应的catch:不匹配 查找当前try对应的catch:匹配 退出当前函数f() 返回调用函数main() 执行catch处理器 执行catch后的第一条语句
chap17 顺序文件的读写 二进制文件的读写 东南大学计算机学院 1/15/2019
fstream库 文件处理必须包含<iostream>和<fstream>两个头文件中 basic_ios basic_istream basic_ostream basic_iostream basic_ifstream basic_ofstream basic_fstream ifstream ofstream fstream 包含在<fstream>头文件中 1/15/2019
创建流对象(3) 打开模式 以ios::out模式打开时,默认是ios::trunc 东南大学计算机学院 1/15/2019
常用操作(1) 测试文件是否成功打开 ture: 没有成功打开 false:成功打开 打开不存在的文件 没有读/写权限 写时空间不够 if( !outFile ) { cerr<<“file can not be opened!”; exit(1); } 打开不存在的文件 没有读/写权限 写时空间不够 东南大学计算机学院 1/15/2019
常用操作(2) 文件关闭 隐式关闭:在输入数据时,用户输入文件结束符,则自 动调用对象的析构函数关闭其关联的文件 显示关闭: outFile.close(); Computer system Keyboard combination UNIX/Linux/Mac OS X <ctrl-d> Microsoft Windows <ctrl-z> VAX (VMS) 东南大学计算机学院 1/15/2019
常用操作(3) 测试文件是否读入结束 while( !inFile.eof() ) { …… } while( inFile >> account >> name >> balance ) { …… } 东南大学计算机学院 1/15/2019
常用操作(4) 测试文件是否读入结束 如果用作条件判断,则隐式的强制进行void *类型的类型转换: 如果输入成功:非空指针 while( inFile >> account >> name >> balance ) { …… } 如果用作条件判断,则隐式的强制进行void *类型的类型转换: 如果输入成功:非空指针 如果输入失败:空指针 再将指针隐式的转换为bool类型: 非空指针:true 如果输入失败:false 参考chap15.4 东南大学计算机学院 1/15/2019
常用操作(5) 定位函数 istream::seekg():指出下一个输入的字节号 ostream::seekp():指出下一个输出的字节号 istream::tellg():当前get指针的位置 ostream::tellp():当前put指针的位置 file.seekg(n); //从文件开始处往后数 ios::beg file.seekg(n, ios::cur);//从当前指针往后数 file.seekg(n, ios::end);//从最后往前数 file.seekg(0, ios::end); 东南大学计算机学院 1/15/2019
创建二进制文件 创建流对象 ofstream outFile( “fileName.dat”, ios::out | ios::binary) ifstream inFile( “fileName.dat”, ios::in | ios::binary) fstream file( “fileName.dat”, ios::in | ios::out | ios::binary) 东南大学计算机学院 1/15/2019
读写随机存取文件 outFile.write(reinterpret_cast< const char *>(&number), sizeof( number ) ); inFile.read(reinterpret_cast< const char *>(&number), sizeof( number ) ); class A{…}; A a; outFile.write(reinterpret_cast< const char *>(&a), sizeof( A ) ); 东南大学计算机学院 1/15/2019
随机存取文件(4) 文件的读取 文件的修改 定位记录: seekg( (n-1) * sizeof(A) ) 读取记录 更新 定位记录: seekp( (n-1) * sizeof(A) ) 修改记录 东南大学计算机学院 1/15/2019
随机存取文件(5) 文件的修改 插入 删除 初始化 定位记录 判断记录位置是否为空 是,则插入记录 (等同于更新操作) 否,则错误 否,则删除记录 (将其更新为初始值) 是,则错误 东南大学计算机学院 1/15/2019
东南大学计算机学院 1/15/2019
其他 编程中经常使用的标准类 编程中经常使用的运算符和函数 基于指针的字符串数组 string vector (chap7.11和本学期涉及到的例子) 二维数组 (chap7.9) 编程中经常使用的运算符和函数 cout/cin ++/-- strcmp()等函数 exit() 注意包含相应的头文件! 东南大学计算机学院 1/15/2019
编程中的注意事项 头文件 using namespace std; 预编译指令 条件判断 构造函数和析构函数 动态内存 运算符是否需要重载(<</>>) 继承关系 多态性和虚函数 抽象类和具体类,以及纯虚函数 东南大学计算机学院 1/15/2019
调试程序 编译 运行 查看错误信息! 一定要熟练! 设置断点 逐语句调试 F11 逐过程调试 F10 上学期课程:王伟老师《实验环境使用手册 》 东南大学计算机学院 1/15/2019
UML 类图 继承关系图 派生类 基类 类名 +:public -:privated #: protected 数据成员 成员函数 考试中不会严格要求,只需表明类的组成和继承方向 东南大学计算机学院 1/15/2019