1.运算符重载的介绍 重载运算符是C++的一个重要特性,使用运算符重载,程序员可以把C++运算符的定义扩展到运算分量是对象的情况 运算符重载的目的是使C++代码更直观,跟易读 由简单的运算符构成的表达式常常比函数调用更简洁、易懂 在基本数据类型上,系统提供了许多预定义的运算符,如: int nNumA, nNumB; nNumA = nNumA + nNumB; 对基本类型,这些运算符的操作具有特定的含义 在拼接字符串时往往需要使用strcat等函数来完成,是否可以通过使用一个'+'号来完成字符串的拼接呢,例: char szStrA[9], szStrB[4]; szStrA = szStrA + szStrB; //使其等价于strcat(szStrA, szStrB),不可能,运算符重载操作对象是类 在C++中除了下面五种运算符外,系统预定义的运算符都能被重载 A. 条件运算符"? :",例a = b > c ? b : c; //操作数太多 B. 指针分量运算符"->", 例pstcTest->nNum; //以下3种用于类的对象,重载后类的操作会出错 C.分量运算符".",例stcTest.nNum; D.范围解析运算符"::" E.取大小运算符"sizeof" //不少内部操作都依赖它,如指针加法 //难以在不违背基本语言规则的前提下表达新的含义 运算符重载要保持原运算符的特性不变(只重载功能,其他不能变): A. 优先级和结合性不变 //重载后"*"比“+”优先级高 B. 操作数个数不变 //重载后"*"必须是双目运算 C. 语法结构不变 //重载后"&"必须在表达式左边 其他定义事项: 运算符重载实际上是通过定义一个函数来实现的。运算符重载归根到底是函数的重载,编译器选择重载的运算符是遵循函数重载的选择原则,即按不同类型或个数的参数来选择不同的重载运算符 运算符重载应符合使用习惯,便于理解。把"+"运算符重载成两个字符串类对象的合并比"*"更易于理解 运算符重载不能创造新的运算符号。不能创造新符号"**" 在C++中,运算符重载是通过运算符重载函数来实现的,运算符重载函数一般采用下述方式: A. 成员函数的形式 B. 友元函数的形式 2.成员函数形式运算符重载 一种特殊的成员函数,语法形式: <函数类型><类名>::operator<符号>(参数表) //类的成员函数 {} int ClassA::operator+(int nNumA, int nNumB) //错误写法 参数表列出该运算符需要的操作数。 单目运算符表中无参数,调用该函数的对象为操作数。 双目运算符参数表中有一个参数,调用该函数的对象为第一操作数,参数表中的参数为第二操作数 运算符函数体对重载的运算符的含义作出新的解释,这种解释仅局限在重载该运算符的类中,即当在X类对象的关联中,运算符含义由函数体解释,否则脱离类对象,该运算符具有系统预定义的含义。 class CCounter { public: CCounter() { m_nValue = 0; } void operator++() { if(m_nValue < 65535) m_nValue++; } int operator*() //单参数,取内容操作符 { return m_nValue; } private: int m_nValue; } int _tmain(int argc, _TCHAR* argv[]) { CCounter objA; ++objA; //隐式调用,一个操作数,通过this指针(ecx)传入operator成员函数 objA.operator++(); //显式调用,两种方式一样 cout<<*objA<<endl; //等价于objA.operator*() return 0; } class CCounter { public: CCounter(int nNum) { m_nValue = nNum; } void operator+(CCounter& obj) { m_nValue += obj.m_nValue; //操作数不是对象,使用系统预定义+ } int operator*() { return m_nValue; } private: int m_nValue; }; int _tmain(int argc, _TCHAR* argv[]) { CCounter objA(5), objB(5); objA + objB; objA.operator+(objB); //两种方式等价 cout<<*objA<<endl; return 0; } 3.友元函数形式运算符重载 class CCounter { public: CCounter() { m_nValue = 0; } friend void operator++(CCounter& obj); //单目运算,友元函数有参数,需要传入this指针 int operator*() //单目运算,成员函数没有参数 { return m_nValue; } private: int m_nValue; }; void operator++(CCounter& obj) { if(obj.m_nValue < 65535) { obj.m_nValue++; } } int _tmain(int argc, _TCHAR* argc[]) { CCounter objA; ++objA; //友元函数、成员函数在隐式调用上无区别 operator++(objA); //友元函数、成员函数在显式调用上有区别 cout<<*objA<<endl; return 0; } 友元函数不可重载结构体、变量,否则无法区分普通运算符、重载运算符 友元函数和类相关,继承了类的功能--运算符重载 普通全局函数无法实现运算符重载,C语言就没有这个功能 class CCounter { public: CCounter(int nNum) { m_nValue = nNum; } friend void operator+(CCounter& ojbA, CCounter objB); //第一个参数为引用为了保持计算结果到实参 friend int operator*(CCounter obj); private: int m_nValue; }; void operator+(CCounter& objA, CCounter objB) { objA.m_nValue = objB.m_nValue; } int operator*(CCounter obj) { return obj.m_nValue; } int _tmain(int argc, _TCHAR* argv[]) { CCounter objA(5),objB(5); objA + objB; operator+(objA, objB); cout<<*objA<<endl; return 0; } 4.成员/友元函数形式的运算符重载总结 成员函数形式 友元函数形式 一元 隐式: 对象# 对象# 显式: 对象.operator#() operator#(对象) 隐式: #对象 # 对象 显式: 对象.operator#() operator#(对象) 二元: 隐式: 对象A#对象B 对象A#对象B 对象A.operator#(对象B) operator(对象A, 对象B) 5.特殊符号的重载 ①“=”重载 class CCounter { public: CCounter(int nNum) { m_nValue = nNum; } CCounter& operator= (CCounter& obj) //必须为引用,否则调用拷贝构造函数 { m_nValue = obj.m_nValue; return *this; //返回当前对象引用 } int operator*() { return m_nValue; } private: int m_nValue; }; int _tmain(int argc, _TCHAR* argc[]) { CCounter objA(15),objB(5); objA = objB; Cout<<*objA<<endl; return 0; } operator= 重载函数:无返回值也可以,但是等号有连续赋值特性,需要返回当前对象引用给下个对象赋值 objA = objB等价于: objA.operator=(objB) objA = objB = objC等价于: objA.operator=(objB).operator=(objC) C++输入输出: << >>具有相同特性 “=”运算符重载与拷贝构造函数对比: “=”运算符重载 : CCounter& operator= (CCounter& obj) //函数头写法不一样,且返拷贝构造函数没有返回值 { m_nValue = obj.m_nValue; return *this; } 拷贝构造函数 : CCounter (CCounter& obj) { m_nValue = obj.m_nValue; } ②“[]”重载 class CCounter { public: CCounter() { for(int i = 0; i < 15; i++) { m_nValue[i] = i; } } int operator[](int nCount) { return m_nValue[cCount]; } private: int m_nValue[15]; }; int _tmain(int argc, _TCHAR* argv[]) { CCounter objA; for(int i = 0; i < 15; i++) { cout<<objA[i]<<' '; } return 0; } ③流运算符">>"重载 class CCounter { public: CCounter() { memset(m_szstr, 0 ,sizeof(m_szStr)); } friend ostream& operator<<(ostream& output, CCounter& obj) { output<<obj.m_szStr; return output; } friend istream& operator>>(ostream& input, CCounter& obj) { input.getline(obj.m_szStr, 15); return input; } private: char m_szStr[15]; }; int _tmain(int argc, _TCHAR* argv[]) { CCounter obj; cin>>obj; cout<<obj; return 0; } 流运算符重载只能使用友元函数,不能使用成员函数的原因: ①ostream,istream类体系采用了构造函数保护继承的方式。。。致使即使以继承的方式来扩展流类,也会在对象实例化时遭遇阻碍 ②如果实现了成员operator<< ,调用其对象格式obj<<cout; 格式很乱 6.小结 ①使用运算符重载可以使程序易于理解并易于对对象进行操作。 ②应注意运算符重载不能改变运算符本身的优先级和结合性,不能改变操作数的数量,不能发明新运算符。 ③如果在类中未定义拷贝构造函数和赋值运算符,编译器将提供缺省的拷贝构造函数和赋值运算符,但只能对简单的类对象适用。 ④运算符重载函数可以定义为成员函数和友元函数形式。对运算符重载函数的调用采用显示或隐式方式进行。 ⑤在前增量和后增量运算符定义中,使用形参数int(加了代表后增量)只是为了标志前后有别,没有其它作用。 ⑥拷贝构造函数用已存在的对象创建一个相同类的新对象,而赋值运算符把一个对象的成员变量赋予一个已存在对象的同名成员变量中。