C++面向对象——运算符的重载

运算符重载的基本概念

运算符重载的需求

在这里插入图片描述

C++预定义的运算符,只能用于基本数据类型的运算:整型、实型、字符型、逻辑型……

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

在数学上,两个复数可以直接进行 +、- 等运算。

但在C++中,直接将 +或- 用于复数对象是不允许的。

有时会希望,让对象也能通过运算符进行运算。

这样代码更简洁,容易理解。

例如:

complex_a和complex_b是两个复数对象;

求两个复数之和,希望能直接写:

complex_a+complex_b

运算符重载

运算符重载就是对已有的运算符(C++中预定义的运算符)赋予多重的含义,使同一运算符作用于不同类型的数据时导致不同类型的行为。

运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之能作用于对象。

同一个运算符,对不同类型的操作数,所发生的行为不同。

complex_a+complex_b   //生成新的复数对象
5+4=9

运算符重载的形式

运算符重载的实质是函数重载

可以重载为普通函数,也可以重载为成员函数
在这里插入图片描述

把含运算符的表达式转换成对运算符函数的调用。

把运算符的操作数转换成运算符函数的参数。
C++面向对象——运算符的重载_第1张图片
运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。

返回值类型 operator 运算符(形参表)
{
     
	……
}

运算符重载的示例

#include
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); //返回一个临时对象
}
// 重载为成员函数时,参数个数为运算符目数减一。
// 重载为普通函数时,参数个数为运算符目数(相当于X+Y中的X和Y)。
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;
}
// 输出:
//	5,5
//	3,3

赋值运算符的重载

赋值运算符“=”重载

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

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

#include
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( ) {
      delete[]str; }
};
String& String::operator=(const char* s) {
     
			//重载“=”以使得obj="hello"能够成立
	delete[]str;
	str = new char[strlen(s) + 1];
	strcpy_s(str, strlen(s) + 1, 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;
}
// 输出:
//	Good Luck,
//	shenzhou 8!

浅拷贝和深拷贝

#include
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) {
     
		delete[] str;
		str = new char[strlen(s) + 1];
		strcpy_s(str, strlen(s) + 1, s);
		return *this;
	};
	~String() {
      delete[]str; }
};
int main() {
     
	String S1,S2;  //运行时程序崩溃
	S1 = "this";
	S2 = "that";
	S1 = S2;
	return 0;
 }

原因:

	String S1,S2;  
	S1 = "this";
	S2 = "that";
	S1 = S2;

如果不定义自己的赋值运算符,那么S1=S2实际上导致S1.str和S2.str指向同一地方。

如果S1对象消亡,析构函数将释放S1.str指向的空间,则S2消亡时还要释放一次,不妥。

另外,如果执行S1=“other”;会导致S2.str指向的地方被delete掉。

因此要在class String里添加成员函数:

 String& operator=(const String& s) {
     
	delete[]str;
	str = new char[strlen(s.str) + 1];
	strcpy(str, s.str);
	return *this;
}

此外,还有需要改进的地方:
添加

if (this == &s)    
			return *this;

到String& operator=(const String& s)中。

完整代码

#include
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) {
     
		delete[] str;
		str = new char[strlen(s) + 1];
		strcpy_s(str, strlen(s) + 1, s);
		return *this;
	};
	String& operator=(const String& s) {
     
		if (this == &s)    //改进::对判断当前对象的地址与s的地址是否一致
			return *this;
		delete[]str;
		str = new char[strlen(s.str) + 1];
		strcpy_s(str,strlen(s.str)+1, s.str);
		return *this;
	};
	~String() {
      delete[]str; }
};
int main() {
     
	String S1, S2;  
	S1 = "this";
	S2 = "that";
	S1 = S2;
	return 0;
}

对operator=返回值类型的讨论

void 好不好?

String 好不好?

为什么是String &

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

在C++中,赋值运算符的返回值是=左边那个变量的引用

考虑:

a=b=c;

(a=b)=c;   //会修改a的值

分别等价于:

a.operator=(b.operator=(c));
(a.operator=(b)).operator=(c);
  • 上面的String类是否就没有问题了?

为String类编写复制构造函数的时候,会面临和=同样的问题,用同样的方法处理。

String(String& s) {
     
	str = new char[strlen(s.str) + 1];
	strcpy_s(str, strlen(s.str) + 1, s.str);
}

运算符重载为友元函数

一般情况下,将运算符重载为类的成员函数,是较好的选择

class Complex {
     
	double real, imag;
public:
	Complex(double r, double i) :real(r), imag(i) {
       };
	Complex operator+(double r);
};
Complex Complex::operator+(double r) {
     
	//能解释 c+5
	return Complex(real + r, imag);
}

但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元

经过上述重载后:

Complex c;
c = c + 5; //有定义;相当于c=c.operator+(5);

但是:

c = 5 + c; //编译出错

所以,为了使得上述的表达式能成立,需要将+重载为普通函数。

Complex operator+(double r, const Complex& c) {
     
		//能解释 5+c
	return Complex(c.real + r, c.imag);
}

但是普通函数又不能访问私有成员,所以,需要将运算符+重载为友元

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

可变长数组类的实现

运算符重载实例:可变长整型数组

#include
using namespace std;
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&operator[](int i){
      //返回值为int不行!不支持a[i]=4
		//用以支持根据下标访问数组元素,
		//如n=a[i]和a[i]=4;这样的语句
		return ptr[i];
	}
};
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; //加入新的数组元素
}
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;
}
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()返回()
		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

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

流插入运算符的重载

cout 是在 iostream 中定义的,ostream 类的对象。

“<<”能用在cout上是因为,在iostream里对“<<”进行了重载。

  • 示例:

cout<<5:

ostream& ostream::operator<<(int n) {
     
	......//输出n的代码
		return*this;
}

cout<<“this”:

ostream& ostream::operator<<(const char* s) {
     
	......//输出s的代码
		return*this;
}
cout<<5<<"this";
 // 调用形式:
cout.operator<<(5).operator<<("this");

假定下面程序输出为 5hello,

#include
using namespace std;
class CStudent {
     
public:int nAge;
};
int main() {
     
	CStudent s;
	s.nAge = 5;
	cout << s << "helllo";
	return 0;
}

则要补写:

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。

#include
#include
#include
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<<endl;
	return 0;
}

在这里插入图片描述

类型转换运算符的重载

#include
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,再多写就比较啰嗦
	//重载强制类型转换运算符 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
	return 0;
}

自增,自减运算符的重载

自增运算符++、自减运算符–有前置/后置之分,为了区分所重载的是前置运算符还是后置运算符,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);

但是在没有后置运算符重载而有前置重载的情况下,

在vs中,obj++也调用前置重载,而dev则令obj++编译出错

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

运算符重载的注意事项

  1. C++不允许定义新的运算符;
  2. 重载后运算符的含义应该符合日常习惯;
comlpex_a + complex_b;
word_a > word_b;
date_b = date_a + n;
  1. 运算符重载不改变运算符的优先级;
  2. 以下运算符不能被重载:“ . ”、“ * ”、“ :: ”、“ ? : ”、sizeof ;
  3. 重载运算符()、[ ]、-> 或者赋值运算符 = 时,运算符重载函数必须声明为类的成员函数。

你可能感兴趣的:(笔记,c++)