欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

运算符重载

程序员文章站 2022-05-18 16:34:26
...

运算符重载

C++自定义的运算符只能用于规定的基本数据类型的运算,但是如果我们要对自定义的类型进行运算,用预定的方法会显得十分麻烦

+、-、*、/、%、^、&、~、!、|、=、<< >>、!=、……

因此我们进行运算符重载进行随心所欲的操作!

 

复数类

在数学上,两个复数可以直接进行+、-等运算。但
在C++中,直接将+或-用于复数对象是不允许的。

有时会希望,让对象也能通过运算符进行运算。这
样代码更简洁,容易理解。

ex:
例如:
complex_a和complex_b是两个复数对象;
求两个复数的和, 希望能直接写:
complex_a + complex_b

运算符重载的实质其实就是函数重载,可以重载为成员函数,也可以重载为友元函数,但是两者在参数的设定上会有所不同。

下面的代码的运算符重载,两种方式都用到了

#include <iostream>
#include <cstring>
using namespace std;
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;
    cout << c.real << "," << c.imag << endl;
    cout << (a-b).real << "," << (a-b).imag << endl;
    return 0;
}

综上所述,一般情况下:

  1. 重载为成员函数时,参数个数为运算符目数减1。
  2. 重载为普通函数时,参数个数为运算符目数。

做一个QUIZ:
如果将 [ ] 运算符重载成一个类的成员函数,则该重载函数有几个参数?

answer:1

赋值运算符"="的重载

有时候希望赋值运算符两边的类型可以不匹配,
ex:把一个int类型变量赋值给一个Complex对象,
或把一个 char * 类型的字符串赋值给一个字符串对
象,此时就需要重载赋值运算符“=”。

!!!
赋值运算符“=”只能重载为成员函数

为什么呢,主要就是为了避免二义性的产生,就是不知道调用哪个函数。

主要记住这个东西就可以了,网上很多都是睁眼说瞎话!

#include <iostream>
#include <cstring>
using namespace std;
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;
}

运算符重载

为什么返回string的引用呢,想想返回引用有什么作用就知道了

String & operator = (const char * s)

下面这条语句是初始化语句,我们没有写相应的构造函数,所以会出错

  String s2 = "hello!"; //这条语句要是不注释掉就会出错

深拷贝和浅拷贝

深浅主要是指指向内存空间是否是一样的,反正数值两者肯定一样

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){
	delete [] str;
	str = new char[strlen(s)+1];
	strcpy( str, s);
	return * this;
};
	~String( ) { delete [] str; }
};

为什么上面程序要让s1=s2,为什么要重载呢?
见图
不想重载效果就是这样
运算符重载不就是其中一个空间变成了内存垃圾而已,电脑内存大,不成什么问题????

但是C++是需要手动删除new出来的存储空间的,你再delete s1,s2之后那不就同一块空间delete两次,程序崩溃。
运算符重载

但是上面的代码确实也存在着缺陷:

String s;
s = "Hello";
s = s;//如果这样子的话上面代码还是会崩溃

虽然人不会那么无聊写s=s这样的语句,不过我们也要考虑进去

solution:

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;
}

对运算符进行重载的时候,好的风格是应该尽量保留运算符原本的特性

这就是我们为什么返回一个引用的原因,考虑:

 a = b = c;
 (a=b)=c;

综上=重载时候主要考虑

  1. s=s return *this
  2. str为非空时候。。。
  3. s.str不为空时。。。
  4. 复制构造函数,无需考虑1,2(新出来的肯定非空,且不会时s)

如果你对上面的理解足够深刻了,下面代码就不用看了

题目链接

#include <cstdlib>
#include <iostream>
using namespace std;
int strlen(const char * s)
{	int i = 0;
	for(; s[i]; ++i);
	return i;
}
void strcpy(char * d,const char * s)
{
	int i = 0;
	for( i = 0; s[i]; ++i)
		d[i] = s[i];
	d[i] = 0;

}
int strcmp(const char * s1,const char * s2)
{
	for(int i = 0; s1[i] && s2[i] ; ++i) {
		if( s1[i] < s2[i] )
			return -1;
		else if( s1[i] > s2[i])
			return 1;
	}
	return 0;
}
void strcat(char * d,const char * s)
{
	int len = strlen(d);
	strcpy(d+len,s);
}
class MyString
{
private:
char * str;
int size;
public:
MyString(){
str = new char[2]; //确保分配的是数组
str[0] = 0;//既然是个字符串,里面起码也是个空串,不能让 str== NULL
size = 0;
}
MyString(const char * s) {
//如果 s == NULL,就让它出错吧
size = strlen(s);
str = new char[size+1];
strcpy(str,s);
}
MyString & operator=(const char * s ) {
  if(!s)
  {
      size=0;
      str=NULL;
  }
else
{
    int len = strlen(s);
if( size < len ) {
delete [] str;
str = new char[len+1];
}
strcpy( str,s);
size = len;
return * this;
}
}
void duplicate(const MyString & s) {
if( size < s.size ) { //否则就不用重新分配空间了
delete [] str;
str = new char[s.size+1];
}
strcpy(str,s.str);
size = s.size;
}
MyString(const MyString & s):size(0),str(new char[1]) {
duplicate(s);
}
MyString & operator=(const MyString & s) {
if( str == s.str )
return * this;
duplicate(s);
return * this;
}
bool operator==(const MyString & s) const {
return strcmp(str,s.str ) == 0;
}
bool operator<(const MyString & s) const {
return strcmp(str,s.str ) < 0;
}
bool operator>(const MyString & s) const {
return strcmp(str,s.str ) > 0;
}
MyString operator + ( const MyString & s ) {
char * tmp = new char[size + s.size + 2];//确保能分配一个数组
strcpy(tmp,str);
strcat(tmp,s.str);
MyString os(tmp);
delete [] tmp;
return os;
}
MyString & operator += ( const MyString & s) {
char * tmp = new char [size + s.size + 2];
strcpy( tmp,str);
strcat( tmp,s.str);
size += s.size;
delete [] str;
str = tmp;
return * this;
}
char & operator[](int i) const {
return str[i];
}
MyString operator()(int start,int len) const {
char * tmp = new char[len + 1];
for( int i = 0;i < len ; ++i)
tmp[i] = str[start+i];
tmp[len] = 0;
MyString s(tmp);
delete [] tmp;
return s;
}
~MyString() { delete [] str; }

friend ostream & operator << ( ostream & o,const MyString & s)
{
o << s.str ;
return o;
}
friend MyString operator +( const char * s1,const MyString & s2)
{
MyString tmp(s1);
tmp+= s2;
return tmp;
}
//your code ends here
//重载:此题+动态二维数组,大数运算
//基本函数,判断函数,重载函数
};


int CompareString( const void * e1, const void * e2)
{
	MyString * s1 = (MyString * ) e1;
	MyString * s2 = (MyString * ) e2;
	if( * s1 < *s2 )
	return -1;
	else if( *s1 == *s2)
	return 0;
	else if( *s1 > *s2 )
	return 1;
}
int main()
{
	MyString s1("abcd-"),s2,s3("efgh-"),s4(s1);
	MyString SArray[4] = {"big","me","about","take"};
	cout << "1. " << s1 << s2 << s3<< s4<< endl;
	s4 = s3;
	s3 = s1 + s3;
	cout << "2. " << s1 << endl;
	cout << "3. " << s2 << endl;
	cout << "4. " << s3 << endl;
	cout << "5. " << s4 << endl;
	cout << "6. " << s1[2] << endl;
	s2 = s1;
	s1 = "ijkl-";
	s1[2] = 'A' ;
	cout << "7. " << s2 << endl;
	cout << "8. " << s1 << endl;
	s1 += "mnop";
	cout << "9. " << s1 << endl;
	s4 = "qrst-" + s2;
	cout << "10. " << s4 << endl;
	s1 = s2 + s4 + " uvw " + "xyz";
	cout << "11. " << s1 << endl;
	qsort(SArray,4,sizeof(MyString),CompareString);
	for( int i = 0;i < 4;i ++ )
	cout << SArray[i] << endl;
	//s1的从下标0开始长度为4的子串
	cout << s1(0,4) << endl;
	//s1的从下标5开始长度为10的子串
	cout << s1(5,10) << endl;
	return 0;
}

重载为友元函数

像前面讲到的一样,重载+;
经过上述重载后:
Complex c ;
c = c + 5; //有定义,相当于 c = c.operator +(5);
但是:
c = 5 + c; //编译出错

因此我们需要重载为友元函数(方便访问)

class Complex 
{
double real,imag;
public:
	Complex( double r, double i):real(r),imag(i){ }; 
	Complex operator+( double r );
	friend Complex operator + (double r,const Complex & c);
};

可变长数组

完整的程序可见Gary书本上的p72页
要求是这样子的

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;
}

要重载“=”,要重载“[ ],要自己写复制构造函数

class CArray {
    int size; //数组元素的个数
    int *ptr; //指向动态分配的数组
public:
    CArray(int s = 0); //s代表数组元素的个数
    CArray(CArray & a);
    ~CArray();
    void push_back(int v); //用于在数组尾部添加一个元素v
    CArray & operator=( const CArray & a); 
    //用于数组对象间的赋值
    int length() { return size; } //返回数组元素个数
    int & CArray::operator[](int i) //返回值为 int 不行!不支持 a[i] = 4
    {//用以支持根据下标访问数组元素,
    // 如n = a[i] 和a[i] = 4; 这样的语句
    return ptr[i]; 
    } 
};
CArray::CArray(int s):size(s) 
{ 
if( s == 0)
    ptr = NULL;
else
    ptr = new int[s]; 
} 
CArray::CArray(CArray & a) {
if( !a.ptr) {
    ptr = NULL;
    size = 0;
return;
}
    ptr = new int[a.size];
    memcpy( ptr, a.ptr, sizeof(int ) * a.size);
    size = a.size;
}
CArray::~CArray() 
{ 
if( ptr) delete [] ptr; 
} 
CArray & CArray::operator=( const CArray & a)
{ //赋值号的作用是使“=”左边对象里存放的数组,大小和内容都和右边的对象一样
if( ptr == a.ptr) //防止a=a这样的赋值导致出错
    return * this;
if( a.ptr == NULL) { //如果a里面的数组是空的
    if( ptr ) delete [] ptr;
        ptr = NULL;
        size = 0;
        return * this;
}
if( size < a.size) { //如果原有空间够大,就不用分配新的空间
if(ptr) 
	delete [] ptr;
	ptr = new int[a.size];
}
	memcpy( ptr,a.ptr,sizeof(int)*a.size);
	size = a.size;
	return * this;
} // CArray & CArray::operator=( const CArray & a)
void CArray::push_back(int v)
{ //在数组尾部添加一个元素
if( ptr) { 
	int * tmpPtr = new int[size+1]; //重新分配空间
	memcpy(tmpPtr,ptr,sizeof(int)*size); //拷贝原数组内容
	delete [] ptr; 
	ptr = tmpPtr; 
}
	else //数组本来是空的
	ptr = new int[1];
	ptr[size++] = v; //加入新的数组元素
}

题目:
写一个二维数组类 Array2,使得下面程序的输出结果是:

0,1,2,3,

4,5,6,7,

8,9,10,11,

next

0,1,2,3,

4,5,6,7,

8,9,10,11,

**链接
**

#include <iostream>
#include <cstring>
using namespace std;

class Array2 {

private:
int * buf;
int row,col; //数组是 row 行,col 列
public:
Array2(int r,int c):row(r),col(c),buf(new int[r*c+2]) { }
Array2():buf(new int[2]),row(0),col(0) { }
//构造函数确保 buf 不会是 NULL,省得还要考虑 buf == NULL 的特殊情况,麻烦
//多分配点空间,无所谓
~Array2() {
delete [] buf;
}
int * operator [](int i) const {
return buf + i * col;                   //这个处理非常高!!!!!!!!
}
void duplicate(const Array2 & a) {
if( a.row == 0 || a.col == 0 )
row = col = 0; //空间暂时不回收也无所谓
else {
if( row * col < a.row * a.col ) { //空间不够大才重新分配空间
delete [] buf;
buf = new int[a.row*a.col];
}
memcpy(buf,a.buf,sizeof(int)*a.row * a.col);
row = a.row;
col = a.col;
}
}
Array2 & operator = (const Array2 & a) {
if( a.buf == buf )
return * this;
duplicate(a);
return * this;
}
Array2(const Array2 & a):buf(new int[2]),row(0),col(0) {
duplicate(a);
}
int  operator() ( int r,int c) const {
return buf[r * col + c];
} 
};

int main() {
    Array2 a(3,4);
    int i,j;
    for(  i = 0;i < 3; ++i )
        for(  j = 0; j < 4; j ++ )
            a[i][j] = i * 4 + j;
    for(  i = 0;i < 3; ++i ) {
        for(  j = 0; j < 4; j ++ ) {
            cout << a(i,j) << ",";
        }
        cout << endl;
    }
    cout << "next" << endl;
    Array2 b;     b = a;
    for(  i = 0;i < 3; ++i ) {
        for(  j = 0; j < 4; j ++ ) {
            cout << b[i][j] << ",";
        }
        cout << endl;
    }
    return 0;
}

流插入运算符和流提取运算符的重载

  1. cout 是在 iostream 中定义的,ostream 类 的对象。
  2. “<<” 能用在cout 上是因为,在iostream 里对 “<<” 进行了重载。

形如

friend ostream & operator<<( ostream & o,const CStudent & s){
o << s.nAge ;
return o;
}

为什么是友元函数呢?
你看看他的返回类型你结合前面讲到和自己理解到的函数重载就知道了!

小题目:
假定c是Complex复数类的对象,现在希望
写“cout << c;”,就能以“a+bi”的形
式输出c的值,写“cin>>c;”,就能从键
盘接受“a+bi”形式的输入,并且使得
c.real = a,c.imag = b。

STL中讲到过string类,我们就用string来写一下

#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Complex {
double real,imag;
public:
Complex( double r=0, double i=0):real(r),imag(i){ };
friend ostream & operator<<( ostream & os,
const Complex & c);
friend istream & operator>>( istream & is,Complex & c);
};
ostream & operator<<( ostream & os,const Complex & c)
{
os << c.real << "+" << c.imag << "i"; //以"a+bi"的形式输出
return os;
}
istream & operator>>( istream & is,Complex & c)
{
string s;
is >> s; //将"a+bi"作为字符串读入, “a+bi” 中间不能有空格
int pos = s.find("+",0);
string sTmp = s.substr(0,pos); //分离出代表实部的字符串
c.real = atof(sTmp.c_str()); //atof库函数能将const char*指针指向的内容转换成 float
sTmp = s.substr(pos+1, s.length()-pos-2); //分离出代表虚部的字符串
c.imag = atof(sTmp.c_str());
return is;
}
int main()
{
Complex c;
int n;
cin >> c >> n;
cout << c << "," << n;
return 0;
}

自增自减运算符和类型转换运算符重载

类型转换运算符重载非常简单

#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
}

自增自减这一部分只要你对自增自减的原理熟悉的话,实现起来是非常简单的

在C++中规定的:
前置运算符作为一元运算符重载
重载为成员函数:
T & operator++();
T & operator–();
重载为全局函数:
T1 & operator++(T2);
T1 & operator—(T2);

后置运算符作为二元运算符重载,多写一个没用的参数:
重载为成员函数:
T operator++(int);
T operator–(int);
重载为全局函数:
T1 operator++(T2,int );
T1 operator–( T2,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;
}

这里,int 作为一个类型强制转换运算符被重载

operator int ( ) { return n; }

重载注意事项

  1. 不允许定义新的运算符
  2. 重载应该不能毁三观,符合正常的运算习惯
  3. 重载过后不改变运算符优先级
  4. 不能重载的运算符:“.”、“.*”、“::”、“?:”、sizeof;
  5. 一定要成员函数的重载:符()、[]、->或者赋值运算符=

学会程序和算法,走遍天下都不怕
运算符重载香港维多利亚港

相关标签: C++