您的位置:首页 > 编程语言 > C语言/C++

第十四章 重载运算与类型转换

2017-10-18 16:46 375 查看
重载运算符参数数量一般和运算符作用的运算对象数量一样多,作为成员函数的重载运算符其显式参数数量比运算对象总数少一。

重载运算符本质是一次函数调用,求值顺序一般不会保留,所以不应该重载逻辑与、或以及逗号和取地址运算符(&& || , &)。

算术或位运算最好配套其符合赋值运算符。

成员or非成员:

(1)成员:有些必须是,还有些改变对象状态,或是与给定类型密切相关——(= [ ] ( ) -> 必须是成员) (++ - - *) (复合赋值一般是成员,与赋值不同)——要求左侧对象是类类型。

(2)非成员:具有对称性的运算符,它有可能对任意一端的运算对象进行转换,所以一般是非成员——(算术、相等、关系、位运算符) (输入、输出运算符必须是非成员,因左侧不能是我们定义的类对象)——要求至少有一个运算对象是类类型,记得声明成类的友元。

1.输入输出运算符的重载(非成员):

重载之输出运算符—— <<

class T
{
friend ostream& operator<<(ostream&, const T&);
//其他成员
};
//参数1:引用——因为无法copy ostream对象,非常——因为向其写入内容会改变状态
//参数2:引用——避免copy,常量——通常输出不会改变其内容
ostream& T::operator<<(ostream& os, T& t)
{
//具体操作
return os;
}


输出运算符尽量减少格式化控制的使用,应该将输出的格式控制交给用户。

重载之输入运算符—— >>

class T
{
friend istream& operator>>(istream&, T&);
//其他成员
};
//参数2:必须为非常量引用,因为本身就是要将数据读入到其中。
istream& T::operator>>(istream& is, T& t)
{
//读取输入
if(is)
{
//操作
}
//输入运算符要检查输入流的合法性,这样才能确保输入的对象被合法使用,
//如果发生输入错误,输入运算符要负责将对象置为合法状态!
return is;
}


2.算术与复合赋值运算符:

复合赋值运算符一般定义在类内部(非必须)

class T
{
public:
T& operator+=(const T& t) //返回左侧对象引用,显式参数只有一个
{
//假设t有成员m1,m2...
m1 += t.m1;
m2 += t.m2;
//...
return *this;
}
//其他成员
};


算术运算符必须为非成员,允许对其左侧或右侧对象进行转换,一般不需改变运算对象的状态,形参为常量引用。

定义算术运算符一般由与其配套的复合赋值运算符来实现比较简单。

class T
{
public:
T operator+(const T& lhs, const T& rhs)
//返回值是sum的副本,不能是引用,因为sum是局部变量,函数调用结束即
//被销毁,返回其引用将引起未定义行为!
{
T sum = lhs;
sum += rhs;
return sum;
}
};


3.相等和关系运算符:

bool operator==(const T& lhs, const T& rhs);
bool operator<(const T& lhs, const T& rhs);
//其他如 != ,>应该由已定义的运算符实现
//定义这类运算符必须确保在逻辑上有可靠的定义,且相互之间不应该冲突,
//否则可能就是画蛇添足


4.赋值运算符:必须为成员

T& operator=(const T& rhs);//拷贝赋值,额外参数须有默认实参
T& operator=(T&& rhs) noexcept;//移动赋值,额外参数须有默认实参,
//不抛出异常须声明为noexcept
T& operator=(//其他类型参数);//如vector类可接受一个
//initializer_list类型对象


5.下标运算符:必须是成员函数

T& operator[](size_t);//非常量版本
const T& operator[](size_t) const;//常量版本
//常量对象只能调用常成员函数
//一般同时定义这两个版本,使成员函数的意义更加清楚,
//将成员函数分修改对象和不修改对象两类,增加程序的健壮性,
//常量成员函数企图修改数据成员或调用非常量成员函数,
//编译器会指出错误


6.递增/递减运算符:建议定义为成员函数

T& operator++();//前置版本返回调用对象的引用(已经递增),
//因为对象调用前置版本得到一个左值。
T operator++(int);//接受一个int参数以便在显式调用时与前置版本区分,
//无实际意义,后置版本返回的是调用对象的副本(未递增),
//是一个值,因为调用后置版本得到一个右值,不能返回其引用。


7.解引用运算符:一元操作运算符,一般定义为成员函数,同时定义常量和非常量版本。通常,对* 操作符进行重载的类都含有一个指针,* 操作符通过类对象取数据,实际上就是从该指针所指的单元取数据

T& operator*();
const T& operator*() const;


8.箭头运算符:

http://blog.csdn.net/qq_38216239/article/details/78241389

9.函数调用运算符:必须是成员函数。定义了调用运算符的类的对象称为函数对象(function object),可以调用它,“行为像函数一样”。

Any_Type operator()(parameter list);//隐式参数是左侧调用对象,
//它必须是我们的类对象,同一个类若要重载多个调用运算符,
//它们在参数数量或类型上要有区别。若确定不会改变调用对象,
//可声明为const的。
int operator()(int);
int operator
b782
()(int,int);
int operator()(char*);

//将函数对象用于泛型算法作为实参,例如:
for_each(v.begin(), v.end(), class_type(...));
//第三个参数是我们类的一个临时函数对象


FYI,lambda是函数对象,它被翻译成一个未命名类的未命名对象,该类中含有一个重载的函数调用运算符,且默认情况下是一个const成员,若lambda被声明成mutable的,则为非const。

标准库以模板形式定义了一组表示算术、关系、逻辑运算符的类

#include<functional> //在这个头文件里
//例如用于泛型算法中:
sort(vec_string.begin(), vec_string.end(), greater<string>());
//有趣的是,标准库定义的函数对象适用于指针!
//直接比较两个无关的指针是未定义行为,但是使用标准库函数对象却可以
vector<string*> v_p;
sort(v_p.begin(), v_p.end(), greater<string*>());// OK!


至此,我们的可调用对象有:函数,函数指针,lambda,bind创建的对象(bind(//原函数名,arg_list)),重载调用运算符的类。

标准库function类型能帮我们整合这些调用对象。

//其形式是:
funtion<retType(args)> f;
//例:
int add(int i, int j) {return i + j;}
function<int(int, int)> f1 = add;
//定义一个可调用对象f1,f1接受两个int参数,
//返回一个int值,这里用add初始化
//所有其他类型的函数对象都可以这样整合成function类型
//调用也很简单
int sum = f1(1,2);


10.类型转换运算符(conversion operator):特殊的成员函数,将一个类类型转换为其他任何类型——只要其能作为函数的返回类型,因此数组、函数类型不被包括。

operator type() const;//无返回类型,形参列表为空,通常为const。
//类类型与将要转换的类型之间要存在明显的映射关系,
//不要过度使用类型转换,可能会带来歧义


类型转换运算符是隐式执行的,但每个类型转换函数都会返回一个对应类型的值。

可以定义显式类型转换运算符来防止隐式转换带来的不便:

class T
{
public:
T(int i = 0):val(i) {}
explicit operator int() const {return val;}
private:
size_t val;
};
T t = 1;//OK,构造函数非显式
t + 2; //error,要将t转换为int,但类型转换运算符是显式的
static_cast<int>(t) + 2; //OK,显式请求转换
//表达式若被用作条件,编译器会自动为其应用显式类型转换!


不要为类定义相同的类型转换,不要定义两个及以上转换源/转换目标为算术类型的转换,因为算数类型之间有标准类型转换,可能造成二义性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息