C++类和对象之运算符重载笔记

1.运算符重载

1.1 运算符重载基本概念

    运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
    注意:运算符重载(operator overloading)只是一种”语法上的方便”,也就是它只是另一种函数调用的方式。
    在c++中,可以定义一个处理类的新运算符。这种定义很像一个普通的函数定义,只是函数的名字由关键字operator及其紧跟的运算符组成。差别仅此而已。它像任何其他函数一样也是一个函数,当编译器遇到适当的模式时,就会调用这个函数。
语法:
定义重载的运算符就像定义函数,只是该函数的名字是operator@,这里的@代表了被重载的运算符。函数的参数中参数个数取决于两个因素。

  • 运算符是一元(一个参数)的还是二元(两个参数);
  • 运算符被定义为全局函数(对于一元是一个参数,对于二元是两个参数)还是成员函数(对于一元没有参数,对于二元是一个参数-此时该类的对象用作左耳参数)
    两个极端:
        有些人很容易滥用运算符重载。它确实是一个有趣的工具。但是应该注意,它仅仅是一种语法上的方便而已,是另外一种函数调用的方式。从这个角度来看,只有在能使涉及类的代码更易写,尤其是更易读时(请记住,读代码的机会比我们写代码多多了)才有理由重载运算符。如果不是这样,就改用其他更易用,更易读的方式。
        对于运算符重载,另外一个常见的反应是恐慌:突然之间,C运算符的含义变得不同寻常了,一切都变了,所有C代码的功能都要改变!并非如此,对于内置的数据类型的表达式的的运算符是不可能改变的。(例如想重载int类型数据的+号)

1.2 运算符重载碰上友元函数

    友元函数是一个全局函数,和我们上例写的全局函数类似,只是友元函数可以访问某个类私有数据。

1.3 可重载的运算符

    几乎C中所有的运算符都可以重载,但运算符重载的使用时相当受限制的。特别是不能使用C中当前没有意义的运算符(例如用**求幂)不能改变运算符优先级,不能改变运算符的参数个数。这样的限制有意义,否则,所有这些行为产生的运算符只会混淆而不是澄清寓语意。
在这里插入图片描述

1.4 加号运算符重载

    对于内置的数据类型,编译器知道如何进行运算,但是对于自定义数据类型,编译器不知道如何运算,利用运算符重载,可以让符号有新的含义,利用运算符重载,可以让符号有新的含义。
实现程序:利用加号重载,实现 Person数据类型的p1 + p2相加操作,成员函数和全局函数 都可以实现重载。

  • 关键字 operator +
  • 成员本质 p1.operator+(p2)
  • 全局本质 operator+(p1,p2)
  • 简化 p1 + p2
  • 运算符重载 也可以发生函数重载

代码实现

class Person {
public:
	Person() {};
	Person(int a, int b) :m_A(a), m_B(b)
	{};
	//利用成员函数实现加号运算符重载
	Person operator+(Person &p)
	{
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}
	int m_A;
	int m_B;
};
//利用全局函数实现加号运算符重载
//Person operator+(Person &p1, Person &p2) 
//{
//	Person temp;
//	temp.m_A = p1.m_A + p2.m_A;
//	temp.m_B = p1.m_B + p2.m_B;
//	return temp;
//}
Person operator+(Person &p1, int num)
{
	Person temp;
	temp.m_A = p1.m_A + num;
	temp.m_B = p1.m_B + num;
	return temp;
}
void test01() 
{
	Person p1(10, 10);
	Person p2(20, 20);

	Person p3 = p1 + p2;
	//Person p3=operator+(p1,p2);//全局函数本质
	//Person p3=p1.operator+(p2);//成员函数本质
	cout << "p3.m_A=" << p3.m_A << " p3.m_B=" << p3.m_B << endl;

	//运算符重载,可不可以发生函数重载?当然可以
	cout<<"(p1+10).m_A="<<(p1 + 10).m_A<<endl;
}

在这里插入图片描述

1.5 左移运算符重载

  • 不要滥用运算符重载,除非有需求;

  • 对于自定义数据类型,不可以直接用 cout << 输出,需要重载左移运算符;

  • 如果利用成员函数重载 ,无法实现让cout 在左侧,因此不用成员函数重载;

  • 利用全局函数 实现左移运算符重载:
    ostream& operator<<(ostream &cout, Person & p1)

  • 如果想访问类中私有内存,可以配置友元实现

class Person 
{
	friend ostream& operator<<(ostream &cout, Person &p1);
public:
	Person(int a, int b) 
	{
		this->m_A = a;
		this->m_B = b;
	}
	//试图利用成员函数 做<<重载
	//void operator<<(Person &p)//p.operator<<(cout)  p<<cout
	//{
	//
	//}
private:
	int m_A;
	int m_B;
};
//利用全局函数 实现左移运算符重载
ostream& operator<<(ostream &cout, Person &p1) 
{
	cout << "m_A=" << p1.m_A << "  m_B=" << p1.m_B << endl;
	return cout;
}
void test01() 
{
	Person p1(10, 10);
	cout << p1<<endl;
}

在这里插入图片描述

1.6 递增运算符重载

    重载的++和–运算符有点让人不知所措,因为我们总是希望能根据它们出现在所作用对象的前面还是后面来调用不同的函数。解决办法很简单,例如当编译器看到++a(前置++),它就调用operator++(a),当编译器看到a++(后置++),它就会去调用operator++(a,int).

  • 前置递增:
    MyInter& operator++()

  • 后置递增:
    MyInter operator++(int)

  • 前置++ 效率高于 后置++ 效率 ,因为后置++会调用拷贝构造,创建新的数据。

class MyInter
{
	friend ostream& operator<<(ostream& cout, MyInter& myInt);
public:
	MyInter()
	{
		m_Num = 0;
	}
	//前置++ 重载
	MyInter& operator++()
	{
		this->m_Num++;
		return *this;
	}

	//后置++ 重载
	MyInter operator++(int)
	{
		//先记录初始状态
		MyInter temp = *this;

		this->m_Num++;

		return temp;
	}

private:
	int m_Num;
};


ostream& operator<<(ostream& cout, MyInter& myInt)
{
	cout << myInt.m_Num;
	return cout;
}

void test01()
{
	MyInter myInt;
	cout << ++(++myInt) << endl;
	cout << myInt << endl;
}

void test02()
{
	MyInter myInt;
	myInt++ ;//这有点问题,还未解决
	cout << myInt << endl;

}

1.5 指针运算符(*、->)重载

  • 智能指针
  • 用途: 托管new出来的对象的释放
  • 设计smartPoint智能指针类,内部维护 Person * ,在析构时候释放堆区new出来的person对象
  • 重载 -> * 让 sp智能指针用起来向真正的指针
class Person
{
public:
	Person(int age)
	{
		cout << "Person的有参构造调用" << endl;
		this->m_Age = age;
	}
	void showAge()
	{
		cout << "年龄为:" << this->m_Age << endl;
	}
	~Person()
	{
		cout << "Person的析构调用" << endl;
	}
	int m_Age;
};
class SmartPoint 
{
public:
	SmartPoint(Person *person)
	{
		this->m_person = person;
	}
	//重载->运算符
	Person *operator->()
	{
		return this->m_person;
	}
	//重载* 运算符
	Person &operator*()
	{
		return *m_person;
	}
	~SmartPoint()
	{
		if (this->m_person)
		{
			delete this->m_person;
			this->m_person = NULL;
		}
	}
private:
	Person *m_person;
};
void test01()
{
	//Person *p=new Person(18);
	//(*p).showAge();
	//p->showAge();
	//delete p;

	//利用智能指针 管理new出来的person的释放操作
	SmartPoint sp(new Person(18));
	sp->showAge();//本质sp->->showAge();编译器简化为sp->showAge();
	(*sp).showAge();
}

在这里插入图片描述

1.6 赋值(=)运算符重载

    赋值符常常初学者的混淆。这是毫无疑问的,因为’=’在编程中是最基本的运算符,可以进行赋值操作,也能引起拷贝构造函数的调用。

  • 编译器会默认个一个类添加4个函数:
    默认构造、析构 、 拷贝构造(值拷贝) 、 operator=(值拷贝)
  • 如果类中有属性创建在堆区,利用编译器提供的 = 赋值运算就会出现堆区内存重复释放的问题
  • 解决方案:利用深拷贝 ,重载 =运算符
    Person& operator=( const Person &p)
//编译器 默认给一个类4个函数   默认构造   析构   拷贝构造 (值拷贝)  operator= (值拷贝)
class Person
{
public:
	Person(const char * name, int age)
	{
		this->m_Name = new char[strlen(name) + 1];
		strcpy(this->m_Name, name);
		this->m_Age = age;
	}

	//重载 =
	Person& operator=(const Person &p)
	{
		//先判断原来堆区释放有内容,如果有先释放
		if (this->m_Name != NULL)
		{
			delete[] this->m_Name;
			this->m_Name = NULL;
		}

		this->m_Name = new char[strlen(p.m_Name) + 1];
		strcpy(this->m_Name, p.m_Name);
		this->m_Age = p.m_Age;
		return *this;
	}

	//拷贝构造
	Person(const Person & p)
	{
		this->m_Name = new char[strlen(p.m_Name) + 1];
		strcpy(this->m_Name, p.m_Name);
		this->m_Age = p.m_Age;
	}

	~Person()
	{
		if (this->m_Name != NULL)
		{
			delete[] this->m_Name;
			this->m_Name = NULL;
		}
	}

	char * m_Name;
	int m_Age;
};


void test01()
{
	Person p1("Tom", 10);

	Person p2("Jerry", 19);
	p2 = p1;

	zPerson p3("", 0);
	p3 = p2 = p1;


	Person p4 = p3;

	cout << "p1姓名: " << p1.m_Name << "  p1年龄: " << p1.m_Age << endl;
	cout << "p2姓名: " << p2.m_Name << "  p2年龄: " << p2.m_Age << endl;
	cout << "p3姓名: " << p3.m_Name << " p3年龄: " << p3.m_Age << endl;

}

在这里插入图片描述

1.7 等于和不等于(==、!=)运算符重载

  • 对于自定义数据类型,编译器不知道如果进行比较
  • 重载 == !=号
  • bool operator==( Person & p)
  • bool operator!=(Person & p)
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	bool operator==(Person &p) 
	{
		if (this->m_Name == p.m_Name&&this->m_Age == p.m_Age)
		{
			return true;
		}
		return false;
	}
	bool operator!=(Person &p) 
	{
		return !(this->m_Name == p.m_Name&&this->m_Age == p.m_Age);
	}
	string m_Name;
	int m_Age;
};
void test01()
{
	/*int a=10;
	int b = 20;
	if (a == b)
	{
		cout << "a==b" << endl;
	}
	else 
	{
		cout << "a!=b" << endl;
	}*/
	Person p1("Tom", 18);
	Person p2("Tom", 19);
	if (p1 == p2)
	{
		cout << "p1==p2" << endl;
	}
	else 
	{
		cout << "a!=b" << endl;
	}
	if (p1 != p2)
	{
		cout << "a!=b" << endl;
	}
	else {
		cout << "p1==p2" << endl;
	}
}

在这里插入图片描述

1.8 函数调用符号()重载

  • 使用时候很像函数调用,因此称为仿函数
  • void operator()(string text)
  • int operator()(int a,int b)
  • 仿函数写法不固定,比较灵活
  • cout << MyAdd()(1, 1) << endl; // 匿名函数对象 特点:当前行执行完立即释放
class MyPrint  
{
public:
	void operator()(string text)
	{
		cout << text << endl;
	}
};

void MyPrint2(string str) 
{
	cout << str << endl;
}
void test01()
{
	MyPrint myPrint;
	myPrint("hello world");//仿函数 本质是一个对象 函数对象
	
	MyPrint2("hello world");//普通函数
}
class MyAdd
{
public:
	int operator()(int a, int b)
	{
		return a + b;
	}
};
void test02()
{
	MyAdd myAdd;
	cout << myAdd(1, 1) << endl;
	cout << MyAdd()(1, 1) << endl;//匿名函数对象特点: 当前行执行完立即释发
}

在这里插入图片描述

1.9 不要重载&&、||

    不能重载operator&& 和 operator|| 的原因是,无法在这两种情况下实现内置操作符的完整语义。说得更具体一些,内置版本版本特殊之处在于:内置版本的&&和||首先计算左边的表达式,如果这完全能够决定结果,就无需计算右边的表达式了–而且能够保证不需要。我们都已经习惯这种方便的特性了。
    我们说操作符重载其实是另一种形式的函数调用而已,对于函数调用总是在函数执行之前对所有参数进行求值。

class Complex{
public:
	Complex(int flag){
		this->flag = flag;
	}
	Complex& operator+=(Complex& complex){
		this->flag = this->flag + complex.flag;
		return *this;
	}
	bool operator&&(Complex& complex){
		return this->flag && complex.flag;
	}
public:
	int flag;
};
int main(){

	Complex complex1(0);  //flag 0 
	Complex complex2(1);  //flag 1

	//原来情况,应该从左往右运算,左边为假,则退出运算,结果为假
	//这边却是,先运算(complex1+complex2),导致,complex1的flag变为complex1+complex2的值, complex1.a = 1
	// 1 && 1
	//complex1.operator&&(complex1.operator+=(complex2))
	if (complex1 && (complex1 += complex2)){   
		cout << "真!" << endl;
	}
	else{
		cout << "假!" << endl;
	}

	return EXIT_SUCCESS;
}

    根据内置&&的执行顺序,我们发现这个案例中执行顺序并不是从左向右,而是先右后左,这就是不满足我们习惯的特性了。由于complex1 += complex2先执行,导致complex1 本身发生了变化,初始值是0,现在经过+=运算变成1,1 && 1输出了真。

1.10 符号重载总结

  • =, [], () 和 -> 操作符只能通过成员函数进行重载
  • << 和 >>只能通过全局函数配合友元函数进行重载
  • 不要重载 && 和 || 操作符,因为无法实现短路规则
    常规建议
    在这里插入图片描述

1.11 强化训练_字符串类封装

  • myString类 实现自定义的字符串类
  • 属性:
    char * pString; 维护 在堆区真实开辟的字符数组
    int m_Size; 字符串长度
  • 行为:
    有参构造 MyString(char * str)
    拷贝构造 MyString(const MyString & str);
    析构 ~MyString();
  • 重载<< 运算符
  • 重载 >> 运算符
  • 重载 = 赋值运算
  • 重载 [] str[0] 按照索引位置设置获取字符
  • 重载 + 字符串拼接
  • 重载 == 对比字符串
    myString.h
#pragma  once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class MyString
{
	//左移运算符友元
	friend ostream& operator<<(ostream & cout, MyString & str);
	//右移运算符 友元
	friend istream&  operator>>(istream & cin, MyString & str);
public:

	//有参构造
	MyString(const char  * str);
	//拷贝构造
	MyString(const MyString & str);

	//重载=运算符 

	MyString& operator=(const char * str);
	MyString& operator=(const MyString & str);

	//重载[]运算符
	char& operator[](int index);

	//重载+运算符
	MyString operator+(const char * str);
	MyString operator+(const MyString&str);

	//重载==运算符 
	bool operator==(const char *str);
	bool operator==(const MyString &str);

	//析构
	~MyString();

private:

	char * pString; //维护在堆区开辟的字符数组

	int m_Size; //字符串长度 不统计\0

};

myString.cpp

#include "myString.h"

//重载左移运算符
ostream& operator<<(ostream & cout, MyString & str)
{
	cout << str.pString;
	return cout;
}

//重载右移运算符
istream&  operator>>(istream & cin, MyString & str)
{
	//先清空原来堆区数据
	if (str.pString)
	{
		delete[] str.pString;
		str.pString = NULL;
	}

	char buf[1024];//开辟临时数组 记录用户输入内容
	cin >> buf;

	str.pString = new char[strlen(buf) + 1];
	strcpy(str.pString, buf);
	str.m_Size = strlen(buf);

	return cin;
}

MyString::MyString(const char * str)
{
	//cout << "MyString有参构造函数调用" << endl;
	this->pString = new char[strlen(str) + 1];
	strcpy(this->pString, str);
	this->m_Size = strlen(str);
}

MyString::MyString(const MyString & str)
{
	//cout << "拷贝构造函数调用" << endl;
	this->pString = new char[strlen(str.pString) + 1];
	strcpy(this->pString, str.pString);
	this->m_Size = str.m_Size;
}

MyString& MyString::operator=(const char * str)
{
	//先判断原来堆区释放有内容,如果有先释放
	if (this->pString != NULL)
	{
		delete[]this->pString;
		this->pString = NULL;
	}

	this->pString = new char[strlen(str) + 1];
	strcpy(this->pString, str);
	this->m_Size = strlen(str);
	return *this;
}

MyString& MyString::operator=(const MyString & str)
{
	if (this->pString != NULL)
	{
		delete[]this->pString;
		this->pString = NULL;
	}

	this->pString = new char[strlen(str.pString) + 1];
	strcpy(this->pString, str.pString);
	this->m_Size = strlen(str.pString);
	return *this;
}

char& MyString::operator[](int index)
{
	return this->pString[index];
}

MyString MyString::operator+(const char * str)
{
	//本身 abc   传入 def
	//计算开辟内存大小
	int newSize = this->m_Size + strlen(str) + 1;

	char * temp = new char[newSize];
	memset(temp, 0, newSize);

	strcat(temp, this->pString);
	strcat(temp, str);

	MyString newString = temp;

	delete[] temp;

	return newString;
}

MyString MyString::operator+(const MyString&str)
{
	int newSize = this->m_Size + strlen(str.pString) + 1;

	char * temp = new char[newSize];
	memset(temp, 0, newSize);

	strcat(temp, this->pString);
	strcat(temp, str.pString);

	MyString newString = temp;

	delete[] temp;

	return newString;
}

bool MyString::operator==(const char *str)
{
	if (strcmp(this->pString, str) == 0)
	{
		return true;
	}
	return false;
}

bool MyString::operator==(const MyString &str)
{
	if (strcmp(this->pString, str.pString) == 0)
	{
		return true;
	}
	return false;
}

MyString::~MyString()
{
	if (this->pString != NULL)
	{
		//cout << "析构调用" << endl;
		delete[] this->pString;
		this->pString = NULL;
	}

}

测试.cpp

#include<iostream>
using namespace std;
//#include <string>
#include "myString.h"


void test01()
{
	MyString str = "abc";

	cout << str << endl;

	cout << "请重新给str赋值:" << endl;

	cin >> str;

	cout << "str 新的值为: " << str << endl;


	MyString str2 = str;

	cout << "str2 = " << str2 << endl;

}

void test02()
{
	MyString str = "abcd";

	MyString str2 = "aaa";

	str2 = str;

	cout << "str2 = " << str2 << endl;

	cout << "str2[0] = " << str2[0] << endl;

	str2[0] = 'z';

	cout << "str2[0]改为z后输出:  " << str2 << endl;


	MyString str3 = "abc";
	MyString str4 = "def";
	MyString str5 = str3 + str4;
	MyString str6 = str5 + "ghe";
	cout << "str5 = " << str5 << endl;
	cout << "str6 = " << str6 << endl;


	if (str5 == str6)
	{
		cout << "str5 == str6" << endl;
	}
	else
	{
		cout << "str5 != str6" << endl;
	}

	if (str6 == "abcdefghe")
	{
		cout << "str6 = abcdefghe" << endl;
	}
	else
	{
		cout << "str6 != abcdefghe" << endl;
	}
}

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值