运算符重载详解

前言

        c++对类的操作都可以通过类内成员函数来实现,还有函数重载等功能。那为什么还需要对运算符的重载呢?让我们一步一步对重载运算符进行了解:


一、运算符重载的引入

        首先我们先来设计一个类

class person  //人类
{
public:
	void add(int age) //增加年龄
	{
		_age += age;
	}
private:
	int _age = 20;    //年龄
	string _name = "张三"; //姓名
};

        我们可以创建一个人类对象,然后通过成员函数(add)对他的年龄进行增加。但是这样的代码一点都不直观,为了使我们的代码更加易于编写和阅读,我们设计了新的语法---运算符重载。通过新语法,我们可以编写一个新函数。

person operator+(person p, int age) // +运算符重载
{
	p._age += age;
	return p;
}

int main() 
{
	person p1;

	p1 = p1 + 1;
	cout << p1._age << endl;  //打印结果 21

	p1.add(1);
	cout << p1._age << endl;  //打印结果 22

    //这两种调用效果相同,都是对年龄+1
	return 0;
}

        通过上述步骤,我们发现调用成员函数add 和 +号运算符重载实现的作用是相同的,并且优先级和结合律都和对应的内置符号保持一致。而且十分方便我们代码的阅读和编写。这就是运算符重载引入的原意,接下来我会对他的语法和使用进行解析。

二、语法解析

1.基础语法

        函数的返回值: 我们规定运算符重载函数会按照不同的运算符设置不同的返回值,+ 号就会设置成返回类的对象,目的是内置类型的一样,比如:int a = b + c ; ( b + c 返回一个int)

        特殊的函数名:语法规定函数名必须是 : operator + (运算符),比如:operator+,

operator-, operator* 等。

        参数列表:    

              1, 和函数类似,编译器可以通过不同的参数传入来决定该调用哪个运算符重载(函数重载(可以看我以前的博客))。同时也可以显示调用

person operator+(person p, int age)
{
    p._age += age;
    return p;
}

//和上面的 + 重载
person operator+(person p1, person p2)
{
    p1._age += p2._age;
    return p1;
}

int main()
{
    person p1, p2;

    //这两个调用是等价的
    p1 = operator+(p1, 10);
    p1 = p1 + p2;

    return 0;
}

               2, 参数数量是需要和运算符对应的+ 是二元运算符就需要两个参数,++是一元就需要一个。operator-, operator*等。

                3,参数必须要有一个类类型,不能都为内置类型,不然会造成混乱。毕竟这个语法设计出来本来就是为了服务类类型(class和struct)

2.类内定义

        但在实践中我们一般会将 运算符重载写在类内:

class person  //人类
{
public:
	person operator+(int age)
	{
		_age += age;
        return *this;
	}

	int _age = 20;    //年龄
	string _name = "张三"; //姓名
};

        但是这里为什么只有一个参数了呢? 其实有一个隐式参数是对象本身(参数列表的最左侧就是,只是看不见罢了,传参的时候要按顺序传), this 指针(很重要)。所以如果将运算符函数写在类内的话要少写一个参数。

int main()
{
	person p1, p2;
	//这两个调用是等价的
	p1.operator+(10); //operator(*this,10) 伪代码
	p1 = p1 + p2;
 
    return 0;
}

        后面我们都写类内的运算符重载,这是最常用的。但是有些运算符只能定义在类外,下面讲

3.各类运算符

(c++ Primer 第五版)

   我会对特殊的运算符进行讲解,普通的比如 + - * / %之类的就不写了


++ 运算符

        c++中有前置++和后置++,按照上面的取名方法,那名字就是重复的,无法区分。为了解决这个问题,规定后置++的参数列表里有一个 int 参数,编译器会传 0 来初始化 该参数,但一般没有作用,只是为了进行区分,- - 运算符同理

class person  //人类
{
public:
	//模仿内置 ++ 返回对应类型
	person operator++(int) //后置++ int++
	{
		person ret = *this; //记录++前状态
		_age++;
		return ret; //拿到类对象
	}

	person& operator++()
	{
		_age++;
		return *this;
	}

	int _age = 20;    //年龄
	string _name = "张三"; //姓名
};

int main()
{
	person p1;
	p1++;  //调用person& operator++(int)
	++p1;  //调用person& operator++()
	p1.operator++(10); //可以显示调用,后置++,但是10没用
	return 0;
}

*  -> 运算符

      首先,* 和 ->在这个person类中没有实质功能,只是为大家讲解下语法。重点是 ->符号,按照下面我写的函数,他不是应该返回一个指针吗,那为什么我可以调用string类型的成员呢。

        c++中规定了 operator->的重载如果返回对象是一个指针就再次进行一次 -> 操作,如果是自定义类型就查看是否有重载->, 有的话继续调用它的->。所以这里其实进行了一次隐式的 -> 操作。代码如下       

class person  //人类
{
public:
	int _age = 20;    //年龄
	string _name = "张三"; //姓名

	string& operator*()
	{
		return _name;
	}

	string* operator->()//
	{
		return &_name;
	}
};


int main()
{
	person p1;

	*p1;

	//以下两种调用等价
	p1->c_str();
	p1.operator->()->c_str();
	//调用operator->()后拿到指针,编译器再次隐式调用->
	return 0;
}


<<  >> 运算符

        我们正常使用比如:cout << "hello world" << endl; 所以对应的重载形式是: 

        operator<<(ostream& os, const operson& p) 

        但如果我们写在类内,但左边第一个一定是给this的,那我们调用就会成为

        peoson << os; 太奇怪了,所以我们规定流符号的重载是放在类外的

//返回值和参数不仅要符合自定义,还必须是引用类型,因为流对象不能拷贝
ostream& operator<<(ostream& os,const person& p)
{ //os不能为const引用,因为流会改变,下面同理
	os << p._age;
	return os;
}

istream& operator>>(istream& is, person& p)
{
	is >> p._age;
	if (!is)
	{	//如果流提却失败记得置为空,防止读取不全有安全隐患
		p = person();
	}
	return is;
}

int main()
{
	person p1;
	person p2;
	cout << "p1: " << p1 << endl;
	cout << "p2: " << p2 << endl;
	cin >> p1;
	cout << "p1:" << p1 << " p2:" << p2;
	
	return 0;
}


new  delete 运算符

        这部分太复杂了太长了,大家有兴趣可以自己去找别人的博客,这里就给大家放一段没什么实际作用的代码看一眼,看看语法

class person  //人类
{
public:
	int _age = 20;    //年龄
	string _name = "张三"; //姓名

	void* operator new(size_t size, int i) = delete //如果不加delete,那重载的new和delete
													//都会被调用然后打印东西
	{
		cout << i << endl;
		return malloc(size);
	}

	void operator delete(void* ptr)
	{
		cout << "void operator delete(void* ptr)" << endl;
		free(ptr);
	}
};

int main()
{
	person* p1 = new(20) person; //报错,不被通过,可以限制只在栈上创建变量,一种设计思想
	
	delete p1;

	return 0;
}


[ ] 运算符

        大家看代码就能懂了,没什么特殊的

class person  //人类
{
public:
	int _age = 20;    //年龄
	string _name = "hello world"; //这里不能给中文了,因为编码的问题会有访问错误

	char& operator[](size_t i)
	{
		return _name[i];
	}
};

int main()
{
	person p1;
	p1[0]; // 是w
	p1.operator[](10);
	p1[0] = 'z'; //成功修改

	return 0;
}

() 运算符

        对()的重载很自由,参数数量,返回值类型,大家都可以随便写。就相当于自己设置了一个新函数(可以多个,构成函数重载就可以)。对这种类的重载,c++给了类专业名字:仿函数。运用地点:比如sort()函数的第三个参数,function模板类

class person  //人类
{
public:
	int _age = 20;    //年龄
	string _name = "hello world"; 

	//随意实现功能
	int operator()(int i) //随便实现,没有要求
	{
		return i + _age;//有类的特点,可以随时保存变量
	}
};

int main()
{
	person p1;
	p1(10);//可以随意实现函数,可以拥有函数的特点,也有类的特点

	return 0;
}

=运算符

        = 运算符是编译器默认生成的八个类内函数之一,但只要自己写了,编译器就不会默认生成。这个运算符重载涉及的只是更多是关于类的其他知识,这里就不多讲解了

class person  //人类
{
public:
	int _age = 20;    //年龄
	string _name = "张三"; //姓名

	//编译器会默认生成的八个函数之一
	person& operator=(const person& p)
	{
		_age = p._age;
		_name = p._name;
		return *this;
	}
};


int main()
{
	person p1;
	//person p2 = p1;注意如果这样写调用的是拷贝构造,不是operaotr=重载
	person p2;
	p2 = p1;

	return 0;
}

三,重载技巧

        1,如果我们重载了 != 符号,那我们实现 == 的时候可以直接调用 !=,节省代码量。<, > 等同理

class person  //人类
{
public:
	int _age = 20;    //年龄
	string _name = "hello world";

	bool operator!=(string name)
	{
		return !(_name == name);
	}

	bool operator==(string name)
	{
		bool ret = operator!=(name);//复用!=
		return !ret;
	}
};

int main()
{
	person p1;
	person p2;
	p1 == "hello world";
	p1 != "hello world";
	return 0;
}

        2,一些运算符不应该被重载,比如 && , || 这类逻辑判断符号。重载了也没意义,要想实现对应的功能可以直接用成员函数


四,代码连接

        我把代码都放gitee了:大家有需要可以自己拿: 博客资源: 这里存放博客中写的代码,欢迎大家来拿 - Gitee.com

        写太多了可能会有错误,可以提出来,马上修改。有不懂的问题都可以随便提问

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值