刘胥影 liuxy@seu.edu.cn 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 liuxy@seu.edu.cn 东南大学计算机学院.

Slides:



Advertisements
Similar presentations
C++语言程序设计教程 第5章 构造数据类型 第6章 C++程序的结构.
Advertisements

移动应用软件开发技术 第二讲:C++编程基础
第 2 章 初探 C++.
Memory Pool ACM Yanqing Peng.
第一章 C语言概述 计算机公共教学部.
第九讲 类与对象 (I)面向对象基础.
第4章 数组 数组是由一定数目的同类元素顺序排列而成的结构类型数据 一个数组在内存占有一片连续的存储区域 数组名是存储空间的首地址
第八章 类和对象.
C++程序设计 王希 图书馆三楼办公室.
C++语言程序设计教程 第7章 类与对象 第7章 类与对象.
走向C++之路 WindyWinter WindyWinter感谢诸位前来捧场。
Chapter 1 用VC++撰寫程式 Text book: Ivor Horton.
資料大樓 --談指標與陣列 綠園.
C++语言程序设计 第四章 类与对象 成都信息工程学院计算机系.
Chap 18 類別與物件 夫有土者,有大物也。有大物者,不可以物。 物而不物,故能物物。 明乎物物者之非物也,豈獨治天下百姓而已哉!
4.1 概述 4.2 类与对象的实现 4.3 对象的初始化和析构 4.4 类的包含 4.5 类模板
Derived Class 前言 衍生類別的定義 單一繼承 public, protected, 和 privated 基底類別
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
CHAPTER 9 建構方法 ROBERT.
Scope & Lifetime 前言 Local Scope Global Functions & Objects
刘胥影 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院.
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
Classes: A Deeper Look, Part 1
Chapter 7: 再論類別 目標 能夠動態地建構和解構物件 能夠指定 const(常數)物件和和 const 成員函式
刘胥影 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院.
Object-Oriented Programming:
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
Function.
Object-Oriented Programming in C++ 第一章 C++的初步知识
程序设计期末复习 黎金宁
第三章 C++中的C 面向对象程序设计(C++).
前處理指令可以要求前處理器 (preprocessor) 在程式編譯之前,先進行加入其它檔案的內容、文字取代以及選擇性編譯等工作。
第 6 章 函式.
2 C++ 的基本語法和使用環境 親自撰寫和執行程式是學好程式語言的不二法門。本章藉由兩個簡單的程式,介紹C++ 程式的基本結構和開發環境,讓初學者能逐漸建立使用C++ 的信心。
C++语言程序设计教程 第7章 类与对象 第7章 类与对象.
第九單元 Classes and data abstraction I
类类型 C++支持的内置类型和操作,如 int i=10; i=i%6; i=i+4;
谭浩强 编著 中国高等院校计算机基础教育课程体系规划教材 C++程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
切換Dev c++顯示語言 工具->環境選項(V)->介面->language (Chinese TW)
Classes (2) Lecture 7.
C++大学基础教程 第11章 多态性 北京科技大学 信息基础科学系 2019/4/8 北京科技大学.
Chapter 2 & Chapter 3.
Speaker: Liu Yu-Jiun Date: 2009/4/29
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
第10讲 构造函数和析构函数 构造函数 析构函数 This 指针.
C++复习2----类与对象.
Oop8 function函式.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
Speaker: Liu Yu-Jiun Date: 2009/5/6
第11章 從C到C++語言 11-1 C++語言的基礎 11-2 C++語言的資料型態與運算子 11-3 C++語言的輸出與輸入
Inheritance -II.
第三章 数据抽象.
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
面向对象技术 练习 ffh.
C/C++基礎程式設計班 C++: 物件的使用、參考、重載函式 講師:林業峻 CSIE, NTU 3/28, 2015.
第 9 章 建構函式與解構函式.
授课老师:龚涛 信息科学与技术学院 2016年3月 教材:《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
第 3 章 类的基础部分 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
挑戰C++程式語言 ──第9章 函數.
#include <iostream.h>
第 5 章 继承、多态和虚函数 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
C++语言程序设计 第十章 C++标准模板库 成都信息工程学院计算机系.
JAVA 程式設計與資料結構 第三章 物件的設計.
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
變數與資料型態  綠園.
資料結構與C++程式設計進階 C++與資料結構 講師:林業峻 CSIE, NTU 7/ 5, 2010.
Presentation transcript:

刘胥影 liuxy@seu.edu.cn 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 liuxy@seu.edu.cn 东南大学计算机学院

Classes: A Deeper Look (I) 教学要求与安排 chap9.1~9.13全部掌握 教学安排 chap9.11自学 chap9.13放在chap13中与chap13.10一起讲 东南大学计算机学院 11/23/2018

Classes: A Deeper Look (I) 9.1 类的定义与声明 9.2 类作用域 9.3 构造函数 9.4 析构函数 9.5 破坏类的封装性:返回私有数据成员的 引用 9.6 默认的复制控制 11/23/2018

类的定义和声明 类成员 类定义 类声明 类对象 this指针 成员函数 数据抽象和封装 避免类重复定义:预编译器指示 东南大学计算机学院 11/23/2018

9.1类成员(1) 组成 访问控制 数据成员(data member) 成员函数(member function) 类型别名typedef 可以有多个成员,也可以没有成员 访问控制 public:使用该类型的所有代码 private:本类的其他类成员、友元类(后面进行介绍) protected:子类(后面进行介绍) private权限:private成员 本类成员 protected权限:protected成员 友元类 public权限:public成员 11/23/2018

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

9.1类定义 一旦定义了类,就确定了类成员和该类对象所需的 存储空间 所有成员必须在类内声明,定义完成后不可再添加成员 类定义后的分号不可丢 (why?类定义了一种新的数据类型) varType varName; ClassName object; class Test{ public: void displayMessage(); private: int num; }; class ClassName{/* */}; class ClassName{/* */} object; 东南大学计算机学院 11/23/2018

9.1类声明 类声明(前向声明) 类不能包含具有自身类型的数据成员 是一个不完全类型(incomplete type), 不能定义该类的对象 创建类对象前必须完成类定义 只能用于: 指向该类型的指针和引用 使用该类型的形参类型或返回类型的函数声明(非定义!) 类不能包含具有自身类型的数据成员 两个类也不能通过包含类类型的成员来相互依赖定义(A包 含B型的成员,B包含A型的成员) 但数据成员可以为指向类型的指针 class ClassName; 东南大学计算机学院 11/23/2018

9.1类对象(1) 类定义了一种新的数据类型 对象的存储 创建对象 编译器根据类类型为对象分配存储空间,对象名指向存储空间 ClassName item; 编译器根据类类型为对象分配存储空间,对象名指向存储空间 对象的存储  数据成员:每个对象都要存储自己的数据  成员函数:所有对象共享一份代码copy  静态变量:为所有对象共享,是类的一部分,不是对象 的一部分,存放在堆区 堆和栈的访问? 栈:快,临时 堆:慢, 内存专题 什么是自动变量 东南大学计算机学院 11/23/2018

9.1类对象(2) 变量的存储:stack or heap? 自动变量:栈区 静态变量:堆区 动态变量:堆区 全局变量:堆区 Stack: 东南大学计算机学院 11/23/2018

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 4 8 8 12 4 11/23/2018

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

9.1类对象(5) 句柄:对象名、引用、指针 操作符 访问形式 圆点成员选择符(.) 箭头成员选择符(->) 对象名 + (.) 引用 + (.) 指针 + (->) (*指针) + (.):必须使用(*指针) 东南大学计算机学院 11/23/2018

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

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

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

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

如有必要,仅在类内实现最简单和最稳定的成员函数 9.1成员函数(2) 类内定义 VS. 类外定义 相同点: 可见性相同:类外定义不改变其可见性 不同点: 编译方式可能不同 类内定义:编译器尝试进行inline调用(可提高效率) 类外定义:当显式指定为inline时,编译器尝试进行inline 调用 内部定义破坏了封装性,用户可以看见其实现,软件复 用性不好(后面还会强调) Inline是非强制的,编译器保留不对任何函数inline调用的权利,行为依赖于具体的编译器 建议:在类外实现成员函数 如有必要,仅在类内实现最简单和最稳定的成员函数 11/23/2018

9.1成员函数(3) 访问函数 工具函数(utility functions/helper function) 读取或显示数据 测试条件的真假(又称为判定函数) 工具函数(utility functions/helper function) 类的private成员函数 辅助其他成员函数 类用户不能使用(不需要知道实现细节) 友元类可以使用 有利于实现封装,增强软件的可复用性 东南大学计算机学院 11/23/2018

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

9.1成员函数(5) const成员函数:不能改变其所操作的对象的数据 成员 可避免对数据成员的误操作 成员函数重载 只能由该类的其他成员函数重载 在类中声明各个版本 提供独立的函数定义 double avg_price() const 东南大学计算机学院 11/23/2018

9.1数据抽象和封装(1) 类的基本思想是数据抽象和封装 数据抽象:一种依赖于接口(Interface)和实现分离的编程 技术 封装:一种将低层次的元素组合起来形成新的高层次实 体的技术,被封装的元素隐藏了其实现细节 函数 类 关心类的实现细节 类设计者 不关心类的实现细节 关心类的接口(类定义 ) 类使用者(类用户) 东南大学计算机学院 11/23/2018

9.1数据抽象和封装(2) 数据抽象和封装的优点 类的抽象和封装 避免类内部出现无意的、可能破坏对象状态的用户级错 误 更改或完善类的实现时,无需改变用户级代码 类的抽象和封装 类的public成员构成抽象视图 private封装类的实现细节 东南大学计算机学院 11/23/2018

#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

9.1数据抽象和封装(4) 类实现 (成员函数定义) 类定义 (接口) 类用户代码 不用提供源代码,实现知识产权保护 设 计 者 A.h A.cpp Main.cpp A.obj Main.obj 编译 EXE 链接 类实现 (成员函数定义) 类定义 (接口) 类用户代码 不用提供源代码,实现知识产权保护 更改类实现时,类用户不必更改代码和重新编译,只需重 新连接,增强软件可复用性 东南大学计算机学院 11/23/2018

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

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 如何避免类重复定义 东南大学计算机学院

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

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

类作用域 作用域 类作用域 东南大学计算机学院 11/23/2018

9.2作用域(1) 作用域分类 全局作用域(Global Scope) 局部作用域(Local Scope) 语句作用域(Statement Scope) 文件作用域(File Scope) 类作用域(Class Scope) 命名空间作用域(Namespace Scope) 东南大学计算机学院 11/23/2018

9.2作用域(2) 全局作用域 局部作用域 语句作用域 文件作用域 全局变量具有全局作用域,在一个文件中定义,所有文件可见,其 他文件使用extern再次声明该变量 局部作用域 静态局部变量具有局部作用域,只被初始化一次,在程 序结束前一直存在,但只在定义它的函数体内可见 自动对象具有局部作用域 语句作用域 for语句()内定义的变量具有语句作用域 文件作用域 静态函数具有文件作用域,又称内部函数 静态全局变量具有文件作用域 东南大学计算机学院 11/23/2018

9.2类作用域(1) 类成员具有类作用域 非成员函数具有文件作用域 成员函数中定义的变量具有函数作用域 变量隐藏 类作用域内,类成员可以被其他成员访问 类作用域外,public成员通过对象的句柄访问 成员函数只能由该类的成员函数重载(类作用域) 非成员函数具有文件作用域 成员函数中定义的变量具有函数作用域 变量隐藏 当函数作用域的变量将隐藏类作用域的变量 (ClassName::member) 隐藏的全局变量(::var) 东南大学计算机学院 11/23/2018

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

构造函数 构造函数 构造函数重载 默认实参的构造函数 默认构造函数 函数重载与默认实参 数据成员初始化 Time类实例研究 东南大学计算机学院 11/23/2018

9.3 构造函数 创建类的对象时,构造函数初始化对象,保证对象 的数据成员具有有效值的初始值 名字和类名相同 不能指定返回类型(void也不允许) 参数:可以没有,可以有多个 编译器自动调用 class Point{ public: Point(int v){x=v;}; private: int x; }; 东南大学计算机学院 11/23/2018

对象初始化的实参类型决定调用哪个构造函数 9.3 构造函数重载 不同的构造函数允许用户以不同的方式初始化数据成员 class Point{ public: Point(){x=0;}; Point(int v){x=v;}; private: int x; }; Point p1; Point p2(3); 对象初始化的实参类型决定调用哪个构造函数 东南大学计算机学院 11/23/2018

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

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

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

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

9.3 数据成员初始化(1) 初始化数据成员可确保数据的有效性,使对象处于 可靠的状态中 四种方式 类内声明时初始化 类外初始化 构造函数初始化表 构造函数内通过赋值初始化 东南大学计算机学院 11/23/2018

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

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

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

成员常量和引用成员,只能在成员初始化列表中进行初始化 9.3 数据成员初始化(5) 初始化顺序不是初始化表中顺序决定,而是由成员在类中被声明的顺序决定 class Point{ public: Point(int v):y(v),x(y){}; private: int x; int y; }; 使用尚未初始化的y值初始化x! 初始化VS. 赋值 成员初始化列表是初始化操作 在构造函数内是赋值操作 成员常量和引用成员,只能在成员初始化列表中进行初始化 11/23/2018

9.3 数据成员初始化(6) 从数据类型看初始化方式 基本数据类型 一般成员变量:构造函数的成员初始化表或函数体 静态成员常量/变量:类外初始化 若为整型常量(int、bool、char),可在声明时初始化 成员常量和引用成员:只在成员初始化表 基本数据类型 类内声明 类外 成员初始化表 构造函数内 一般成员变量   静态成员变量 非整型静态常量 整型静态常量 成员常量/引用成员 东南大学计算机学院 11/23/2018

9.3 数据成员初始化(7) 从数据类型看初始化方式 自定义数据类型(包括标准库中包含的string等类型) 一般成员变量:只在成员初始化表 若自定义数据类型存在缺省构造函数,或没有定义构造 函数而使用编译器提供的默认构造函数,则可以省略 静态成员常量/变量:类外初始化 成员常量和引用成员:只在成员初始化表 自定义数据类型 类内声明 类外 成员初始化表 构造函数内 一般成员变量   静态成员变量/常量 成员常量/引用成员 东南大学计算机学院 11/23/2018

9.3 数据成员初始化(8) 数据成员初始化总结表 静态成员可在类外初始化 所有的非静态成员(常量/变量)均可在成员初始化表中 基本数据类型 类内声明 类外 成员初始化表 构造函数内 一般成员变量   静态成员变量 非整型静态常量 整型静态常量 成员常量/引用成员 自定义数据类型 类内声明 类外 成员初始化表 构造函数内 一般成员变量   静态成员变量/常量 成员常量/引用成员 静态成员可在类外初始化 所有的非静态成员(常量/变量)均可在成员初始化表中 整型静态成员常量可在类内声明时初始化 基本数据类型的非静态成员变量还可以在构造函数体内初始化

9.3 数据成员初始化(9) 没有显式初始化的成员 使用变量初始化规则进行初始化 内置或复合类型成员 类类型成员 局部作用域:不被初始化 没有显式初始化的成员 使用变量初始化规则进行初始化 内置或复合类型成员 局部作用域:不被初始化 全局作用域:初始化为0 类类型成员 编译器自动调用其默认的构造函数 若无默认构造函数,则编译器失败 东南大学计算机学院 11/23/2018

9.3Time类实例研究(1) Fig. 9.8~9.10 Fig. 9.8: Time.h 10 class Time{ 12 public: 13 Time( int = 0, int = 0, int = 0 ); // default constructor 14 16 void setTime( int, int, int ); // set hour, minute, second 17 void setHour( int ); // set hour (after validation) 18 void setMinute( int ); // set minute (after validation) 19 void setSecond( int ); // set second (after validation) 20 22 int getHour(); // return hour 23 int getMinute(); // return minute 24 int getSecond(); // return second 25 26 void printUniversal(); // output time in universal-time format 27 void printStandard(); // output time in standard-time format 28 private: 29 int hour; // 0 - 23 (24-hour clock format) 30 int minute; // 0 - 59 31 int second; // 0 - 59 32 }; 东南大学计算机学院 11/23/2018

1. 如何确保数据的有效性? Fig. 9.9: Time.cpp (part1) 14 Time::Time( int hr, int min, int sec ){ 16 setTime( hr, min, sec ); // validate and set time 17 } 18 21 void Time::setTime( int h, int m, int s ){ 23 setHour( h ); // set private field hour 24 setMinute( m ); // set private field minute 25 setSecond( s ); // set private field second 26 } 27 29 void Time::setHour( int h ){ 31 hour = ( h >= 0 && h < 24 ) ? h : 0; // validate hour 32 } 33 35 void Time::setMinute( int m ){ 37 minute = ( m >= 0 && m < 60 ) ? m : 0; // validate minute 38 } 39 41 void Time::setSecond( int s ){ 43 second = ( s >= 0 && s < 60 ) ? s : 0; // validate second 44 } Fig. 9.9: Time.cpp (part1) 1. 如何确保数据的有效性? 11/23/2018 东南大学计算机学院

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(){ 67 cout <<setfill('0')<<setw(2)<<getHour()<<":" 68 <<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(){ 74 cout<<((getHour()==0||getHour()==12)?12:getHour()%12) 75 <<":"<<setfill('0')<<setw(2)<<getMinute() 76 <<":"<<setw(2)<<getSecond()<<(hour<12?" AM":" PM"); 77 } 2. 为什么使用set和get函数,为什么不直接访问数据成员? 如果使用秒来计时,如何改动代码? 11/23/2018 东南大学计算机学院

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(){ 11 Time t1; // all arguments defaulted 12 Time t2(2); // hour specified; minute and second defaulted 13 Time t3(21,34); // hour and minute specified; second defaulted 14 Time t4(12,25,42); // hour, minute and second specified 15 Time t5(27,74,99); // all bad values specified ...... 42 return 0; 43 } 3. 对t1、t2、t3、t4分别调用printUniversal()和printStandard()函数,输出是什么? 11/23/2018 东南大学计算机学院

析构函数 析构函数 构造函数与析构函数的调用顺序 东南大学计算机学院 11/23/2018

9.4析构函数(1) 构造函数创建对象时申请资源, 析构函数撤销对象时释放资源,是构造函数的补充 析构函数的调用 构造函数创建对象时申请资源, 析构函数撤销对象时释放资源,是构造函数的补充 本身不进行资源释放,为操作系统回收资源作准备 析构函数唯一,必须是public的,也不能重载 没有定义析构函数时,编译器自动生成一个“空 的”(empty)析构函数 析构函数的调用 可以由用户显式调用 撤销自动对象时由编译器自动调用 ~ClassName() 变量在超出作用域时应自动撤销 东南大学计算机学院 11/23/2018

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

9.4析构函数(3) 合成析构函数 编译器总会对类自动合成一个析构函数,按类成员声明次 序的逆序撤销每个非static成员 类类型成员:调用其析构函数 不删除指针成员指向的对象(造成内存泄漏!) 东南大学计算机学院 11/23/2018

9.4构造函数与析构函数的调用顺序(1) 一般而言,析构函数的调用顺序和其相应的构造函数的调用顺序相反,对象的存储类别和作用域能改变顺序 全局对象: 在任何函数(含main)执行前,构造; 在main程序结束时,析构. 局部对象: 自动对象:变量定义时,构造;块结束时,析构. 静态对象:首次定义时,构造;main程序结束时,析构. 全局和静态对象(均为静态存储类别)析构顺序恰好与构造顺序相反. 东南大学计算机学院 11/23/2018

9.4构造函数与析构函数的调用顺序(2) 两个特例 exit:输入错误或文件不能打开 不调用剩余自动对象的析构函数 全局和静态对象仍然被析构 abort: 程序异常中止 不调用任何剩余对象的析构函数 An example (Fig.9.11 ~ Fig.9.13) 东南大学计算机学院 11/23/2018

1C 2C 3C 5C 6C 7C 7D 5D 4C 4D 2D 6D 3D 1D create main 7 6 全局 栈区 数据区 5 11/23/2018 Fig.13

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

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

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

破坏类的封装性:返回私有数据成员的引用 成员函数返回private数据成员的引用,可以 使用户对private数据成员直接访问,破坏封 装性 11/23/2018

应避免返回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 东南大学计算机学院

默认的逐个成员赋值 东南大学计算机学院 11/23/2018

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

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

作业 deadline: 4.5 24:00 Ex.9.7 Ex.9.8 Ex.9.9 Ex.9.11 东南大学计算机学院 11/23/2018