14 C++ 重载运算与类型转换

本文详细介绍了C++中的运算符重载规则,包括哪些运算符可以被重载,如何重载以及何时应避免重载。此外,还讨论了输入输出运算符(如`<<`和`>>`)的特性和实现方式,以及算术和关系运算符的重载。文章进一步阐述了相等、关系和赋值运算符的用法,并展示了如何实现自定义的递增递减运算符。同时,文章探讨了成员访问运算符、函数调用运算符以及lambda表达式作为函数对象的角色。最后,提到了标准库提供的函数对象和如何使用`<functional>`头文件中的工具。


前言

提示:这里可以添加本文要记录的大概内容:


提示:以下是本篇文章正文内容,下面案例可供参考

基本概念

运算符函数

//错误:不能为int重新定义内置的运算符
int operator+(int, int);

只能重载已有的运算符,而无权发明新的运算符

可以被重载的运算符
+		-		*		/		%		^
&		|		~		!		,		=
<		>		<=		>=		++		--
<<	    >>	    ==  	!=	   	&&		||
+=  	-=		/=		%=	     ^=	     &=
|=		*=  	<<=	    >>=	     []	     ()
->		->*	    new		new[]	delete	delete[]
不能被重载的运算符
::		.*		.		? :

直接调用一个重载的运算符函数

//等价调用
data1 + data2;
operator+(data1, data2);

data1 += data2;
data1.operator+=(data2);

某些运算符不应该被重载
重载运算符的求值顺序等属性无法保留

使用与内置类型一致的含义

选择作为成员函数或者非成员
在这里插入图片描述
在这里插入图片描述

输入和输出运算符

ostream & operator<<(ostream &os, const Sales_data &item){
	os << item.isbn() << " " << item.units_sold << " " 
	<< item.revenue << " " << item.avg_price();
	return os;
}

在这里插入图片描述
输入输出运算符必须是非成员函数

istream &operator>>(istream &is, Sales_data &item){
	double price;
	is >> item.bookNo >> item.units_sold >> price;
	if (is)	//检查输入是否成功
		item.revenue = item.units_sold * price;
	else 	//输入失败:对象被赋予默认的状态
		item = Sales_data();
	return is;
}

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

istream& operator>>(istream& is, Test&t){
    is >> t.a;
    if (is)
        return is;
    else
        t.a = 999;
    return is;
}

算数和关系运算符

Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs){
	Sales_data sum = lhs;
	sum += rhs;
	return sum;
}

相等运算符

bool operator==(const Sales_data &lhs, const Sales_data &rhs){
	return lhs.isbn() == rhs.isbn() && 
			  lhs.units_sold == rhs.units_sold &&
			  lhs.revenue == rhs.revenue;
}
bool operator!=(const Slaes_data &lhs, const Sales_data &rhs){
	return !(lhs == rhs);  //yeah!!!
}

关系运算符

赋值运算符

拷贝赋值和移动赋值运算符
再加个

class StrVec{
public:
	StrVec &operator=(std::initializer_list<std::string>);
};
StrVec& StrVec::operator(initializer_list<string> il){
	// alloc_n_copy分配内存空间并从给定范围内拷贝元素
	auto data = alloc_n_copy(il.begin, il.end());
	free(); 				//销毁对象中的元素并释放内存空间
	elements = data.first;	//更新数据成员使其指向新空间
	first_free = cap = data.second;
	return *this;
}

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

复合赋值运算符
与赋值运算符相差无几

下标运算符

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];
	}
	//...
};

递增递减运算符

前置递增递减

class StrBlovPtr{
public:
	//前置
	StrBlobPtr& operator++();
	StrBlobPtr& operator--();
	//后置, int仅用于标识后置
	StrBlobPtr operator++(int);
	StrBlobPtr operator--(int);
	
};
//前置版本:返回递增/递减对象的引用
StrBlobPtr& StrBlobPtr::operator++(){
	//如果curr已经指向了容器的尾后位置,则无法递增它
	check(curr, "increment past end of StrBlobPtr");
	++curr;
	return *this;
}
StrBlobPtr& StrBlobPtr::operator--(){
	//如果curr是0,则继续递减它将产生一个无效的下标
	--curr;
	check(curr, "decrement past begin of StrblobPtr");
	return *this;
}
//后置版本
StrBlobPtr StrBlobPtr::operator++(int){
	//此处无需检查有效性,调用前置递增运算时才需要检查
	StrBlobPtr ret = *this;		//记录当前值
	++*this;					//前向移动一个元素,前置++需要检查递增的有效性
	return ret;					//返回之前记录的状态
}
StrBlobPtr StrBlobPtr::operator--(int){
	//此处无需检查有效性,调用前置递减运算时才需要检查
	StrBlobPtr ret = *this;		//记录当前值
	--*this;					//前后移动一个元素,前置--需要检查递减的有效性
	return ret;					//返回之前记录的状态
}

//显示调用
p.operator++(0); //调用后置版本的++
p.operator++( ); //调用前置版本的++

此处int形参并不真正使用,仅用来区分前后置
注意

const StrBlobPtr StrBlobPtr::operator++(int){
	StrBlobPtr ret = *this;		
	++*this;					
	return ret;	
// cosnt 可以防止如下的调用
Test tst(9);
Test a =( tst ++ ++ );//报错
cout<< a << " "<<tst;

成员访问运算符

class StrBlovPtr{
public:
	std::string& operator*() const{
		auto p = check(curr, "dereference past end")
		return (*p)[curr];	//(*p)是对象所指的vector
	}
	std::string* operator->() const{
		//将实际工作委托给解引用运算符
		return & this->operator*();
	}
};

在这里插入图片描述
对箭头返回值的限定
在这里插入图片描述

函数调用运算符

struct absInt{
	int operator()(int val) cosnt {
		return val < 0 ? -val : val;
	}
};
int i = -42;
absInt absObj;	
int ui = absObj(i); //将i传递给absObj.operator()

函数调用运算符必须是成员函数,可以重载
absObj称为函数对象function object ,“行为像函数一样”

class PrintString{
public:
	PrintString(ostream &o = cout, char c = ' '):
		os(o), sep(c) {}
	void operator()(cosnt string &s) const {os << s << sep;}
private:
	ostream *os;	//用于写入的目的流
	char sep;		//用于将不同输出隔开的字符
};
PrintString printer;	//使用默认值,打印到cout	
printer(s);				//在cout中打印s,后面跟一个空格
PrintString errors(cerr, '\n');
errors(s);				//在cerr中打印s,后面跟一个换行符

for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));

lambda是函数对象

当编写了一个lambda后,编译器将该表达式翻译成一个未命名类的未命名对象。在lambda表达式产生的类中含有一个重载的函数调用运算符。

stable_sort(wds.begin(), wds.end(), 
			[](const string &a, const string &b){return a.size() < b.size();} );
//其行为类似于下面这个类的一个未命名对象
class ShorterString{
public:
	bool operator()(const string&s1, const string&s2)const
	{ return s1.size()<s2.size(); }
};
//用上面的类代器
stable_sort(wds.begin(), wds.end(), ShorterString() );

表示lambda及相应捕获行为的类

//获得第一个指向满足条件元素的迭代器,该元素满足size() is >= sz
auto wc = find_if(wds.begin(), wds.end(), 
				[sz](cosnt string &a){return a.size() >= sz;} );
//该lambda表达式产生的类将形如:
class SizeComp{
public:
	SizeComp(size_t n):sz(n) { } //该形参对应捕获的变量
	//该调用运算符的返回类型、形参和函数体都与lambda一致
	bool operator()(const string &s)const
	{return s.size() >= sz;}
private:
	size_t sz;
};
auto wc = find_if(wds.begin(), wds.end(), SizeComp(sz) );

标准库定义的函数对象

<functional.h>头文件中
在这里插入图片描述

plus<int> intAdd;		//可执行int加法的函数对
negate<int> intNegate;	//可对int值取反的函数对
//使用intAdd::operator(int, int)求和
int sum = intAdd(10, 20); //等价于sum=30
sum = intNegate(intAdd(10, 20));
//
sum = intAdd(10, intNegate(10)); //sum=0

在算法中使用标准库函数对象

//降序排序
sort(svec.begin(), svec.end(), greater<string>() );
//greater<string>() 为greater类型的一个未命名的对象

vector<string*> nameTable;  //指针的vector
//错误❌:nameTable中的指针彼此之间没有关系,所以<将产生未定义的行为
sort(nameTable.begin(), nameTable.end(),
	[](string*a, string*b) {return a<b;} );
//正确:标准库规定指针的less是定义良好的
sort(nameTable.begin(), nameTable.end(), less<string*>() );

关联容器使用less<key_type>对元素排序,因此我们可以定义一个指针的set或者在map中使用指针作为关键值而无需直接声名less

可调用对象与function

不同类型可能具有相同的调用形式

//普通函数
int add(int i, int j){ return i + j; }
//lambda, 其产生一个未命名的函数对象类
auto mod = [](int i, int j){ return i%j; };
//函数对象类
struct divide{
	int operator()(int denominator, int divisor)
	{ return denominator / divisor;}
};

调用形式:int(int, int)

//构建从运算符到函数指针的映射关系,其中函数接受连个int、返回一个int
map<string, int(*)(int, int)> binops;
//正确:add是一个指向正确类型函数的指针
binops.insert({"+", add});
//错误:mod不是一个函数指针
//mod是个lambda表达式,而每个lambda有它自己的类类型,该类型与binops不符
binops.insert({"%", mod}); //❌

标准库function类型
<functional.h>
在这里插入图片描述

function<int(int, int)> f1 = add;  //函数指针
function<int(int, int)> f2 = divide();  //函数对象的类
function<int(int, int)> f3 = [](int i, int j) //lambda
							   { return i*j;};
cout << f1(4,2) << endl;
cout << f2(4,2) << endl;
cout << f3(4,2) << endl;

map<string, function<int(int, int)>> binops = {
	{"+", add},
	{"-", std::minus<int>()},
	{"*", [](int i,int j){return i*j;}},
	{"/", divide()},
	{"%", mod}
};

binops["+"](10,5);

//有个问题
int add(int i, int j){return i + j;}
double add(double i, doublej);
binops.insert( {"+", add} ); //错误:哪个add??
//消除二义性
int (*fp)(int, int) = add;
binops.insert({"+", fp});
binops.insert( {"+", [](int a, int b){return add(a, b);} } );

重载、类型转换与运算符

类类型转换class-type conversions

类型转换运算符

类型转换运算符conversion operator是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型。
operator type() const;
一个类型转换函数必须是类的成员函数;它不能声名返回类型,形参列表也必须为空。类型转换函数通常应该是const

class SmallInt{
public:
	SmallInt(int i=0):val(i)
	{ if ( i<0 || i>255) throw std::out_of_range("Bad SmallInt value"); }
	operator int() const {return val;}
private:
	std::size_t val;
};

SmallInt si;
si = 4;	//首先将4隐式的转换成SmallInt,然后调用SmallInt::operator=
si + 3; //首先将si隐式的转换成int, 然后执行整数的加法

//内置类型转换将double实参转换成int
SmallInt si = 3.14;	//调用SmallInt(int)构造函数
//SmallInt 的类型转换运算符将si转换成int
si + 3.14;          //内置类型转换将所得的int继续转换成double

class SmallInt;
operator int(SmallInt&); //错误,不是成员函数
class SmallInt{}
public:
	int operator int() const;		   //❌指定了返回类型
	operator int (int = 0) const;	   //❌参数列表不为空
	operator int*() const {return 42;} //❌42不是一个指针

显示的类型转换运算符explicit conversion operator

避免右二义性的类型转换

函数匹配与重载运算符

a sym b
//以下都具有二义性
a.operator\sym(b);	//a有一个operatorsym成员函数
operator\sym(a,b);  //operatorsym是一个普通函数

在这里插入图片描述


总结

调用形式 call signature
类类型转换 conversion operator
类型转换运算符 conversion operator
显式的类型转换运算符 explicit conversion operator
函数对象 function object
函数表 function table
函数模板 function template
重载的运算符 overloaded operator

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值