Operator Overloading; String and Array Objects Chapter 11 Operator Overloading; String and Array Objects
OBJECTIVES What operator overloading is and how it makes programs more readable and programming more convenient. To redefine (overload) operators to work with objects of user-defined classes. The differences between overloading unary and binary operators. To convert objects from one class to another class. When to, and when not to, overload operators.
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
11.1 Introduction 复习:函数重载 要求 函数名相同,参数列表不同 仅返回值不同不作为重载 注意:有缺省参数的函数 2018年11月30日5时32分 11.1 Introduction 复习:函数重载 要求 函数名相同,参数列表不同 仅返回值不同不作为重载 注意:有缺省参数的函数 func(int i, int j = 0) func(int i) 特例:const成员函数 void Employee::display(){} void Employee::display() const {} pNum+1什么效果?指针值+1?
11.1 Introduction cout << int_variable;// 整型变量 2018年11月30日5时32分 11.1 Introduction cout << int_variable;// 整型变量 cout << ptrInt;// 整型指针 cout << ptrChar;// 字符指针 “<< ”流插入运算符 & 按位左移运算符 temp = 14 << 2 temp = int num = 10; num = num + 1; double num = 1.0; num = num + 1.0; int num[10], *pNum = num; pNum = pNum + 1; operator overloading 运算符重载 56 pNum+1什么效果?指针值+1?
11.1 Introduction C++语言为了支持基本数据类型数据运算,内置了多种运算符,并且其中部分已针对操作数类型的不同进行了重载; 当需要将这些运算符用于用户自定义类型时,用户可进行运算符重载。 重载运算符的基本概念、限制,何时选择重载? 如何实现重载?全局 vs 成员函数 拷贝构造函数 / 转换构造函数 自定义String类 vs 标准string类
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
11.2 Fundamentals & Restrictions --- 需求 2018年11月30日5时32分 11.2 Fundamentals & Restrictions --- 需求 目的:提高类代码的可用、可读性 HugeintA.add(HugeIntB) vs HugeintA + HugeintB 提高C++的可扩展性 特别适合于和数学相关的类 复数类 大整数类 多个表达式可以用逗号分开,其中用逗号分开的表达式的值分别结算,但整个表达式的值是最后一个表达式的值。
11.2 Fundamentals & Restrictions --- 语法 运算符重载只是一种“语法上的方便”,也就是说它只是另一种函数调用的方式。区别: 定义方式 调用方式 定义重载的运算符(可视为特殊函数)就像定义函数(全局/成员),区别是该函数的名称是 operator@ 其中operator是关键词,@是被重载的运算符,如: HugeInt operator+(const HugeInt& a);
11.2 Fundamentals & Restrictions --- 语法 运算符重载只是一种“语法上的方便”,也就是说它只是另一种函数调用的方式。区别: 定义方式 调用方式 普通函数 全局函数: 函数名(参数列表) 类成员函数: 对象.函数名(参数列表)等 重载的运算符 使用时以表达式形式出现: HugeIntA + HugeIntB
11.2 Fundamentals & Restrictions --- 限制 2018年11月30日5时32分 11.2 Fundamentals & Restrictions --- 限制 To use an operator on class objects, that operator must be overloaded with three exceptions(针对对象,有三个运算符不用重载): assignment operator (=) address operators (&) comma operators (,) a=(b=2,c=7,d=5), a1=(++b,c--,d+3); a2=++b,c--,d+3;
11.2 Fundamentals & Restrictions --- 限制
11.2 Fundamentals & Restrictions --- 限制 重载运算符应该仿效其相应的内置对象的功能 重载成的成员函数必须是非static的(带着问题思考) HugeInt operator+(const HugeInt& a); 不能更改Precedence(优先级), Associativity(结合律) 以及 Number of Operands(操作数数目) 仅能重载现有运算符,不能创造新运算符
11.2 Fundamentals & Restrictions --- 限制 continue 仅能重载应用于用户定义数据类型操作数的运算符 int + int X Hugeint + Hugeint √ Hugeint + int √ int + Hugeint √ 运算符必须显性重载 重载+和=不代表重载了+=
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
11.3 Operator Functions as Class Members vs. Global Functions 运算符函数可以是成员函数或者全局函数 当重载为类的成员函数时 非静态的类成员函数 class HugeInt { public: HugeInt operator+( int ); }; 使用this指针隐性获取操作左值 左操作数(或唯一的操作数)必须为该类对象(或对象引用) 将自动包含该类对象(或其引用)作为操作数,函数参数个数等于运算符目数-1
11.3 Operator Functions as Class Members vs. Global Functions 2018年11月30日5时32分 11.3 Operator Functions as Class Members vs. Global Functions 当重载为全局函数时 形式 class HugeInt { friend HugeInt operator+(const HugeInt &, int); }; 函数参数个数等于运算符的目数 全局函数: 是否访问私有数据 Friend(可访问私有数据)复习~~~ Non-friend 思考:必须重载为全局函数的情况 左操作数必须为一个基本类型对象 使运算符具有可交换性 HugeInteger + int 和 int + HugeInteger 成员函数+参数调换的全局函数 实例
11.3 Operator Functions as Class Members vs. Global Functions (对比) HugeIntA + intA HugeIntA.operator+( intA ); operator+( HugeIntA, intA ); class HugeInt { friend HugeInt operator+( const HugeInt &, int ); }; class HugeInt { public: HugeInt operator+( int ); };
11.3 Operator Functions as Class Members vs. Global Functions intB + HugeIntB intB.operator+( HugeIntB ); operator+( intB, HugeIntB ); class HugeInt { friend HugeInt operator+( int, const HugeInt & ); };
11.3 Operator Functions as Class Members vs. Global Functions --- 设计原则 2018年11月30日5时32分 11.3 Operator Functions as Class Members vs. Global Functions --- 设计原则 ( ), [ ], ->和赋值(=, +=, -=等)运算符必须重载为成员函数 >>, <<和需要支持交换律(Commutative)的运算符重载为全局函数 其余运算符可以选择重载为成员或全局函数 他们或为一元操作符,如(),->,[],或为第一个参数必为类本身,如=,赋值操作符不可能左边的的赋给右边的,避免潜在的风险 http://hi.baidu.com/unixfy/blog/item/0b7d4ced90d82b27b90e2d07.html 如果赋值操作符可以作为全局函数重载的话,可能会出现表达错误的语句 如 int operator=(int a, integer b); 这样重载之后,语句 2 = a; 表述也是正确的,但是却是明显的语法错误 为了避免此类错误,需要将赋值操作符重载为成员函数 -------------------------------------------------- 首先要知道,如果类中没有重载赋值操作符时,类会自动生成一个默认的赋值操作符。例如,有两个同类对象A和B,当你没有将赋值操作符重载,而进行 A=B 的操作时,编译器会自动调用赋值操作将B的数据成员拷贝到A中。 而如果你重载了一个全局的赋值操作符,那么编译器不知道是否还需要再自己合成一个赋值操作符,从而引发歧义。
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
11.4 Overloading Stream Insertion and Stream Extraction Operators cout << phone; cout.operator<<( phone ); operator<<( cout, phone ); class PhoneNumber{ friend ostream &operator<<(ostream&, const PhoneNumber &) }; 程序解读 P 11.3-5 注意类头文件的友元声明、友元函数实现以及运算符调用
11.4 Overloading Stream Insertion and Stream Extraction Operators 2018年11月30日5时32分 11.4 Overloading Stream Insertion and Stream Extraction Operators cout << phone; 左值为ostream & cout << phone1 << phone2; cin >> phone; 左值为istream & cin >> phone1 >> phone2; 必须要定义为全局函数! 虚悬引用(针对临时对象返回引用) 注意<<和>>函数形参中PhoneNumber的不同类型(见图11.3 15-16行)
11.4 Overloading Stream Insertion and Stream Extraction Operators 存在隐患(>>)* 图11.4 从缓存中获取信息 采用setw及ignore函数精确控制 空格间隔多个输入 输入类型不正确 解决方法 判断cin流是否正常 if(cin) if(cin.fail()) 清空标志位及缓存 cin.clear();//清除错误状态 flushall();//清除缓存 输入:a 程序死循环运行
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
11.5 Overloading Unary Operators 2018年11月30日5时32分 11.5 Overloading Unary Operators 一元运算符重载 As a non-static member function with no arguments or as a global function with one argument; 不带参数的成员函数 带有一个参数的全局函数(参数为对象或对象引用)
11.5 Overloading Unary Operators String s; !s判断是否为空字符串 operator!( s ); s.operator!(); class String { friend bool operator!( const String &); }; class String { public: bool operator!( ) const; };
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
11.6 Overloading Binary Operators 二元运算符重载 as a non-static member function with one argument or as a global function with two arguments 带有一个参数的成员函数 前提条件是仅当左操作数是该函数所在类的对象 带有二个参数的全局函数 one of those arguments must be either a class object or a reference to a class object 其中一个参数必须是对象或对象引用
11.6 Overloading Binary Operators string1 < string2 operator<( string1, string2 ); string1.operator<(string2); class String { friend bool operator<( const String &, const String &); }; class String { public: bool operator<( const String &) const; };
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
需求分析 程序解读 P 11.6-11.8 写一个基于指针的Array 类,要求实现以下功能: 解决方法: 2018年11月30日5时32分 需求分析 写一个基于指针的Array 类,要求实现以下功能: 提供检查数组的下标的有效性(是否越界) 非字符数组可以使用cin/cout一次性输入/输出 对两个数组直接用“==”进行比较 数组作为参数时,不用传递数组大小(*) 一个数组可以用“=”赋给另一个数组(关于数组名) 解决方法: 在函数中检查下标范围 重载“<<”和“>>” 重载“==” 数组大小作为类的数据成员 重载“=” c和c++语言中数组下标越界,编译器是不会检查出错误的,但是实际上后果可能会很严重,比如程序崩溃等,所以在日常的编程中,程序员应当养成良好的编程习惯,避免这样的错误发生。 程序解读 P 11.6-11.8
Const类型的返回值表示对相关对象的保护,实现禁左。禁止(a=b)=c class Array { friend ostream &operator<<( ostream &, const Array & ); friend istream &operator>>( istream &, Array & ); public: Array( int = 10 ); Array( const Array & ); ~Array(); int getSize() const; const Array &operator=( const Array & ); bool operator==( const Array & ) const; bool operator!=( const Array &right ) const return ! ( *this == right ); } int &operator[]( int ); int operator[]( int ) const; private: int size; int *ptr; }; 为何要重载拷贝构造函数? 为何返回值要加&和const? &为了在a=(b=c)时提高性能 Const类型的返回值表示对相关对象的保护,实现禁左。禁止(a=b)=c
&:避免递归调用 const:避免修改源对象, 支持复制const对象 为什么重载? 返回值 const &? 参数为什么是引用类型? Array::Array(const Array &arrayToCopy):size(arrayToCopy.size) { ptr = net int [size]; for(int i = 0; i < size; i++) ptr[i] = arrayToCopy.ptr[i]; } Array::~Array() { delete [] ptr;} Const Array &Array::operator=( const Array &right) if(&right != this) if(size != right.size) delete [] ptr; size = right.size; ptr = new int [size]; for(int i = 0; i < size; i++) ptr[i] = right.ptr[i]; return * this; &:避免递归调用 const:避免修改源对象, 支持复制const对象 为何要自定义析构函数? 为什么重载? 返回值 const &? 参数为什么是引用类型? 为什么检查“自我赋值”?
返回&:支持左值(可以被赋值) 注意同上面函数的区别 全局函数 int &Array::operator[](int subscript) { …… return ptr[subscript]; } int Array::operator[](int subscript) const istream &operator>>(istream &input, Array &a) return input; ostream &operator<<(ostream &output, const Array &a) return output; 返回&:支持左值(可以被赋值) 注意同上面函数的区别 全局函数
先计算返回int,然后进行<<运算 int main() { Array integers1( 7 ); Array integers2; cout << integers1.getSize() << integers1; cout << integers2.getSize() << integers2; cin >> integers1 >> integers2; cout << integers1 << integers2; Array integers3( integers1 ); // Array integers3 = integers1; cout << integers3.getSize() << integers3; integers1 = integers2; cout << integers1 << integers2; cout << "\nintegers1[5] is " << integers1[ 5 ]; integers1[ 5 ] = 1000; cout << integers1; integers1[ 15 ] = 1000; // ERROR: out of range return 0;} 先计算返回int,然后进行<<运算 会调用哪个[]函数?
总结 Copy constructor “=”重载 “[ ]”的重载 “>>”和“<<”的重载 避免递归调用 2018年11月30日5时32分 总结 Copy constructor 参数为什么是const &类型? “=”重载 为什么重载? 返回值 const &? 参数为什么是引用类型? 为什么检查“自我赋值”? “[ ]”的重载 Line88和Line103的区别? 整数、浮点数、字符(串)等 “>>”和“<<”的重载 为什么返回为引用类型? 避免递归调用 避免修改源对象 支持复制const对象
总结——进一步讨论 虚悬指针(产生原因)与指针回收问题 通常会为任何一个使用动态分配内存的类同时提供一组函数(重要!): 2018年11月30日5时32分 总结——进一步讨论 虚悬指针(产生原因)与指针回收问题 关于delete之后的指针使用(有的编译器可以访问,但会有潜在的错误) 通常会为任何一个使用动态分配内存的类同时提供一组函数(重要!): 复制构造函数 析构函数 重载的赋值运算符函数 如何阻止类对象的复制 将重载的赋值运算符以及拷贝构造函数设置为private
总结 原则:重载的运算符和已经存在的运算符在使用规则上没有区别 运算符重载什么情况下返回引用类型,什么情况下返回数值类型? 返回值还要继续被处理的情况: 返回引用类型 例如:+=运算符 a += b += c; return *this; 返回值用过就丢弃的情况: 返回值类型 例如:+运算符 a = (b + c) * d;
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
11.8 Converting between types(类型转换) Fundamental types(基本数据类型) int x; char y = static_cast<char>(x); Or char y = (char)x; User-defined types(用户自定义类型) 转换构造函数 强制类型转换
(编译器可以隐性调用这些函数创建临时对象) 对象转换 同类对象转换 非同类对象转换 初始化对象 拷贝构造函数 转换构造函数 重载强制类型转换运算符 (编译器可以隐性调用这些函数创建临时对象)
11.8 Converting between types(类型转换) 转换构造函数 Conversion Constructor 单实参的构造函数,用于将其他类型的对象(包括基本数据类型)转换为当前类的对象 任何单参数构造函数都可看做是转换构造函数 目的:使编译器自动执行类型转化!(隐式调用) 基本数据类型之间(char->int) 抽象数据类型之间(Time->Date) 抽象数据类型和基本数据类型之间(int->HugeInt)
11.8 Converting between types(类型转换) class One{ public: One() { cout << "One Constructor called." << endl; } ~One() { cout << "One Destructor called." << endl; } }; class Two{ Two( const One & ) { cout << "Conversion Constructor called." << endl; } ~Two() { cout << "Two Destructor called." << endl; } void f( Two ){ cout << "Function f called." << endl; } int main() { One one; f(one); cout << "Check whether Two has been destructed?" << endl; return 0; } 转换构造函数,参数为其它数据类型 One Constructor called. Conversion Constructor called. Function f called. Two Destructor called. Check whether Two has been destructed? One Destructor called. 隐式调用
11.8 Converting between types(类型转换) 强制类型转换函数的重载 函数声明形式 A::operator int() const; // 不需要返回值,不修改原对象 实现:A -> int A::operator OtherClass() const; 实现:A-> OtherClass 用户调用: A s; static_cast<int>(s); static_cast<OtherClass>(s); 编译器调用: s.operator int(); s.operator OtherClass();
cout<<static_cast<int>(s); return 0; } 2018年11月30日5时32分 class A {public: A(int=5); A( const A & ); ~A(); int getSize() const; operator int() const; int *ptr; int size; }; A::operator int() const { int temp=0; for ( int i = 0; i < size; i++ ) temp=temp+ptr[i]*pow(10,i); return temp; } ; int main() { A s; cout<<static_cast<int>(s); return 0; } int main() { A s; cout<<s; return 0; } 有问题! 隐式调用 此时不需重载<<
11.8 Converting between types(类型转换) 总结 转换构造函数 重载强制类型转换运算符 隐式调用原则: 先查找符合要求的函数 再调用转换运算 转换运算不能级联调用(仅能调用一次)
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
需求:实现一个String类,对字符串操作 用已有字符串初始化当前字符串 定义拷贝构造函数 私有数据成员:int length; char *sPtr;需要哪些重载函数? 需要重载赋值运算符 提供取子串功能,如何重载运算符? 重载函数调用运算符“()” () – 函数调用运算符function call operator, can take arbitrarily long and complex parameter lists. 对象名(参数列表); //可以重载多种不同的情况 例:string1(2,2)//取string1从第二个位置开始的两个字符 程序解读 P 11.9-11.11
class String { friend ostream &operator<<( ostream &, const String & ); friend istream &operator>>( istream &, String & ); public: String( const char * = "" ); String( const String & ); ~String(); const String &operator=( const String & ); const String &operator+=( const String & ); bool operator!() const; bool operator==( const String & ) const; bool operator<( const String & ) const; bool operator!=( const String &right ) const return !( *this == right ); } bool operator>( const String &right ) const return right < *this; bool operator<=( const String &right ) const { return !( right < *this ); } bool operator>=( const String &right ) const return !( *this < right ); char &operator[ ]( int ); char operator[ ]( int ) const; String operator()( int, int = 0 ) const; int getLength() const; private: int length; char *sPtr; void setString( const char * ); }; #endif
#include <iomanip> using std::setw; #include <cstring> 2018年11月30日5时32分 #include <iomanip> using std::setw; #include <cstring> using std::strcmp; using std::strcpy; using std::strcat; #include <cstdlib> using std::exit; #include "String.h" String::String( const char *s ) : length( ( s != 0 ) ? strlen( s ) : 0 ) { cout << "Conversion (and default) constructor: " << s << endl; setString( s ); } String::String( const String © ) : length( copy.length ) { cout << "Copy constructor: " << copy.sPtr << endl; setString( copy.sPtr ); String::~String() { cout << "Destructor: " << sPtr << endl; delete [ ] sPtr; void String::setString( const char *string2 ) { sPtr = new char[ length + 1 ]; if ( string2 != 0 ) strcpy( sPtr, string2 ); else sPtr[ 0 ] = '\0'; } const String &String::operator=( const String &right ) { cout << "operator= called" << endl; if ( &right != this ) // avoid self assignment delete [ ] sPtr; // prevents memory leak length = right.length; setString( right.sPtr ); cout << "Attempted assignment of a String to itself" << endl; return *this; } \0是C++中字符串的结尾标志,存储在字符串的结尾。比如char cha[5]表示可以放4个字符的数组,由于c/c++中规定字符串的结尾标志为'\0',它虽然不计入串长,但要占内存空间,而一个汉字一般用两个字节表示,且c/c++中如一个数组cha[5],有5个变量,分别是 cha[0] , cha[1] , cha[2] , cha[3] , cha[4] , 所以cha[5]可以放4个字母(数组的长度必须比字符串的元素个数多1,用以存放字符串结束标志'\0')或者放2个汉字(1个汉字占2个字节,1个字母占一个字节),cha[5]占5个字节内存空间。
const String &String::operator+=( const String &right ) { 2018年11月30日5时32分 const String &String::operator+=( const String &right ) { int newLength = length + right.length; char *tempPtr = new char[ newLength + 1 ]; strcpy( tempPtr, sPtr ); strcpy( tempPtr + length, right.sPtr ); delete [] sPtr; sPtr = tempPtr; length = newLength; return *this; } bool String::operator!() const return length == 0; bool String::operator==( const String &right ) const return strcmp( sPtr, right.sPtr ) == 0; bool String::operator<( const String &right ) const return strcmp( sPtr, right.sPtr ) < 0; char &String::operator[ ]( int subscript ) { if ( subscript < 0 || subscript >= length ) { cerr << "Error: Subscript " << subscript << " out of range" << endl; exit( 1 ); //退出整个程序,返回值为1 } return sPtr[ subscript ]; char String::operator[ ]( int subscript ) const // 右值 { if ( subscript < 0 || subscript >= length ) cerr << "Error: Subscript " << subscript exit( 1 ); } int strcmp(const char *src,const char *dst) { int i = 0; while(src[i] && dst[i]) { if(src[i] > dst[i]) return 1; else if(src[i] < dst[i]) return -1; else i++; } return 0;
String String::operator() ( int index, int subLength ) const { if ( index < 0 || index >= length || subLength < 0 ) return ""; int len; if ( ( subLength == 0 ) || ( index + subLength > length ) ) len = length - index; else len = subLength; char *tempPtr = new char[ len + 1 ]; strncpy( tempPtr, &sPtr[ index ], len ); tempPtr[ len ] = '\0'; String tempString( tempPtr ); delete [ ] tempPtr; return tempString; } int String::getLength() const { return length; } ostream &operator<<( ostream &output, const String &s ) { output << s.sPtr; return output; istream &operator>>( istream &input, String &s ) char temp[ 100 ]; input >> setw( 100 ) >> temp; s = temp; return input; //转换构造函数+ ’=’的重载
11.9 Case Study: String Class copy constructor 例1:String s1(s2); conversion constructor 例2:String s3(“hello”); 例3:myString = “hello”; Step1.调用转换构造函数,生成一个临时对象; Step2.调用重载的赋值运算
cout << "\n\ns1 += \" to you\" yields" << endl; cout << "s1 = " << s1 << "\n\n"; cout << "The substring of s1 starting at\n" << "location 0 for 14 characters, s1(0, 14), is:\n" << s1( 0, 14 ) << “\n\n”; cout << "The substring of s1 starting at\n" << s1( 15 ) << "\n\n"; String *s4Ptr = new String( s1 ); cout << “\n*s4Ptr = ” << *s4Ptr ; cout << "assigning *s4Ptr to *s4Ptr" ; *s4Ptr = *s4Ptr; cout << "*s4Ptr = " << *s4Ptr << endl; delete s4Ptr; s1[ 0 ] = 'H'; s1[ 6 ] = 'B'; s1[ 30 ] = 'd'; // ERROR: subscript out of range return 0; } int main() { String s1( "happy" ); String s2( " birthday" ); String s3; cout << boolalpha << "\n\nThe results of comparing s2 and s1:" << "\ns2 == s1 yields " << ( s2 == s1 ) << "\ns2 != s1 yields " << ( s2 != s1 ) << "\ns2 > s1 yields " << ( s2 > s1 ) << "\ns2 < s1 yields " << ( s2 < s1 ) << "\ns2 >= s1 yields " << ( s2 >= s1 ) << "\ns2 <= s1 yields " << ( s2 <= s1); if ( !s3 ) { s3 = s1; cout << "s3 is \"" << s3 << "\""; } s1 += s2; cout << s1;
int main() { String s1( "happy" ); String s2( " birthday" ); String s3; cout << boolalpha << "\n\nThe results of comparing s2 and s1:" << "\ns2 == s1 yields " << ( s2 == s1 ) << "\ns2 != s1 yields " << ( s2 != s1 ) << "\ns2 > s1 yields " << ( s2 > s1 ) << "\ns2 < s1 yields " << ( s2 < s1 ) << "\ns2 >= s1 yields " << ( s2 >= s1 ) << "\ns2 <= s1 yields " << ( s2 <= s1); if ( !s3 ) { s3 = s1; cout << "s3 is \"" << s3 << "\""; } s1 += s2; cout << s1; cout << "\n\ns1 += \" to you\" yields" << endl; s1 += " to you"; cout << "s1 = " << s1 << "\n\n"; cout << "The substring of s1 starting at\n" << "location 0 for 14 characters, s1(0, 14), is:\n" << s1( 0, 14 ) << “\n\n”; //调用哪些函数? cout << "The substring of s1 starting at\n" << s1( 15 ) << "\n\n"; String *s4Ptr = new String( s1 ); cout << “\n*s4Ptr = ” << *s4Ptr ; cout << "assigning *s4Ptr to *s4Ptr" ; *s4Ptr = *s4Ptr; cout << "*s4Ptr = " << *s4Ptr << endl; delete s4Ptr; s1[ 0 ] = 'H'; s1[ 6 ] = 'B'; s1[ 30 ] = 'd'; // ERROR: subscript out of range return 0; }
cout << "\n\ns1 += \" to you\" yields" << endl; cout << "s1 = " << s1 << "\n\n"; cout << "The substring of s1 starting at\n" << "location 0 for 14 characters, s1(0, 14), is:\n" << s1( 0, 14 ) << “\n\n”; //调用哪些函数? cout << "The substring of s1 starting at\n" << s1( 15 ) << "\n\n"; String *s4Ptr = new String( s1 ); cout << “\n*s4Ptr = ” << *s4Ptr ; cout << "assigning *s4Ptr to *s4Ptr" ; *s4Ptr = *s4Ptr; cout << "*s4Ptr = " << *s4Ptr << endl; delete s4Ptr; s1[ 0 ] = 'H'; s1[ 6 ] = 'B'; s1[ 30 ] = 'd'; // ERROR: subscript out of range return 0; }
11.9 Case Study: String Class 2018年11月30日5时32分 11.9 Case Study: String Class 临时对象问题 例:myString = “hello”; Step1.调用转换构造函数,生成一个临时对象; Step2.调用重载的赋值运算 类型转换 void f( Two ){ cout << "Function f called." << endl; } f(one); //Two的临时对象 函数返回对象 cout<<String(5,15); 无名临时对象 Integer &t = Integer(10); 等价于 Integer t(10); 在使用一个临时对象( 可能是无名对象 或者 返回对象值时 ) 创建构造另一个对象的过程的中,c++会优化掉该临时对象的产生,直接以相同参数调用相关构造函数构或者 直接调用拷贝构造函数 到 目标对象
11.9 Case Study: String Class 2018年11月30日5时32分 11.9 Case Study: String Class 函数返回对象时 // Code 1: Integer Func() { Integer itgr; return itgr; } void main() Integer in = Func(); Integer& iRef = Func(); cout<< iRef; // Code 2: Integer & Func() { Integer itgr; return itgr; } void main() Integer & in = Func(); cout<< in;//bad 表达式 Func() 处创建了一个临时对象,用来存储Func() 函数中返回的对象,临时对象由 Func() 中返回的 itgr 对象拷贝构造(值传递),临时对象赋值给 in后,赋值表达式结束,临时对象被析构。
如果一个临时对象被创建之后赋值给了一个引用,那么这个临时对象将被保存,否则将在当前代码行结束后析构 9:28:59 天使&海豚 2016/4/5 9:28:59 #include <iostream> using namespace std; class A { public: A(int a):x(a){cout<<"Constructor Called, and x = "<<x<<endl;} A(A &a):x(a.x){cout<<"Copy Constructor Called, and x = "<<x<<endl;} void print(){cout<<"print function called, and x = "<<x<<endl;} A ReturnA(){A a(1); return a;} A & ReturnRef(){A a(3); return a;} ~A(){cout<<"Deconstructor Called, and x = "<<x<<endl;} private: int x; }; void main(void) { A & ref = A(2); ref.print(); cout<<endl; A& refB = ref.ReturnA(); refB.print(); cout<<endl; ref.ReturnA(); cout<<endl; A& refC = ref.ReturnRef(); refC.print(); } 如果一个临时对象被创建之后赋值给了一个引用,那么这个临时对象将被保存,否则将在当前代码行结束后析构
千万不要返回临时对象和局部对象的引用! // Code 2: Complex &Complex::operator+( const Complex &operand2 ) const { return Complex( real + operand2.real, imaginary + operand2.imaginary ); } 千万不要返回临时对象和局部对象的引用! // Code 3: Complex &Complex::operator+( const Complex &operand2 ) const { Complex temp; temp= Complex( real + operand2.real, imaginary + operand2.imaginary ); return temp; }
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
11.10 Standard Library Class string(自学,同String的区别) three member functions of standard class string - empty, substr and at that were not part of our String example Header file: <string> 程序解读 11.15
int main() { string s1( "happy" ); string s2( " birthday" ); string s3; cout << "s1 is \"" << s1 << "\"; s2 is \"" << s2 << "\"; s3 is \"" << s3 << '\"' ; if ( s3.empty() ) { cout << "s3 is empty; assigning s1 to s3;" << endl; s3 = s1; } s1 += s2; cout << s1; s1 += " to you"; cout << "s1 = " << s1 << "\n\n"; cout << "The substring of s1 starting at location 0 for\n" << "14 characters, s1.substr(0, 14), is:\n" << s1.substr( 0, 14 ) << "\n\n"; s1.substr( 15 ) << endl; string *s4Ptr = new string( s1 ); cout << "\n*s4Ptr = " << *s4Ptr << "\n\n"; cout << "assigning *s4Ptr to *s4Ptr" << endl; *s4Ptr = *s4Ptr; //测试自我赋值 cout << "*s4Ptr = " << *s4Ptr << endl; delete s4Ptr; s1[ 0 ] = 'H'; s1[ 6 ] = 'B'; cout << "\ns1 after s1[0] = 'H' and s1[6] = 'B' is: “ << s1 << "\n\n"; s1.at( 30 ) = 'd'; // ERROR: subscript out of range return 0; }
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
11.11 Overloading ++ and – – 前置自增(自减) 后置自增(自减) 目标:实现对用户自定义类型对象的自增与自减 ++a;--a; 后置自增(自减) a++;a--; 目标:实现对用户自定义类型对象的自增与自减 date ++;
Date &operator++(Date &); 11.11 Overloading ++ and – – ++d1 (前置自增) 全局函数 成员函数 d1.operator++() operator++(d1) Date &operator++(); Date &operator++(Date &);
Date operator++(int); Date operator++(Date &, int); 11.11 Overloading ++ and – – d1++ (后置自增) 全局函数 成员函数 d1.operator++(0) operator++(d1,0) Date operator++(int); Date operator++(Date &, int);
Class C{ friend ostream & operator<<(ostream &output, const C&c); int value; Public: C() {value = 0;} C& operator++() {value ++; return *this;}//前置 C operator++(int) //后置 { C temp = *this; ++(*this); return temp; } }; ostream & operator<<(ostream &output, const C&c) output<<c.value; return output;
临时对象 int main() { C c1; cout<<c1++<<endl; cout<<"++c1++ = "<< ++c1++<<endl cout<<"current c1 = "<<c1<<endl; return 0; } 临时对象
说明:后置自增(自减)运算符需要创建临时对象,将对性能造成影响,特别是在循环中使用时,应尽量减少使用
Topics 11.1 Introduction 11.2 Fundamentals & Restrictions 11.3 Operator Functions as Class Members vs. Global Functions 11.4 Overloading Stream Insertion and Stream Extraction Operators 11.5 Overloading Unary Operators 11.6 Overloading Binary Operators 11.7 Case Study: Array Class 11.8 Converting between types 11.9 Case Study: String Class 11.10 Standard Library Class string(self study) 11.11 Overloading ++ and ––(self study) 11.12 Case Study: A Date Class(self study)
Summary 哪些运算符可以重载?何时需要重载?有何限制?如何重载? 成员函数 vs 全局函数 拷贝构造函数和转换构造函数 “.”,”.*”,”::”,”?:”不能重载 “=“,”&”,”,”可以直接使用但有时也需要重载 成员函数 vs 全局函数 ( ), [ ], ->和赋值(=, +=, -=等)运算符必须重载为成员函数(有时函数需要被重载为常成员函数) 支持交换律的运算符必须重载为全局函数 拷贝构造函数和转换构造函数 自定义String类 vs 标准string类
Summary “=“ “<<” and “>>” 拷贝构造函数 const Array &operator=( const Array & ); “<<” and “>>” friend ostream &operator<<(ostream&,const Array &) friend istream &operator>>(istream&, Array &) 拷贝构造函数 Num (const Num & n);
Homework! 实验必选题目(交实验报告): 11.13,11.14,11.15,11.17
Return int main() { Array integers1( 7 ); Array integers2; const Array integers4=integers2; cout << “integers4:\n” <<integers4[0]; //调用const运算符函数 [ ] if ( integers1 != integers2 ) cout << "integers1 and integers2 are not equal" << endl; Array integers3( integers1 ); cout << integers3.getSize()<< integers3; integers1 = integers2; // note target Array is smaller cout << integers1 << integers2; if ( integers1 == integers2 ) cout << "integers1 and integers2 are equal" << endl; cout << "\nintegers1[5] is " << integers1[ 5 ]; integers1[ 5 ] = 1000; cout << "integers1:\n" << integers1; integers1[ 15 ] = 1000; // ERROR: out of range return 0;} Return
补充:cout和cerr的区别 Return cout可以重定向到一个文件中,cerr必须输出到显示器上 cerr的作用? #include <iostream> using std::cout; using std::cin; using std::endl; using std::cerr; int main() {cout<<"hello world--cout"<<endl; cerr<<"hello world--cerr"<<endl; return 0; } cerr的作用? 当栈用完,无内存时,cerr可不经过缓冲区,直接向显示器输出信息。 C:\>test.exe>>out.txt Return
Return class HugeInt { friend HugeInt operator+(const HugeInt &, int); public: HugeInteger( long = 0 ); // conversion/default constructor HugeInteger( const char * ); // copy constructor private: short integer[ 40 ]; }; HugeInt operator+(const HugeInt & add1, int add2) HugeInteger temp; value = add2; for(int index = 39 ; value != 0 && index >=0; index --) …… temp.interger[index] = add1.interger[index] + value%10; value /= 10; } Return error C2248: cannot access private member declared in class ‘HugeInt'
数组名代表了数组的首地址,在作为函数的参数的时候,会自动退化为指针 是const类型的指针,不能用在赋值运算符的左侧 int name[20]; int data; name = &data; Return
拷贝构造函数 Copy Constructor int a = 10; //初始化 a = 100; //赋值 Num b; Num a = b; //拷贝构造,调用拷贝构造函数 a = b; //赋值
class Num{ public: Num(){ nums = new int[10]; for(int i=0; i<10; i++) nums[i] = i; } void setvalue(int idx, int v){ nums[idx] = v; } void print(){ cout << nums << ": "; for(int i=0; i<10; i++) cout << nums[i] << " "; cout << endl; ~Num(){ delete [ ] nums; } private: int *nums; }; int main() { Num a; a.setvalue(0, 100); a.print(); Num b = a; b.print(); return 0; } 00031090: 100 1 2 3 4 5 6 7 8 9
for(int i=0; i<10; i++) nums[i] = n.nums[i]; class Num{ public: ………… Num (const Num & n){ nums = new int[10]; for(int i=0; i<10; i++) nums[i] = n.nums[i]; cout << "Copy constructor called." << endl; } }; 拷贝构造函数,参数为同类对象const引用 00031090: 100 1 2 3 4 5 6 7 8 9 Copy constructor called. 00031168: 100 1 2 3 4 5 6 7 8 9
Return 拷贝构造函数 Copy Constructor,被调用时机: 传值方式传递对象参数 Num (Num n){…} 函数返回对象 Time Time::getTime(); 使用同类对象来初始化对象 Time t2 = t1; 举例:后两种情况混合使用 总结:当类中含有需要动态分配内存的指针数据成员,应提供拷贝构造函数并重载赋值运算符,以避免缺省拷贝和赋值. Return