4--设计类[运算符重载,可调用对象,类类型转换,Lambda]

重载的运算符

重载运算符函数的参数数量与该运算符作用的运算对象数量一样多。
除了重载的函数调用运算符operator()之外,其他重载运算符不能含有默认实参。
对一个重载的运算符,其优先级和结合律与对应的内置运算符保持一致。

当一个重载的运算符是成员函数时,this绑定到左侧运算对象。
故,成员运算符函数的参数数量比运算对象数量少一个。
对一个运算符函数,或者是类的成员,或者至少含一个类类型的参数。


- 运算符函数的调用形式:
// 假设有非成员函数 operator+
显式 operator+(data1 + data2);
隐式 data1+data2
// 假设有成员函数 operator+
显式 data1.operator+(data2)
隐式 data1+data2



- 关于将运算符函数定义为成员还是非成员:
赋值(=),下标([]),调用(()),成员访问箭头(->)须为成员。

重载<<

通常,输出运算符的第一个形参是一个非常量ostream对象的引用。 
为引用,是因为ostream对象不能被拷贝。
非常量是因为,向流写入内容会改变其状态。 
第二个形参一般是一个常量的引用,该常量是我们想打印的类类型。 
是引用,是因为可以避免值拷贝。
常量是因为,打印对象不会改变对象的内容。 
为与其他输出运算符一致,operator<<一般返回它的ostream形参。
	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;
	}
执行输入时,可能发生下列错误:
流含有错误类型的数据时,读取操作可能失败。
读取操作到达文件末尾或遇到输入流的其它错误时也会失败。

算术运算符

类定义了一个算术运算符时,一般也要定义一个复合赋值运算符。
一般用复合赋值运算符来定义算术运算符。
通常,算术和关系运算符定义为非成员函数以允许左侧或右侧的运算对象进行转换。
这些运算符一般不需改变运算对象的状态,故形参是常量的引用。
算术运算的结果一般位于局部变量内。
操作完成一般返回局部变量的副本作为结果。
	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 Sales_data& lhs, const Sales_data& rhs)
	{
		return !(lhs == rhs);
	}

关系运算符:

类似算术

赋值运算符

	Type& Type::operator=(xxx)
	{
    
    
		// 处理好自赋值
		// 注重效率
		return *this;
	}

复合赋值运算符

	Type& Type::operator?=(...)
	{
    
    
		...
		return *this;
	}

下标运算符

对下标运算符最好定义常量和非常量版本,对常量版本返回常量引用。
	ElemType& Type::operator[](std::size_t )
	{
		...
	}
	
	const ElemType& Type::operator[](std::size_t ) const
	{
		...
	}

递增和递减运算符

建议:定义递增和递减运算符的类应该同时定义前置版本和后置版本。
	// 前置版本:++
	Type& Type::operator++()
	{
		...
		return *this;
	}

	// 后置版本:++
	Type Type::operator++(int)
	{
		...
	}

成员访问运算符

	? Type::operator*()
	{
		...
	}

	? Type::operator->()
	{
			// 一般模式
			// return & this->operator*();
			...
	}

对形如point->mem的表达式来说,
point必须是指向类对象的指针或是一个重载了operator->的类的对象
根据point类型的不同,point->mem分别等价于
(*point).mem
point.operator()->mem

point->mem的执行过程:
1.如果point是指针,则等价于 (*point).mem。
2.如果point是定义了operator->的类的一个对象,则使用 point.operator->()的结果来获取mem。
2.1.如结果是指针,执行1.
2.2.如结果是对象且含有 重载的 operator->(),取得该对象 operator->()调用的结果。重新进入执行过程。

operator->()只能返回 类的指针 或 重载了operator->类的对象。其他情况,会报错。

函数调用运算符

	ret-type Type::operator()(arglist)
	{
		...
	}
定义类函数调用运算符的类,类的对象称作函数对象。

Lambda是函数对象

编写一个Lambda后,
编译器将该表达式翻译为一个未命名类的未命名对象,未命名类含有一个重载的函数调用运算符
一个lambda表达式具有如下形式
[capture list](parameter list)->return type
{ function body }
	
- capture list
是一个lambda所在函数中定义的局部变量列表
可忽略parameter list及return type
lambda不可有默认实参
捕获局部自动变量方式
1.值捕获
被捕获变量值在lambda未命名类未命名对象创建时,存入对象的某个数据成员
2.引用捕获
需保证lambda对应的未命名类的未命名对象执行operator(...)时,引用的局部对象存在

可调用对象与function

c++语言的几种可调用对象:
函数
函数指针
lambda表达式
bind创建的对象
重载了函数调用运算符的类	

标准库<functional>头文件,提供的function<T>用来实现一致化调用形式一致的不同类型的可调用对象的一致使用问题.
	// 例子
	int add(int i, int j){
    
    ...}
	auto mode = [](int i, int j){
    
    ...}
	struct divide
	{
    
    
		int operator(int, int){
    
    ...}
	}

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

重载,类型转换与运算符

转换构造函数[由一个实参调度的非显式构造函数]
类型转换运算符
共同定义了类类型转换


转化构造函数,类型转换运算符声明为explicit时,
需要执行转换时需显式用 ?_cast/(TargetType)(...) 来调用
在条件判断中,explicit转换可隐式发生
	class TypeA
	{
    
    
		TypeA(TypeC x)
		{
    
    ...}
		
		operator TypeB() const
		{
    
    
			...
		}
	}

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/109066458