Download presentation
Presentation is loading. Please wait.
Published byΞένη Αλεξόπουλος Modified 6年之前
1
刘胥影 liuxy@seu.edu.cn http://cse.seu.edu.cn/people/xyliu/ 东南大学计算机学院
面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院
2
Classes: A Deeper Look (I)
3
知识要点 预编译器指示(要点) 类成员(基础) 访问控制(基础) 句柄和对象(基础) 对象大小(要点) 类作用域和成员访问(要点)
为什么需要? 怎么用? 类成员(基础) 访问控制(基础) 句柄和对象(基础) 对象大小(要点) 类作用域和成员访问(要点) 接口和实现相分离(了解) 东南大学计算机学院 11/24/2018
4
知识要点 构造函数、带默认参数的构造函数、默认的构造 函数(要点) 析构函数(要点)
在创建对象和撤销对象时,构造函数和析构函数 的调用顺序(要点) 避免返回指向private数据成员的指针(要点) 默认的逐个成员赋值(了解) 东南大学计算机学院 11/24/2018
5
Time类实例研究 预编译器指示 类成员 访问控制 对象大小 this指针 成员函数 东南大学计算机学院 11/24/2018
6
预编译器封装 11/24/2018 东南大学计算机学院
7
避免类重复定义:预编译器指示(1) 如何避免类重复定义 关于类定义 Rule:类只能被定义一次 定义多次导致 “类型重定义”编译错误
Fact:类通常定义在头文件中 大型程序具有多个头文件,因此会发生企图多次包含同一头 文件的情况 error! // a.h class A{ int a; }; // b.h #include "a.h" class B{ double b; A item; // test.cpp #include "b.h" void main(){ //… … } class A{ int a;} class B{ double b;} 如何避免类重复定义 11/24/2018 东南大学计算机学院
8
避免类重复定义:预编译器指示(2) 预编译器封装(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/24/2018 东南大学计算机学院
9
避免类重复定义:预编译器指示(3) 使用预编译器封装 A_H已经定义 不再编译 // a.h #ifndef A_H #define A_H
class A{ int a; }; #endif // b.h #include "a.h" class B{ double b; A item; // test.cpp #include "b.h" void main(){ //… … } class A{ int a;} class B{ double b; A item;} A_H已经定义 不再编译 11/24/2018 11/24/2018 东南大学计算机学院
10
11/24/2018 东南大学计算机学院
11
类成员 类成员组成 数据成员(data member): 属性 成员函数(member function):功能
可以有多个成员,也可以没有成员 11/24/2018
12
访问控制(1) 访问控制 public:使用该类型的所有代码 private:本类的其他类成员/友元 protected:子类/友元
private权限:private成员 本类成员/友元 protected权限:protected成员 子类/友元类 public权限:public成员 东南大学计算机学院 11/24/2018
13
访问控制(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/24/2018
14
东南大学计算机学院 11/24/2018
15
构造函数对数据成员进行初始化,使其具有有效的初始值。可以有多个(重载的构造函数),分别声明和定义
要注意数据的有效性 11/24/2018 东南大学计算机学院
16
setw(): 设定显示宽度,不具有粘滞性,作用范围为下个变量/对象
setfill(): 设定填充字符,具有粘滞性,作用范围为整个cout语句 (请参考教材) 11/24/2018 东南大学计算机学院
17
成员函数(1) 成员(函数)必须在类内声明 实现的两种方式 类内定义 类外定义 通常采用的方式! 指明成员函数的类 class Test{
public: void displayMessage() { /* code */ }; private: int num; }; class Test{ public: void displayMessage(); private: int num; }; void Test::displayMessage(){ /* code */ } 指明成员函数的类 11/24/2018 东南大学计算机学院
18
如有必要,仅在类内实现最简单和最稳定的成员函数
成员函数(2) 类内定义 VS. 类外定义 相同点: 可见性相同:类外定义不改变其可见性(类作用域) 不同点: 编译方式可能不同 类内定义:编译器尝试进行inline调用(可提高效率) 类外定义:当显式指定为inline时,编译器尝试进行inline调用 内部定义破坏了封装性,用户可以看见其实现,软件复用性不好(后面还会强调) inline是非强制的,编译器保留不对任何函数inline调用的权利,行为依赖于具体的编译器(了解) 建议:在类外实现成员函数 如有必要,仅在类内实现最简单和最稳定的成员函数 11/24/2018
19
成员函数(3) 成员函数重载(尤其针对构造函数) 只能由该类的其他成员函数重载 在类中声明各个版本 提供独立的函数定义
成员函数重载(尤其针对构造函数) 只能由该类的其他成员函数重载 在类中声明各个版本 提供独立的函数定义 析构函数只有一个,不能重载 (本章后面会将析构函数) 11/24/2018 东南大学计算机学院
20
成员函数(4) 成员函数的隐形参数– this指针 (了解) this指针是成员函数的隐含形参,用来指向该对象的指针(const指针类型)
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/24/2018 东南大学计算机学院
21
类对象(1) 类定义了一种新的数据类型 创建对象 对象的存储 编译器根据类类型为对象分配存储空间,对象名指向存储空间
ClassName item; 编译器根据类类型为对象分配存储空间,对象名指向存储空间 对象的存储 数据成员:每个对象都要存储自己的数据 成员函数:所有对象共享一份代码copy 静态变量:为所有对象共享,是类的一部分,不是对 象的一部分,存放全局区(了解,第10章中详细讲) 堆和栈的访问? 栈:快,临时 堆:慢, 内存专题 什么是自动变量 11/24/2018 东南大学计算机学院
22
类对象(2) 对象的大小 sizeof() 不计算static数据成员(了解,第10章中详细讲) 每个语句分配的变量存储区至少为4个字节
按语句将各个变量分配的存储区大小进行累加 class test1{ int num; //4B }; class test2{ char c; //max(1B,4B) class test3{ char c, t; //4B class test4{ char s; //4B int num; //4B char c, t; //4B }; class test5{ static char s;//omit 假设每个int型变量占4B,char型变量占1B 11/24/2018
23
类作用域和类成员的访问 句柄和对象 类作用域 全局作用域/语句块作用域/类作用域 变量的可见性和隐藏性,访问被隐藏的变量 类成员的访问
东南大学计算机学院 11/24/2018
24
句柄和对象(1) 类的使用 – 通过对象 Time sunset; // 创建Time类的对象
Time arrayOfTimes[5]; //建立Time类对象的数组 Time &dinnerTime = sunset; //对象sunset的引用(别名) Time *timeptr1 = &dinnerTime;//指向对象的指针 Time *timeptr2 = &sunset;//指向对象的指针 11/24/2018 东南大学计算机学院
25
句柄和对象(2) 句柄:对象名、引用、指针 操作符 访问形式 圆点成员选择符(.) 箭头成员选择符(->) 对象名 + (.)
引用 + (.) 指针 + (->) (*指针) + (.):必须使用(*指针) 11/24/2018 东南大学计算机学院
26
句柄和对象(2) An example // 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/24/2018
27
11/24/2018 东南大学计算机学院
28
11/24/2018 东南大学计算机学院
29
类作用域(1) 作用域分类 全局作用域(Global Scope) 局部作用域(Local Scope)
语句作用域(Statement Scope) 文件作用域(File Scope) 类作用域(Class Scope) 命名空间作用域(Namespace Scope) 东南大学计算机学院 11/24/2018
30
类作用域(2) 类作用域 用户代码 类 在类内,类成员可以直接被类的所有成员访问(可以利 用引用)-- 类作用域
在类外,public成员通过对象的句柄(handle)使用 类设计者 类用户 Public 成员 Protected Private 用户代码 类 东南大学计算机学院 11/24/2018
31
在类内,同类的其他对象的成员也可直接访问
class Complex { public: Complex( double, double); // default constructor Complex add( const Complex & ); // function add private: double realPart; double imaginaryPart; }; Complex Complex::add( const Complex &right ) return Complex( realPart + right.realPart, imaginaryPart + right.imaginaryPart ); } 举例说明同类的其他对象的成员之间也可见 在类内,同类的其他对象的成员也可直接访问 11/24/2018 东南大学计算机学院
32
类作用域(2) 全局作用域 局部作用域 语句作用域 文件作用域
全局变量具有全局作用域,在一个文件中定义,所有文件可见,其 他文件使用extern再次声明该变量 局部作用域 静态局部变量具有局部作用域,只被初始化一次,在程 序结束前一直存在,但只在定义它的函数体内可见 自动对象具有局部作用域 语句作用域 for语句()内定义的变量具有语句作用域 文件作用域 静态函数具有文件作用域,又称内部函数 静态全局变量具有文件作用域 东南大学计算机学院 11/24/2018
33
类作用域(3) 类成员具有类作用域 非成员函数具有文件作用域 成员函数中定义的变量具有函数作用域 变量隐藏
类作用域内,类成员可以被其他成员访问 类作用域外,public成员通过对象的句柄访问 成员函数只能由该类的成员函数重载(类作用域) 非成员函数具有文件作用域 成员函数中定义的变量具有函数作用域 变量隐藏 当函数作用域的变量隐藏类作用域的变量时,访问类成 员:ClassName::member 访问隐藏的全局变量 ::var 东南大学计算机学院 11/24/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; File Scope Block Scope Time::a 访问隐藏的类的成员 ::a 访问隐藏的全局变量 this 指针指向当前对象 Class Scope
35
接口实现与分离 数据抽象 数据封装 接口与实现分离 东南大学计算机学院 11/24/2018
36
数据抽象与封装(1) 类的基本思想是数据抽象和封装 数据抽象:一种依赖于接口(Interface)和实现分离的编程技术
封装:一种将低层次的元素组合起来形成新的高层 次实体的技术,被封装的元素隐藏了其实现细节 函数 类 关心类的实现细节 类设计者 不关心类的实现细节 关心类的接口(类定义 ) 类使用者(类用户) 11/24/2018 东南大学计算机学院
37
数据抽象与封装(2) 数据抽象和封装的优点 类的抽象和封装 避免类内部出现无意的、可能破坏对象状态的用户级错误
更改或完善类的实现时,无需改变用户级代码 类的抽象和封装 类的public成员构成抽象视图 private封装类的实现细节 11/24/2018 东南大学计算机学院
38
#include “ClassName.cpp”
接口实现与分离(1) ClassName.cpp ClassName.h 类实现 (不包含inline函数) 类声明: public成员 private成员 inline函数 code 实现 接口 #include “ClassName.cpp” 如果头文件中包含部分类实现,那么必须是inline函数 定义在内类则为隐式的inline函数 定义在类外必须显式声明inline 实现必须包含相应接口的头文件 代理类(Proxy Class) 可以进一步隐藏类的private数据成员(有兴趣的自己了解) 11/24/2018 东南大学计算机学院
39
接口实现与分离(2) 类实现 (成员函数定义) 类定义 (接口) 类用户代码 不用提供源代码,实现知识产权保护
设 计 者 A.h A.cpp Main.cpp A.obj Main.obj 编译 EXE 链接 类实现 (成员函数定义) 类定义 (接口) 类用户代码 不用提供源代码,实现知识产权保护 更改类实现时,类用户不必更改代码和重新编译,只需重新连接,增强软件可复用性 11/24/2018 东南大学计算机学院
40
访问函数和工具函数 访问函数 工具函数 东南大学计算机学院 11/24/2018
41
访问函数和工具函数(1) 访问函数 工具函数(utility functions/helper function)
读取或显示数据 测试条件的真假(又称为判定函数) 工具函数(utility functions/helper function) 类的private成员函数 辅助其他成员函数 类用户不能使用(不需要知道实现细节) 友元类可以使用 有利于实现封装,增强软件的可复用性 11/24/2018 东南大学计算机学院
42
void getSalesFromUser(); void setSales( int, double );
// Fig. 9.5: SalesPerson.h … … class SalesPerson{ public: SalesPerson(); void getSalesFromUser(); void setSales( int, double ); void printAnnualSales(); private: double totalAnnualSales(); double sales[ 12 ]; }; //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/24/2018
43
默认实参的构造函数 构造函数 默认参数的函数 默认参数的构造函数 默认的构造函数 东南大学计算机学院 11/24/2018
44
构造函数 构造函数的作用 创建类的对象时,构造函数初始化对象,保证对象的数据成员具有有效值的初始值 名字和类名相同
参数:可以没有,可以有多个 不能指定返回类型(void也不允许) 因为构造函数只是进行数据的初始化工作,无需返回任何值,因此,也无所谓返回值类型 必须为public(why?) 编译器在创建对象时自动调用 class Point{ public: Point(int v){x=v;}; private: int x; }; 11/24/2018 东南大学计算机学院
45
11/24/2018 东南大学计算机学院
46
构造函数使数据有有效的初始值 11/24/2018 东南大学计算机学院
47
默认实参的构造函数(1) = + 默认实参的函数 应在函数名第一次出现时设定: 函数原型(声明时) 或无函数原型时的函数头部
Func(int a) { x=a; y=0; }; 使用默认实参可以减少代码重复 Func(int a, int b = 0) { x=a; y=b; }; = + Func(int a, int b) { x=a; y=b; }; 11/24/2018
48
默认实参的构造函数(2) 构造函数也可以默认实参 设定方式:类定义(成员函数声明) 对形参表中的形参直接赋值,形参名可以省略
赋值可以是常量、变量、表达式、调用函数, 还可以是static数据成员 可以为全部/部分的形参指定默认参数,但默认实参必须从右边开始 当默认值改变时,需要重新编译(思考why?) 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 func1(3,1);//a=3,b=1,c=0 func1(3,1,4);//a=3,b=1,c=4 默认实参在类定义中设定,改变默认实参就改变了头文件,所以要重新编译 11/24/2018
49
默认构造函数(1) 默认构造函数 不指定实参即可调用的构造函数 不带参数的构造函数 或所有参数都有默认值的构造函数
每个类最多只有一个默认构造函数 class Time{ public: Time(); Time(int v=0); //link error, .obj文件中存在两个函数, //连接时无法确定 private: int a; }; 11/24/2018 东南大学计算机学院
50
默认的构造函数(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/24/2018
51
构造函数:public, 与类名同名,没有返回值类型 构造函数的默认参数 设定方式:类定义(成员函数声明)
对形参表中的形参直接赋值,形参名可以省略 11/24/2018 东南大学计算机学院
52
11/24/2018 东南大学计算机学院
53
11/24/2018 东南大学计算机学院
54
11/24/2018 东南大学计算机学院
55
11/24/2018 东南大学计算机学院
56
思考 为什么不直接,而是通过调用setTime()来初始化数据成员? 通过调用setTime(), 避免代码重复,利于维护
void setTime(int h, int m, int s) { hour=(h>=0 && h<24)?h:0; minute=(m>=0 && m<24)?m:0; second=(s>=0 && s<24)?s:0; } void setHour(int h) {hour=(h>=0 && h<24)?h:0;} void setMinute(int m) {minute=(m>=0 && m<24)?m:0; void setSecond(int s) {second=(m>=0 && m<24)?s:0;} 代码重复 通过调用setTime(), 避免代码重复,利于维护 假设需要修改代码,则要改多处 11/24/2018 东南大学计算机学院
57
11/24/2018 东南大学计算机学院
58
思考 为什么不直接,而是通过调用get函数来访问数据成员?
当时间以00:00:00开始的总秒数表示 (例如01:00:00用总秒数表示为3600秒) 在直接访问数据成员/通过get函数访问数据成员 两种方式下有何异同? 哪种更好?为什么? (周日上机前做好,带着程序去上机) 11/24/2018 东南大学计算机学院
59
11/24/2018 东南大学计算机学院
60
11/24/2018 东南大学计算机学院
61
11/24/2018 东南大学计算机学院
62
析构函数 析构函数 析构函数的调用 东南大学计算机学院 11/24/2018
63
析构函数 构造函数创建对象时申请资源, 析构函数撤销对象时释放资源,是构造函数的补充 本身不进行资源释放,为操作系统回收资源作准备
析构函数唯一,必须是public的,也不能重载 不接受参数,也没有返回值类型 没有定义析构函数时,编译器自动生成一个“空的”(empty)析构函数 ~ClassName() 11/24/2018 东南大学计算机学院
64
析构函数的调用 显示调用:可以由用户显式调用 隐式调用:撤销自动对象时由编译器自动调用 变量在超出作用域时应自动撤销 构造函数 Time()
{ Time t; …… ~Time(); } { Time t; …… } 隐式调用: 析构函数 ~Time() 构造函数 Time() 显示调用: 析构函数 ~Time() 11/24/2018 东南大学计算机学院
65
作业 为什么不直接,而是通过调用get函数来访问数据成员? 当时间以00:00:00开始的总秒数表示
哪种更好?为什么? (周日上机前做好,带着程序去上机) 上机时间地点: 每周日晚8:00~10:00 计算机楼237 11/24/2018 东南大学计算机学院
66
回顾(1) 实现与接口分离 在C++中,实现和接口分离是如何实现的? 对于程序员和用户来说,分别需要什么文件,以及如何生成可执行文件?
成员函数在ClassName.cpp中实现,接口即类定义,在ClassName.h中实现 11/24/2018 东南大学计算机学院
67
回顾(2) 为什么能实现知识产权保护? 为什么能增强软件的可复用性? 类实现 (成员函数定义) 类定义 (接口) 类用户代码 不用提供源代码
设 计 者 A.h A.cpp Main.cpp A.obj Main.obj 编译 EXE 链接 类实现 (成员函数定义) 类定义 (接口) 类用户代码 类 用 户 不用提供源代码 更改类实现时,类用户不必更改代码和重新编译,只需重新连接 11/24/2018 东南大学计算机学院
68
回顾(3) 访问函数和工具函数 默认实参的构造函数 访问函数:set/get/条件测试函数
工具函数:private函数,辅助其他成员函数的实现 默认实参的构造函数 默认值在哪里设置? 当有多个参数时,默认参数在左边还是右边开始设置 在类定义中(函数第一次出现的地方) 右边 11/24/2018 东南大学计算机学院
69
回顾(4) 默认实参的构造函数 什么是默认的构造函数? 下面哪个是默认的构造函数? (b) (a) (c) 可以不指定实参调用的构造函数
Class Time { Public: Time(); } Class Time { Public: Time(int =0, int =0); } (b) (a) Class Time { Public: Time(int, int =0); } (c) (a)(b) 11/24/2018 东南大学计算机学院
70
X 回顾(5) 默认实参的构造函数 一个类可以有几个默认的构造函数? Link error Class Time { Public:
Time(int =0); } X Link error Time t; 11/24/2018 东南大学计算机学院
71
构造函数和析构函数的调用顺序 东南大学计算机学院 11/24/2018
72
构造函数和析构函数(1) 功能上相互补足 相互对应 构造函数 析构函数 功能 创建对象, 初始化对象 撤销对象, 为系统回收内存资源做准备
何时调用 对象生成时 对象撤销时 功能上相互补足 11/24/2018 东南大学计算机学院
73
构造函数和析构函数(2) 定义与使用上的异同 相互对应 构造函数 析构函数 Time() 函数名固定 ~Time() 函数名固定 函数名
参数 可以有 不能有 返回值 不能有任何返回值类型(包括void) 不能有任何返回值类型(包括void) 访问属性 public public 调用方式 由编译器自动调用 由编译器自动调用 可否重载 可以 不能 11/24/2018 东南大学计算机学院
74
构造函数和析构函数(3) 隐式调用 构造函数 隐式调用: 析构函数 Time() ~Time() { Time t; …… }
11/24/2018 东南大学计算机学院
75
构造函数与析构函数的调用顺序(1) 一般而言,析构函数的调用顺序和其相应的构造函数的调用顺序相反,对象的存储类别和作用域能改变顺序 t3
1. Time() { Time t1; Time t2; Time t3; } 2. Time() t3 3. Time() t2 t1 4. ~Time() 5. ~Time() 栈(Stack):先入后出 6. ~Time() 11/24/2018 东南大学计算机学院
76
对象类型及存储方式(1) 静态对象 全局对象 自动对象 在同一个存储区: 析构函数的调用顺序和其相应的构造函数的调用顺序相反
静态存储区 动态存储区 静态对象 全局对象 自动对象 栈 栈 在同一个存储区: 析构函数的调用顺序和其相应的构造函数的调用顺序相反 对象类型决定了在哪个存储区进行创建/撤销操作 11/24/2018 东南大学计算机学院
77
对象类型及存储方式(2) 局部对象 全局对象 静态对象 自动对象 构造:任何函数(含main)执行前 构造:首次定义时
构造:变量定义 析构:块结束时 静态存储区 动态存储区 静态对象的构造函数只被调用一次 11/24/2018 东南大学计算机学院
78
构造函数与析构函数的调用顺序(2) 两个特例(特殊语句影响调用顺序)
exit:输入错误或文件不能打开 不调用剩余自动对象的析构函数 全局和静态对象(静态存储区)仍然被析构 abort: 迫使程序立即终止(异常) 不调用任何剩余对象的析构函数 在main函数中 return 0 相当于 exit(0) 11/24/2018 东南大学计算机学院
79
11/24/2018 东南大学计算机学院
80
11/24/2018 东南大学计算机学院
81
执行序列 1C 1 静态存储区 动态存储区
82
11/24/2018 东南大学计算机学院
83
执行序列 1C 2C 1 静态存储区 2 动态存储区
84
11/24/2018 东南大学计算机学院
85
执行序列 1C 2C 3 3C 1 静态存储区 2 动态存储区
86
11/24/2018 东南大学计算机学院
87
执行序列 1C 2C 3 3C 1 5C 静态存储区 5 2 动态存储区
88
11/24/2018 东南大学计算机学院
89
执行序列 1C 6 2C 3 3C 1 5C 6C 静态存储区 5 2 动态存储区
90
11/24/2018 东南大学计算机学院
91
执行序列 1C 6 2C 3 3C 1 5C 6C 静态存储区 7C 7 5 2 动态存储区
92
11/24/2018 东南大学计算机学院
93
执行序列 1C 6 2C 3 3C 1 5C 6C 静态存储区 7C 7D 5D 7 5 2 动态存储区
94
11/24/2018 东南大学计算机学院
95
执行序列 1C 6 2C 3 3C 1 5C 6C 静态存储区 7C 7D 5D 4C 4 2 动态存储区
96
11/24/2018 东南大学计算机学院
97
执行序列 1C 2C 3C 6 5C 3 6C 1 7C 静态存储区 7D 5D 4C 4D 2D 4 6D 2 3D 动态存储区 1D
98
11/24/2018 东南大学计算机学院
99
1C 2C 3C 6 5C 3 6C 1 7C return 0; //相当于exit(0) 7D 5D 4C 6D 3D 4 1D 2
执行序列 1C 2C 3C 6 5C 3 6C 1 7C return 0; //相当于exit(0) 静态存储区 7D exit: 不调用剩余自动对象的析构函数 全局和静态对象(静态存储区)仍然被析构 } 5D 4C 6D 3D 4 1D 2 动态存储区
100
abort: 程序异常中止 不调用任何剩余对象的析构函数 5D
执行序列 1C 2C 3C 6 5C 3 6C 1 7C 静态存储区 abort(); 7D abort: 程序异常中止 不调用任何剩余对象的析构函数 } 5D 4C 4 2 动态存储区
101
静态对象的构造函数只被调用一次 create(); 执行序列 1C 2C 3C 6 5C 3 6C 1 7C 静态存储区 7D
abort(); 7D } 5D 静态对象的构造函数只被调用一次 4C 7 5C 5 7C 4 7D 2 5D 动态存储区
102
Time类实例研究: 微妙的陷阱—返回Private数据成员引用
东南大学计算机学院 11/24/2018
103
传递引用(1) 传值 VS. 传引用 返回引用可以作为左值(可以被赋值) 返回值 返回引用 num = 10 int num;
int func() { return num; //返回num的当前值 } int main() num=1; f = func(); //ok,可以做右值 func() = 10; //error, 不能做左值 int num; int &func() { return num; //返回num本身 } int main() num=1; f = func(); //ok,可以做右值 func() = 10; //ok, 可以做左值 num = 10 返回引用可以作为左值(可以被赋值) 东南大学计算机学院 11/24/2018
104
传递引用(2) 传值 VS. 传引用 注意区别,何时适用! 传值:right是实参的一个拷贝 传引用:right是实参本身,可以被改变
Complex Complex::add(Complex right) 传引用:right是实参本身,可以被改变 Complex Complex::add(Complex &right) 传const引用:right是实参本身,而且不能被改变 Complex Complex::add(const Complex &right) 注意区别,何时适用! 东南大学计算机学院 11/24/2018
105
若返回private数据成员的引用会如何?
思考: 若返回private数据成员的引用会如何? 11/24/2018 东南大学计算机学院
106
应避免返回private数据的引用,否则破坏类的封装性!
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; } 20 30 int &Time::badSetHour( int hh ){ hour=(hh>=0&&hh<24)?hh:0; return hour; } 12->74 Fig.9.14 ~ Fig.9.16 私有数据成员 应避免返回private数据的引用,否则破坏类的封装性! 11/24/2018 东南大学计算机学院
107
默认的逐个成员赋值 东南大学计算机学院 11/24/2018
108
赋值运算符 ? 普通变量: 对象之间也可以通过”=”进行赋值 int a = 1; int b = a; //将a的值赋给b
Time t1; Time t2 = t1; ? 东南大学计算机学院 11/24/2018
109
默认的逐个成员赋值(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/24/2018
110
默认的逐个成员赋值(2) 何时进行默认的逐个成员赋值 何时拷贝对象? 对象间进行赋值运算(=) 拷贝对象 对象作为函数的实参按值传递时
对象作为返回值时 VS.传递和返回引用?(不是副本,而是对象本身) 对每个类,编译器都提供了一个默认的拷贝构造函数,由拷贝构造函数来创建对象的副本或进行赋值运算 东南大学计算机学院 11/24/2018
111
默认的逐个成员赋值(3) 当成员包含指针时,默认的拷贝构造函数会带来问题 内存泄露! 定制拷贝构造函数!(第11章)
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章) 东南大学计算机学院 11/24/2018
112
知识要点 预编译器指示(要点) 类成员(基础) 访问控制(基础) 句柄和对象(基础) 对象大小(要点) 类作用域和成员访问(要点)
接口和实现相分离(了解) 东南大学计算机学院 11/24/2018
113
知识要点 构造函数、带默认参数的构造函数、默认的构造 函数(要点) 析构函数(要点)
在创建对象和撤销对象时,构造函数和析构函数 的调用顺序(要点) 避免返回指向private数据成员的指针(要点) 默认的逐个成员赋值(了解) 东南大学计算机学院 11/24/2018
114
End of Chap 9 东南大学计算机学院 11/24/2018
Similar presentations