C++入门--运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,对已有的运算符重新进行定义,赋予其另一种功能,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

运算符重载的定义

函数名字:operator后面接需要重载的运算符符号。

函数原型:Type operator操作符 (参数列表)。

注意

1、不能通过连接其他符号来创建新的操作符:比如operator@。

2、重载操作符必须有一个类类型,没有类型那么根本没有意义。

3、用于内置类型的操作符,其含义不能改变,例如:内置的整数+,不能改变其含义。

4、作为类成员的运算符重载函数时,其形参看起来比操作数数目少1个,其中成员函数的操作符有一个默认的形参this,限定为第一个形参。对于全局的运算符重载函数的参数和操作数目的个数相等。

5、.*、::、sizeof、?:、. 这5种运算符不能重载。

重载输入输出运算符

class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
public:
	Date(int year, int month, int day): _year(year),_month(month), _day(day){}

private:
	int _year;
	int _month;
	int _day;
};
//返回引用可以实现链式编程,ostream和istream用非常量,在输入或输出时要改变状态,用引用是因为我们无法直接复制一个ostream和istream对象。
ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout<<d._year<<"-"<<d._month<<"-"<<d._day;
	return _cout;
}
返回引用可以实现链式编程,Date声明为非常量因为我们本来的目的就是将数据读入到这个对象当中。
istream& operator>>(istream& _cin, Date& d)
{
	_cin>>d._year;
	_cin>>d._month;
	_cin>>d._day;
	return _cin;
}
//输入运算符必须处理输入可能失败的情况,如果输入失败应该将参数恢复成原始的状态。
int main()
{
	Date d;
	cin>>d;
	cout<<d<<endl;
	return 0;
}

输入输出运算符必须是非成员函数

与iostream 标准库兼容的输入输出运算符必须是普通的非成员函数,而不是类的成员函数。否则,它们的左侧运算对象将是我们的类的一个对象:

Date date;
date<<cout;		//如果operator<<是Date的成员

我们无法给标准库中的类添加任何成员。IO运算符通常需要读写类的非共有数据成员,所以IO运算符一般被声明为友元。

重载赋值运算符

赋值符常常初学者的混淆。这是毫无疑问的,因为’=’在编程中是最基本的运算符,可以进行赋值操作,也能引起拷贝构造函数的调用。当对象没有初始化而赋值时会引起拷贝构造函数的调用,当已经初始化了而赋值时会调用赋值运算重载函数的调用。

赋值运算符主要有以下几点

1、返回值(返回*this)。

2、检测是否自己给自己赋值。

3、一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝(浅拷贝)。当有指针时,调用析构函数会引发内存问题。

代码实例1:

class Person {
public:
	Person(string name, int age) :_name(name),_age(age){}
	//当准备给两个相同对象赋值时,检查是否自己给自己赋值
	Person& operator=(const Person& person) {
		if (this != &person) {
			_name = person._name;
			_age = person._age;
		}
		return *this;
	}
private:
	string _name;
	int _age;
};
void test() {
	Person person1("小明", 20);
	Person person2 = person1; //调用拷贝构造
	//如果一个对象还没有被创建,则必须初始化,也就是调用构造函数
	//上述例子由于person2还没有初始化,所以会调用构造函数
	//由于person2是从已有的person1来创建的,所以只有一个选择
	//就是调用拷贝构造函数

	person2 = person1; //调用operator=函数
	//由于person2已经创建,不需要再调用构造函数,这时候调用的是重载的赋值运算符
}

代码实例2:

//一个类默认创建 默认构造、析构、拷贝构造 operator=赋值运算符 进行简单的值传递(浅拷贝)
class Person {
public:
	Person() = default;//合成默认构造函数
	Person(const char *name,int age ) {
		this->_name = new char[strlen(name)+1];
		strcpy(this->_name,name);
		_age = age;
	}
	//重载赋值运算符
	Person& operator=(const Person& person) {
		//注意:由于当前对象已经创建完毕,那么就有可能pName指向堆内存
		//这个时候如果直接赋值,会导致内存没有及时释放,所以要先将堆内存释放
		if (this->_name != NULL) {
			delete[] this->_name;
			this->_name=NULL;
		}
		//重新申请空间,进行深拷贝
		this->_name = new char[strlen(person._name)+1];
		strcpy(this->_name,person._name);
		this->_age = person._age;
		return *this;
	}
	//调用析构函数
	~Person() {
		if (this->_name != NULL) {
			delete[] this->_name;
			this->_name = NULL;
		}
	}
private:
	char* _name;
	int _age;
};

有关深浅拷贝问题参考我的另一篇博客:C++入门–构造函数、拷贝构造函数、析构函数


重载自增自减(++/–)运算符

普通的重载形式无法同时定义前置和后置运算符。前置和后置版本使用的是同一个符号,意味着其重载版本所用的名字将是相同的,并且运算对象的数量和类型也相同。后置版本接受一个额外的int类型的形参(operator++(int))。当我们使用后置运算符时,编译器为这个形式提供一个值为0的参数。

注意:

为了和内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。而后置运算符应该返回对象的原值(递增或递减之前的值),返回的形式是一个临时值而非引用。

实例代码:

class Integer {
	friend ostream& operator<<(ostream& cout, Integer& it);
public:
	Integer() {
		num = 0;
	}
	//前置++
	Integer& operator++() {
		++this->num;
		return *this;
	}
	//后置++
	Integer operator++(int) {
		Integer temp = *this;
		++this->num;
		return temp;
	}
	//前置--
	Integer& operator--() {
		--this->num;
		return *this;
	}
	//后置--
	Integer& operator--(int) {
		Integer temp = *this;
		--this->num;
		return temp;
	}
private:
	int num;
};

ostream& operator<<(ostream& cout,Integer &it) {
	cout << it.num;
	return cout;
}

结论:调用代码时候,要优先选择前缀形式,除非确实需要后缀形式返回的原值,前缀和后缀形式语义上是等价的,前缀形式少创建了一个临时对象,所以效率经常会略高一些。

重载下标运算符:[]

表示容器的类通常可以通过元素的容器中的位置访问元素,这些类一般会定义下标运算符 operator [ ] 。下标运算符必须是成员函数,并且下标运算符返回所访问元素的引用,这样做为了能够作为左值修改元素的值。

实例代码:

class StrVec{
public:
	std::string& operator[](std::size_t n){
		return elements[n];
	}
	const std::string& operator[](std::size_t n) const {
		return elements[n];
	}//当定义常量对象时,只能调用这个函数
private:
	std::string *elements;
};

如果一个类包含下标运算符,则它通常会定义两个版本,一个返回普通引用,另一个是类的常量成员并且返回常量引用。当我们对常量对象取下标时,不能为其赋值。

重载成员访问(->、*)运算符[智能指针]

如果new出来的对象,就要让程序员自己去释放,但是实际开发中常常会忘记释放对象引起内存泄漏。因此我们可以用智能指针来托管这个对象,所以对象的释放就不用程序员操心了,但是有了智能指针想要和指针一样就要重载-> 和 * 运算符。

代码实例:

class Person {
public:
	Person(int age) {
		m_Age = age;
	}
	void showAge() {
		cout << "年龄为:" << this->m_Age << endl;
	}
	~Person() {
		cout << "Person析构函数调用" << endl;
	}
private:
	int m_Age;
};

//智能指针
//用来托管自定义类型的对象,让对象进行自动释放
class smartPointer {
public:
	smartPointer(Person *person) {
		this->person = person;
	}
	//重载->让智能指针对象Person* p 一样去使用
	Person* operator->() {
		return this->person;
	}
	Person& operator*() {
		return *this->person;
	}
	~smartPointer() {
		cout << "智能指针析构了" << endl;
		if (this->person != NULL) {
			delete this->person;
			this->person = NULL;
		}
	}
private:
	Person* person;
};
void test01() {

	smartPointer sp(new Person(10));//sp开辟到了栈上,自动释放
	
	sp->showAge();	// sp->->showAge(); 编译器优化了 写法
	
}

符号重载总结

■1、=、[]、()和 -> 操作符只能通过成员函数进行重载。

■2、<<和>>只能通过全局函数配合友元函数进行重载。

■3、不要重载||和&&因为无法实现短路规则

常规建议

运算符建议使用
所有的一元运算符成员
=、[]、()、->、*必须是成员
+=、-=、/=、*=、^=、&=、!=、%=成员
其他二元运算符非成员
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值