函数的重载操作符中也有不少细节
1 重载操作符特点
重载操作符本质上就是重载函数,它的特点:
<1>除了函数调用操作符‘()’外,所有的重载操作符的形参数目必须与操作符的操作数目相等。
<2>重载操作符的操作数必须至少有一个类类型,否则就不是重载而是原型了。
<3>优先级和结合性不会发生改变,例如对于算数操作符的重载,*的优先级肯定还是大于+
<4>不具有短路求值特性(就是根据已有的值就可以判断出最终的逻辑结果的话就会停止计算)
<5>操作符可以可以是类的成员函数,或者普通的非成员函数。
区别是当是类的成员函数,那么this形参为第一个操作数
<6>重载操作符的使用,既可以像操作符之前的调用方式那样进行操作,也可以像函数调用那样。
Item item1 ,item2; item1 + item2; // call method 1 operator+(item1,item2) // call method 2 // if + is operator of class item1 + item2; item1.operator+(item2);
2 重载操作符的谨记
<1> 赋值操作符,取地址操作符,逗号操作符
这些操作符对类类型都有默认的含义,也就是如果没有自己重载,编译器就会自己去合成这些这些操作符函数。
这些操作符都是具有自己的意义的,如果我们自己重写就会失去原本的意义。
<2> 赋值操作‘=’,下标‘[]‘,箭头‘->’,调用操作度‘()’必须定义为类成员,定义为类成员操作符,否则会出现变异错误。
<3>复合赋值操作符‘+=’等,一斤改变类状态的操作符自增,自减等一般也要定义为类成员
<4>对称的操作符如,算数运算符 ‘+’,‘-’,‘*’,‘%’一般最好定义为非成员操作符
3 类成员操作符
类成员操作符比非成员操作符要看起来要少一个参数,其实这个少的参数是this参数,而且他被限定为操作符的第一个操作数。
4 输入输出操作符的重载
<1>输出操作符中应该尽量包含少的格式化,因为这样可能会违背使用者要表达的意愿
<2>IO操作符必须为非成员函数
这个说一定是为了兼容对象的输出以及一般类型变量的输出。但是如果把输出操作符设置为类成员函数并没有编
译错误,并且实际上也是可行的。如下所示:
// output.h #pragma once #include <iostream> using namespace std; class Input { public: Input(void); ~Input(void); ostream& operator <<(ostream& out); private: string str; int fun; }; // output.cpp #include "Input.h" Input::Input(void) { str = "keep move on"; fun = 1001; } Input::~Input(void) { } ostream& Input::operator<<(ostream& out) { out<<"persist on"<<fun<<endl; return out; } //main.cpp #include "Input.h" void main() { Input in; in<<cout<<"I'll always find you"<<endl; }
输出结果:
persist on1001
I‘ll always find you
结论:这样因为对象必须作为第一个参数,如:in<<cout<<"I‘ll always find you"<<endl;这样和平时的书写习惯不一致,所以最好不好把输出输入操作符定义为类成员操作符。
5 算数操作符的重载
算数操作符要谨记的是它返回一个新的值,不是引用,这个值是两个值计算的结果。因为如果返回的是临时变量的引用的话,临时变量会被销毁。
6 相等操作符
通常比较每个类的数据成员,如果所有的对应成员都相等,那么就认为两个对象相等。
7 关系操作符
关系操作符通常用于需要对类对象进行排序的时候,所以它和相等操作符有点不一样,例如小于操作符只需要类对象数据成员中的某一个而不是所有的,容器中的对象的相等的定义也是根据‘<’来定义的,如果 A !<B && B
!<A ,那么A和B就相等。
8 赋值操作符
<1>一个类可以定义多个赋值操作符
<2>没有自己定义赋值操作符,编译器会自己定义一个
<3>赋值操作符必须返回对*this的引用,这样就不需要重新创建副本,销毁之前的版本一系列复杂的操作
9 下标操作符
C++中的容器在检索单个元素的时候一般会定义下标操作符‘[]’,因为下标操作符是跟类对象密切相关的,所以它必须定义为类的成员。定义类的下标操作符的时候,一般要定义连个版本,一个是const成员并返回const引用,一个是非const成员并且
返回引用
#pragma once #include <vector> using namespace std; class XB { public: XB(void); ~XB(void); int& operator [](const size_t index); const int& operator [](const size_t index)const; private: vector<int> data; }; //XB.cpp #include "XB.h" XB::XB(void) { data = vector<int>(5,2); } XB::~XB(void) { } int& XB::operator [](const size_t index) { return data[index]; } const int& XB::operator[](const size_t index)const { return data[index]; } //main.cpp #include "XB.h" #include <iostream> using namespace std; void main() { XB xb; cout<<xb[3]; xb[1] = 10; cout<<xb[1]; system("pause"); }
10 解引用操作符‘*‘和箭头操作符‘->‘
这两个操作符经常用在智能指针上面,就是把一些类对象给它赋予指针的功能。智能指针除了要操作的类外,另外包含两个类,一个类用来存放类对象的指针,以及使用计数。另一个类用来操作使用计数,并根据使用计数的大小来确定是否释放对象。下面的例子中Tv类是要操作类,TvPtr是包含使用技术和操作类的类。
#include <iostream> using namespace std; class Tv { public: Tv(void):m_height(100),m_width(390){}; ~Tv(void){}; int func(){return m_height * m_width;} private: int m_height; int m_width; }; class TvPtr { friend class TvPtrCnt; Tv* sp; size_t use; TvPtr(Tv* p):sp(p),use(1){} ~TvPtr(){delete sp;} }; class TvPtrCnt { public: TvPtrCnt(Tv* p):ptr(new TvPtr(p)){} TvPtrCnt(const TvPtrCnt& orig):ptr(orig.ptr){++ptr->use;}; TvPtrCnt& operator =(const TvPtrCnt &orig){++ptr->use;} ~TvPtrCnt(){if(--ptr->use ==0) delete ptr;} Tv& operator*(){return *ptr->sp;} Tv* operator->(){return ptr->sp;} const Tv& operator*()const{return *ptr->sp;} const Tv* operator->()const{return ptr->sp;} private: TvPtr* ptr; }; void main() { Tv *p = new Tv(); TvPtrCnt zp(p); std::cout<<(*zp).func()<<std::endl; std::cout<<zp->func()<<std::endl; system("pause"); }
注意:这里说的是箭头操作符‘->’它不同于一般的操作符,就那point->action来讲:
<1>如果point是指针,指向具有action成员的对象,那么编译器将代码编译为调用该对象的action的成员
<2>如果action是定义了‘->’操作符的类的一个成员,那么point->action与point.operation->()->action等同
<3>否则,代码错误
总结:使用箭头‘->’操作符,必须返回一个指针类型或者自定义了‘->’的类对象。
解引用操作符和箭头操作符赋予了类对象看似指针的行为功能。
11 自增操作符,自减操作符
这些操作符又分为前自增(prefix),后自增(postfix),前自减(prefix),后自减(postfix)
<1>prefix
XXX& operator++(); XXX& operator--();
<2>postfix
为了区分前和后的区别,在后自增,后自减的函数中增加了一个int的参数,编译器提供0做位实参值
XXX operator++(int); XXX operator--(int);
注意:后自增,后自减的返回值不是引用,因为返回的是一个临时的变量
<3> 显式的调用前缀后缀操作符
XXX.operator++(); //prefix XXX.operator++(0); //postfix
12 调用操作符和函数对象
<1>
类重载函数调用操作符‘()‘,这样通过控制通过在对象后面加括弧,加参数,可以把类对象当做函数一样使用,我们把定义了调用操作符的类,器对象成为函数对象。
#include <iostream> using namespace std; class Abs { public: Abs(){} ~Abs(){} int operator ()(int val){return val <0 ? -val : val;} }; void main() { Abs XX; cout<<XX(-108)<<endl; system("pause"); }
<2>函数对象用于标准库算法
标准库中有很多函数,需要提供函数指针或者函数对象作为参数,例如count_if()函数
<3>标准卡中还有很多定义的函数对象
plus<Type>,minus<Type>,multiplies<Type>,greater<Type>...
很多函数对象都是存在<functional>头文件中的函数对象
plus<int> Add;
cout<<Add(100,-23)<<endl;