Download presentation
Presentation is loading. Please wait.
Published byΜήδεια Μοσχοβάκης Modified 6年之前
1
刘胥影 liuxy@seu.edu.cn 东南大学计算机学院
面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院
2
Classes: A Deeper Look (I) 教学要求与安排
chap9.1~9.13全部掌握 教学安排 chap9.11自学 chap9.13放在chap13中与chap13.10一起讲 东南大学计算机学院 11/23/2018
3
Classes: A Deeper Look (I)
9.1 类的定义与声明 9.2 类作用域 9.3 构造函数 9.4 析构函数 9.5 破坏类的封装性:返回私有数据成员的 引用 9.6 默认的复制控制 11/23/2018
4
类的定义和声明 类成员 类定义 类声明 类对象 this指针 成员函数 数据抽象和封装 避免类重复定义:预编译器指示 东南大学计算机学院
11/23/2018
5
9.1类成员(1) 组成 访问控制 数据成员(data member) 成员函数(member function) 类型别名typedef
可以有多个成员,也可以没有成员 访问控制 public:使用该类型的所有代码 private:本类的其他类成员、友元类(后面进行介绍) protected:子类(后面进行介绍) private权限:private成员 本类成员 protected权限:protected成员 友元类 public权限:public成员 11/23/2018
6
9.1类成员(2) 访问控制符的使用 访问控制符的有效范围具有连续性 可在任何访问控制符之前定义类成员,默认属性
class关键字定义的类:private struct关键字定义的类:public class Test{ int a; //private public: void func_a(); //public void func_b(); //public private: int b; //private double c; //private }; 11/23/2018
7
9.1类定义 一旦定义了类,就确定了类成员和该类对象所需的 存储空间 所有成员必须在类内声明,定义完成后不可再添加成员
类定义后的分号不可丢 (why?类定义了一种新的数据类型) varType varName; ClassName object; class Test{ public: void displayMessage(); private: int num; }; class ClassName{/* */}; class ClassName{/* */} object; 东南大学计算机学院 11/23/2018
8
9.1类声明 类声明(前向声明) 类不能包含具有自身类型的数据成员
是一个不完全类型(incomplete type), 不能定义该类的对象 创建类对象前必须完成类定义 只能用于: 指向该类型的指针和引用 使用该类型的形参类型或返回类型的函数声明(非定义!) 类不能包含具有自身类型的数据成员 两个类也不能通过包含类类型的成员来相互依赖定义(A包 含B型的成员,B包含A型的成员) 但数据成员可以为指向类型的指针 class ClassName; 东南大学计算机学院 11/23/2018
9
9.1类对象(1) 类定义了一种新的数据类型 对象的存储 创建对象 编译器根据类类型为对象分配存储空间,对象名指向存储空间
ClassName item; 编译器根据类类型为对象分配存储空间,对象名指向存储空间 对象的存储 数据成员:每个对象都要存储自己的数据 成员函数:所有对象共享一份代码copy 静态变量:为所有对象共享,是类的一部分,不是对象 的一部分,存放在堆区 堆和栈的访问? 栈:快,临时 堆:慢, 内存专题 什么是自动变量 东南大学计算机学院 11/23/2018
10
9.1类对象(2) 变量的存储:stack or heap? 自动变量:栈区 静态变量:堆区 动态变量:堆区 全局变量:堆区 Stack:
东南大学计算机学院 11/23/2018
11
9.1类对象(3) 对象的大小 sizeof() 不计算static数据成员 每个语句分配的变量存储区至少为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 11/23/2018
12
9.1类对象(4) 类的使用 在类内,类成员可以直接被类的所有成员访问 (可以利用引用)
在类外,public成员通过对象的句柄(handle)使用 Time sunset; // 创建Time类的对象 Time arrayOfTimes[5]; //建立Time类对象的数组 Time &dinnerTime = sunset; //对象sunset的引用(别名) Time *timeptr1 = &dinnerTime;//指向对象的指针 Time *timeptr2 = &sunset;//指向对象的指针 东南大学计算机学院 11/23/2018
13
9.1类对象(5) 句柄:对象名、引用、指针 操作符 访问形式 圆点成员选择符(.) 箭头成员选择符(->) 对象名 + (.)
引用 + (.) 指针 + (->) (*指针) + (.):必须使用(*指针) 东南大学计算机学院 11/23/2018
14
9.1类对象(6) An example // P359. fig9.4 #include <iostream>
using namespace std; class Count { …… }; int main() { Count counter; Count &counterRef = counter; Count *counterPtr = &counter; counter.setX(1); counter.print(); counterRef.setX(2); counterRef.print(); (*counterPtr).setX(3); (*counterPtr).print(); counterPtr->setX(4); counterPtr->print(); return 0; } 11/23/2018
15
9.1this指针(1) class Test{ public: void func(); private: int num; }; item1.num item2.num Test::func() Stack: 代码区 ClassName item1; ClassName item2; item2.func(); void func(Test *this) this指针是成员函数的隐含形参,用来指向该对象的指针 (const指针类型) 定义方式:由编译器隐含定义,不能由成员函数定义 东南大学计算机学院 11/23/2018
16
9.1this指针(2) this指针的使用 可以由成员函数显式使用 返回对调用函数的对象的引用 class Screen{ public:
Screen& move(int); Screen& set(char); … … }; Screen& Screen::move(int a){ return *this } Screen& Screen::set(char c){ myScreen.move(4).set(‘x’); myScreen.move(4); myScreen.set(‘x’); 11/23/2018
17
9.1成员函数(1) 成员(函数)必须在类内声明 实现的两种方式 类内定义 类外定义 指明成员函数的类 class Test{
public: void displayMessage() { /* code */ }; private: int num; }; class Test{ public: void displayMessage(); private: int num; }; void Test::displayMessage(){ /* code */ } 指明成员函数的类 东南大学计算机学院 11/23/2018
18
如有必要,仅在类内实现最简单和最稳定的成员函数
9.1成员函数(2) 类内定义 VS. 类外定义 相同点: 可见性相同:类外定义不改变其可见性 不同点: 编译方式可能不同 类内定义:编译器尝试进行inline调用(可提高效率) 类外定义:当显式指定为inline时,编译器尝试进行inline 调用 内部定义破坏了封装性,用户可以看见其实现,软件复 用性不好(后面还会强调) Inline是非强制的,编译器保留不对任何函数inline调用的权利,行为依赖于具体的编译器 建议:在类外实现成员函数 如有必要,仅在类内实现最简单和最稳定的成员函数 11/23/2018
19
9.1成员函数(3) 访问函数 工具函数(utility functions/helper function)
读取或显示数据 测试条件的真假(又称为判定函数) 工具函数(utility functions/helper function) 类的private成员函数 辅助其他成员函数 类用户不能使用(不需要知道实现细节) 友元类可以使用 有利于实现封装,增强软件的可复用性 东南大学计算机学院 11/23/2018
20
9.1成员函数(4) An example // Fig. 9.5: SalesPerson.h … …
class SalesPerson{ public: SalesPerson(); void getSalesFromUser(); void setSales( int, double ); void printAnnualSales(); private: double totalAnnualSales(); double sales[ 12 ]; }; 9.1成员函数(4) An example //Fig. 9.6: SalesPerson.cpp void SalesPerson::printAnnualSales(){ cout << setprecision( 2 ) << fixed 49 << "\nThe total annual sales are: $" 50 << totalAnnualSales() << endl; } double SalesPerson::totalAnnualSales(){ double total = 0.0; for ( int i = 0; i < 12; i++ ) total += sales[ i ]; return total; 11/23/2018
21
9.1成员函数(5) const成员函数:不能改变其所操作的对象的数据 成员 可避免对数据成员的误操作
成员函数重载 只能由该类的其他成员函数重载 在类中声明各个版本 提供独立的函数定义 double avg_price() const 东南大学计算机学院 11/23/2018
22
9.1数据抽象和封装(1) 类的基本思想是数据抽象和封装 数据抽象:一种依赖于接口(Interface)和实现分离的编程 技术
封装:一种将低层次的元素组合起来形成新的高层次实 体的技术,被封装的元素隐藏了其实现细节 函数 类 关心类的实现细节 类设计者 不关心类的实现细节 关心类的接口(类定义 ) 类使用者(类用户) 东南大学计算机学院 11/23/2018
23
9.1数据抽象和封装(2) 数据抽象和封装的优点 类的抽象和封装 避免类内部出现无意的、可能破坏对象状态的用户级错 误
更改或完善类的实现时,无需改变用户级代码 类的抽象和封装 类的public成员构成抽象视图 private封装类的实现细节 东南大学计算机学院 11/23/2018
24
#include “ClassName.cpp”
9.1数据抽象和封装(3) ClassName.cpp ClassName.h 类实现 (不包含inline函数) 类声明: public成员 private成员 inline函数 code 实现 接口 #include “ClassName.cpp” 如果头文件中包含部分类实现,那么必须是inline函数 定义在内类则为隐式的inline函数 定义在类外必须显式声明inline 实现必须包含相应接口的头文件 代理类(Proxy Class) 可以进一步隐藏类的private数据成 员(后面介绍) 东南大学计算机学院 11/23/2018
25
9.1数据抽象和封装(4) 类实现 (成员函数定义) 类定义 (接口) 类用户代码 不用提供源代码,实现知识产权保护
设 计 者 A.h A.cpp Main.cpp A.obj Main.obj 编译 EXE 链接 类实现 (成员函数定义) 类定义 (接口) 类用户代码 不用提供源代码,实现知识产权保护 更改类实现时,类用户不必更改代码和重新编译,只需重 新连接,增强软件可复用性 东南大学计算机学院 11/23/2018
26
9.1避免类重复定义:预编译器指示(1) 关于类定义的Rules & Facts
大型程序具有多个头文件,因此会发生企图多次包含同 一头文件的情况 error! // a.h class A{ int a; }; // b.h #include "a.h" class B{ double b; // test.cpp #include "b.h" void main(){ //… … } class A{ int a;} class B{ double b;} 东南大学计算机学院 11/23/2018
27
9.1避免类重复定义:预编译器指示(2) 如何避免类重复定义 大型程序具有多个头文件,因此会发生企图多次包含同 一头文件的情况 error!
// a.h class A{ int a; }; // b.h #include "a.h" class B{ double b; // test.cpp #include "b.h" void main(){ //… … } class A{ int a;} class B{ double b;} 11/23/2018 如何避免类重复定义 东南大学计算机学院
28
9.1避免类重复定义:预编译器指示(3) 编译器封装(Preprocessor Wrapper)
编译器指示(Preprocessor Inductive) #ifdef FLAG :如果定义了 FLAG 则执行… #ifndef FLAG:如果没定义 FLAG 则执行… #define FLAG:定义 FLAG #undef FLAG :撤销 FLAG 的定义 #else :当条件为假时执行 #endif :结束执行 #ifdef 或 #ifndef FLAG 可嵌套使用 #ifndef FLAG #define FLAG /* 类定义代码 */ #endif 条件为真时才进行编译—“条件编译” 东南大学计算机学院 11/23/2018
29
9.1避免类重复定义:预编译器指示(4) 使用编译器封装 A_H已经定义 不再编译 // a.h #ifndef A_H
#define A_H class A{ int a; }; #endif // b.h #include "a.h" class B{ double b; // test.cpp #include "b.h" void main(){ //… … } class A{ int a;} class B{ double b;} A_H已经定义 不再编译 11/23/2018 东南大学计算机学院 11/23/2018
30
类作用域 作用域 类作用域 东南大学计算机学院 11/23/2018
31
9.2作用域(1) 作用域分类 全局作用域(Global Scope) 局部作用域(Local Scope)
语句作用域(Statement Scope) 文件作用域(File Scope) 类作用域(Class Scope) 命名空间作用域(Namespace Scope) 东南大学计算机学院 11/23/2018
32
9.2作用域(2) 全局作用域 局部作用域 语句作用域 文件作用域
全局变量具有全局作用域,在一个文件中定义,所有文件可见,其 他文件使用extern再次声明该变量 局部作用域 静态局部变量具有局部作用域,只被初始化一次,在程 序结束前一直存在,但只在定义它的函数体内可见 自动对象具有局部作用域 语句作用域 for语句()内定义的变量具有语句作用域 文件作用域 静态函数具有文件作用域,又称内部函数 静态全局变量具有文件作用域 东南大学计算机学院 11/23/2018
33
9.2类作用域(1) 类成员具有类作用域 非成员函数具有文件作用域 成员函数中定义的变量具有函数作用域 变量隐藏
类作用域内,类成员可以被其他成员访问 类作用域外,public成员通过对象的句柄访问 成员函数只能由该类的成员函数重载(类作用域) 非成员函数具有文件作用域 成员函数中定义的变量具有函数作用域 变量隐藏 当函数作用域的变量将隐藏类作用域的变量 (ClassName::member) 隐藏的全局变量(::var) 东南大学计算机学院 11/23/2018
34
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
35
构造函数 构造函数 构造函数重载 默认实参的构造函数 默认构造函数 函数重载与默认实参 数据成员初始化 Time类实例研究
东南大学计算机学院 11/23/2018
36
9.3 构造函数 创建类的对象时,构造函数初始化对象,保证对象 的数据成员具有有效值的初始值 名字和类名相同
不能指定返回类型(void也不允许) 参数:可以没有,可以有多个 编译器自动调用 class Point{ public: Point(int v){x=v;}; private: int x; }; 东南大学计算机学院 11/23/2018
37
对象初始化的实参类型决定调用哪个构造函数
9.3 构造函数重载 不同的构造函数允许用户以不同的方式初始化数据成员 class Point{ public: Point(){x=0;}; Point(int v){x=v;}; private: int x; }; Point p1; Point p2(3); 对象初始化的实参类型决定调用哪个构造函数 东南大学计算机学院 11/23/2018
38
9.3 默认实参的构造函数 使用默认实参可以减少代码重复 默认实参应在函数名第一次出现时设定 构造函数也可以默认实参
全局函数:函数原型,或无函数原型时的函数头部 类成员函数:类定义 构造函数也可以默认实参 对形参表中的形参直接赋值,形参名可以省略 赋值可以是常量、变量、表达式、调用函数, 还可以是static数据成员 可以为全部/部分的形参指定默认参数,但默认实参必 须从右边开始 void func1(int a, int b=0, int c=0); void func1(int, int =0, int =0); func1(3); //a=3,b=0,c=0 11/23/2018
39
9.3 默认构造函数(1) 不指定实参即可调用的构造函数称为默认构造函数 不带参数的构造函数 或所有参数都有默认值的构造函数
每个类最多只有一个默认构造函数 class Time{ public: Time(); Time(int v=0); //link error, .obj文件中存在两个函数, //连接时无法确定 private: int a; }; Time t(); //define a function, not an object Time t; 东南大学计算机学院 11/23/2018
40
9.3 默认构造函数(2) 编译器与默认构造函数 使用默认构造函数 没有定义构造函数:编译器自动生成一个默认的构造函 数
定义了构造函数:编译器就不再生成默认构造函数 使用默认构造函数 class Time{ public: Time(); dispMessage(); private: int a; }; Time t(); //define a function, not an object t.dispMessage(); //error Time t1; //define an object 11/23/2018
41
9.3 函数重载与默认实参 ? class A{ private: int x, y; public: A(int x1, int y1){
x = x1; y = y1; } A(int x1){ y = 0; }; A(int x1=0, int y1=0){ A a(3); ? error C2668: 'A::A' : ambiguous call to overloaded function 东南大学计算机学院 11/23/2018
42
9.3 数据成员初始化(1) 初始化数据成员可确保数据的有效性,使对象处于 可靠的状态中 四种方式 类内声明时初始化 类外初始化
构造函数初始化表 构造函数内通过赋值初始化 东南大学计算机学院 11/23/2018
43
9.3 数据成员初始化(2) 类内声明时初始化(非常特殊) 类外初始化 静态整型常量: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; 东南大学计算机学院 11/23/2018
44
9.3 数据成员初始化(3) 构造函数初始化表/成员初始化表 (Constructor Initializer List)
只能被构造函数使用 只能在构造函数的定义(而不是声明)中指定 只能对数据成员初始化 Point::Point():x(2),y(1){} Point::Point():y(1){} //可指定部分 Point::Point():y(1),x(2){} //顺序无关 使用成员初始化列表初始化 class Point{ public: Point(); private: int x; int y; }; 用 数 值 初 始 化 Point::Point(){ x=2; y=1; } //在构造函数内通过赋值初始化 构造函数执行: 初始化 函数体 11/23/2018
45
9.3 数据成员初始化(4) 用实参初始化 使用成员初始化列表初始化 初始化可用值或表达式 class Point{ public:
Point(int m=2, int n=1); private: int x; int y; }; Point::Point(int m, int n){ x=m; y=n; } //在构造函数内通过赋值初始化 Point::Point(int m,int n):x(m),y(n){} 使用成员初始化列表初始化 Point::Point(int m,int n):y(n),x(m){} Point::Point(int m,int n):y(m*n){} 初始化可用值或表达式 11/23/2018
46
成员常量和引用成员,只能在成员初始化列表中进行初始化
9.3 数据成员初始化(5) 初始化顺序不是初始化表中顺序决定,而是由成员在类中被声明的顺序决定 class Point{ public: Point(int v):y(v),x(y){}; private: int x; int y; }; 使用尚未初始化的y值初始化x! 初始化VS. 赋值 成员初始化列表是初始化操作 在构造函数内是赋值操作 成员常量和引用成员,只能在成员初始化列表中进行初始化 11/23/2018
47
9.3 数据成员初始化(6) 从数据类型看初始化方式 基本数据类型 一般成员变量:构造函数的成员初始化表或函数体
静态成员常量/变量:类外初始化 若为整型常量(int、bool、char),可在声明时初始化 成员常量和引用成员:只在成员初始化表 基本数据类型 类内声明 类外 成员初始化表 构造函数内 一般成员变量 静态成员变量 非整型静态常量 整型静态常量 成员常量/引用成员 东南大学计算机学院 11/23/2018
48
9.3 数据成员初始化(7) 从数据类型看初始化方式 自定义数据类型(包括标准库中包含的string等类型)
一般成员变量:只在成员初始化表 若自定义数据类型存在缺省构造函数,或没有定义构造 函数而使用编译器提供的默认构造函数,则可以省略 静态成员常量/变量:类外初始化 成员常量和引用成员:只在成员初始化表 自定义数据类型 类内声明 类外 成员初始化表 构造函数内 一般成员变量 静态成员变量/常量 成员常量/引用成员 东南大学计算机学院 11/23/2018
49
9.3 数据成员初始化(8) 数据成员初始化总结表 静态成员可在类外初始化 所有的非静态成员(常量/变量)均可在成员初始化表中
基本数据类型 类内声明 类外 成员初始化表 构造函数内 一般成员变量 静态成员变量 非整型静态常量 整型静态常量 成员常量/引用成员 自定义数据类型 类内声明 类外 成员初始化表 构造函数内 一般成员变量 静态成员变量/常量 成员常量/引用成员 静态成员可在类外初始化 所有的非静态成员(常量/变量)均可在成员初始化表中 整型静态成员常量可在类内声明时初始化 基本数据类型的非静态成员变量还可以在构造函数体内初始化
50
9.3 数据成员初始化(9) 没有显式初始化的成员 使用变量初始化规则进行初始化 内置或复合类型成员 类类型成员 局部作用域:不被初始化
没有显式初始化的成员 使用变量初始化规则进行初始化 内置或复合类型成员 局部作用域:不被初始化 全局作用域:初始化为0 类类型成员 编译器自动调用其默认的构造函数 若无默认构造函数,则编译器失败 东南大学计算机学院 11/23/2018
51
9.3Time类实例研究(1) Fig. 9.8~9.10 Fig. 9.8: Time.h 10 class Time{
12 public: Time( int = 0, int = 0, int = 0 ); // default constructor 14 void setTime( int, int, int ); // set hour, minute, second void setHour( int ); // set hour (after validation) void setMinute( int ); // set minute (after validation) void setSecond( int ); // set second (after validation) 20 int getHour(); // return hour int getMinute(); // return minute int getSecond(); // return second 25 void printUniversal(); // output time in universal-time format void printStandard(); // output time in standard-time format 28 private: int hour; // (24-hour clock format) int minute; // int second; // 32 }; 东南大学计算机学院 11/23/2018
52
1. 如何确保数据的有效性? Fig. 9.9: Time.cpp (part1)
14 Time::Time( int hr, int min, int sec ){ setTime( hr, min, sec ); // validate and set time 17 } 18 21 void Time::setTime( int h, int m, int s ){ setHour( h ); // set private field hour setMinute( m ); // set private field minute setSecond( s ); // set private field second 26 } 27 29 void Time::setHour( int h ){ hour = ( h >= 0 && h < 24 ) ? h : 0; // validate hour 32 } 33 35 void Time::setMinute( int m ){ minute = ( m >= 0 && m < 60 ) ? m : 0; // validate minute 38 } 39 41 void Time::setSecond( int s ){ second = ( s >= 0 && s < 60 ) ? s : 0; // validate second 44 } Fig. 9.9: Time.cpp (part1) 1. 如何确保数据的有效性? 11/23/2018 东南大学计算机学院
53
2. 为什么使用set和get函数,为什么不直接访问数据成员? 如果使用秒来计时,如何改动代码?
Fig. 9.9: Time.cpp (part2) 47 int Time::getHour(){return hour;} 51 53 int Time::getMinute(){return minute;} 57 59 int Time::getSecond(){return second;} 63 64 // print Time in universal-time format (HH:MM:SS) 65 void Time::printUniversal(){ cout <<setfill('0')<<setw(2)<<getHour()<<":" <<setw(2)<<getMinute()<<":"<<setw(2)<<getSecond(); 69 } 70 71 // print Time in standard-time format (HH:MM:SS AM or PM) 72 void Time::printStandard(){ cout<<((getHour()==0||getHour()==12)?12:getHour()%12) <<":"<<setfill('0')<<setw(2)<<getMinute() <<":"<<setw(2)<<getSecond()<<(hour<12?" AM":" PM"); 77 } 2. 为什么使用set和get函数,为什么不直接访问数据成员? 如果使用秒来计时,如何改动代码? 11/23/2018 东南大学计算机学院
54
3. 对t1、t2、t3、t4分别调用printUniversal()和printStandard()函数,输出是什么?
Fig. 9.10: fig09_10.cpp 1 // Fig. 9.10: fig09_10.cpp 2 // Demonstrating a default constructor for class Time. 3 #include <iostream> 4 using std::cout; 5 using std::endl; 6 7 #include "Time.h" // include definition of class Time from Time.h 8 9 int main(){ Time t1; // all arguments defaulted Time t2(2); // hour specified; minute and second defaulted Time t3(21,34); // hour and minute specified; second defaulted Time t4(12,25,42); // hour, minute and second specified Time t5(27,74,99); // all bad values specified ...... return 0; 43 } 3. 对t1、t2、t3、t4分别调用printUniversal()和printStandard()函数,输出是什么? 11/23/2018 东南大学计算机学院
55
析构函数 析构函数 构造函数与析构函数的调用顺序 东南大学计算机学院 11/23/2018
56
9.4析构函数(1) 构造函数创建对象时申请资源, 析构函数撤销对象时释放资源,是构造函数的补充 析构函数的调用
构造函数创建对象时申请资源, 析构函数撤销对象时释放资源,是构造函数的补充 本身不进行资源释放,为操作系统回收资源作准备 析构函数唯一,必须是public的,也不能重载 没有定义析构函数时,编译器自动生成一个“空 的”(empty)析构函数 析构函数的调用 可以由用户显式调用 撤销自动对象时由编译器自动调用 ~ClassName() 变量在超出作用域时应自动撤销 东南大学计算机学院 11/23/2018
57
9.4析构函数(2) 动态分配对象,需先删除指向它的指针,才会调用析构 函数,否则导致内存泄漏
撤销容器(标准库容器/内置数组)时,运行容器中类类型 元素的析构函数 { ClassName *p = new ClassName; delete p; } ~ClassName() { ClassName *p = new ClassName[]; vector<ClassName> vec(p, p+10); … … delete [] p; } 每个元素都调用析构函数, 逆序撤销 东南大学计算机学院 11/23/2018
58
9.4析构函数(3) 合成析构函数 编译器总会对类自动合成一个析构函数,按类成员声明次 序的逆序撤销每个非static成员
类类型成员:调用其析构函数 不删除指针成员指向的对象(造成内存泄漏!) 东南大学计算机学院 11/23/2018
59
9.4构造函数与析构函数的调用顺序(1) 一般而言,析构函数的调用顺序和其相应的构造函数的调用顺序相反,对象的存储类别和作用域能改变顺序
全局对象: 在任何函数(含main)执行前,构造; 在main程序结束时,析构. 局部对象: 自动对象:变量定义时,构造;块结束时,析构. 静态对象:首次定义时,构造;main程序结束时,析构. 全局和静态对象(均为静态存储类别)析构顺序恰好与构造顺序相反. 东南大学计算机学院 11/23/2018
60
9.4构造函数与析构函数的调用顺序(2) 两个特例 exit:输入错误或文件不能打开 不调用剩余自动对象的析构函数 全局和静态对象仍然被析构
abort: 程序异常中止 不调用任何剩余对象的析构函数 An example (Fig.9.11 ~ Fig.9.13) 东南大学计算机学院 11/23/2018
61
1C 2C 3C 5C 6C 7C 7D 5D 4C 4D 2D 6D 3D 1D create main 7 6 全局 栈区 数据区 5
11/23/2018 Fig.13
62
1C 2C 3C 5C 6C 7C 7D 5D 4C 4D 2D 6D 3D 1D exit(0); create main 全局 栈区
数据区 栈区 5 4 2 main 11/23/2018 Fig.13
63
1C 2C 3C 5C 6C 7C 7D 5D 4C 4D 2D 6D 3D 1D abort(); create main 6 全局 栈区
数据区 栈区 3 5 4 1 2 main 11/23/2018 Fig.13
64
Line23~25: create(); create main 1C 2C 3C 5C 6C 7C 7D 5D 4C 4D 2D 6D
全局 数据区 栈区 3 4 5 1 2 main 11/23/2018 Fig.9.13
65
破坏类的封装性:返回私有数据成员的引用 成员函数返回private数据成员的引用,可以 使用户对private数据成员直接访问,破坏封 装性
11/23/2018
66
应避免返回private数据的引用! 私有数据成员 Fig.9.14 ~ Fig.9.16 class Time{ public:
Time(int=0,int=0,int=0); void setTime(int,int,int); int getHour(); int &badSetHour( int ); private: int hour; int minute; int second; }; int main(){ Time t; int &hourRef = t.badSetHour( 20 ); cout << "Valid hour before modification: " << hourRef; hourRef = 30; cout << "\n Invalid hour after modification: " << t.getHour(); // Dangerous: Function call that returns // a reference can be used as an lvalue! t.badSetHour( 12 ) = 74; cout << t.getHour() << endl; return 0; } int &Time::badSetHour( int hh ){ hour=(hh>=0&&hh<24)?hh:0; return hour; } Fig.9.14 ~ Fig.9.16 私有数据成员 应避免返回private数据的引用! 11/23/2018 东南大学计算机学院
67
默认的逐个成员赋值 东南大学计算机学院 11/23/2018
68
9.6 默认的复制控制(1) 赋值运算符(=)将对象赋值给另一个同类型的对象 操作:逐个成员赋值 何时复制对象? 对象作为函数的实参传递时
对象作为返回值时 class Date{ public: Date(int=1,int=1,int=2000); private: int month; int day; int year; }; Date date1(7,4,2004); Date date2; date2 = date1; //date2.month = date1.month; //date2.day = date1.day; //date2.year = date1.year; 对每个类,编译器都提供了一个默认的复制构造函数 东南大学计算机学院 11/23/2018
69
9.6 默认的复制控制(2) 当成员包含指针时,默认的复制构造函数会带来问题 定制复制构造函数! test t1(4), t2(5);
class test{ int *pArray; public: test(int n){ pArray = new int[n]; } ~test(){ delete [ ] pArray; }; test t1(4), t2(5); t2 = t1; t1.pArray t2.pArray 定制复制构造函数! 东南大学计算机学院 11/23/2018
70
作业 deadline: 4.5 24:00 Ex.9.7 Ex.9.8 Ex.9.9 Ex.9.11
东南大学计算机学院 11/23/2018
Similar presentations