Classes: A Deeper Look, Part 1 Chapter 9 Classes: A Deeper Look, Part 1 东方 fdong@seu.edu.cn
OBJECTIVES How to use a preprocessor wrapper to prevent multiple definition errors caused by including more than one copy of a header file in a source-code file. (使用预处理操作避免重复定义) To understand class scope and accessing class members via the name of an object, a reference to an object or a pointer to an object.( 访问类的成员) To define constructors with default arguments.(定义具有默认实参的构造函数) How destructors are used to perform “termination housekeeping” on an object before it is destroyed.(析构函数的使用) When constructors and destructors are called and the order in which they are called.(构造函数和析构函数的调用顺序) The logic errors that may occur when a public member function of a class returns a reference to private data.(访问权限引起的逻辑错误) To assign the data members of one object to those of another object by default memberwise assignment.(通过成员赋值将一个对象的数据成员赋给另一个对象的相应成员)
9.1 Introduction Preprocessor wrapper(预处理器封套) access functions(访问函数) & utility function (工具函数, also called a helper function) arguments & default arguments for constructors(使用默认实参定义构造函数) Destructor(析构函数) Reference to private data 如何从类现有的对象生成新的对象? default memberwise assignment 包装 访问 拷贝 类
Topics 9.1 Introduction 9.2 Time Class Case Study 9.3 Class Scope and Accessing Class Members 9.4 Separating Interface from Implementation 9.5 Access Functions and Utility Functions 9.6 Time Class Case Study: Constructors with Default Arguments 9.7 Destructors 9.8 When Constructors and Destructors Are Called 9.9 Time Class Case Study: A Subtle Trap - Returning a Reference to a private Data Member 9.10 Default Memberwise Assignment
9.2 Time Class Case Study 程序解读 (Textbook P370 Fig. 9.1) Preprocessor wrapper(预处理器封套) 成员函数定义的两种方法 How to use class The size of class (Textbook P370 Fig. 9.1) 程序解读
9.2 Time Class Case Study --- 流程 Edit Preprocess 宏、文件包含、条件编译 Compile 编译错误(语法等) Link Load Execute Runtime Error(运行时错误), Fatal致命错误,Logic逻辑错误 http://www.cnblogs.com/magicsoar/p/3760201.html
9.2 Time Class Case Study --- Preprocessor Wrapper 现象:头文件被重复引用 Test.cpp预处理include结果 class A{ int a;} class B{ double b;} int main() { A a; B b; return 0; } // test.cpp #include "a.h" #include "b.h" int main() { A a; B b; return 0; } // a.h class A{ int a; }; // b.h #include "a.h" class B{ double b; }; error C2011: 'A' : 'class' type redefinition
9.2 Time Class Case Study --- 预处理器指令 #define #define PI 3.1415 #define FLAG 预处理器定义 #ifdef / #ifndef #else #endif
9.2 Time Class Case Study --- 条件编译 一般情况下,源程序中所有的行都参加编译;但是有时希望其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。 #ifndef FLAG cout << “代码1” << endl; #endif #ifdef FLAG cout << “代码2” << endl; #ifdef FLAG cout << “代码1” << endl; #else cout << “代码2” << endl; #endif
9.2 Time Class Case Study --- Preprocessor Wrapper Test.cpp预处理include结果 #ifndef A_H #define A_H class A{ int a;} #endif class B{ double b;} // a.h #ifndef A_H #define A_H class A{ int a; }; #endif // test.cpp #include "a.h" #include "b.h" int main() { A a; B b; return 0; } // b.h #include "a.h" class B{ double b; };
9.2 Time Class Case Study --- Preprocessor Wrapper Test.cpp预处理include结果 #ifndef A_H #define A_H #endif Preprocessor Wrapper 预处理器封装 #ifndef A_H #define A_H class A{ int a;} #endif class B{ double b;} 建议所有头文件均使用预处理器封装,以避免重复的头文件引用 预处理器定义:FILENAME_H
9.2 Time Class Case Study --- Two ways to define member functions 2018年11月29日5时41分 9.2 Time Class Case Study --- Two ways to define member functions 类中数据成员的初始化 可以在类体中声明时初始化么? 复习三目运算符(?:) A = B?C:D; 软件工程知识 成员函数vs全局函数 (print函数) 面向对象编程可以通过减少传递的参数个数来简化函数调用 对于条件表达式b?x:y,先计算条件b,然后进行判断。如果b的值为true,计算x的值,运算结果为x的值;否则,计算y的值,运算结果为y的值 (Textbook P371 Fig9.2) 程序解读
9.2 Time Class Case Study --- Two ways to define member functions 2018年11月29日5时41分 9.2 Time Class Case Study --- Two ways to define member functions // test.h class test{ public: void displayMessage(); private: int num; }; // test.cpp void test::displayMessage() { cout << num << endl; } // test.h class test{ public: void displayMessage(){ cout<<num<<endl; } private: int num; }; 编译器将使用函数的定义体来替代函数调用语句,这种替代行为发生在编译阶段而非程序运行阶段 相同点:均必须通过建立对象来访问; 区别:调用方式,后者当inline函数调用;前者为接口与实现分离 采用作用域运算符定义
9.2 Time Class Case Study --- using class 一旦定义了类,它就可以作为一种类型进行应用 Time sunset; // 创建Time类的对象 Time arrayOfTimes[5];//建立Time类对象的数组 Time &dinnerTime = sunset; // 对象sunset的引用(别名) Time *timeptr1 = &dinnerTime;//指向对象的指针 Time *timeptr2 = &sunset;//指向对象的指针 timeptr1 == timeptr2 (Textbook P374 Fig9.3) 程序解读
9.2 Time Class Case Study --- Object Size 思考:对象有多大? sizeof(A) , sizeof(a) 包括数据成员和成员函数? 结论:只包括数据成员的大小!why? 现象:sizeof的结果将大于等于类中数据成员的大小
9.2 Time Class Case Study --- Object Size class test{ // 1 public: void displayMessage(); private: int num; }; class test{ // 2 int num; char c; }; class test{ // 3 int num; char c, t; }; class test{ // 5 static char s; int num; }; class test{ // 4 char s; int num; char c, t; }; 在默认情况下,VC规定各成员变量存放的起始地址相对于结 构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的 对齐方式(vc6.0,32位系统)。 VC为了确保结构的大小为结构的字节边 界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员 变量申请空间后,还会根据需要自动填充空缺的字节。 在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。 VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。 4 8 8 12 4
Topics 9.1 Introduction 9.2 Time Class Case Study 9.3 Class Scope and Accessing Class Members 9.4 Separating Interface from Implementation 9.5 Access Functions and Utility Functions 9.6 Time Class Case Study: Constructors with Default Arguments 9.7 Destructors 9.8 When Constructors and Destructors Are Called 9.9 Time Class Case Study: A Subtle Trap - Returning a Reference to a private Data Member 9.10 Default Memberwise Assignment
9.3 Class Scope and Accessing Class Members --- Class Scope Function Scope File Scope Block Scope (Local Scope) Function-prototype Scope Class Scope The name of a class member (数据成员/成员函数) 类内:accessible by all of that class's member functions and can be referenced by name 类外:public class members are referenced through one of the handles(句柄) on an object an object name (对象名) a reference to an object (对象引用) a pointer to an object (对象指针)
How to Access 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; b = 99; Test::a = a; Test::b = b; this->a = a; this->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
类的存储与this指针 全局区 代码区 a b 栈区 类中定义的数据成员对该类的每个对象都有一个拷贝 类中的成员函数对该类来说只有一个拷贝。 class A { public: void f(); void g(int i) {x=I; f()} private: static t; int x,y,z; }; A a,b; 全局区 代码区 shared shared t 成员函数代码 a b a.x a.y a.z b.x b.y b.z 栈区
this指针 问题: 每一个成员函数都有一个隐藏的指针类型形参:this 类中成员函数对该类所有对象只有一个拷贝,类的成员函数如何知道对那一个对象操作呢? 每一个成员函数都有一个隐藏的指针类型形参:this <类名> *const this; 通过对象调用成员函数时,编译程序会把对象地址作为隐含参数传递给形参this 访问this指向对象的成员时:this-> 也可以省略不写this->(具体情况看是否有冲突)
#include <iostream>// P375. fig9.4 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; } How to Access using the object's name dot member selection operator Using a reference Using a pointer with * operator Using a pointer directly Arrow member selection operator * :取内容运算符 1 2 3 4
9.3 Class Scope and Accessing Class Members --- How to Access 总结: Public class members are referenced through one of the handles(句柄) on an object an object name (对象名): 点操作符 a reference to an object (对象引用): 点操作符 a pointer to an object (对象指针): 箭头操作符
Topics 9.1 Introduction 9.2 Time Class Case Study 9.3 Class Scope and Accessing Class Members 9.4 Separating Interface from Implementation 9.5 Access Functions and Utility Functions 9.6 Time Class Case Study: Constructors with Default Arguments 9.7 Destructors 9.8 When Constructors and Destructors Are Called 9.9 Time Class Case Study: A Subtle TrapReturning a Reference to a private Data Member 9.10 Default Memberwise Assignment
9.4 Separating Interface from Implementation
#include <iostream> using namespace std; // max.h int maximum(int a, int b, int c); #include <iostream> using namespace std; int maximum(int a, int b, int c) { int max = a; if(b > max) max = b; if(c > max) max = c; return max; } int main() cout << maximum(10, 99, 73) << endl; return 0; #include "max.h"// max.cpp int maximum(int a, int b, int c) { int max = a; if(b > max) max = b; if(c > max) max = c; return max; } #include <iostream>// main.cpp using namespace std; #include "max.h" int main() { cout << maximum(10, 99, 73) << endl; return 0; }
9.4 Separating Interface from Implementation --隐藏类的源代码 类实现(成员函数定义) 类定义(接口) 类客户代码 A.cpp A.h Main.cpp 编译 编译 修改类实现代码 A.obj Main.obj 链接 EXE 结论:软件供应商在他们的产品中只需提供头文件和类库(目标模块),而不需提供源代码。
Topics 9.1 Introduction 9.2 Time Class Case Study 9.3 Class Scope and Accessing Class Members 9.4 Separating Interface from Implementation 9.5 Access Functions and Utility Functions 9.6 Time Class Case Study: Constructors with Default Arguments 9.7 Destructors 9.8 When Constructors and Destructors Are Called 9.9 Time Class Case Study: A Subtle TrapReturning a Reference to a private Data Member 9.10 Default Memberwise Assignment
9.5 Access Functions and Utility Functions(访问函数和工具函数) 读取或显示数据 测试条件的真假(又称为判定函数) 例如:对容器类对象(例:链表,栈,对列)的访问之前,要判断isEmpty(); isFull(); 例如:vector <int> v(10); v.empty(): 如果v为空,返回true,否则返回false; 特点:需要声明为public 工具函数(utility functions or helper function) 不属于类的public成员函数,用来支持类的public成员函数 不是提供给类客户使用的
9.5 Access Functions and Utility Functions(访问函数和工具函数)
SalesPerson.cpp (1 of 3) 构造函数对数组进行初始化
SalesPerson.cpp (2 of 3) 判断数据的有效性
一般情况下,类的数据成员和在类内部使用的成员函数应该指定为private,只有提供给外界使用的成员函数才能指定为public。 SalesPerson.cpp (3 of 3) 工具函数 一般情况下,类的数据成员和在类内部使用的成员函数应该指定为private,只有提供给外界使用的成员函数才能指定为public。 操作一个对象时只能通过访问对象类中的public成员来实现。
fig09_07.cpp (1 of 1) 控制逻辑封装在SalesPerson类的成员函数中
Topics 9.1 Introduction 9.2 Time Class Case Study 9.3 Class Scope and Accessing Class Members 9.4 Separating Interface from Implementation 9.5 Access Functions and Utility Functions 9.6 Time Class Case Study: Constructors with Default Arguments 9.7 Destructors 9.8 When Constructors and Destructors Are Called 9.9 Time Class Case Study: A Subtle TrapReturning a Reference to a private Data Member 9.10 Default Memberwise Assignment
Review(带有缺省参数值的函数定义) 默认实参应在函数名第一次出现时设定 全局函数: 9.6 Time Class Case Study: Constructors with Default Arguments(具有默认实参的构造函数) Review(带有缺省参数值的函数定义) 默认实参应在函数名第一次出现时设定 全局函数: 函数原型 或无函数原型时的函数头部 类成员函数: 类定义
9.6 Time Class Case Study: Constructors with Default Arguments 构造函数可以指定默认实参 缺省构造函数 不带参数的构造函数 或所有参数都有默认值的构造函数 总结:不指定实参即可调用的构造函数称为缺省构造函数
9.6 Time Class Case Study: Constructors with Default Arguments 函数重载与缺省参数 class A { int x, y; public: A(int x = x1; y = y1; } A(int x1){ y = 0; }; Why? x1, int y1){ x1=0, int y1=0){ ERROR error C2668: 'A::A' : ambiguous call to overloaded function
9.6 Time Class Case Study: Constructors with Default Arguments (具有默认实参的构造函数) 注意事项: 函数重载(overloading) (回顾学过的内容) C++允许函数同名,但要求参数不同(数量、类型、顺序)。返回值不同? 构造函数的重载类似 Class A { int x, y; public: A( ) { x=y=0; } A( int x1 ) {x=x1;y=0;} A( int x1,int y1 ) {x=x1;y=y1;} }
9.6 Time Class Case Study: Constructors with Default Arguments 构造函数可以指定默认实参 不带参数的(或所有参数都有默认值的)构造函数称为缺省构造函数。 作用:即使不提供任何参数,构造函数仍能够初始化数据成员,保证对象的数据成员处于正常状态。 Class A { int x, y; public: A(int x1=0,int y1=0 ) { x=x1; y=y1; } }
2018年11月29日5时41分 只要类中提供了构造函数,即使没有提供缺省构造函数,编译程序将不再提供缺省构造函数。 注意构造函数通过调用其它成员函数对数据成员初始化,而没有直接进行初始化。Why? 对类的维护有何影响? 类的成员函数对数据成员的访问都是通过set和get函数完成。有何好处?为什么设置hour,minute,second的set和get函数? 考虑: 如果数据成员改为一个seconds(定义为从凌晨到目前的秒数),需要改动哪些函数? (Textbook P378 Fig9.8-9.10) 程序解读
Topics 9.1 Introduction 9.2 Time Class Case Study 9.3 Class Scope and Accessing Class Members 9.4 Separating Interface from Implementation 9.5 Access Functions and Utility Functions 9.6 Time Class Case Study: Constructors with Default Arguments 9.7 Destructors 9.8 When Constructors and Destructors Are Called 9.9 Time Class Case Study: A Subtle TrapReturning a Reference to a private Data Member 9.10 Default Memberwise Assignment
9.7 Destructors (析构函数) --- 特点 系统在对象结束生命周期时,通过析构函数收回对象所占用的存储空间,即撤销一个对象。 A class’s destructor is called implicitly when an object is destroyed. (隐性调用) If the programmer does not explicitly provide a destructor, the compiler creates an “empty” destructor. (提供默认的析构函数)
9.7 Destructors (析构函数) --- 特点 析构函数是一个特殊的成员函数,它的名字同类名,并在前面加“~”字符,用来与构造函数加以区别。析构函数不接收任何参数,也不可以指定返回类型,甚至void也不行。(Fig 9.11) 一个类中只能定义一个析构函数,析构函数不能重载,且必须是公有的。 在下面两种情况下,析构函数会被自动调用: 如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用; *当一个对象是使用new运算符被动态创建的,在使用delete运算符释放它时,delete将会自动调用析构函数
9.7 Destructors (析构函数) --- 复习构造函数 构造函数是成员函数,函数体可写在类体内,也可写在类体外。 构造函数是一个特殊的函数,该函数的名字与类名相同,该函数不指定类型说明,它有隐含的返回值,该值由系统内部使用。该函数可以有一个参数,也可以有多个参数。 构造函数可以重载,即可以定义多个参数个数不同的函数。 程序中不能直接调用构造函数,在创建对象时系统自动调用构造函数。
Topics 9.1 Introduction 9.2 Time Class Case Study 9.3 Class Scope and Accessing Class Members 9.4 Separating Interface from Implementation 9.5 Access Functions and Utility Functions 9.6 Time Class Case Study: Constructors with Default Arguments 9.7 Destructors 9.8 When Constructors and Destructors Are Called 9.9 Time Class Case Study: A Subtle Trap - Returning a Reference to a private Data Member 9.10 Default Memberwise Assignment
Review: Storage Classes(存储类别) -- 标识符的相关特性 标识符的存储类别确定了标识符在内存中存在的时间。有些标识符的存在时间很短,有些则重复生成和删除,有些存在于整个程序的执行期间。 作用域 标识符的作用域是程序中能引用这个标识符的区域。有些标识符可以在整个程序中引用,而有些标识符只能在程序中的有限部分引用。
Review: Storage Classes(存储类别) --小结 2018年11月29日5时41分 Review: Storage Classes(存储类别) --小结 生存期:定义它的块 auto:局部变量和形参 自动存储类 register:仅适用于自动变量 存储类别 生存期:整个程序运行期间 static:可用于函数中的局部 变量 静态存储类 Extern 函数: 函数如果定义在本.cpp文件中, 或者定义在一个.h文件中并且本.cpp文件#include了这个.h就不用加extern,因为这两种情况下本.cpp文件都可以直接找到。 一旦有一个函数定义在另一个.cpp文件中,而你要想在本.c文件中使用的时候就必须用extern关健字声明一下,否则链接会出错。 extern的作用就是告诉链接器,在这个工程中的某一个obj文件中有extern修饰的后边这函数,这样链接器就可以正常工作了。否则链接器在本.obj文件中找不到你后边写的这个函数符号,就会报错 extern:全局变量和函数名 注意:1. 局部变量默认为自动存储类 2. register通常不必声明 3. 一个标识符不能使用多个存储类指示符
9.8 When Constructors and Destructors Are Called 全局对象:在任何函数(含main)执行前,构造;在程序结束时,析构. 局部对象: 自动变量:对象定义时,构造;块结束时,析构. 静态变量:首次定义时,构造?;程序结束时,析构. 对象间调用构造函数和析构函数的顺序恰好相反. 全局和静态对象(均为静态存储类别)同理. 特例1:调用exit函数退出程序执行时,不调用剩余自动对象的析构函数. 特例2:调用abort函数退出程序执行时,不调用任何剩余对象的析构函数. 顺序,反序。存储类别可以改变析构的顺序 局部静态跟全局静态内存中性质一样,文件里都在数据段。唯一区别局部静态变量有作用域限制,是靠编译器帮忙语法检测。 局部静态变量有个初始化问题,当多次调用一个含有局部静态变量的函数时候,怎么保持之初始化一次。 程序会在内存中第一个bit位,来标识是否初始化过。
全局 数据区 栈区 1C 2C 3C 5C 6C 7C 7D 5D 4C 4D 2D 6D 3D 1D create main 7 6 5 VC6.0 1D没有输出,但实际上调用了析构函数 全局数据区非栈,但析构过程与构造过程恰相反 7 create 全局 数据区 6 4 5 3 栈区 1 2 main
全局 数据区 栈区 1C 2C 3C 5C 6C 7C 7D 5D 4C 6D 3D 1D exit(0); create main 7 6
全局 数据区 栈区 1C 2C 3C 5C 6C 7C 7D 5D 4C abort(); create main 7 6 5 4 3 1
全局 数据区 栈区 Line23~25: create(); create main 1C 2C 3C 5C 6C 7C 7D 5D 4C
Topics 9.1 Introduction 9.2 Time Class Case Study 9.3 Class Scope and Accessing Class Members 9.4 Separating Interface from Implementation 9.5 Access Functions and Utility Functions 9.6 Time Class Case Study: Constructors with Default Arguments 9.7 Destructors 9.8 When Constructors and Destructors Are Called 9.9 Time Class Case Study: A Subtle Trap - Returning a Reference to a private Data Member 9.10 Default Memberwise Assignment
9.9 Time Class Case Study: A Subtle Trap — Returning a Reference to a private Data Member int& test() { static int ret = 0;// 必须为静态变量 ret++; cout << ret << endl; return ret; } int main(){ test(); test(); test(); int& ret_ref = test(); ret_ref = 100; test() = 200; test(); return 0; 1 2 3 4 101 201 以test返回的引用作为左值
9.9 Time Class Case Study: A Subtle Trap — Returning a Reference to a private Data Member 类成员函数同样可以返回引用,并且是私有数据成员的引用, 但应避免这种用法。
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; } // end main 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; }; // end class Time const ? 如何加? 私有数据成员 int &Time::badSetHour( int hh ) { hour = ( hh >= 0 && hh < 24 ) ? hh : 0; return hour; } // end function badSetHour Returning a reference or a pointer to a private data member breaks the encapsulation of the class.
Topics 9.1 Introduction 9.2 Time Class Case Study 9.3 Class Scope and Accessing Class Members 9.4 Separating Interface from Implementation 9.5 Access Functions and Utility Functions 9.6 Time Class Case Study: Constructors with Default Arguments 9.7 Destructors 9.8 When Constructors and Destructors Are Called 9.9 Time Class Case Study: A Subtle Trap - Returning a Reference to a private Data Member 9.10 Default Memberwise Assignment
9.10 Default Memberwise Assignment --- 赋值运算 目标:从一个已构建的对象生成另一个相同的对象 The assignment operator (=) can be used to assign an object to another object of the same type. Example: Date date1, date2; date2 = date1;
9.10 Default Memberwise Assignment --- 赋值运算 2018年11月29日5时41分 9.10 Default Memberwise Assignment --- 赋值运算 By default, such assignment is performed by memberwise assignment(按成员赋值/逐个成员赋值). Example: Date date1, date2;// year, month, day date2 = date1; 即: date2. year = date1.year; date2. month = date1.month; date2. day = date1.day; (Textbook P387 Fig 9.17-9.19) 程序解读
9.10 Default Memberwise Assignment --- Copy Constructor 2018年11月29日5时41分 9.10 Default Memberwise Assignment --- Copy Constructor void copyDate(Date date); Date date1,date2; date1.copyDate(date2); // date1 = date2 赋值 Date getDate(); Date date2 = getDate(); 均为传值,编译器为后者提供default copy constructor (缺省拷贝构造函数),将原对象的每个数据成员的值拷贝至新对象的相应成员,即memberwise copy. 注意:赋值与拷贝构造函数的区别
9.10 Default Memberwise Assignment --- 问题 2018年11月29日5时41分 9.10 Default Memberwise Assignment --- 问题 class test{ int *pArray; public: test(int n){ pArray = new int[n]; } ~test(){ delete [ ] pArray; }; test t1(4); test t2(5); t2 = t1; 问题: 一个对象改变,另一个受影响 析构时,同一块内存被归还两次,导致程序异常 New运算符介绍 t2.pArray 增加new和delete的介绍 t1.pArray
9.10 Default Memberwise Assignment --- 自定义拷贝构造函数 2018年11月29日5时41分 9.10 Default Memberwise Assignment --- 自定义拷贝构造函数 test::test(const test &a) { pArray = new int[n]; //申请资源 for(int i=0;i++;i<n) { pArray[i]=a.pArray[i]; } } 按引用传递 板书! 思考:自定义赋值运算符如何定义?(具体见11章)
9.10 Default Memberwise Assignment ——性能分析 按值传递与按引用(指针)传递的区别? 按值传递对象 优点:安全 缺点:性能差 按引用(指针)传递对象 优点:开销小 缺点:容易破坏封装性,造成潜在危险 解决思路: 常量引用——用const限定 :const int & const int *
Summary 条件编译指令 访问成员函数的三种方式(句柄+操作符) 成员函数的作用域:class scope 访问函数和工具函数 带默认实参的构造函数 构造函数和析构函数被调用的顺序 破坏类的封装的一种做法:返回对私有数据成员的引用 利用一个对象初始化另一个对象(拷贝构造函数)
Homework! 实验题目: 5,6,7,14
具有缺省参数的函数 形式: void func(int x = 100) { cout<<x; } void main(void) func();
6.15 Default Arguments --- 函数定义 int boxVolume( int length, int width, int height ); int main() { boxVolume(); return 0; } int boxVolume( int length = 1, int width = 1, int height = 1) { return length * width * height; error C2660: 'boxVolume' : function does not take 0 parameters 缺省参数的说明必须出现在函数调用之前! 既可以在函数声明时也可以在函数定义中指定默认实参
6.15 Default Arguments --- 函数定义 int boxVolume( int length = 1, int width = 1, int height = 1 ); int main() { …….. } int boxVolume( int length = 1, int width = 1, int height = 1) { return length * width * height; error C2572: 'boxVolume' : redefinition of default parameter
6.15 Default Arguments --- 函数定义 默认实参必须是函数参数列表中右端(尾部)的参数,即如果某个参数设定了默认实参,那么该参数右边的所有参数都必须具有默认实参! // OK int boxVolume( int length = 1, int width = 1, int height = 1 ); int boxVolume( int length, int width = 1, int height = 1 ); int boxVolume( int length, int width, int height = 1 ); // ERROR int boxVolume( int length = 1, int width, int height = 1 ); int boxVolume( int length, int width=1, int height ); // ERROR: missing default parameter for parameter x 为何error? boxVolume() boxVolume(1) boxVolume(1,2)
test.aaa(2); test.aaa(2,3); return 0; } // test.h,类接口定义 class Test{ 2018年11月29日5时41分 // main.cpp,测试程序 #include "test.h" int main() { Test test; test.aaa(2); test.aaa(2,3); return 0; } // test.h,类接口定义 class Test{ public: void aaa(int=1, int=2, int=3); }; // test.cpp,类实现 #include <iostream> using namespace std; #include "test.h" void Test::aaa(int a, int b, int c) { cout << "a=" << a << ", b=" << b << ", c=" << c << endl; } 223 233
6.15 Default Arguments --- 函数调用 函数调用时,若省略了某个(些)实参,那么编译器会自动插入默认实参,并生成新的函数调用 显式传递给函数的实参是从左至右给形参赋值 int boxVolume( int length=1, int width=1,int height=1 ); boxVolume(); // length=1, width=1, height=1 boxVolume(10); // length=10, width=1, height=1 boxVolume(10,5); // length=10, width=5, height=1 boxVolume(10,5,2); // length=10, width=5, height=2 Return
10.6 Dynamic Memory Management with Operators new and delete Motivation char sentence[ 1000 ]; Fixed-size array. 1000? Dynamic memory management 根据需求分配(allocate)、释放(deallocate)内存 new / delete operator
10.6 Dynamic Memory Management with Operators new and delete use the new operator to dynamically allocate the exact amount of memory required at execution time in the free store (sometimes called the heap) 用new运算符在执行期间从堆中申请动态内存空间 return the memory to the free store by using the delete operator to deallocate (i.e., release) the memory, which can then be reused by future new operations 通过delete关键字释放内存空间 memory leak (内存泄露)
10.6 Dynamic Memory Management with Operators new and delete allocates storage of the proper size for an object calls the default constructor to initialize the object returns a pointer of the type specified to the right of the new operator delete 对象: calls the destructor for the object to which pointer points deallocates the memory associated with the object
10.6 Dynamic Memory Management with Operators new and delete 2018年11月29日5时41分 10.6 Dynamic Memory Management with Operators new and delete 使用堆空间的理由 直到运行时才能知道需要多少对象空间; 不知道对象的生存期到底有多长; 直到运行时才知道一个对象需要多少内存空间 申请时不能调用带参数的构造函数 [1]从静态存 储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 [2]在栈上创建。 在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 [3]从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在 何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内 存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
10.6 Dynamic Memory Management with Operators new and delete 2018年11月29日5时41分 10.6 Dynamic Memory Management with Operators new and delete (1) 基本数据类型 double *ptr = new double; cout << *ptr << endl; delete ptr; ( 3.14159 ); 初始值 可增加对数组的操作 -6.27744e+066 3.14159
10.6 Dynamic Memory Management with Operators new and delete (2) 类对象 class Time{ public: Time(){ cout << "Time constructor called.\n"; } ~Time(){ cout << "Time destructor called.\n"; } }; int main() { Time *timePtr = new Time; delete timePtr; return 0; } Time constructor called. Time destructor called.
10.6 Dynamic Memory Management with Operators new and delete (2) 类对象 class Time2{ public: Time2( int, int, int); ~Time2(); }; int main() { Time2 *timePtr = new Time2( 12, 45, 0 ); delete timePtr; return 0; } 构造函数参数列表
10.6 Dynamic Memory Management with Operators new and delete (3) 数组 – 基本数据类型 int size = 10; int *gradesArray = new int[ size ]; delete [ ] gradesArray; 注意与Fixed size数组的区别: Constant integral expression vs Any integral expression
10.6 Dynamic Memory Management with Operators new and delete (3) 数组 – 类对象 Time *timePtr = new Time[5]; delete [ ] timePtr; Time constructor called. Time destructor called.
10.6 Dynamic Memory Management with Operators new and delete (3) 数组 – 类对象 Time *timePtr = new Time[5]; delete timePtr; Time constructor called. Time destructor called.
10.6 Dynamic Memory Management with Operators new and delete 2018年11月29日5时41分 10.6 Dynamic Memory Management with Operators new and delete (3) 数组 – 类对象 when allocating an array of objects dynamically, the programmer cannot pass arguments to each object's constructor. 无法传递构造函数参数 Instead, each object in the array is initialized by its default constructor. 只能调用默认的构造函数 申请时不能调用带参数的构造函数 Return