第十四章 重载运算与类型转换
2017-10-18 16:46
375 查看
重载运算符参数数量一般和运算符作用的运算对象数量一样多,作为成员函数的重载运算符其显式参数数量比运算对象总数少一。
重载运算符本质是一次函数调用,求值顺序一般不会保留,所以不应该重载逻辑与、或以及逗号和取地址运算符(&& || , &)。
算术或位运算最好配套其符合赋值运算符。
成员or非成员:
(1)成员:有些必须是,还有些改变对象状态,或是与给定类型密切相关——(= [ ] ( ) -> 必须是成员) (++ - - *) (复合赋值一般是成员,与赋值不同)——要求左侧对象是类类型。
(2)非成员:具有对称性的运算符,它有可能对任意一端的运算对象进行转换,所以一般是非成员——(算术、相等、关系、位运算符) (输入、输出运算符必须是非成员,因左侧不能是我们定义的类对象)——要求至少有一个运算对象是类类型,记得声明成类的友元。
1.输入输出运算符的重载(非成员):
重载之输出运算符—— <<
输出运算符尽量减少格式化控制的使用,应该将输出的格式控制交给用户。
重载之输入运算符—— >>
2.算术与复合赋值运算符:
复合赋值运算符一般定义在类内部(非必须)
算术运算符必须为非成员,允许对其左侧或右侧对象进行转换,一般不需改变运算对象的状态,形参为常量引用。
定义算术运算符一般由与其配套的复合赋值运算符来实现比较简单。
3.相等和关系运算符:
4.赋值运算符:必须为成员
5.下标运算符:必须是成员函数
6.递增/递减运算符:建议定义为成员函数
7.解引用运算符:一元操作运算符,一般定义为成员函数,同时定义常量和非常量版本。通常,对* 操作符进行重载的类都含有一个指针,* 操作符通过类对象取数据,实际上就是从该指针所指的单元取数据
8.箭头运算符:
http://blog.csdn.net/qq_38216239/article/details/78241389
9.函数调用运算符:必须是成员函数。定义了调用运算符的类的对象称为函数对象(function object),可以调用它,“行为像函数一样”。
FYI,lambda是函数对象,它被翻译成一个未命名类的未命名对象,该类中含有一个重载的函数调用运算符,且默认情况下是一个const成员,若lambda被声明成mutable的,则为非const。
标准库以模板形式定义了一组表示算术、关系、逻辑运算符的类
至此,我们的可调用对象有:函数,函数指针,lambda,bind创建的对象(bind(//原函数名,arg_list)),重载调用运算符的类。
标准库function类型能帮我们整合这些调用对象。
10.类型转换运算符(conversion operator):特殊的成员函数,将一个类类型转换为其他任何类型——只要其能作为函数的返回类型,因此数组、函数类型不被包括。
类型转换运算符是隐式执行的,但每个类型转换函数都会返回一个对应类型的值。
可以定义显式类型转换运算符来防止隐式转换带来的不便:
不要为类定义相同的类型转换,不要定义两个及以上转换源/转换目标为算术类型的转换,因为算数类型之间有标准类型转换,可能造成二义性。
重载运算符本质是一次函数调用,求值顺序一般不会保留,所以不应该重载逻辑与、或以及逗号和取地址运算符(&& || , &)。
算术或位运算最好配套其符合赋值运算符。
成员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,显式请求转换 //表达式若被用作条件,编译器会自动为其应用显式类型转换!
不要为类定义相同的类型转换,不要定义两个及以上转换源/转换目标为算术类型的转换,因为算数类型之间有标准类型转换,可能造成二义性。
相关文章推荐
- c++ primer(第五版)学习笔记及习题答案代码版(第十四章)重载运算与类型转换
- 实验测试1《C++ Primer》第五版——第十四章 重载运算与类型转换
- 《c++ primer》第五版 第十四章 重载运算和类型转换 笔记
- c++primer(第五版) 第十四章 重载运算与类型转换习题答案
- c++ primer(第五版)笔记 第十四章 重载运算与类型转换
- C++学习笔记【第三部分第十四章:重载运算与类型转换】
- C++ Primer : 第十四章 : 重载运算与类型转换之重载运算符
- c++primer第十四章重载运算与类型转换小结-14
- C++primer第五版笔记-第十四章重载运算与类型转换
- 读书笔记《C++ Primer》第五版——第十四章 重载运算与类型转换
- 《C++ Primer》读书笔记 第14章:重载运算与类型转换
- CppPrimer笔记 Chapter14 重载运算与类型转换
- C++ Primer 5th 第14章 重载运算与类型转换
- 第14章重载运算与类型转换
- C++primer阅读笔记-----------重载运算与类型转换
- 第14章 重载运算与类型转换
- c++ 重载运算与类型转换
- 14 重载运算和类型转换
- C++primer阅读笔记-重载运算与类型转换(可调用对象与function)
- C++11(13):重载运算与类型转换