Presentation is loading. Please wait.

Presentation is loading. Please wait.

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

Similar presentations


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

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

2 运算符重载 教学要求与安排 教学要求 全部要求 11.13了解(自学) 教学安排 chap10.6放在chap11中讲 东南大学计算机学院
12/3/2018

3 运算符重载 11.1 运算符重载基础 (chap11.1,11.2,11.3) (chap11.4,11.6,11.7)
11.2 两种运算符函数的比较 11.3 重载流插入/提取运算符 11.4 动态内存管理 11.5 实例研究:Array类 11.6 类型转换 11.7 实例研究:String类 11.8 重载++和--运算符 11.9 实例研究:Date类 (chap11.1,11.2,11.3) (chap11.4,11.6,11.7) (chap11.5) (chap10.6) (chap11.8) (chap11.9, 11.14) (chap 11.10) (chap 11.11) (chap 11.12)

4 运算符重载基础 基本概念 运算符重载的必要性 运算符重载的适用性 运算符重载的基本原则 运算符重载的限制

5 基本概念(1) 重载函数 调用 出现在相同作用域中的两个函数,具有相同的名字而形参表 不同,则称为重载函数,它们是不同的函数 形参个数不同
11.1运算符重载基础 基本概念(1) 回顾 重载函数 出现在相同作用域中的两个函数,具有相同的名字而形参表 不同,则称为重载函数,它们是不同的函数 形参个数不同 形参类型不同 成员函数可以有const类型的重载,供const对象调用 调用 根据实参的类型 根据对象是否为const对象 东南大学计算机学院 12/3/2018

6 基本概念(2) 函数重载VS.重复声明 若形参表相同而返回类型不同,则第二个声明错误 重复声明: 函数名、返回类型、形参表都相同
11.1运算符重载基础 基本概念(2) 回顾 函数重载VS.重复声明 重复声明: 函数名、返回类型、形参表都相同 若形参表相同而返回类型不同,则第二个声明错误 void f1(int a); void f1(int); typedef A B; void f2(A &); void f2(B &); void f3(int a, int b); void f3(int a, int b = 0); //默认实参不改变形参个数 重复声明 重复声明 重复声明 void f4(A); void f4(const A); void f5(A&); void f5(const A&); void A::f(); void A::f() const; 对副本操作,无法改变实参,因此效果一样 重复声明 传递实参,第一个可改变实参,第二个不可以改变实参,因此效果不一样 函数重载 函数重载 东南大学计算机学院 12/3/2018

7 operator+(double,double)
11.1运算符重载基础 基本概念(3) 运算符重载 3 + 1 运算符重载 operator+(int,int) 重载的运算符函数 operator+(double,double) 东南大学计算机学院 12/3/2018

8 11.1运算符重载基础(1) 运算符重载的必要性 代码清晰直观,符合人的习惯 根据语义,隐藏实现细节 一般来说,类对象使用运算符必须重载
不可能为用户自定义的类对象预先定义运算规则 函数调用 运算符 a.plus(b); a.minus(b); a = a + b; a = a – b; int x1, y1, a; double x2, y2, b; a = x1 + y1; //int b = x2 + y2; //double 运算符重载是C++可扩展性的一个重要方面 东南大学计算机学院 12/3/2018

9 11.1运算符重载基础(2) 运算符重载的适用性 不能创造新的运算符 Appendix A 通过指针的成员指针 通过对象的成员指针
唯一的三元运算符

10 X 11.1运算符重载基础(3) 运算符重载的基本原则 函数名:operator@ 类对象使用运算符,必须重载该运算符 三个例外:
赋值运算符 = 取址(&)和逗号(,) 只能显式重载,不能自动实现 需重载的运算符 + 运算符重载函数名 operator+() 第9章: 默认的逐个成员赋值 (&) 既是“取址”运算符,又是“按位与”运算符 只有当它是取值运算符时才能直接用于对象 obj2 = obj1 + obj2 已重载+ obj2 += obj1 +=被重载 X

11 11.1运算符重载基础(4) 运算符重载的基本原则 两种运算符重载方式 以成员函数重载:non-static成员函数
运算符是对对象的操作,由对象调用 以全局函数重载:一般声明为友元 友元可以访问类的所有数据成员 why? why? 区别? 东南大学计算机学院 12/3/2018

12 11.1运算符重载基础(5) 运算符重载的限制 不能创造新的运算符 不能改变优先级、结合律 不能改变元数 一元运算符 二元运算符
三元运算符:不可重载(?:) 如果希望重载的运算符具有不同的优先级和结合律,只能使用( ) 元数:运算符的操作数的个数 一元运算符:+1, -1, !, … 二元运算符: a+b, a-b, a*b, a/b, … 三元运算符:?: 一元/二元运算符: + - * & 一元 正号 负号 指针 取址 二元 按位与 重载哪个运算符由元数控制 东南大学计算机学院

13 11.1运算符重载基础(6) 运算符重载的限制 不能改变对基本类型对象的操作语义 类库重载了基本类型对象的运算符
只有当用户自定义类型的对象为操作数时,运算符重载 才起作用 一元运算符:自定义类型的对象 二元运算符:(不考虑顺序) 自定义类型对象 +自定义类型对象 自定义类型对象 + 基本类型对象 当只包含基本类型对象时,运算符重载不起作用 保证 东南大学计算机学院 12/3/2018

14 11.1运算符重载基础(7) 运算符重载的限制 重载不保证操作数的求值顺序 (补充) 逗号(,): a, b 逻辑与(&&): a && b
避免重载这些运算符 东南大学计算机学院 12/3/2018

15 两种运算符函数的比较 两种运算符函数 必须作为成员/全局函数重载的运算符 两种运算符函数的调用 函数的参数传递类型 一元和二元运算符重载
重载流插入/提取运算符 重载可交换的运算符

16 11.2两种运算符函数的比较(1) 两种运算符函数 必须作为成员函数重载的运算符 必须作为全局函数重载的运算符
成员运算符函数:必须是non-static成员函数 全局运算符函数:一般为友元 必须作为成员函数重载的运算符 ()、[]、-> 任何赋值运算符 必须作为全局函数重载的运算符 流插入操作符(<<)和流提取运算符(>>) 可交换的运算符(指操作对象类型不同) 其他运算符可以作为成员函数或全局函数进行重载 用法一样,实现不同 东南大学计算机学院 12/3/2018

17 11.2两种运算符函数的比较(2) 两种运算符函数的调用 成员运算符函数 一元运算符 二元运算符 全局运算符函数 + obj1
obj1 + obj2 obj1.operator+( ) obj1.operator+( obj2 ) + obj1 obj1 + obj2 operator+( obj1 ) operator+( obj1, obj2 ) 东南大学计算机学院 12/3/2018

18 11.2两种运算符函数的比较(3) 成员运算符函数 一元运算符 二元运算符 左操作数必须为该类的对象
class A{ public: operator+(); }; A obj1; obj1.operator+( obj2 ) obj1.operator+( ) obj1 + obj2 + obj1 一元运算符 二元运算符 左操作数必须为该类的对象 右操作数可以是任意类类型对象或基本类型对象 适用情况 (有顺序) 本类对象 本类对象 + 自定义/基本类型对象 左操作数是其他类对象时,必须使用全局运算符函数

19 11.2两种运算符函数的比较(4) 全局函数运算符函数 顺序无关 + obj1 obj1 + obj2 operator+( obj1 )
operator+( obj1, obj2 ) obj1 + obj2 顺序无关 东南大学计算机学院 12/3/2018

20 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 东南大学计算机学院 12/3/2018

21 11.2两种运算符函数的比较(6) 函数的参数传递类型(形参/返回值类型) 传递值:返回对象的副本,而非对象本身
传递引用:返回对象本身,而非对象的副本 效率高,但由于能改变对象,容易出错 避免改变对象的方法:const对象引用 重要知识点回顾 bool operator!( A a ) const; 需拷贝对象,效率低 bool operator!( const A & a ) const; 不需拷贝对象,效率高 东南大学计算机学院 12/3/2018

22 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中有另一个例子) 东南大学计算机学院 12/3/2018

23 11.2两种运算符函数的比较(8) 函数的参数传递类型(形参/返回值类型) 传递指针:作用等同于返回引用,但不安全
指针:指向可以改变,容易误操作 引用:是const类型的指针,指向无法改变,更安全 重要知识点回顾 A & function(A & obj){ … … return obj; } A * function(A & obj){ … … return &obj; } A a, b; A & p; p = function(a); //ok p = &b; //error A a, b; A * p; p = function(a); //ok p = &b; //no error 东南大学计算机学院 12/3/2018

24   11.2两种运算符函数的比较(9) 返回引用:避免“虚悬引用”(dangling references) 虚悬引用:引用的对象不存在
从重载的流操作符返回的cin、cout等大部分stream对象 一般是全局的,返回引用一般会成功 避免返回自动变量和临时变量的引用 int & f(){ int a; return a; } int & f( int a ){ return a; } a是实参的副本,是临时变量 虚悬引用! 函数结束时,自动变量a被撤销 虚悬引用! 东南大学计算机学院 12/3/2018

25 内 容 回 顾 10.6数据抽象和信息隐藏 11.1 运算符重载基础 信息隐藏 数据抽象 抽象数据类型 重载函数和运算符重载
运算符重载的限制 不能改变对基本类型对象的操作语义 只有当用户自定义类型的对象为操作数时,运算符重载 才起作用 东南大学计算机学院 12/3/2018

26 内 容 回 顾 11.2两种运算符函数的比较 两种运算符函数 成员运算符函数:必须是non-static成员函数 全局运算符函数:一般为友元
东南大学计算机学院 12/3/2018

27 内 容 回 顾 成员运算符函数 一元运算符 二元运算符 左操作数必须为该类的对象 右操作数可以是任意类类型对象或基本类型对象
class A{ public: operator+(); }; A obj1; obj1.operator+( obj2 ) obj1.operator+( ) obj1 + obj2 + obj1 一元运算符 二元运算符 左操作数必须为该类的对象 右操作数可以是任意类类型对象或基本类型对象 适用情况 (有顺序) 本类对象 本类对象 + 自定义/基本类型对象 左操作数是其他类对象时,必须使用全局运算符函数

28 内 容 回 顾 全局函数运算符函数 顺序无关 + obj1 obj1 + obj2 operator+( obj1 )
operator+( obj1, obj2 ) obj1 + obj2 顺序无关 东南大学计算机学院 12/3/2018

29 11.2两种运算符函数的比较(10) 一元运算符重载 成员运算符函数 全局运算符函数 non-static成员函数 一般为友元函数 没有参数
一个参数 参数必须是该类对象或引用 obj1.operator+( ) + obj1 operator+( obj1 ) + obj1 东南大学计算机学院 12/3/2018

30 11.2两种运算符函数的比较(11) 一元运算符重载 成员运算符重载 全局运算符重载 class String { public:
bool operator!() const; ... }; // end class String bool operator!( const String & ); 东南大学计算机学院 12/3/2018

31 11.2两种运算符函数的比较(12) 二元运算符重载 成员运算符函数 全局运算符函数 non-static成员函数 一般为友元函数 一个参数
左操作数必须为本类 对象 全局运算符函数 一般为友元函数 两个参数 参数必须有一个自定义类 对象/引用 operator+( obj1, obj2 ) obj1 + obj2 obj1.operator+( obj2 ) obj1 + obj2 东南大学计算机学院 12/3/2018

32   11.2两种运算符函数的比较(13) 重载流插入/提取运算符 成员运算符函数 全局运算符函数 ostream &
cout << obj class A cout.operator<<( obj ) cout << obj operator<<( cout, obj ) 东南大学计算机学院 12/3/2018

33   11.2两种运算符函数的比较(14) 重载可交换的运算符 成员运算符函数 如果两个操作对象是同类型 全局运算符函数
obj1.operator+( obj2 ) obj1 + obj2 class A obj2.operator+( obj1 ) obj2 + obj1 class A 左操作数为本类对象 operator+( obj1, obj2 ) obj1 + obj2 class A operator+( obj2, obj1 ) obj2 + obj1 class A 东南大学计算机学院 12/3/2018

34  11.2两种运算符函数的比较(15) 可交换的运算符的重载 成员运算符函数 如果两个操作对象不是同类型 左操作数为A类对象
obj1.operator+( obj2 ) obj1 + obj2 class A class B obj2.operator+( obj1 ) obj2 + obj1 class B class A 左操作数为A类对象 左操作数不为本类对象 东南大学计算机学院 12/3/2018

35  11.2两种运算符函数的比较(16) 可交换的运算符的重载 全局运算符函数 如果两个操作对象不是同类型
operator+( obj1, obj2 ) obj1 + obj2 class A class B operator+( obj2, obj1 ) obj2 + obj1 class B class A operator+( A & a, B & b ){……} operator+( B & b, A & a ){ operator+( a, b ); } 12/3/2018

36 重载流插入/提取运算符 东南大学计算机学院 12/3/2018

37 11.3重载流插入/提取运算符(1) Phone number的输入输出 格式: (800) 555-1212
能够级联调用:cout<<obj1<<obj2 函数返回值类型:ostream & 重载方式 必须为全局运算符函数 code: Fig. 11.3~11.5 areaCode exchange line (800) How? 东南大学计算机学院 12/3/2018

38 (800) 555-1212 using namespace std areaCode exchange line
1 // Fig. 11.3: PhoneNumber.h 2 // PhoneNumber class definition 3 #ifndef PHONENUMBER_H 4 #define PHONENUMBER_H 5 6 #include <iostream> 7 using std::ostream; 8 using std::istream; 9 10 #include <string> 11 using std::string; 12 13 class PhoneNumber 14 { friend ostream & operator<<( ostream &, const PhoneNumber & ); friend istream & operator>>( istream &, PhoneNumber & ); 17 private: string areaCode; // 3-digit area code string exchange; // 3-digit exchange string line; // 4-digit line 21 }; // end class PhoneNumber 22 23 #endif using namespace std areaCode exchange line (800)

39 const PhoneNumber & vs. PhoneNumber
friend ostream & operator<<( ostream &, const PhoneNumber & ); cout << obj 此函数是不是成员函数? 友元函数不是类成员 重载方式:全局运算符函数 为何要声明为友元? 方便访问私有数据 函数返回值类型: ostream & 返回对象本身,可以级联调用 形参类型 ostream & vs. ostream const PhoneNumber & vs. PhoneNumber 传入cout对象本身,才能保证返回时是对象本身,才可以级联调用 传引用:不进行复制,节省时间和内存开销,效率高 限定为const:保证对象不被改变 12/3/2018 东南大学计算机学院

40 PhoneNumber & vs. PhoneNumber PhoneNumber & vs. const PhoneNumber &
friend istream & operator>>( istream &, PhoneNumber & ); 形参类型 PhoneNumber & vs. PhoneNumber PhoneNumber & vs. const PhoneNumber & cin需要改变对象的数据成员,因此既不能传入对象的副本,又不能限定为const 12/3/2018 东南大学计算机学院

41 (800) 555-1212 1 // Fig. 11.4: PhoneNumber.cpp
2 // Overloaded stream insertion and stream extraction operators 3 // for class PhoneNumber. 4 #include <iomanip> 5 using std::setw; 6 7 #include "PhoneNumber.h" 8 9 // overloaded stream insertion operator; cannot be 10 // a member function if we would like to invoke it with 11 // cout << somePhoneNumber; 12 ostream &operator<<( ostream &output, const PhoneNumber &number ) 13 { output << "(" << number.areaCode << ") " << number.exchange << "-" << number.line; return output; // enables cout << a << b << c; 17 } // end function operator<< 18 (800) 以全局函数实现重载 返回对象本身, 进行级联调用 12/3/2018 东南大学计算机学院

42 (800) 555-1212 19 // overloaded stream extraction operator; cannot be
20 // a member function if we would like to invoke it with 21 // cin >> somePhoneNumber; 22 istream &operator>>( istream &input, PhoneNumber &number ) 23 { input.ignore(); // skip ( input >> setw( 3 ) >> number.areaCode; // input area code input.ignore( 2 ); // skip ) and space input >> setw( 3 ) >> number.exchange; // input exchange input.ignore(); // skip dash (-) input >> setw( 4 ) >> number.line; // input line return input; // enables cin >> a >> b >> c; 31 } // end function operator>> 以全局函数实现重载 限定读入字符串数组的字符个数 返回对象本身, 进行级联调用 (800) 12/3/2018 东南大学计算机学院

43 2 // Demonstrating class PhoneNumber's overloaded stream insertion
1 // Fig. 11.5: fig11_05.cpp 2 // Demonstrating class PhoneNumber's overloaded stream insertion 3 // and stream extraction operators. 4 #include <iostream> 5 using std::cout; 6 using std::cin; 7 using std::endl; 8 9 #include "PhoneNumber.h" 10 11 int main() 12 { PhoneNumber phone; // create object phone 14 12/3/2018 东南大学计算机学院

44 18 // the global function call operator>>( cin, phone )
cout << "Enter phone number in the form (123) :" << endl; 16 // cin >> phone invokes operator>> by implicitly issuing // the global function call operator>>( cin, phone ) cin >> phone; 20 cout << "The phone number entered was: "; 22 // cout << phone invokes operator<< by implicitly issuing // the global function call operator<<( cout, phone ) cout << phone << endl; return 0; 27 } // end main 调用函数 operator>>(cin, phone) 调用函数 operator<<(cout, phone) 根据c++标准,任何不含return的语句,最后一个语句默认为”return 0;” Enter phone number in the form (123) : (800) The phone number entered was: (800) cin >> phone1 >> phone2 如何调用函数? operator>>(cin, phone1); operator>>(cin, phone2); 12/3/2018 东南大学计算机学院

45   11.3重载流插入/提取运算符(9) 返回引用:避免“虚悬引用”(dangling references)
虚悬引用:引用的对象不存在 从重载的流操作符返回的cin、cout等大部分stream对象 一般是全局的,返回引用一般会成功 避免返回自动变量和临时变量的引用 回顾 int & f(){ int a; return a; } int & f( int a ){ return a; } a是实参的副本,是临时变量 虚悬引用! 函数结束时,自动变量a被撤销 虚悬引用! 东南大学计算机学院 12/3/2018

46 动态内存管理 标准C++数组的限制 动态内存管理 C++的内存管理 operator new/delete 初始化 动态数组
东南大学计算机学院 12/3/2018

47 如果只能在运行时才能确定数组长度,怎么办?
11.4动态内存管理 标准C++数组的限制 回顾 数组长度固定不变 在编译时必须知道其长度 只在定义它的语句块内存在 数组长度: 必须是≥1的常量表达式 字面值常量 枚举常量 用常量表达式初始化的 整型const对象 不能是: non-const变量 运行阶段才能确定值的 const变量 int arr[10]; //ok const int size1 = 10; int arr1[size1]; //ok int arr1a[size1 + 1]; //ok int size2 = 5; int arr2[size2]; //error const int size3 = getSize(); int arr3[size3]; //error, not known until runtime 如果只能在运行时才能确定数组长度,怎么办?  动态内存管理 东南大学计算机学院 12/3/2018

48 动态内存管理 动态内存管理:在运行时动态管理内存 作用范围 动态内存管理操作 变量存储在自由存储区:堆区(heap) 内置类型
11.4动态内存管理 动态内存管理 动态内存管理:在运行时动态管理内存 作用范围 内置类型 用户自定义类类型 动态内存管理操作 new:申请内存 delete:释放内存 变量生命期:从new开始,delete结束 变量存储在自由存储区:堆区(heap) A *p = new A; delete p; 东南大学计算机学院 12/3/2018

49 C++的内存管理 程序段:程序的机器码和只读数据,只读操作 全局区:全局变量、static变量 堆区 :动态变量 栈区 :自动变量 程序段
11.4动态内存管理 C++的内存管理 程序段 全局区 (静态存储区) 内存低端 内存高端 堆区 (自由态存储区) 栈区 内存分配 内存回收 管理者 程序开始之前 程序开始之后 编译器 new delete 程序员 声明时 语句块或 函数体(}) 编译器 程序段:程序的机器码和只读数据,只读操作 全局区:全局变量、static变量 堆区 :动态变量 栈区 :自动变量 东南大学计算机学院 12/3/2018

50 如果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可使其不再指向自由区内存,可以避免虚悬指针 东南大学计算机学院 12/3/2018

51 初始化 基本类型变量 对象 默认的初始化:0或’\0’ 调用构造函数进行初始化 int *p1 = new int; //没有初始化
11.4动态内存管理 初始化 基本类型变量 默认的初始化:0或’\0’ 对象 调用构造函数进行初始化 int *p1 = new int; //没有初始化 int *p2 = new int(); //默认初始化为0 int *p3 = new int(3); //初始化为3 A *p = new A; A *p = new A(1,2); 调用默认构造函数A::A()初始化 调用A::A(int,int)初始化 东南大学计算机学院 12/3/2018

52 动态数组(1) 操作符 new [ ] delete [ ] new[ ]和delete[ ]必须配对使用!
11.4动态内存管理 动态数组(1) 操作符 new [ ] delete [ ] delete只调用第一个元素对象的析构函数 new[ ]和delete[ ]必须配对使用! int *p = new int[10]; //指向第1个元素 int p[] = new int[10]; //error delete p; //error delete[] p; //right 东南大学计算机学院 12/3/2018

53 动态数组(2) 数组长度可以在运行时确定 用new创建长度为0的数组合法 不能进行解引操作(*) 可以:比较、加减0,减去自身得0
11.4动态内存管理 动态数组(2) 数组长度可以在运行时确定 用new创建长度为0的数组合法 不能进行解引操作(*) 可以:比较、加减0,减去自身得0 int *p1, *p2, *p3; int s1 = 10; p1 = new int[s1]; //ok int s2 = getSize(); p2 = new int[s2]; //ok p3 = new int[0]; //ok 东南大学计算机学院 12/3/2018

54 动态数组(3) 初始化 基本数据类型变量作为元素 对象作为元素 默认的初始化:每个元素初始化为0或’\0’ 无法用一个参数值为所有元素初始化
11.4动态内存管理 动态数组(3) 初始化 基本数据类型变量作为元素 默认的初始化:每个元素初始化为0或’\0’ 无法用一个参数值为所有元素初始化 对象作为元素 无法为每个对象元素通过参数传递进行初始化 调用默认的构造函数为每个对象初始化 int a1[3] = {0,1,2}; int a1[4] = {0,1,2}; //ok, a[3]=0 int a3[]= {0,1,2}; //ok, length=3 int *p1 = new int[5]; //没有初始化 int *p2 = new int[5](); //默认初始化为0 int *p3 = new int[5](3); //error 东南大学计算机学院 12/3/2018

55 动态数组(4) 对象作为数组元素 无法为每个对象元素通过参数传递进行初始化 调用默认的构造函数为每个对象初始化 析构 delete[ ]
11.4动态内存管理 动态数组(4) 对象作为数组元素 无法为每个对象元素通过参数传递进行初始化 调用默认的构造函数为每个对象初始化 析构 delete[ ] 调用每个对象的析构函数 delete只调用第一个对象的析构函数 东南大学计算机学院 12/3/2018

56 动态数组(5) const对象作为数组元素 基本类型对象必须初始化 类类型对象通过默认构造函数初始化
11.4动态内存管理 动态数组(5) const对象作为数组元素 基本类型对象必须初始化 类类型对象通过默认构造函数初始化 const int *p1 = new const int[5]; //error, 没有初始化 const int *p2 = new const int[5](); //默认初始化为0 东南大学计算机学院 12/3/2018

57 内容回顾 两种运算符函数的比较 必须为成员运算符函数 必须为全局运算符函数 成员运算符:左操作是必须是该类的对象
全局运算符函数:一般声明为友元 必须为成员运算符函数 ( ), [ ], -> 必须为全局运算符函数 流插入<<和提取运算符>> 可交换的运算符 左操作数不是该类对象 东南大学计算机学院 12/3/2018

58 内容回顾 重载流插入<<和提取运算符>> 避免虚悬引用 必须为全局运算符函数
要实现运算符级联调用,函数的形参类型和返回类 型需为引用 避免虚悬引用 返回局部对象和临时对象的引用是危险的 东南大学计算机学院 12/3/2018

59 内容回顾 动态内存管理 动态数组 new:申请内存 delete:释放内存 必须成对使用 new[]:申请内存 delete[]:释放内存
若元素为对象则自动调用默认的构造函数为每个对象初始化 东南大学计算机学院 12/3/2018

60 实例研究:Array类 基于指针的数组的缺点 Array类的功能 Array类实现 总结 东南大学计算机学院 12/3/2018

61 基于指针的数组的缺点 数组名就是指向第一元素的const指针 缺点 下标必须是0,…,n-1 不能进行越界检查
11.5实例研究:Array类 基于指针的数组的缺点 数组名就是指向第一元素的const指针 缺点 下标必须是0,…,n-1 不能进行越界检查 不能对数组进行一次性输入/输出 只能针对单个元素分别进行 不能对两个数据进行有意义的比较运算(>, <) 作为参数传递时,必须传递数组长度 不能使用赋值运算符(=)将一个数组赋值给另一个数组 int a[3]; a a[0] a[1] a[2] 数组名是const指针,不能用作左值 东南大学计算机学院 12/3/2018

62 Array类的功能 重载<<和>>:实现数组整体的输入输出 重载赋值运算符=:将一个数组对象赋值给另一个数组 对象
重载关系运算符==:比较两个数组对象是否相同 重载关系运算符!=:比较两个数组对象是否不同 重载下标运算符[ ]: 可以进行越界检查 东南大学计算机学院 12/3/2018

63 Array类代码 using namespace std; Array.h (part 1) 1 // Fig. 11.6: Array.h
2 // Array class for storing arrays of integers. 3 #ifndef ARRAY_H 4 #define ARRAY_H 5 6 #include <iostream> 7 using std::ostream; 8 using std::istream; 9 10 class Array 11 { …… 34 private: 35 int size; // pointer-based array size 36 int *ptr; // pointer to first element of pointer-based array 37 }; 38 39 #endif using namespace std; 东南大学计算机学院 12/3/2018

64 10 class Array 11 { 12 friend ostream &operator<<( ostream &, const Array & ); 13 friend istream &operator>>( istream &, Array & ); 14 public: 15 Array( int = 10 ); // default constructor 16 Array( const Array & ); // copy constructor 17 ~Array(); // destructor 18 int getSize() const; // return size 19 20 const Array &operator=( const Array & ); // assignment operator 21 bool operator==( const Array & ) const; // equality operator 22 23 // inequality operator; returns opposite of == operator 24 bool operator!=( const Array &right ) const 25 { 26 return ! ( *this == right ); // invokes Array::operator== 27 } // end function operator!= 28 29 // subscript operator for non-const objects returns modifiable lvalue 30 int &operator[]( int ); …… 37 }; Array.h (part 2) 以何种方式重载运算符? 为什声明为friend? 返回值类型为什是ostream &或istream &? 传对象和传引用的区别? 函数operator<<的第2个形参是否可以是Array?const限定符有什么作用 ? 函数operator>>的第2个形参是否可以是Array?是否可以用const限定 ? 全局函数 可以直接访问对象的所有数据成员 可以进行函数的级联调用cout<<a<<b 前者传递副本,后者传递实参对象本身 可以,但是由于产生副本开销大。const限定符传入的实参对象不可改变。 均不可以,因为要改变对象

65 118 istream &operator>>( istream &input, Array &a ){
for ( int i = 0; i < a.size; i++ ) input >> a.ptr[ i ]; 122 return input; // enables cin >> x >> y; 124 } Array.cpp 127 ostream &operator<<( ostream &output, const Array &a ){ int i; 130 // output private ptr-based array for ( i = 0; i < a.size; i++ ) { output << setw( 12 ) << a.ptr[ i ]; 135 if ( ( i + 1 ) % 4 == 0 ) // 4 numbers per row of output output << endl; } // end for 139 if ( i % 4 != 0 ) // end last line of output output << endl; 142 return output; // enables cout << x << y; 144 } 12/3/2018 东南大学计算机学院

66 10 class Array 11 { 12 friend ostream &operator<<( ostream &, const Array & ); 13 friend istream &operator>>( istream &, Array & ); 14 public: 15 Array( int = 10 ); // default constructor 16 Array( const Array & ); // copy constructor 17 ~Array(); // destructor 18 int getSize() const; // return size 19 20 const Array &operator=( const Array & ); // assignment operator 21 bool operator==( const Array & ) const; // equality operator 22 23 // inequality operator; returns opposite of == operator 24 bool operator!=( const Array &right ) const 25 { 26 return ! ( *this == right ); // invokes Array::operator== 27 } // end function operator!= 28 29 // subscript operator for non-const objects returns modifiable lvalue 30 int &operator[]( int ); …… 37 }; Array.h (part 2) 默认值是否可以在函数定义中指定? 不可以,只能在声明处指定 18 Array::Array( int arraySize ) 19 { size = ( arraySize > 0 ? arraySize : 10 ); // validate arraySize ptr = new int[ size ]; // create space for pointer-based array 22 for ( int i = 0; i < size; i++ ) ptr[ i ] = 0; // set pointer-based array element } Array.cpp

67 Array::Array(Array arrayToCopy )
29 Array::Array( const Array &arrayToCopy ) : size( arrayToCopy.size ) 31 { ptr = new int[ size ]; // create space for pointer-based array 33 for ( int i = 0; i < size; i++ ) ptr[ i ] = arrayToCopy.ptr[ i ]; // copy into object } Array.cpp 拷贝构造函数的作用? 何时调用拷贝构造函数? 拷贝构造函数的参数类型为什么是const Array & ?可以是Array 吗? 通过建立一个现有对象的副本来初始化一个Array对象 默认的赋值运算符=不自动调用拷贝构造函数 需要创建对象副本时 按值传递参数时 (函数形参/函数返回值) 用对象的副本初始化另一对象时 const引用不需要拷贝const对象 不可以是Array,若为Array: Array::Array(Array arrayToCopy ) 需要创建实参的副本(拷贝),此时需要调用拷贝构造函数,即该函数本身,造成无穷递归!

68 29 Array::Array( const Array &arrayToCopy )
: size( arrayToCopy.size ) 31 { ptr = new int[ size ]; // create space for pointer-based array 33 for ( int i = 0; i < size; i++ ) ptr[ i ] = arrayToCopy.ptr[ i ]; // copy into object } Array.cpp 4. 是否可以直接拷贝指针?ptr=&arrayToCopy.ptr 不可以。当传入的参数被析构时,ptr成为虚悬指针!

69 10 class Array 11 { 12 friend ostream &operator<<( ostream &, const Array & ); 13 friend istream &operator>>( istream &, Array & ); 14 public: 15 Array( int = 10 ); // default constructor 16 Array( const Array & ); // copy constructor 17 ~Array(); // destructor 18 int getSize() const; // return size 19 20 const Array &operator=( const Array & ); // assignment operator 21 bool operator==( const Array & ) const; // equality operator 22 23 // inequality operator; returns opposite of == operator 24 bool operator!=( const Array &right ) const 25 { 26 return ! ( *this == right ); // invokes Array::operator== 27 } // end function operator!= 28 29 // subscript operator for non-const objects returns modifiable lvalue 30 int &operator[]( int ); …… 37 }; Array.h (part 2) getSize()函数为什么是const类型?const限定符放在返回值类型前可以吗? 是一个get函数,不需要修改对象的值,根据最小权限原则,应该实现为const成员函数。不可以,否则限定函数返回值的类型为const。 39 Array::~Array() 40 { delete [] ptr; // release pointer-based array space 42 } 45 int Array::getSize() const 46 { return size; 48 } Array.cpp

70 10 class Array 11 { 12 friend ostream &operator<<( ostream &, const Array & ); 13 friend istream &operator>>( istream &, Array & ); 14 public: 15 Array( int = 10 ); // default constructor 16 Array( const Array & ); // copy constructor 17 ~Array(); // destructor 18 int getSize() const; // return size 19 20 const Array &operator=( const Array & ); // assignment operator 21 bool operator==( const Array & ) const; // equality operator 22 23 // inequality operator; returns opposite of == operator 24 bool operator!=( const Array &right ) const 25 { 26 return ! ( *this == right ); // invokes Array::operator== 27 } // end function operator!= 28 29 // subscript operator for non-const objects returns modifiable lvalue 30 int &operator[]( int ); …… 37 }; Array.h (part 2)

71 52 const Array &Array::operator=( const Array &right ) 53 {
53 { if ( &right != this ) // avoid self-assignment { // for Arrays of different sizes, deallocate original // left-side array, then allocate new left-side array if ( size != right.size ) { delete [] ptr; // release space size = right.size; // resize this object ptr = new int[ size ]; // create space for array copy } // end inner if 64 for ( int i = 0; i < size; i++ ) ptr[ i ] = right.ptr[ i ]; // copy array into object } // end outer if 68 return *this; // enables x = y = z, for example 70 } Array.cpp 2. 为什么返回类型是const引用? 避免(a=b)=c. x对象调用该函数,虽然返回值类型是const引用,但对x没影响。 这里返回值类型指示赋值表达式的值的类型 为什么要检测自我赋值? 若为自我赋值,则在赋值前,该对象的内存就被释放,导致致命的运行时错误 12/3/2018 东南大学计算机学院

72 10 class Array 11 { 12 friend ostream &operator<<( ostream &, const Array & ); 13 friend istream &operator>>( istream &, Array & ); 14 public: 15 Array( int = 10 ); // default constructor 16 Array( const Array & ); // copy constructor 17 ~Array(); // destructor 18 int getSize() const; // return size 19 20 const Array &operator=( const Array & ); // assignment operator 21 bool operator==( const Array & ) const; // equality operator 22 23 // inequality operator; returns opposite of == operator 24 bool operator!=( const Array &right ) const 25 { 26 return ! ( *this == right ); // invokes Array::operator== 27 } // end function operator!= 28 29 // subscript operator for non-const objects returns modifiable lvalue 30 int &operator[]( int ); …… 37 }; Array.h (part 2) 内联函数 复用性

73 74 bool Array::operator==( const Array &right ) const 75 {
75 { if ( size != right.size ) return false; // arrays of different number of elements 78 for ( int i = 0; i < size; i++ ) if ( ptr[ i ] != right.ptr[ i ] ) return false; // Array contents are not equal 82 return true; // Arrays are equal 84 } // end function operator== Array.cpp 12/3/2018 东南大学计算机学院

74 10 class Array 11 { 12 friend ostream &operator<<( ostream &, const Array & ); 13 friend istream &operator>>( istream &, Array & ); 14 public: 15 Array( int = 10 ); // default constructor 16 Array( const Array & ); // copy constructor 17 ~Array(); // destructor 18 int getSize() const; // return size 19 20 const Array &operator=( const Array & ); // assignment operator 21 bool operator==( const Array & ) const; // equality operator 22 23 // inequality operator; returns opposite of == operator 24 bool operator!=( const Array &right ) const …… 28 29 // subscript operator for non-const objects returns modifiable lvalue int &operator[]( int ); 32 // subscript operator for const objects returns rvalue int operator[]( int ) const; … … 37 }; Array.h (part 2) 两者的关系和区别 为什么需要重载为const成员函数?const对象能否调用30行的函数? 为什么const函数返回rvalue? 它们是重载函数 前者被一般对象调用,后者被const对象调用 前者返回lvalue,后者返回rvalue 不能,const对象只能调用const成员函数 可以保证const对象不能被赋值

75 88 int &Array::operator[]( int subscript ) 89 {
89 { // check for subscript out-of-range error if ( subscript < 0 || subscript >= size ) { cerr << "\nError: Subscript " << subscript << " out of range" << endl; exit( 1 ); // terminate program; subscript out of range } // end if 97 return ptr[ subscript ]; // reference return 99 } Array.cpp 进行越界检查 #include <cstdlib> using std::exit; 103 int Array::operator[]( int subscript ) const 104 { // check for subscript out-of-range error if ( subscript < 0 || subscript >= size ) { cerr << "\nError: Subscript " << subscript << " out of range" << endl; exit( 1 ); // terminate program; subscript out of range } // end if 112 return ptr[ subscript ]; // returns copy of this element 114 } 12/3/2018 东南大学计算机学院

76 2 // Array class test program. 3 #include <iostream>
1 // Fig. 11.8: fig11_08.cpp 2 // Array class test program. 3 #include <iostream> 4 using std::cout; 5 using std::cin; 6 using std::endl; 7 8 #include "Array.h" 9 10 int main() 11 { Array integers1( 7 ); // seven-element Array Array integers2; // 10-element Array by default 14 // print integers1 size and contents cout << "Size of Array integers1 is " << integers1.getSize() << "\nArray after initialization:\n" << integers1; 19 // print integers2 size and contents cout << "\nSize of Array integers2 is " << integers2.getSize() << "\nArray after initialization:\n" << integers2; Size of Array integers1 is 7 Array after initialization: Size of Array integers2 is 10 调用: operator<<( cout, integers1 ); 调用: operator<<( cout, integers2 ); 12/3/2018 东南大学计算机学院

77 级联调用: operator>>( cint, integers1 );
Enter 17 integers: After input, the Arrays contain: integers1: integers2: Evaluating: integers1 != integers2 integers1 and integers2 are not equal 级联调用: operator>>( cint, integers1 ); operator>>( cint, integers2 ); // input and print integers1 and integers2 cout << "\nEnter 17 integers:" << endl; cin >> integers1 >> integers2; 28 cout << "\nAfter input, the Arrays contain:\n" << "integers1:\n" << integers1 << "integers2:\n" << integers2; 32 // use overloaded inequality (!=) operator cout << "\nEvaluating: integers1 != integers2" << endl; 35 if ( integers1 != integers2 ) cout << "integers1 and integers2 are not equal" << endl; 级联调用: operator<<(cout, integers1 ); operator<<(cout, integers2 ); 调用: integers1 .operator!=(integers2 ); 12/3/2018 东南大学计算机学院

78 39 // create Array integers3 using integers1 as an
// initializer; print size and contents Array integers3( integers1 ); // invokes copy constructor 42 cout << "\nSize of Array integers3 is " << integers3.getSize() << "\nArray after initialization:\n" << integers3; 46 // use overloaded assignment (=) operator cout << "\nAssigning integers2 to integers1:" << endl; integers1 = integers2; // note target Array is smaller 50 cout << "integers1:\n" << integers1 << "integers2:\n" << integers2; 53 // use overloaded equality (==) operator cout << "\nEvaluating: integers1 == integers2" << endl; 56 if ( integers1 == integers2 ) cout << "integers1 and integers2 are equal" << endl; 若为: Array integers3 = integers1 ; 同样调用拷贝构造函数,而非赋值运算符函数 Size of Array integers3 is 7 Array after initialization: Assigning integers2 to integers1: integers1: integers2: Evaluating: integers1 == integers2 integers1 and integers2 are equal 调用: integers1 .operator=(integers2 ); 调用: integers1 .operator==(integers2 ); 12/3/2018 东南大学计算机学院

79 60 // use overloaded subscript operator to create rvalue
cout << "\nintegers1[5] is " << integers1[ 5 ]; 62 // use overloaded subscript operator to create lvalue cout << "\n\nAssigning 1000 to integers1[5]" << endl; integers1[ 5 ] = 1000; cout << "integers1:\n" << integers1; 67 // attempt to use out-of-range subscript cout << "\nAttempt to assign 1000 to integers1[15]" << endl; integers1[ 15 ] = 1000; // ERROR: out of range return 0; 72 } // end main 调用: integers1 .operator[](5); 返回左值 integers1[5] is 13 Assigning 1000 to integers1[5] integers1: Attempt to assign 1000 to integers1[15] Error: Subscript 15 out of range 12/3/2018 东南大学计算机学院

80 总结(1) 关于动态内存管理 通常会为需要动态内存管理的类同时提供 若无拷贝构造函数和重载的赋值运算符 若无析构函数
11.5实例研究:Array类 总结(1) 关于动态内存管理 通常会为需要动态内存管理的类同时提供 拷贝构造函数(需要分配内存) 析构函数(需要释放内存) 重载的赋值运算符(需要删除原有内存并重新分配内存) 若无拷贝构造函数和重载的赋值运算符 执行操作:默认的逐个成员赋值 指向动态内存的指针指向同一地址,并造成内存泄漏 若无析构函数 不能自动释放动态内存区的变量 a.ptr b.ptr 东南大学计算机学院 12/3/2018

81 总结(2) 成员函数为private 重载的赋值运算符函数为private:阻止对象赋值 拷贝构造函数为private:阻止对象拷贝
11.5实例研究:Array类 总结(2) 成员函数为private 重载的赋值运算符函数为private:阻止对象赋值 拷贝构造函数为private:阻止对象拷贝 无法调用默认的赋值运算函数,它被重载 无法调用默认的拷贝构造函数,它被重载 东南大学计算机学院 12/3/2018

82 类型转换 何时需要类型转换 基本类型的类型转换 用户自定义类型的类型转换 转换构造函数 转换运算符 东南大学计算机学院 12/3/2018

83 何时需要类型转换 赋值 计算 参数传递 … … 11.6 类型转换 int a; a = 3.2; a = 3 double int
double int + double + double void f( int ){}; f( 2.0 ); f( 2 ) double int 东南大学计算机学院 12/3/2018

84 基本类型的类型转换 隐式:由编译器自动进行类型转换 显式:由程序员强制进行类型转换 1:int  double
11.6 类型转换 基本类型的类型转换 隐式:由编译器自动进行类型转换 显式:由程序员强制进行类型转换 1:int  double (int)3.0; 3.0:double  int 东南大学计算机学院 12/3/2018

85 用户自定义类型的类型转换 两种类型转换方式 编译器不知道怎样进行类型转换,必须由程序员显式指 定 转换构造函数: 转换运算符:
11.6 类型转换 用户自定义类型的类型转换 编译器不知道怎样进行类型转换,必须由程序员显式指 定 两种类型转换方式 转换构造函数: 转换运算符: 其他类型对象  本类对象 本类对象  其他类型对象 东南大学计算机学院 12/3/2018

86 转换构造函数(1) 转换构造函数(conversion constructors) 定义:任何单实参的构造函数
11.6 类型转换 转换构造函数(1) 转换构造函数(conversion constructors) 定义:任何单实参的构造函数 作用:其他类型对象  本类对象 A::A(string); A a(“hello”); a = “hello”; A string a = b A A b(“Hello”) 调用A::A(string)创建 临时对象b 何时调用? 调用重载的赋值运算符 A A::operator=(A) 或者,定义重载的赋值运算符: A A::operator=(string) 东南大学计算机学院 12/3/2018

87 转换构造函数(2) 转换构造函数的使用 隐式转换 显式转换 转换构造函数的形参类型不必与实参类型完全匹配
11.6 类型转换 转换构造函数(2) 转换构造函数的使用 隐式转换 显式转换 SmallInt::SmallInt(int); void f(SmallInt); short v; f(v); 应用了标准类型转换 v  int  SmallInt SmallInt(3); SmallInt(3.2); 3.2  int  SmallInt 转换构造函数的形参类型不必与实参类型完全匹配 东南大学计算机学院 12/3/2018

88 explicit构造函数(1) 转换构造函数:任何单实参的构造函数 explicit关键字的作用是禁止使用转换构造函数进 行隐式类型转换
11.6 类型转换 explicit构造函数(1) 转换构造函数:任何单实参的构造函数 有时会在无意之中将单参数构造函数用作转换构造函数进行隐式的类型转换,但这并不是我们希望发生的,因此需要防止 explicit关键字的作用是禁止使用转换构造函数进 行隐式类型转换 东南大学计算机学院 12/3/2018

89 explicit构造函数(2) explicit关键字的作用是禁止使用转换构造函数进 行隐式类型转换 explicit关键字只能用于构造函数
11.6 类型转换 explicit构造函数(2) explicit关键字的作用是禁止使用转换构造函数进 行隐式类型转换 explicit关键字只能用于构造函数 只在声明处使用,定义处不再出现 需要使用explicit构造函数进行类型转换时必须显式转换 class Array{ public: explicit Array(int); }; Array::Array(int a){……} Array a; f(a); //ok f(3); //error,不会隐式的将 //3转换为Array类对象 f(Array(3)); //ok,显式调用 //转换构造函数 void f(Array &); 东南大学计算机学院 12/3/2018

90 运算符函数的个数 = 3×运算符个数×其他类类型个数
11.6 类型转换 转换运算符(1) 为什么需要将本类转换为其他类类型? SmallInt实现安全小整数0~255,可以进行数据的有效性检查 运算符:+,-,*, /, %, <, <=, >, >=, == != 若以重载运算符实现SmallInt和int类型的上述算术运算 需对每个运算符定义三个实例,共11×3=33个运算符函数 int operator+(int, const SmallInt &); int operator+(const SmallInt &, int); int operator+(const SmallInt &, const SmallInt &); 若考虑SmallInt和int、float、double等其他不同类型的上述算 术运算,共33×3=99个运算符函数 运算符函数的个数 = 3×运算符个数×其他类类型个数 组合爆炸! 东南大学计算机学院 12/3/2018

91 转换运算符(2) 为什么需要将本类转换为其他类类型? 类型转换可减少运算符函数的数目
11.6 类型转换 转换运算符(2) 为什么需要将本类转换为其他类类型? SmallInt实现安全小整数0~255,可以进行数据的有效性检查 运算符:+,-,*, /, %, <, <=, >, >=, == != 若以类型转换实现,只需定义SmallInt类到int类型的转换 s + 3 3 + s s - 3 3 - s s s s – 3.14 s int int double int和int类型的运算 1. s转换为int类型 2. 编译器将int类型自动转换 为double类型 类型转换可减少运算符函数的数目 东南大学计算机学院 12/3/2018

92 A::operator char *( ) const;
11.6 类型转换 转换运算符(3) 转换运算符(conversion/cast operator) 是一种特殊的类成员函数 作用:本类对象  其他类型对象 A  char * 函数没有返回类型 形参表必须为空 A::operator char *( ) const; A类的non-static成员函数 实际返回类型定义为运算符 一般为const 不修改实参 东南大学计算机学院 12/3/2018

93 转换运算符(4) 转换运算符的使用 隐式转换   cout << s cout<<(char *) A
11.6 类型转换 转换运算符(4) 转换运算符的使用 隐式转换 cout << s cout<<(char *) cout << c A char * 调用 s.operator char *() 临时对象c 或者,定义重载的<<运算符符: friend ostream & operator<<(ostream &, const A &) 东南大学计算机学院 12/3/2018

94 转换运算符(5) 转换运算符的使用 隐式转换 显式转换 SmallInt s; double v; s >= v;
11.6 类型转换 转换运算符(5) 转换运算符的使用 隐式转换 显式转换 SmallInt s; double v; s >= v; s  int  double 应用了标准类型转换 if(s); s  int  bool int calc(int); int i1 = calc(s); s  int 转换的目标类型不必与所需的类型完全匹配 int i2 = static_cast<int>(s) + 1; s  int 东南大学计算机学院 12/3/2018

95  转换运算符(6) 一次只能应用一个类类型转换   int f(int); Integral intVal;
11.6 类型转换 转换运算符(6) 一次只能应用一个类类型转换 Integral SmallInt int int f(int); Integral intVal; SmallInt s(intVal); //ok int i = f(s); //ok int j = f(intVal); //error Integral  SmallInt, then copy to s SmallInt  int no Integral  int 东南大学计算机学院 12/3/2018

96 内容回顾 类型转换 转换构造函数:将其他类对象转换为本类对象 转换运算符:将本类对象转换为其他类对象
任何但参数的构造函数 explicit构造函数禁止使用转换构造函数进行隐式类型转 换 转换运算符:将本类对象转换为其他类对象 是一类特殊的成员函数 不管是转换构造函数还是转换运算符,需要的类型 和实际使用的类型可以不完全匹配,此时利用标准 的内置类型转换完成转换通路 东南大学计算机学院 12/3/2018

97 实例研究:String类 一些说明 程序解读 东南大学计算机学院 12/3/2018

98 C风格字符串(1) C风格字符串 以空字符null(‘\0’)结尾的字符数组 char c1[] = {‘C’,’+’,’+’};
11.7 实例研究:String类 C风格字符串(1) C风格字符串 以空字符null(‘\0’)结尾的字符数组 char c1[] = {‘C’,’+’,’+’}; //是一个字符数组,但不是C风格字符串 char c2[] = {‘C’,’+’,’+’,’\0’}; char c3[] = “C++”; //自动在末尾加入’\0’ char *cp = “C++”; //自动在末尾加入’\0’ 东南大学计算机学院 12/3/2018

99 C风格字符串(2) C风格字符串的标准库函数 #include <cstring>
strlen(s):返回s的长度,不包含null char c2[] = {‘C’,’+’,’+’,’\0’}; strlen(c2) = 3; char c3[] = “C++”; //自动在末尾加入’\0’ strlen(c3) = 3; 东南大学计算机学院 12/3/2018

100 C风格字符串(3) strcmp(s1,s2):比较字符串s1和s2是否相同 0:s1和s2相等 1:s1 > s2
11.7 实例研究:String类 C风格字符串(3) strcmp(s1,s2):比较字符串s1和s2是否相同 0:s1和s2相等 1:s1 > s2 -1: s1 < s2 根据第一个不同的字符进行判断 > 还是 < 关系 ‘\0’ ‘A’ ‘B’ ‘a’ ‘b’ 65 66 97 98 (int)’a’ 东南大学计算机学院 12/3/2018

101 C风格字符串(4) strcpy(s1,s2):将s2复制给s1,返回s1
11.7 实例研究:String类 C风格字符串(4) strcpy(s1,s2):将s2复制给s1,返回s1 strncpy(s1,s2,n):将s2的前n个字符复制给s1,返 回s1 strcpy(s1,s2,5) s2 H E L O W R D \0 s1 H E L O \0 东南大学计算机学院 12/3/2018

102 C风格字符串(5) strcat(s1,s2):将s2连接到s1后,返回s1
11.7 实例研究:String类 C风格字符串(5) strcat(s1,s2):将s2连接到s1后,返回s1 strncpy(s1,s2,n):将s2的前n个字符连接到s1后, 返回s1 s1 H E L O \0 s2 W O R L D \0 s1 H E L O W R D \0 strncpy(s1,s2,3) s1 H E L O \0 s2 W O R L D \0 s1 H E L O W R \0 东南大学计算机学院 12/3/2018

103 String类定义(1) 使用动态数组 Fig. 11.9 new [ ] delete [ ] sPtr 10 class String
   11 { … …    54 private:    55    int length; // string length (not counting null terminator)    56    char *sPtr; // pointer to start of pointer-based string    57     59 }; 东南大学计算机学院 12/3/2018

104 String类定义(2) 10 class String 11 {
   11 {    12    friend ostream &operator<<( ostream &, const String & );    13    friend istream &operator>>( istream &, String & );    14 public:    15    String( const char * = "" ); // conversion/default constructor    16    String( const String & ); // copy constructor    17    ~String(); // destructor    18     19    const String &operator=( const String & ); // assignment operator    20    const String &operator+=( const String & ); // concatenation operator    21     22    bool operator!() const; // is String empty?    23    bool operator==( const String & ) const; // test s1 == s2    24    bool operator<( const String & ) const; // test s1 < s2 注意参数和返回值类型 不再需要重载的赋值运算符: const string & operator=(const char *) String s = “Hello”: 1.调用转换构造函数得到String类的临时对象; 2.String对象间进行赋值运算 东南大学计算机学院 12/3/2018

105 String类定义(3) 26 // test s1 != s2
   27    bool operator!=( const String &right ) const    28    {    29       return !( *this == right );    30    } // end function operator!=    31     32    // test s1 > s2    33    bool operator>( const String &right ) const    34    {    35       return right < *this;    36    } // end function operator>    37     38    // test s1 <= s2    39    bool operator<=( const String &right ) const    40    {    41       return !( right < *this );    42    } // end function operator <= 代码的复用性 东南大学计算机学院 12/3/2018

106 11.7 实例研究:String类 String类定义(4) 50    char &operator[]( int ); // subscript operator (modifiable lvalue)    51    char operator[]( int ) const; // subscript operator (rvalue)   52    String operator()( int, int = 0 ) const; // return a substring    53    int getLength() const; // return string length    54 private:    55    int length; // string length (not counting null terminator)    56    char *sPtr; // pointer to start of pointer-based string    57     58    void setString( const char * ); // utility function    59 }; // end class String 成员函数可以有const的重载函数 东南大学计算机学院 12/3/2018

107 String类实现(1) Fig. 11.10 3 #include <iostream> 4 using std::cerr;
    5 using std::cout;     6 using std::endl;     7      8 #include <iomanip>     9 using std::setw;    10     11 #include <cstring> // strcpy and strcat prototypes    12 using std::strcmp;    13 using std::strcpy;    14 using std::strcat;    15     16 #include <cstdlib> // exit prototype    17 using std::exit;    18     19 #include "String.h" // String class definition 东南大学计算机学院 12/3/2018

108 11.7 实例研究:String类 String类实现(2)    21 // conversion (and default) constructor converts char * to String    22 String::String( const char *s )    23    : length( ( s != 0 ) ? strlen( s ) : 0 )    24 {    25    cout << "Conversion (and default) constructor: " << s << endl;    26    setString( s ); // call utility function    27 } // end String conversion constructor 成员初始化列表 指针为0表示不指向任何地址,如果不进行判断会出错   159 void String::setString( const char *string2 )   160 {   161    sPtr = new char[ length + 1 ]; // allocate memory   162    163    if ( string2 != 0 ) // if string2 is not null pointer, copy contents   164       strcpy( sPtr, string2 ); // copy literal to object   165    else // if string2 is a null pointer, make this an empty string   166       sPtr[ 0 ] = '\0'; // empty string   167 } // end function setString 动态内存管理 数组以’\0’结尾 东南大学计算机学院 12/3/2018

109 String类实现(3) 29 // copy constructor
   30 String::String( const String &copy )    31    : length( copy.length )    32 {    33    cout << "Copy constructor: " << copy.sPtr << endl;    34    setString( copy.sPtr ); // call utility function    35 } // end String copy constructor    36     37 // Destructor    38 String::~String()    39 {    40    cout << "Destructor: " << sPtr << endl;    41    delete [] sPtr; // release pointer-based string memory    42 } // end ~String destructor 成员初始化列表 是否可以直接复制指针? sPtr=copy.sPtr 不可以,否则导致内存泄漏和错误的修改对象 obj1 sPtr length obj2 特别要注意: 动态数组的内存要在析构函数中显式释放 东南大学计算机学院

110 String类实现(4) 44 // overloaded = operator; avoids self assignment
   45 const String &String::operator=( const String &right )    46 {    47    cout << "operator= called" << endl;    48     49    if ( &right != this ) // avoid self assignment    50    {            51       delete [] sPtr; // prevents memory leak    52       length = right.length; // new String length    53       setString( right.sPtr ); // call utility function    54    } // end if    55    else    56       cout << "Attempted assignment of a String to itself" << endl;    57     58    return *this; // enables cascaded assignments    59 } // end function operator= 注意返回类型 avoid (a=b)=c 在使用动态内存管理时,定义赋值运算符时必须进行自我赋值的检查,避免错误释放内存 东南大学计算机学院 12/3/2018

111 11.7 实例研究:String类 String类实现(5)    61 // concatenate right operand to this object and store in this object    62 const String &String::operator+=( const String &right )    63 {    64    size_t newLength = length + right.length; // new length    65    char *tempPtr = new char[ newLength + 1 ]; // create memory    66     67    strcpy( tempPtr, sPtr ); // copy sPtr    68    strcpy( tempPtr + length, right.sPtr ); // copy right.sPtr    69     70    delete [] sPtr; // reclaim old space    71    sPtr = tempPtr; // assign new array to sPtr    72    length = newLength; // assign new length to length    73    return *this; // enables cascaded calls    74 } // end function operator+= 注意返回类型 avoid (a+=b)+=c String s1, s2; s1 += s2; s1 += “abc”; //ok,调用转换构造函数 12/3/2018

112 String类实现(6) this.sPtr right.sPtr length right.length tempPtr+length
O \0 W O R L D \0 right.length tempPtr+length tempPtr H E L O \0 right.length length length + right.length + 1 W O R L D \0 东南大学计算机学院 12/3/2018

113 String类实现(7) 76 // is this String empty?
   77 bool String::operator!() const    78 {    79    return length == 0;    80 } // end function operator!    81     82 // Is this String equal to right String?    83 bool String::operator==( const String &right ) const    84 {    85    return strcmp( sPtr, right.sPtr ) == 0;    86 } // end function operator==    87     88 // Is this String less than right String?    89 bool String::operator<( const String &right ) const    90 {    91    return strcmp( sPtr, right.sPtr ) < 0;    92 } // end function operator< 根据最小权限原则 东南大学计算机学院 12/3/2018

114 在String类中下标运算符返回左值是危险的,可以在任何地方插入’\0’
运算符[ ]必须作为成员函数重载    94 // return reference to character in String as a modifiable lvalue    95 char &String::operator[]( int subscript )    96 {    97    // test for subscript out of range    98    if ( subscript < 0 || subscript >= length )    99    {   100       cerr << "Error: Subscript " << subscript   101          << " out of range" << endl;   102       exit( 1 ); // terminate program   103    } // end if   104    105    return sPtr[ subscript ]; // non-const return; modifiable lvalue   106 } // end function operator[] 返回左值,注意返回类型 下标的有效性检查 在String类中下标运算符返回左值是危险的,可以在任何地方插入’\0’ 东南大学计算机学院 12/3/2018

115 String类实现(9) 运算符[ ]必须作为成员函数重载
  108 // return reference to character in String as rvalue   109 char String::operator[]( int subscript ) const   110 {   111    // test for subscript out of range   112    if ( subscript < 0 || subscript >= length )   113    {   114       cerr << "Error: Subscript " << subscript   115            << " out of range" << endl;   116       exit( 1 ); // terminate program   117    } // end if   118    119    return sPtr[ subscript ]; // returns copy of this element   120 } // end function operator[] 返回右值,注意返回类型 实现和前面的相同, 但是返回类型不同 const对象调用: cosnt成员函数 东南大学计算机学院 12/3/2018

116 函数调用运算符运算符( )可以有任意数量的参数
11.7 实例研究:String类 String类实现(10) 函数调用运算符运算符( )必须作为成员函数重载 函数调用运算符运算符( )可以有任意数量的参数 122 // return a substring beginning at index and of length subLength   123 String String::operator()( int index, int subLength ) const   124 {   125    // if index is out of range or substring length < 0,   126    // return an empty String object   127    if ( index < 0 || index >= length || subLength < 0 )    128       return ""; // converted to a String object automatically   129    130    // determine length of substring   131    int len;   132    133    if ( ( subLength == 0 ) || ( index + subLength > length ) )   134       len = length - index;   135    else   136       len = subLength; 根据最小权限原则 注意返回类型 有效性检查 如果子串的长度不足或超出有效范围,则将长度重设为最大的有效长度,即,直至字符串末尾 东南大学计算机学院 12/3/2018

117 String类实现(11) 138 // allocate temporary array for substring and
  139    // terminating null character   140    char *tempPtr = new char[ len + 1 ];   141    142    // copy substring into char array and terminate string   143    strncpy( tempPtr, &sPtr[ index ], len );   144    tempPtr[ len ] = '\0';   145    146    // create temporary String object containing the substring   147    String tempString( tempPtr );   148    delete [] tempPtr; // delete temporary array   149    return tempString; // return copy of the temporary String   150 } // end function operator() 有效长度 以’\0’结尾 注意释放动态内存 返回值 tempString对象被析构 东南大学计算机学院 12/3/2018

118 11.7 实例研究:String类 String类测试函数 Fig (看书) 东南大学计算机学院 12/3/2018

119 string类的相关内容 string类是C++标准库类 Chap 11.13简介了string类的使用 (了解)
东南大学计算机学院 12/3/2018

120 重载++和--运算符 ++和--运算符 重载++和--运算符 东南大学计算机学院 12/3/2018

121 ++和--运算符 前置的++和--运算符:先+/-,再赋值 后置的++和--运算符:先赋值,再+/- 返回对象本身
11.8 重载++和--运算符 ++和--运算符 前置的++和--运算符:先+/-,再赋值 返回对象本身 后置的++和--运算符:先赋值,再+/- 返回改变前的对象的副本,产生临时对象 int a=1; int b= ++a; //b=2, a=2 int a=1; int b= a++; //b=1, a=2 int a=1; int b= --a; //b=0, a=0 int a=1; int b= a--; //b=1, a=0 东南大学计算机学院 12/3/2018

122 重载++和--运算符(1) 前置/后置形式均可重载 重载前置形式等同于重载一元运算符 成员运算符函数 全局运算符函数 返回对象本身
11.8 重载++和--运算符 重载++和--运算符(1) 前置/后置形式均可重载 重载前置形式等同于重载一元运算符 Date d; ++d; 成员运算符函数 全局运算符函数 d.operator++(); operator++(d); Date & operator++() Date & operator++(Date &) 返回对象本身 返回对象本身 东南大学计算机学院 12/3/2018

123 0是“哑值”,仅用于区分前置和后置的自增和自减运算符
11.8 重载++和--运算符 重载++和--运算符(2) 前置/后置形式均可重载 重载后置形式 Date d; d++; 成员运算符函数 全局运算符函数 d.operator++(0); operator++(d, 0); 0是“哑值”,仅用于区分前置和后置的自增和自减运算符 Date operator++(int) Date operator++(Date &, int) 返回值 返回值 东南大学计算机学院 12/3/2018

124 实例研究:Date类 东南大学计算机学院 12/3/2018

125 Date类定义 Fig 11.12 Date.h 默认构造函数 前置形式的重载 后置形式的重载 避免(a=b)=c 最小权限原则
9 class Date    10 {    11    friend ostream &operator<<( ostream &, const Date & );    12 public:    13    Date( int m = 1, int d = 1, int y = 1900 ); // default constructor    14    void setDate( int, int, int ); // set month, day, year    15    Date &operator++(); // prefix increment operator    16    Date operator++( int ); // postfix increment operator    17    const Date &operator+=( int ); // add days, modify object    18    bool leapYear( int ) const; // is date in a leap year?    19    bool endOfMonth( int ) const; // is date at the end of month?    20 private:    21    int month;    22    int day;    23    int year;    24     25    static const int days[]; // array of days per month    26    void helpIncrement(); // utility function for incrementing date    27 }; 默认构造函数 前置形式的重载 后置形式的重载 避免(a=b)=c 最小权限原则 是对象的组成部分吗?如何初始化? 不是。只能类体外初始化。 东南大学计算机学院 12/3/2018

126 Date类实现(1) Fig. 11.13: Date.cpp static const int[]数据成员的初始化
    7 const int Date::days[] =     8    { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };     9     10 // Date constructor    11 Date::Date( int m, int d, int y )    12 {    13    setDate( m, d, y );    14 } // end Date constructor    15     16 // set month, day and year    17 void Date::setDate( int mm, int dd, int yy )    18 {    19    month = ( mm >= 1 && mm <= 12 ) ? mm : 1;    20    year = ( yy >= 1900 && yy <= 2100 ) ? yy : 1900;    21     22    // test for a leap year    23    if ( month == 2 && leapYear( year ) )    24       day = ( dd >= 1 && dd <= 29 ) ? dd : 1;    25    else    26       day = ( dd >= 1 && dd <= days[ month ] ) ? dd : 1;    27 } // end function setDate static const int[]数据成员的初始化 下标从1-12访问每个月份的天数 有效性检查 闰年2月有29天 东南大学计算机学院 12/3/2018

127 Date类实现(2) Fig. 11.13: Date.cpp 11.9 实例研究:Date类
 56 // if the year is a leap year, return true; otherwise, return false    57 bool Date::leapYear( int testYear ) const    58 {    59    if ( testYear % 400 == 0 ||    60       ( testYear % 100 != 0 && testYear % 4 == 0 ) )    61       return true; // a leap year    62    else    63       return false; // not a leap year    64 } // end function leapYear 东南大学计算机学院 12/3/2018

128 Date类实现(3) Fig. 11.13: Date.cpp 前置形式的重载 返回对象的引用 后置形式的重载,返回原值的副本
   29 // overloaded prefix increment operator    30 Date &Date::operator++()    31 {    32    helpIncrement(); // increment date    33    return *this; // reference return to create an lvalue    34 }    35     36 // overloaded postfix increment operator; note that the     37 // dummy integer parameter does not have a parameter name    38 Date Date::operator++( int )    39 {    40    Date temp = *this; // hold current state of object    41    helpIncrement();    42     43    // return unincremented, saved, temporary object    44    return temp; // value return; not a reference return    45 } // end function operator++ 前置形式的重载 返回对象的引用 后置形式的重载,返回原值的副本 形参没任何实际作用,仅指示为后置重载 注意temp是局部变量,若返回引用则引起“虚悬引用”! 东南大学计算机学院 12/3/2018

129 Date类实现(4) Fig. 11.13: Date.cpp 11.9 实例研究:Date类
   75 // function to help increment the date    76 void Date::helpIncrement()    77 {    78    // day is not end of month    79    if ( !endOfMonth( day ) )    80       day++; // increment day    81    else    82       if ( month < 12 ) // day is end of month and month < 12    83       {    84          month++; // increment month    85          day = 1; // first day of new month    86       } // end if    87       else // last day of year    88       {    89          year++; // increment year    90          month = 1; // first month of new year    91          day = 1; // first day of new month    92       } // end else    93 } // end function helpIncrement 东南大学计算机学院 12/3/2018

130 Date类实现(5) Fig. 11.13: Date.cpp 需要逐一进行判断 11.9 实例研究:Date类
   66 // determine whether the day is the last day of the month    67 bool Date::endOfMonth( int testDay ) const    68 {    69    if ( month == 2 && leapYear( year ) )    70       return testDay == 29; // last day of Feb. in leap year    71    else    72       return testDay == days[ month ];    73 } // end function endOfMonth    47 // add specified number of days to date    48 const Date &Date::operator+=( int additionalDays )    49 {    50    for ( int i = 0; i < additionalDays; i++ )    51       helpIncrement();    52     53    return *this; // enables cascading    54 } // end function operator+= 需要逐一进行判断 东南大学计算机学院 12/3/2018

131 Date类实现(6) Fig. 11.13: Date.cpp 注意参数和返回类型 11.9 实例研究:Date类
   95 // overloaded output operator    96 ostream &operator<<( ostream &output, const Date &d )    97 {    98    static char *monthName[ 13 ] = { "", "January", "February",    99       "March", "April", "May", "June", "July", "August",   100       "September", "October", "November", "December" };   101    output << monthName[ d.month ] << ' ' << d.day << ", " << d.year;   102    return output; // enables cascading   103 } // end function operator<< 注意参数和返回类型 东南大学计算机学院 12/3/2018

132 11.9 实例研究:Date类 Date类测试函数 Fig (看书) 东南大学计算机学院 12/3/2018

133 运算符重载 教学要求与安排 教学要求 全部要求 11.13了解(自学) 教学安排 chap10.6放在chap11中讲 东南大学计算机学院
12/3/2018

134 作业 deadline: 4.24 24:00 H4: 11.13, 11.15 deadline: 5.3 24:00
H4: 下标从1-n,修改Array类 H5: 11.11, 11.14, 11.17 东南大学计算机学院 12/3/2018


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

Similar presentations


Ads by Google