C++初阶讲解—运算符重载汇总+实例(上)

目录

运算重载符

选择作为成员或非成员

赋值运算符重载

算术运算符(加号)重载

输出和输入运算符重载

前置++和后置++运算符重载


运算重载符

概念: 运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数原型:

返回值 operator操作符(参数列表)

注意:

  • 运算符重载,就是对已有的运算符重新进行定义,赋予其另外一种功能,以适应不同的数据类型
  • 不能通过重载操作符来创建新的操作符,如operator@
  • 必须含有一个类类型或枚举类型的操作数
  • 不能改变内置类型的含义
  • 重载函数如果写成成员函数,左侧第一个参数隐含了一个Date* const this的形参,限定为第一个形参
  • 重载函数如果写成全局,那么左边是你输入的第一个参数,右边是第二个参数
  • .*,::,?:,sizeof,.这五个运算符不能重载

注意以下几点:

  • 除了赋值号(=)之外,基类中被重载的操作符都将被派生类继承
  • =,[],(),->操作符只能通过成员函数进行重载
  • << 和 >> 操作符最好通过友元进行重载
  • 不要重载&&和||运算符,因为无法实现短路规则

选择作为成员或非成员

当我们定义重载的运算符时,必须首先决定是将其声明为类的成员函数还是声明为一个普通的非成员函数。在某些时候我们别无选择,因为有的运算符必须作为成员;另一些情况下,运算符作为普通函数比作为成员更好。

下面的准则有利于我们在运算符定义为成员函数还是普通的非成员函数做出抉择:

  • 赋值(=)、下标([ ])、调用(())和成员访问箭头)(->),运算符必须是成员
  • 复合运算符一般来说是成员,但是非必须
  • 改变成员状态的运算符或者给定类型密切相关的运算符,比如递增、递减和解引用运算符,通常应该是成员
  • 具有对称性的运算符可能转换任意一端的运算对象,例如算术、相等性、关系和位运算符等,因此他们通常应该是普通的非成员对象

赋值运算符重载

我们可以重载赋值运算符,无论形参的类型是什么,赋值运算符都必须定义为成员函数

赋值运算符,赋值之后,左侧运算对象和右侧运算对象的值相等,并且运算应该返回它左侧运算对象的一个引用

特性:

  • 返回是*this
  • 如果没有显示定义,编译器也会生成一个,按字节序拷贝
Date& operator=(const Date& d)
{
	// 检测是否自己给自己赋值
	if (this == &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	return *this;
}

实例:

#define _CRT_SECURE_NO_WARNINGS
#include //引入头文件
#include//C++中的字符串
using namespace std; //标准命名空间
class Maker
{
public:
	Maker()
	{
		id = 0;
		age = 0;
	}
	Maker(int id, int age)
	{
		this->id = id;
		this->age = age;
	}
public:
	int id;
	int age;
};
void test01()
{
	Maker m1(10, 20);
	Maker m2;
	m2 = m1;
	//默认的赋值运算符重载函数进行了简单的赋值操作
	//就类似于字节序的浅拷贝
	cout << m2.age <<  m2.id << endl;
}
class Student
{
public:
	Student(const char * name)
	{
		pName = new char[strlen(name)+1];
		strcpy(pName, name);
	}
	//防止浅拷贝
	Student(const Student& stu)
	{ 
		pName = new char[strlen(stu.pName) + 1];
		strcpy(pName, stu.pName);
	}
	//重写赋值运算符重载函数
	//为什么要返回引用
	Student& operator =(const Student& stu)//第一个参数默认是this指针
	{
		//1.不能确定this指向的空间是否能装下stu中的数据,所以先释放this指向的空间
		if (this->pName!= NULL)
		{
			delete[] this->pName;
			this->pName = NULL;
		}
		//2.释放了之后再来申请堆区空间,大小由stu决定
		this->pName = new char[(strlen(stu.pName) + 1)];
		//3.拷贝函数
		strcpy(this->pName, stu.pName);
		//返回对象本身
		return *this;//this中存放的是对象的地址,对地址取*表示对象本身
	}
	void printfStudent()
	{
		cout << this->pName << endl;
	}
	~Student()
	{
		delete[] pName;
		pName = NULL;
	}
public:
	char* pName ;
};
void test02()
{
	Student s1("悟空");
	Student s2("唐僧");
	Student s3("八戒");
	s1 = s2 = s3;
	s1.printfStudent();
	s2.printfStudent();
	s3.printfStudent();
	cout << &(s2 = s3) << endl;
	cout << &s2 << endl;
}
int main()
{
	test01();
	cout << "-------------------------------" << endl;
	test02();
	system("pause");
	return EXIT_SUCCESS;
}

运行结果如下:

C++初阶讲解—运算符重载汇总+实例(上)_第1张图片

 

在代码中留下了一个问题,为什么重载的赋值操作运算符要返回左侧对象的引用呢?

从s1 = s2 = s3开始说,赋值运算符本来的寓意是s3赋值s2,s2赋值s1,也就是说s2=s3这个表达式要返回原来s2的对象,如果不是引用那么s2将会生成一个新的对象,所以要返回引用
本质:Student &operator =(const Student& stu)这个函数返回的是return *this,如果加上引用Student & = *this;实际上就是this空间重新取了个名字

但是如果不是引用,那么就是Student = *this,会重新生成一个对象。就比如 int &a = b和int a = b的区别,第一种返回的还是原来的b,第二个返回的就是新的a

算术运算符(加号)重载

一般来说,我们把算数和关系运算符定义成非成员函数以允许对左侧或者右侧的运算对象进行转换。因为这些运算符一般不需要改变运算对象的状态,所以形参都是常量的引用。

下面的代码从成员函数和非成员函数都介绍算术运算符的重载

#define _CRT_SECURE_NO_WARNINGS
#include //引入头文件
#include//C++中的字符串
using namespace std; //标准命名空间
class Maker
{
public:
	Maker(int id, int age)
	{
		this->age = age;
		this->id = id;
	}
	//1.成员函数,就需要一个参数,这个参数就是加号的右边
	Maker operator+(const Maker& m2)
	{
		//加号左边变成this
		Maker temp(this->id + m2.id, this->age + m2.age);
		return temp;//返回的时候调用拷贝构造
	}
public:
	int id;
	int age;
};
//2.全局的方式
//编译器会调用这个函数,编译器会检查参数是否对应
Maker operator+(Maker& m1, Maker &m2)
{
	Maker temp(m1.id + m2.id, m1.age + m2.age);//调用有参构造
	return temp;//返回的时候会调用拷贝构造
}
void test()
{
	Maker m1(1, 20);
	Maker m2(2, 22);
	//m1+m2显然是不可以的,要重载运算符
	Maker m3 = m1 + m2;//编译器看到两个对象相加,编译器会去找有没有operator+函数
	cout << m3.id << endl;
	cout << m3.age << endl;
}
int main()
{
	test();
	system("pause");
	return EXIT_SUCCESS;
}

运行结果如下:

C++初阶讲解—运算符重载汇总+实例(上)_第2张图片

 关系运算符重载

bool operator == (Maker &m)
{
	if(this->id == m.id && this->age == m.age){
		return true;
	}
	return false;
}

输出和输入运算符重载

输出 << 运算符 的重载

输出运算符应该主要负责打印对象的内容而非控制格式,输出运算符不应该打印换行符

输出运算符的第一个形参是一个非常量ostream对象的引用,之所以ostream是个非常量是因为向流写入内容会改变其状态;而该形参是引用是因为我们无法直接复制一个ostream对象

第二个形参一般来说是一个常量的引用,该常量就是我们想要打印的类类型。第二个形参是引用是因为我们希望避免复制实参;而之所以该形参可以是常量是因为通常情况下打印对象不会改变对象的内容

#define _CRT_SECURE_NO_WARNINGS
#include //引入头文件
#include//C++中的字符串
using namespace std; //标准命名空间
class Maker
{
	//友元函数
	friend ostream& operator <<(ostream& out, Maker& m);
public:
	Maker(int id, string name)
	{
		this->id = id;
		this->name = name;
	}
private:
	int id;
	string name;
};
//1.形参和实参是一个对象,因为用的是引用,只是对同一块空间取别名
//2.对 << 运算符进行重载,左边是ostream对象,右边是Maker对象
//3.一定要用引用,因为ostream把拷贝构造函数私有化了
//4.如果要和endl一起用,那么就必须返回ostream
ostream& operator << (ostream& out, Maker& m)
{
	cout << m.id << " " << m.name << endl;
	return out;
}
void test01()
{
	Maker m(10, "小花");
	cout << m;
	cout << endl;
	/*
		endl是一个函数
		operator << (函数指针)
		operator(endl)把endl函数传进来
	*/
	cout << 10;//在内部重载了基础数据类型
}
int main()
{
	test01();
	system("pause");
	return EXIT_SUCCESS;
}

输入 >> 运算符的重载

输入运算符必须处理输入可能失败的情况,而输出运算符不需要

输入运算符的第一个形参是运算符想要读取的流的引用,第二个形参是将要读入到的(非常量)对象的引用。该运算符通常会返回某个给定流的引用。第二个形参之所以必须是个非常量是因为输入运算符本身的目的就是将数据读入到这个对象中

#define _CRT_SECURE_NO_WARNINGS
#include //引入头文件
#include//C++中的字符串
using namespace std; //标准命名空间
void test01()
{
	int a;
	cin >> a;//从键盘中获取数据
	cout << a << endl;
}

class Maker
{
	//访问私有成员要设置为友元函数
	friend istream& operator >>(istream& in, Maker& m);
public:
	Maker(string name, int age)
	{
		this->name = name;
		this->age = age;
	}
	int getAge()
	{
		return age;
	}
private:
	string name;
	int age;
};
//重载>>右移运算符
//同一个对象取别名
istream& operator >>(istream& in, Maker& m)
{
	in >> m.age;
	in >> m.name;
	return in;
}
void test02()
{
	Maker m("悟空", 15);
	Maker m2("悟空2", 25);
	cin >> m >> m2;
	cout << m.getAge() << endl;
	cout << m2.getAge() << endl;
}
int main()
{
	test02();
	system("pause");
	return EXIT_SUCCESS;
}

前置++和后置++运算符重载

前置++和后置++最大的区别就是返回值不同,前置是返回变化之后的值,后置是返回变化之前的值,两个在重载是,都是operator++,我们如何区分呢?
一般operator++默认是前置++,为了区分后置++,我们通常会在参数列表加一个占位参数,且这个参数必须是int类型的,从而构造成函数重载。

  • 前置和后置运算符应该返回递增或者递减后对象的引用,因为是在同一个对象上进行的操作
  • 后置运算应该返回对象的原值,返回的形式是一个值而并非是引用
#define _CRT_SECURE_NO_WARNINGS
#include //引入头文件
#include//C++中的字符串
using namespace std; //标准命名空间
class Person
{
public:
	Person(string name, int age)
	{
		this->_name = name;
		this->_age = age;
	}
	// 前置++
	Person& operator++()
	{
		//传入一个this指针
		this->_age++;
		return *this;// 返回变化之后的值,传引用
	}
	// 后置++,占位参数(必须是int)
	Person operator++(int)
	{
		//后置++,先返回,后++
		Person ret = *this;//调用拷贝构造,拷贝this的一个临时备份,用于返回
		this->_age++;//this也就是传入的对象中的_age值会改变
		return ret;// 返回变化之前的值,传值,返回的时候也调用的是拷贝构造
	}
	void Print()
	{
		cout << _name << "-" << _age << endl;
	}
private:
	string _name;
	int _age;
};

int main()
{
	Person p("wxj", 19);
	p.Print();

	cout << "前置++" << endl;
	// 前置++
	Person ret = ++p;
	ret.Print();
	p.Print();

	cout << "后置++" << endl;
	// 后置++
	ret = p++;
	ret.Print();
	p.Print();
	return 0;
}

运行结果如下:

C++初阶讲解—运算符重载汇总+实例(上)_第3张图片

 值得注意的是成员函数的操作运算符左边第一个参数是Date* const this一个this指针,所以只要对this指针进行操作,并且返回的是引用,那么依旧是同一个对象的值发生改变并且里面的值也会发生改变。

你可能感兴趣的:(c++,开发语言)