1 运算符重载基本概念和形式
1.1 运算符重载基本概念
- 在数学上,两个复数可以直接进行+、-等运算。但在C++中,直接将+或-用于复数对象是不允许的,因为C++中预定义的运算符并未提供这种功能。
- 为了让对象也能通过运算符进行运算(这样代码更简洁,容易理解),C++提供了运算符重载的机制。
- 运算符重载的含义:对已有的运算符(C++中预定义的运算符)赋予多重的含义,使同一运算符作用于不同类型的数据时导致不同类型的行为。比如 5 + 4 = 9; complex_a + complex_b 生成新的复数对象。
- 运算符重载的目的:扩展C++中提供的运算符的适用范围,使之能作用于对象。
1.2 运算符重载的形式
返回值类型 operator 运算符(形参表){ …… }
- 运算符重载的实质是函数重载;
- 把含运算符的表达式转换成对运算符函数的调用,把运算符的操作数转换成运算符函数的参数;
- 运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。
- 可以重载为普通函数,也可以重载为成员函数;
- 重载为成员函数时,参数个数为运算符目数减一;重载为普通函数时参数个数为运算符目数。
//示例 class Complex{ public: double real,imag; Complex( double r = 0.0, double i= 0.0 ):real(r),imag(i) { } Complex operator-(const Complex & c); }; Complex operator+( const Complex & a, const Complex & b){ return Complex( a.real+b.real,a.imag+b.imag); // 返回一个临时对象 } Complex Complex::operator-(const Complex & c){ return Complex(real - c.real, imag - c.imag); // 返回一个临时对象 } int main(){ Complex a(4,4),b(1,1),c; c = a + b; // 等价于c=operator+(a,b); cout << c.real << "," << c.imag << endl; cout << (a-b).real << "," << (a-b).imag << endl; //a-b 等价于a.operator-(b) return 0; }
2 常见运算符的重载
2.1 赋值运算符 ‘=’ 的重载
- 有时候希望赋值运算符两边的类型可以不匹配,比如,把一个int类型变量赋值给一个Complex对象,或把一个 char * 类型的字符串赋值给一个字符串对象,此时就需要重载赋值运算符“=”。
- 赋值运算符“=”只能重载为成员函数。
class String { private: char * str; public: String():str(new char[1]) { str[0] = 0;} const char * c_str() { return str; }; String & operator=(const char * s); String::~String() { delete [] str; } }; String & String::operator = (const char * s){ // 重载“=”得 以使得 obj = “hello” 能够成立 delete [] str; str = new char[strlen(s)+1]; strcpy( str, s); return * this; } int main(){ String s; s = "Good Luck," ; //等价于 s.operator=("Good Luck,"); cout << s.c_str() << endl; // String s2 = "hello!"; // 这条语句要是不注释掉就会出错 s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!"); cout << s.c_str() << endl; return 0; }
上述代码实现了两个功能:将 char* 类型赋给string对象;返回值为String&类型,故可以作为左值。但还需要解决以下问题:
- 使用对象赋值 s2 = s1 时,造成浅拷贝。因为复制的是指针,故两者指向同一个地方;若一个对象消亡或指向其他地方,则会调用delete,那么另一个对象的指针将无法访问这块内容。
- 对象自身赋给自身:s2 = s2 ,可能会导致将指向的内容删掉。
- 编译器默认加上的复制构造函数也会形成浅拷贝。
因此需要加上两个成员函数:赋值运算符的重载函数和复制构造函数。
//赋值运算符的重载函数 String & operator = (const String & s){ if( this == & s) return * this; //排除自身赋给自身的情况 delete [] str; str = new char[strlen(s.str)+1]; strcpy( str,s.str); return * this; } //复制构造函数 String( String & s){ str = new char[strlen(s.str)+1]; strcpy(str,s.str); }
另外:
- 一般情况下,将运算符重载为类的成员函数,是较好的选择。
- 但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。
2.2 运算符重载实例:可变长整型数组
//要编写可变长整型数组类,使之能如下使用: int main() { CArray a; //开始里的数组是空的 for( int i = 0;i < 5;++i) a.push_back(i); CArray a2,a3; a2 = a; for( int i = 0; i < a.length(); ++i ) cout << a2[i] << " " ; a2 = a3; //a2是空的 for( int i = 0; i < a2.length(); ++i ) //a2.length()返回0 cout << a2[i] << " "; cout << endl; a[3] = 100; CArray a4(a); for( int i = 0; i < a4.length(); ++i ) cout << a4[i] << " "; return 0; } //程序输出结果为 //0 1 2 3 4 //0 1 2 100 4
下面的代码可以满足当前要求:
#include <iostream> #include <cstring> using namespace std; class CArray{ int* p; int n; public: CArray(int s=0); CArray(CArray& a); ~CArray(){ if(n>0) delete []p;} CArray& operator=(const CArray& a); int& operator[](int i){if(i>=0 && i<n) return p[i];} void push_back(int k); int length(){return n;} }; CArray::CArray(int s):n(s){ if(n == 0) p = NULL; else p = new int[n]; } CArray::CArray(CArray& a){ n = a.n; if(n == 0){ p = NULL; }else{ //n>0 p = new int[n]; memcpy(p, a.p, sizeof(int)*n); } } CArray& CArray::operator=(const CArray& a){ if(this == &a) return *this; //若原空间不足,则需要重新开辟空间,否则不需要 if(n < a.n){ //n可能为0 if(n > 0) delete []p; p = new int[a.n]; } //复制数据 if(a.n == 0){ p = NULL; }else{ //a.n>0 memcpy(p, a.p, sizeof(int)*a.n); } n = a.n; return *this; } void CArray::push_back(int k){ //if(n > 0) delete []p; int* temp = new int[n+1]; memcpy(temp, p, sizeof(int)*n); temp[n] = k; if(n > 0) delete []p; p = temp; n++; } int main() { CArray a; //开始里的数组是空的 for( int i = 0;i < 5;++i) a.push_back(i); CArray a2,a3; a2 = a; for( int i = 0; i < a.length(); ++i ) cout << a2[i] << " " ; a2 = a3; //a2是空的 for( int i = 0; i < a2.length(); ++i ) //a2.length()返回0 cout << a2[i] << " "; cout << endl; a[3] = 100; CArray a4(a); for( int i = 0; i < a4.length(); ++i ) cout << a4[i] << " "; return 0; }
2.3 流插入运算符和流提取运算符的重载
cout 是ostream 类的对象,是在 iostream 中定义的。
“<<” 能用在cout上是因为,在iostream里对 “<<” 进行了重载。
cout << 5 << “this” 的本质就是cout.operator<<(5).operator<<(“this”);
//重载成成员函数 ostream & ostream::operator<<(int n){ …… // 输出n的代码 return * this; } //重载成全局函数 ostream & operator<<( ostream & o, const CStudent & s){ o << s.nAge ; return o; }
2.4 类型强制转换运算符的重载
类型强制转换运算符被重载时不能写返回值类型,实际上其返回值类型就是该类型强制转换运算符代表的类型。
#include <iostream> using namespace std; class Complex{ double real,imag; public: Complex(double r=0,double i=0):real(r),imag(i) { }; operator double () { return real; } //重载强制类型转换运算符 double }; int main(){ Complex c(1.2,3.4); cout << (double)c << endl; //输出 1.2 double n = 2 + c; //等价于 double n=2+c.operator double() cout << n; //输出 3.2 }
2.5 自增自减运算符的重载
自增运算符++、自减运算符--有前置/后置之分,为了区分所重载的是前置运算符还是后置运算符,C++规定:
- 前置运算符作为一元运算符重载。比如重载为成员函数:T & operator++();重载成全局函数:T1 & operator—(T2)。
- 后置运算符作为二元运算符重载,多写一个没用的参数:比如重载为成员函数:T operator++(int);重载成全局函数:T1 operator—(T2,int)。
此处需注意:
- 通常前置返回引用,后置返回类。且后置涉及较多的开销,故尽量用前置。
- int 作为一个类型强制转换运算符被重载,故(int) s 等效于 s.int()。
class CDemo { private : int n; public: CDemo(int i=0):n(i) { } CDemo & operator++(); // 用于前置形式 CDemo operator++( int ); // 用于后置形式 operator int ( ) { return n; } //强制类型转换运算符的重载 friend CDemo & operator--(CDemo & ); friend CDemo operator--(CDemo & ,int); }; CDemo & CDemo::operator++(){ //前置 ++ n ++; return * this; } // ++s即为: s.operator++(); CDemo CDemo::operator++( int k ){ //后置 ++ CDemo tmp(*this); // 记录修改前的对象 n ++; return tmp; // 返回修改前的对象 } // s++即为: s.operator++(0); CDemo & operator--(CDemo & d){// 前置-- d.n--; return d; } //--s即为: operator--(s); CDemo operator--(CDemo & d,int){// 后置-- CDemo tmp(d); d.n --; return tmp; } //s--即为: operator--(s, 0); int main(){ CDemo d(5); cout << (d++ ) << ","; //于 等价于 d.operator++(0); cout << d << ","; cout << (++d) << ","; //于 等价于 d.operator++(); cout << d << endl; cout << (d-- ) << ","; //于 等价于 operator--(d,0); cout << d << ","; cout << (--d) << ","; //于 等价于 operator--(d); cout << d << endl; return 0; } //输出结果: //5,6,7,7 //7,6,5,5
对于运算符的重载,注意事项:
- C++不允许定义新的运算符;
- 重载后运算符的含义应该符合日常习惯;
- 运算符重载不改变运算符的优先级;
- 以下运算符不能被重载:“.”、“ .* ”、“::”、“?:”、sizeof;
- 重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数。
3 练习
需要掌握String对象和可变长数组对象的设计。
//001:MyString http://cxsjsxmooc.openjudge.cn/2019t3fall4/001/ #include <iostream> #include <cstring> using namespace std; class MyString { char * p; public: MyString(const char * s) { if(s){ p = new char[strlen(s) + 1]; strcpy(p,s); }else p = NULL; } ~MyString() { if(p) delete [] p; } //your code start here MyString(const MyString& str1){ p = new char[strlen(str1.p)+1]; strcpy(p, str1.p); } MyString& operator=(MyString& str1){ if(this==&str1) return *this; if(p) delete[]p; p = new char[strlen(str1.p)+1]; //即便str1.p为空也可以 strcpy(p, str1.p); return *this; } MyString& operator=(const char* s){ if(p) delete []p; p = new char[strlen(s)+1]; strcpy(p, s); return *this; } void Copy(const char* s){ if(p) delete []p; p = new char[strlen(s)+1]; strcpy(p, s); } friend ostream& operator<<(ostream& o, MyString& str1){ cout << str1.p; return o; } //your code end here }; int main(){ char w1[200],w2[100]; cin >> w1 >> w2; MyString s1(w1),s2 = s1; MyString s3(NULL); s3.Copy(w1); cout << s1 << "," << s2 << "," << s3 << endl; s2 = w2; s3 = s2; s1 = s3; cout << s1 << "," << s2 << "," << s3 << endl; }
来源:https://www.cnblogs.com/inchbyinch/p/12233222.html