刘胥影 liuxy@seu.edu.cn 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 liuxy@seu.edu.cn 东南大学计算机学院
运算符重载 教学要求与安排 教学要求 全部要求 11.13了解(自学) 教学安排 chap10.6放在chap11中讲 东南大学计算机学院 12/3/2018
运算符重载 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)
运算符重载基础 基本概念 运算符重载的必要性 运算符重载的适用性 运算符重载的基本原则 运算符重载的限制
基本概念(1) 重载函数 调用 出现在相同作用域中的两个函数,具有相同的名字而形参表 不同,则称为重载函数,它们是不同的函数 形参个数不同 11.1运算符重载基础 基本概念(1) 回顾 重载函数 出现在相同作用域中的两个函数,具有相同的名字而形参表 不同,则称为重载函数,它们是不同的函数 形参个数不同 形参类型不同 成员函数可以有const类型的重载,供const对象调用 调用 根据实参的类型 根据对象是否为const对象 东南大学计算机学院 12/3/2018
基本概念(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
operator+(double,double) 11.1运算符重载基础 基本概念(3) 运算符重载 3 + 1 运算符重载 operator+(int,int) 重载的运算符函数 3.0 + 1.0 operator+(double,double) 东南大学计算机学院 12/3/2018
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
11.1运算符重载基础(2) 运算符重载的适用性 不能创造新的运算符 Appendix A 通过指针的成员指针 通过对象的成员指针 唯一的三元运算符
X 11.1运算符重载基础(3) 运算符重载的基本原则 函数名:operator@ 类对象使用运算符,必须重载该运算符 三个例外: 赋值运算符 = 取址(&)和逗号(,) 只能显式重载,不能自动实现 需重载的运算符 + 运算符重载函数名 operator+() 第9章: 默认的逐个成员赋值 (&) 既是“取址”运算符,又是“按位与”运算符 只有当它是取值运算符时才能直接用于对象 obj2 = obj1 + obj2 已重载+ obj2 += obj1 +=被重载 X
11.1运算符重载基础(4) 运算符重载的基本原则 两种运算符重载方式 以成员函数重载:non-static成员函数 运算符是对对象的操作,由对象调用 以全局函数重载:一般声明为友元 友元可以访问类的所有数据成员 why? why? 区别? 11.2 东南大学计算机学院 12/3/2018
11.1运算符重载基础(5) 运算符重载的限制 不能创造新的运算符 不能改变优先级、结合律 不能改变元数 一元运算符 二元运算符 三元运算符:不可重载(?:) 如果希望重载的运算符具有不同的优先级和结合律,只能使用( ) 元数:运算符的操作数的个数 一元运算符:+1, -1, !, … 二元运算符: a+b, a-b, a*b, a/b, … 三元运算符:?: 一元/二元运算符: + - * & 一元 正号 负号 指针 取址 二元 加 减 乘 按位与 重载哪个运算符由元数控制 东南大学计算机学院
11.1运算符重载基础(6) 运算符重载的限制 不能改变对基本类型对象的操作语义 类库重载了基本类型对象的运算符 只有当用户自定义类型的对象为操作数时,运算符重载 才起作用 一元运算符:自定义类型的对象 二元运算符:(不考虑顺序) 自定义类型对象 +自定义类型对象 自定义类型对象 + 基本类型对象 当只包含基本类型对象时,运算符重载不起作用 保证 东南大学计算机学院 12/3/2018
11.1运算符重载基础(7) 运算符重载的限制 重载不保证操作数的求值顺序 (补充) 逗号(,): a, b 逻辑与(&&): a && b 避免重载这些运算符 东南大学计算机学院 12/3/2018
两种运算符函数的比较 两种运算符函数 必须作为成员/全局函数重载的运算符 两种运算符函数的调用 函数的参数传递类型 一元和二元运算符重载 重载流插入/提取运算符 重载可交换的运算符
11.2两种运算符函数的比较(1) 两种运算符函数 必须作为成员函数重载的运算符 必须作为全局函数重载的运算符 成员运算符函数:必须是non-static成员函数 全局运算符函数:一般为友元 必须作为成员函数重载的运算符 ()、[]、-> 任何赋值运算符 必须作为全局函数重载的运算符 流插入操作符(<<)和流提取运算符(>>) 可交换的运算符(指操作对象类型不同) 其他运算符可以作为成员函数或全局函数进行重载 用法一样,实现不同 东南大学计算机学院 12/3/2018
11.2两种运算符函数的比较(2) 两种运算符函数的调用 成员运算符函数 一元运算符 二元运算符 全局运算符函数 + obj1 obj1 + obj2 obj1.operator+( ) obj1.operator+( obj2 ) + obj1 obj1 + obj2 operator+( obj1 ) operator+( obj1, obj2 ) 东南大学计算机学院 12/3/2018
11.2两种运算符函数的比较(3) 成员运算符函数 一元运算符 二元运算符 左操作数必须为该类的对象 class A{ public: operator+(); … }; A obj1; obj1.operator+( obj2 ) obj1.operator+( ) obj1 + obj2 + obj1 一元运算符 二元运算符 左操作数必须为该类的对象 右操作数可以是任意类类型对象或基本类型对象 适用情况 (有顺序) 本类对象 本类对象 + 自定义/基本类型对象 左操作数是其他类对象时,必须使用全局运算符函数
11.2两种运算符函数的比较(4) 全局函数运算符函数 顺序无关 + obj1 obj1 + obj2 operator+( obj1 ) operator+( obj1, obj2 ) obj1 + obj2 顺序无关 东南大学计算机学院 12/3/2018
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
11.2两种运算符函数的比较(6) 函数的参数传递类型(形参/返回值类型) 传递值:返回对象的副本,而非对象本身 传递引用:返回对象本身,而非对象的副本 效率高,但由于能改变对象,容易出错 避免改变对象的方法:const对象引用 重要知识点回顾 bool operator!( A a ) const; 需拷贝对象,效率低 bool operator!( const A & a ) const; 不需拷贝对象,效率高 东南大学计算机学院 12/3/2018
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
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
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
内 容 回 顾 10.6数据抽象和信息隐藏 11.1 运算符重载基础 信息隐藏 数据抽象 抽象数据类型 重载函数和运算符重载 运算符重载的限制 不能改变对基本类型对象的操作语义 只有当用户自定义类型的对象为操作数时,运算符重载 才起作用 东南大学计算机学院 12/3/2018
内 容 回 顾 11.2两种运算符函数的比较 两种运算符函数 成员运算符函数:必须是non-static成员函数 全局运算符函数:一般为友元 东南大学计算机学院 12/3/2018
内 容 回 顾 成员运算符函数 一元运算符 二元运算符 左操作数必须为该类的对象 右操作数可以是任意类类型对象或基本类型对象 class A{ public: operator+(); … }; A obj1; obj1.operator+( obj2 ) obj1.operator+( ) obj1 + obj2 + obj1 一元运算符 二元运算符 左操作数必须为该类的对象 右操作数可以是任意类类型对象或基本类型对象 适用情况 (有顺序) 本类对象 本类对象 + 自定义/基本类型对象 左操作数是其他类对象时,必须使用全局运算符函数
内 容 回 顾 全局函数运算符函数 顺序无关 + obj1 obj1 + obj2 operator+( obj1 ) operator+( obj1, obj2 ) obj1 + obj2 顺序无关 东南大学计算机学院 12/3/2018
11.2两种运算符函数的比较(10) 一元运算符重载 成员运算符函数 全局运算符函数 non-static成员函数 一般为友元函数 没有参数 一个参数 参数必须是该类对象或引用 obj1.operator+( ) + obj1 operator+( obj1 ) + obj1 东南大学计算机学院 12/3/2018
11.2两种运算符函数的比较(11) 一元运算符重载 成员运算符重载 全局运算符重载 class String { public: bool operator!() const; ... }; // end class String bool operator!( const String & ); 东南大学计算机学院 12/3/2018
11.2两种运算符函数的比较(12) 二元运算符重载 成员运算符函数 全局运算符函数 non-static成员函数 一般为友元函数 一个参数 左操作数必须为本类 对象 全局运算符函数 一般为友元函数 两个参数 参数必须有一个自定义类 对象/引用 operator+( obj1, obj2 ) obj1 + obj2 obj1.operator+( obj2 ) obj1 + obj2 东南大学计算机学院 12/3/2018
11.2两种运算符函数的比较(13) 重载流插入/提取运算符 成员运算符函数 全局运算符函数 ostream & cout << obj class A cout.operator<<( obj ) cout << obj operator<<( cout, obj ) 东南大学计算机学院 12/3/2018
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
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
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
重载流插入/提取运算符 东南大学计算机学院 12/3/2018
11.3重载流插入/提取运算符(1) Phone number的输入输出 格式: (800) 555-1212 能够级联调用:cout<<obj1<<obj2 函数返回值类型:ostream & 重载方式 必须为全局运算符函数 code: Fig. 11.3~11.5 areaCode exchange line (800) 555-1212 How? 东南大学计算机学院 12/3/2018
(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 { 15 friend ostream & operator<<( ostream &, const PhoneNumber & ); 16 friend istream & operator>>( istream &, PhoneNumber & ); 17 private: 18 string areaCode; // 3-digit area code 19 string exchange; // 3-digit exchange 20 string line; // 4-digit line 21 }; // end class PhoneNumber 22 23 #endif using namespace std areaCode exchange line (800) 555-1212
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 东南大学计算机学院
PhoneNumber & vs. PhoneNumber PhoneNumber & vs. const PhoneNumber & friend istream & operator>>( istream &, PhoneNumber & ); 形参类型 PhoneNumber & vs. PhoneNumber PhoneNumber & vs. const PhoneNumber & cin需要改变对象的数据成员,因此既不能传入对象的副本,又不能限定为const 12/3/2018 东南大学计算机学院
(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 { 14 output << "(" << number.areaCode << ") " 15 << number.exchange << "-" << number.line; 16 return output; // enables cout << a << b << c; 17 } // end function operator<< 18 (800) 555-1212 以全局函数实现重载 返回对象本身, 进行级联调用 12/3/2018 东南大学计算机学院
(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 { 24 input.ignore(); // skip ( 25 input >> setw( 3 ) >> number.areaCode; // input area code 26 input.ignore( 2 ); // skip ) and space 27 input >> setw( 3 ) >> number.exchange; // input exchange 28 input.ignore(); // skip dash (-) 29 input >> setw( 4 ) >> number.line; // input line 30 return input; // enables cin >> a >> b >> c; 31 } // end function operator>> 以全局函数实现重载 限定读入字符串数组的字符个数 返回对象本身, 进行级联调用 (800) 555-1212 12/3/2018 东南大学计算机学院
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 { 13 PhoneNumber phone; // create object phone 14 12/3/2018 东南大学计算机学院
18 // the global function call operator>>( cin, phone ) 15 cout << "Enter phone number in the form (123) 456-7890:" << endl; 16 17 // cin >> phone invokes operator>> by implicitly issuing 18 // the global function call operator>>( cin, phone ) 19 cin >> phone; 20 21 cout << "The phone number entered was: "; 22 23 // cout << phone invokes operator<< by implicitly issuing 24 // the global function call operator<<( cout, phone ) 25 cout << phone << endl; 26 return 0; 27 } // end main 调用函数 operator>>(cin, phone) 调用函数 operator<<(cout, phone) 根据c++标准,任何不含return的语句,最后一个语句默认为”return 0;” Enter phone number in the form (123) 456-7890: (800) 555-1212 The phone number entered was: (800) 555-1212 cin >> phone1 >> phone2 如何调用函数? operator>>(cin, phone1); operator>>(cin, phone2); 12/3/2018 东南大学计算机学院
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
动态内存管理 标准C++数组的限制 动态内存管理 C++的内存管理 operator new/delete 初始化 动态数组 东南大学计算机学院 12/3/2018
如果只能在运行时才能确定数组长度,怎么办? 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
动态内存管理 动态内存管理:在运行时动态管理内存 作用范围 动态内存管理操作 变量存储在自由存储区:堆区(heap) 内置类型 11.4动态内存管理 动态内存管理 动态内存管理:在运行时动态管理内存 作用范围 内置类型 用户自定义类类型 动态内存管理操作 new:申请内存 delete:释放内存 变量生命期:从new开始,delete结束 变量存储在自由存储区:堆区(heap) A *p = new A; delete p; 东南大学计算机学院 12/3/2018
C++的内存管理 程序段:程序的机器码和只读数据,只读操作 全局区:全局变量、static变量 堆区 :动态变量 栈区 :自动变量 程序段 11.4动态内存管理 C++的内存管理 程序段 全局区 (静态存储区) 内存低端 内存高端 堆区 (自由态存储区) 栈区 内存分配 内存回收 管理者 程序开始之前 程序开始之后 编译器 new delete 程序员 声明时 语句块或 函数体(}) 编译器 程序段:程序的机器码和只读数据,只读操作 全局区:全局变量、static变量 堆区 :动态变量 栈区 :自动变量 东南大学计算机学院 12/3/2018
如果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
初始化 基本类型变量 对象 默认的初始化: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
动态数组(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
动态数组(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
动态数组(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
动态数组(4) 对象作为数组元素 无法为每个对象元素通过参数传递进行初始化 调用默认的构造函数为每个对象初始化 析构 delete[ ] 11.4动态内存管理 动态数组(4) 对象作为数组元素 无法为每个对象元素通过参数传递进行初始化 调用默认的构造函数为每个对象初始化 析构 delete[ ] 调用每个对象的析构函数 delete只调用第一个对象的析构函数 东南大学计算机学院 12/3/2018
动态数组(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
内容回顾 两种运算符函数的比较 必须为成员运算符函数 必须为全局运算符函数 成员运算符:左操作是必须是该类的对象 全局运算符函数:一般声明为友元 必须为成员运算符函数 ( ), [ ], -> 必须为全局运算符函数 流插入<<和提取运算符>> 可交换的运算符 左操作数不是该类对象 东南大学计算机学院 12/3/2018
内容回顾 重载流插入<<和提取运算符>> 避免虚悬引用 必须为全局运算符函数 要实现运算符级联调用,函数的形参类型和返回类 型需为引用 避免虚悬引用 返回局部对象和临时对象的引用是危险的 东南大学计算机学院 12/3/2018
内容回顾 动态内存管理 动态数组 new:申请内存 delete:释放内存 必须成对使用 new[]:申请内存 delete[]:释放内存 若元素为对象则自动调用默认的构造函数为每个对象初始化 东南大学计算机学院 12/3/2018
实例研究:Array类 基于指针的数组的缺点 Array类的功能 Array类实现 总结 东南大学计算机学院 12/3/2018
基于指针的数组的缺点 数组名就是指向第一元素的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
Array类的功能 重载<<和>>:实现数组整体的输入输出 重载赋值运算符=:将一个数组对象赋值给另一个数组 对象 重载关系运算符==:比较两个数组对象是否相同 重载关系运算符!=:比较两个数组对象是否不同 重载下标运算符[ ]: 可以进行越界检查 东南大学计算机学院 12/3/2018
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
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限定符传入的实参对象不可改变。 均不可以,因为要改变对象
118 istream &operator>>( istream &input, Array &a ){ 120 for ( int i = 0; i < a.size; i++ ) 121 input >> a.ptr[ i ]; 122 123 return input; // enables cin >> x >> y; 124 } Array.cpp 127 ostream &operator<<( ostream &output, const Array &a ){ 129 int i; 130 131 // output private ptr-based array 132 for ( i = 0; i < a.size; i++ ) 133 { 134 output << setw( 12 ) << a.ptr[ i ]; 135 136 if ( ( i + 1 ) % 4 == 0 ) // 4 numbers per row of output 137 output << endl; 138 } // end for 139 140 if ( i % 4 != 0 ) // end last line of output 141 output << endl; 142 143 return output; // enables cout << x << y; 144 } 12/3/2018 东南大学计算机学院
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 { 20 size = ( arraySize > 0 ? arraySize : 10 ); // validate arraySize 21 ptr = new int[ size ]; // create space for pointer-based array 22 23 for ( int i = 0; i < size; i++ ) 24 ptr[ i ] = 0; // set pointer-based array element } Array.cpp
Array::Array(Array arrayToCopy ) 29 Array::Array( const Array &arrayToCopy ) 30 : size( arrayToCopy.size ) 31 { 32 ptr = new int[ size ]; // create space for pointer-based array 33 34 for ( int i = 0; i < size; i++ ) 35 ptr[ i ] = arrayToCopy.ptr[ i ]; // copy into object } Array.cpp 拷贝构造函数的作用? 何时调用拷贝构造函数? 拷贝构造函数的参数类型为什么是const Array & ?可以是Array 吗? 通过建立一个现有对象的副本来初始化一个Array对象 默认的赋值运算符=不自动调用拷贝构造函数 需要创建对象副本时 按值传递参数时 (函数形参/函数返回值) 用对象的副本初始化另一对象时 const引用不需要拷贝const对象 不可以是Array,若为Array: Array::Array(Array arrayToCopy ) 需要创建实参的副本(拷贝),此时需要调用拷贝构造函数,即该函数本身,造成无穷递归!
29 Array::Array( const Array &arrayToCopy ) 30 : size( arrayToCopy.size ) 31 { 32 ptr = new int[ size ]; // create space for pointer-based array 33 34 for ( int i = 0; i < size; i++ ) 35 ptr[ i ] = arrayToCopy.ptr[ i ]; // copy into object } Array.cpp 4. 是否可以直接拷贝指针?ptr=&arrayToCopy.ptr 不可以。当传入的参数被析构时,ptr成为虚悬指针!
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 { 41 delete [] ptr; // release pointer-based array space 42 } 45 int Array::getSize() const 46 { return size; 48 } Array.cpp
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)
52 const Array &Array::operator=( const Array &right ) 53 { 53 { 54 if ( &right != this ) // avoid self-assignment 55 { 56 // for Arrays of different sizes, deallocate original 57 // left-side array, then allocate new left-side array 58 if ( size != right.size ) 59 { 60 delete [] ptr; // release space 61 size = right.size; // resize this object 62 ptr = new int[ size ]; // create space for array copy 63 } // end inner if 64 65 for ( int i = 0; i < size; i++ ) 66 ptr[ i ] = right.ptr[ i ]; // copy array into object 67 } // end outer if 68 69 return *this; // enables x = y = z, for example 70 } Array.cpp 2. 为什么返回类型是const引用? 避免(a=b)=c. x对象调用该函数,虽然返回值类型是const引用,但对x没影响。 这里返回值类型指示赋值表达式的值的类型 为什么要检测自我赋值? 若为自我赋值,则在赋值前,该对象的内存就被释放,导致致命的运行时错误 12/3/2018 东南大学计算机学院
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) 内联函数 复用性
74 bool Array::operator==( const Array &right ) const 75 { 75 { 76 if ( size != right.size ) 77 return false; // arrays of different number of elements 78 79 for ( int i = 0; i < size; i++ ) 80 if ( ptr[ i ] != right.ptr[ i ] ) 81 return false; // Array contents are not equal 82 83 return true; // Arrays are equal 84 } // end function operator== Array.cpp 12/3/2018 东南大学计算机学院
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对象不能被赋值
88 int &Array::operator[]( int subscript ) 89 { 89 { 90 // check for subscript out-of-range error 91 if ( subscript < 0 || subscript >= size ) 92 { 93 cerr << "\nError: Subscript " << subscript 94 << " out of range" << endl; 95 exit( 1 ); // terminate program; subscript out of range 96 } // end if 97 98 return ptr[ subscript ]; // reference return 99 } Array.cpp 进行越界检查 #include <cstdlib> using std::exit; 103 int Array::operator[]( int subscript ) const 104 { 105 // check for subscript out-of-range error 106 if ( subscript < 0 || subscript >= size ) 107 { 108 cerr << "\nError: Subscript " << subscript 109 << " out of range" << endl; 110 exit( 1 ); // terminate program; subscript out of range 111 } // end if 112 113 return ptr[ subscript ]; // returns copy of this element 114 } 12/3/2018 东南大学计算机学院
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 { 12 Array integers1( 7 ); // seven-element Array 13 Array integers2; // 10-element Array by default 14 15 // print integers1 size and contents 16 cout << "Size of Array integers1 is " 17 << integers1.getSize() 18 << "\nArray after initialization:\n" << integers1; 19 20 // print integers2 size and contents 21 cout << "\nSize of Array integers2 is " 22 << integers2.getSize() 23 << "\nArray after initialization:\n" << integers2; Size of Array integers1 is 7 Array after initialization: 0 0 0 0 0 0 0 Size of Array integers2 is 10 0 0 调用: operator<<( cout, integers1 ); 调用: operator<<( cout, integers2 ); 12/3/2018 东南大学计算机学院
级联调用: operator>>( cint, integers1 ); Enter 17 integers: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 After input, the Arrays contain: integers1: 1 2 3 4 5 6 7 integers2: 8 9 10 11 12 13 14 15 16 17 Evaluating: integers1 != integers2 integers1 and integers2 are not equal 级联调用: operator>>( cint, integers1 ); operator>>( cint, integers2 ); 25 // input and print integers1 and integers2 26 cout << "\nEnter 17 integers:" << endl; 27 cin >> integers1 >> integers2; 28 29 cout << "\nAfter input, the Arrays contain:\n" 30 << "integers1:\n" << integers1 31 << "integers2:\n" << integers2; 32 33 // use overloaded inequality (!=) operator 34 cout << "\nEvaluating: integers1 != integers2" << endl; 35 36 if ( integers1 != integers2 ) cout << "integers1 and integers2 are not equal" << endl; 级联调用: operator<<(cout, integers1 ); operator<<(cout, integers2 ); 调用: integers1 .operator!=(integers2 ); 12/3/2018 东南大学计算机学院
39 // create Array integers3 using integers1 as an 40 // initializer; print size and contents 41 Array integers3( integers1 ); // invokes copy constructor 42 43 cout << "\nSize of Array integers3 is " 44 << integers3.getSize() << "\nArray after initialization:\n" << integers3; 46 47 // use overloaded assignment (=) operator 48 cout << "\nAssigning integers2 to integers1:" << endl; 49 integers1 = integers2; // note target Array is smaller 50 51 cout << "integers1:\n" << integers1 52 << "integers2:\n" << integers2; 53 54 // use overloaded equality (==) operator 55 cout << "\nEvaluating: integers1 == integers2" << endl; 56 57 if ( integers1 == integers2 ) 58 cout << "integers1 and integers2 are equal" << endl; 若为: Array integers3 = integers1 ; 同样调用拷贝构造函数,而非赋值运算符函数 Size of Array integers3 is 7 Array after initialization: 1 2 3 4 5 6 7 Assigning integers2 to integers1: integers1: 8 9 10 11 12 13 14 15 16 17 integers2: Evaluating: integers1 == integers2 integers1 and integers2 are equal 调用: integers1 .operator=(integers2 ); 调用: integers1 .operator==(integers2 ); 12/3/2018 东南大学计算机学院
60 // use overloaded subscript operator to create rvalue 61 cout << "\nintegers1[5] is " << integers1[ 5 ]; 62 63 // use overloaded subscript operator to create lvalue 64 cout << "\n\nAssigning 1000 to integers1[5]" << endl; 65 integers1[ 5 ] = 1000; 66 cout << "integers1:\n" << integers1; 67 68 // attempt to use out-of-range subscript 69 cout << "\nAttempt to assign 1000 to integers1[15]" << endl; 70 integers1[ 15 ] = 1000; // ERROR: out of range 71 return 0; 72 } // end main 调用: integers1 .operator[](5); 返回左值 integers1[5] is 13 Assigning 1000 to integers1[5] integers1: 8 9 10 11 12 1000 14 15 16 17 Attempt to assign 1000 to integers1[15] Error: Subscript 15 out of range 12/3/2018 东南大学计算机学院
总结(1) 关于动态内存管理 通常会为需要动态内存管理的类同时提供 若无拷贝构造函数和重载的赋值运算符 若无析构函数 11.5实例研究:Array类 总结(1) 关于动态内存管理 通常会为需要动态内存管理的类同时提供 拷贝构造函数(需要分配内存) 析构函数(需要释放内存) 重载的赋值运算符(需要删除原有内存并重新分配内存) 若无拷贝构造函数和重载的赋值运算符 执行操作:默认的逐个成员赋值 指向动态内存的指针指向同一地址,并造成内存泄漏 若无析构函数 不能自动释放动态内存区的变量 a.ptr b.ptr 东南大学计算机学院 12/3/2018
总结(2) 成员函数为private 重载的赋值运算符函数为private:阻止对象赋值 拷贝构造函数为private:阻止对象拷贝 11.5实例研究:Array类 总结(2) 成员函数为private 重载的赋值运算符函数为private:阻止对象赋值 拷贝构造函数为private:阻止对象拷贝 无法调用默认的赋值运算函数,它被重载 无法调用默认的拷贝构造函数,它被重载 东南大学计算机学院 12/3/2018
类型转换 何时需要类型转换 基本类型的类型转换 用户自定义类型的类型转换 转换构造函数 转换运算符 东南大学计算机学院 12/3/2018
何时需要类型转换 赋值 计算 参数传递 … … 11.6 类型转换 int a; a = 3.2; a = 3 double int 1 + 2.0 double int + double + double void f( int ){}; f( 2.0 ); f( 2 ) double int 东南大学计算机学院 12/3/2018
基本类型的类型转换 隐式:由编译器自动进行类型转换 显式:由程序员强制进行类型转换 1:int double 11.6 类型转换 基本类型的类型转换 隐式:由编译器自动进行类型转换 显式:由程序员强制进行类型转换 1 + 3.0; 1:int double (int)3.0; 3.0:double int 东南大学计算机学院 12/3/2018
用户自定义类型的类型转换 两种类型转换方式 编译器不知道怎样进行类型转换,必须由程序员显式指 定 转换构造函数: 转换运算符: 11.6 类型转换 用户自定义类型的类型转换 编译器不知道怎样进行类型转换,必须由程序员显式指 定 两种类型转换方式 转换构造函数: 转换运算符: 其他类型对象 本类对象 本类对象 其他类型对象 东南大学计算机学院 12/3/2018
转换构造函数(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
转换构造函数(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
explicit构造函数(1) 转换构造函数:任何单实参的构造函数 explicit关键字的作用是禁止使用转换构造函数进 行隐式类型转换 11.6 类型转换 explicit构造函数(1) 转换构造函数:任何单实参的构造函数 有时会在无意之中将单参数构造函数用作转换构造函数进行隐式的类型转换,但这并不是我们希望发生的,因此需要防止 explicit关键字的作用是禁止使用转换构造函数进 行隐式类型转换 东南大学计算机学院 12/3/2018
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
运算符函数的个数 = 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
转换运算符(2) 为什么需要将本类转换为其他类类型? 类型转换可减少运算符函数的数目 11.6 类型转换 转换运算符(2) 为什么需要将本类转换为其他类类型? SmallInt实现安全小整数0~255,可以进行数据的有效性检查 运算符:+,-,*, /, %, <, <=, >, >=, == != 若以类型转换实现,只需定义SmallInt类到int类型的转换 s + 3 3 + s s - 3 3 - s s + 3.14 3.14 + s s – 3.14 3.14 - s int int double int和int类型的运算 1. s转换为int类型 2. 编译器将int类型自动转换 为double类型 类型转换可减少运算符函数的数目 东南大学计算机学院 12/3/2018
A::operator char *( ) const; 11.6 类型转换 转换运算符(3) 转换运算符(conversion/cast operator) 是一种特殊的类成员函数 作用:本类对象 其他类型对象 A char * 函数没有返回类型 形参表必须为空 A::operator char *( ) const; A类的non-static成员函数 实际返回类型定义为运算符 一般为const 不修改实参 东南大学计算机学院 12/3/2018
转换运算符(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
转换运算符(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
转换运算符(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
内容回顾 类型转换 转换构造函数:将其他类对象转换为本类对象 转换运算符:将本类对象转换为其他类对象 任何但参数的构造函数 explicit构造函数禁止使用转换构造函数进行隐式类型转 换 转换运算符:将本类对象转换为其他类对象 是一类特殊的成员函数 不管是转换构造函数还是转换运算符,需要的类型 和实际使用的类型可以不完全匹配,此时利用标准 的内置类型转换完成转换通路 东南大学计算机学院 12/3/2018
实例研究:String类 一些说明 程序解读 东南大学计算机学院 12/3/2018
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
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
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
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
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
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
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
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
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
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
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
String类实现(3) 29 // copy constructor 30 String::String( const String © ) 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 特别要注意: 动态数组的内存要在析构函数中显式释放 东南大学计算机学院
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
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
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
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
在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
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
函数调用运算符运算符( )可以有任意数量的参数 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
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
11.7 实例研究:String类 String类测试函数 Fig. 11.11(看书) 东南大学计算机学院 12/3/2018
string类的相关内容 string类是C++标准库类 Chap 11.13简介了string类的使用 (了解) 东南大学计算机学院 12/3/2018
重载++和--运算符 ++和--运算符 重载++和--运算符 东南大学计算机学院 12/3/2018
++和--运算符 前置的++和--运算符:先+/-,再赋值 后置的++和--运算符:先赋值,再+/- 返回对象本身 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
重载++和--运算符(1) 前置/后置形式均可重载 重载前置形式等同于重载一元运算符 成员运算符函数 全局运算符函数 返回对象本身 11.8 重载++和--运算符 重载++和--运算符(1) 前置/后置形式均可重载 重载前置形式等同于重载一元运算符 Date d; ++d; 成员运算符函数 全局运算符函数 d.operator++(); operator++(d); Date & operator++() Date & operator++(Date &) 返回对象本身 返回对象本身 东南大学计算机学院 12/3/2018
0是“哑值”,仅用于区分前置和后置的自增和自减运算符 11.8 重载++和--运算符 重载++和--运算符(2) 前置/后置形式均可重载 重载后置形式 Date d; d++; 成员运算符函数 全局运算符函数 d.operator++(0); operator++(d, 0); 0是“哑值”,仅用于区分前置和后置的自增和自减运算符 Date operator++(int) Date operator++(Date &, int) 返回值 返回值 东南大学计算机学院 12/3/2018
实例研究:Date类 东南大学计算机学院 12/3/2018
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
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
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
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
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
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
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
11.9 实例研究:Date类 Date类测试函数 Fig. 11.14(看书) 东南大学计算机学院 12/3/2018
运算符重载 教学要求与安排 教学要求 全部要求 11.13了解(自学) 教学安排 chap10.6放在chap11中讲 东南大学计算机学院 12/3/2018
作业 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