运算符重载
运算符重载是一种形式的C++多态。运算符重载将重载的概念扩展到运算符上,允许赋予C++运算符多种含义。
operator+()就是重载+;
(C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。)
可重载和不可重载运算符C++对用户定义的运算符重载的先知。
1.重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。因此,不能将减法运算符重载为计算两个double值的和,而不是他们的差。虽然这种限制将对创造性有所影响,但可以确保程序正常运行。
2.使用运算符是不能违反运算符原来的句法规则。例如,不能将求模运算符重载成使用一个操作数。
同样,不能修改运算符的优先级。因此,如果将加号运算符重载成两个类相加,则新的运算符和原来的加号具有相同的优先级。
3.不能创建新的运算符。例如,不能定义operator**()函数来表示求幂。
4.不能重载下面运算符
1.sizeof 2. . 成员运算符3. .*成员指针运算符。4.::作用域解析运算符 5.?:条件运算符 6.typeid 一个RTTI运算符 7.const_cast 强制类型转换运算符 8.dynamic_cast 强制类型转换运算符 9.reinterpret_cast 强制类型转换运算符10.static_cast 强制类型转换运算符
下面函数只能通过成员函数进行重载。
1. =赋值运算符 2.()函数调用运算符 3.[] 下标运算符 4.->通过指针访问类成员的运算符
友元
友元有三种:
1.友元函数 2.友元类 3.友元成员函数。
当重载运算面对两个不同类型时要注意 左侧是操作数还是右侧是操作数。这样就固定了左侧或右侧必须是某种类型。
另一种解决方式是 非成员函数。非成员函数不是由对象调用的,它使用的所有值都是显示参数。例如
Time operator* (double m,const Time &t);
运算符表达式的左边的操作数对应运算符第一个参数,运算符表达式右边的操作数对应于运算符函数的第二个参数,而原来的成员函数则按相反的顺序处理操作数。
但利用非成员函数引发了一个新问题,非成员函数不能直接访问类的私有数据,至少常规非成员函数不能访问,这里就要用到友元函数。
friend Time operator* (double m,const Time &t);
该原型意味着下面两点:
1.虽然operator*()函数是在类声明中声明的,但他不是成员函数,因此不能使用成员运算符来调用;
2.虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同。
另外不要再定义中使用friend。
实际上 按下面的方式对定义进行修改,可以将这个友元函数编写为非友元函数:
Time operator*(double m,const Time &t){
return t*m; // use t.operator(m)
}
运用乘法交换 使得重新调用类成员函数
另外 如果要为类重载运算符,并将非类的项作为其第一个操作数,则可以用友元函数来反转操作数的顺序。
常用的友元:重载<<运算符
如果我们想使得cout<<trip;// trip是一个对象。成立的话 则可以进行<<运算符重载。
第一种重载版本
要使Time类知道使用cout,必须使用友元函数。因为下面这样的语句使用两个对象,其中第一个是ostream类对象(cout)
如果使用一个Time成员函数来重载<<,Time对象将是第一个操作数,就像使用成员函数重载*与那算符那样。这意味着必须这样使用:
trip<<cout;
这样会令人迷惑。但通过使用友元函数,可以像下面这样重载运算符;
void operator<<(ostream &os,const Time &t)
{
os<<t.hours<<"hours "<<t.minutes<<"minutes ";
}
第二种重载版本
前面介绍的实现存在一个问题
cout<< trip; 确实可用
但是 cout<<"Trip Time: "<<trip<<"xxx"<<endl;这种实现不允许向通常那样将重新定义的<<运算符与cout一起使用
C++从左到右读取输出语句这相当于 (cout<<x)<<y;
正如iostream中定义的那样,<<运算符要求左边是一个ostream对象。显然,又因为cout是ostream对象,所以表达式cout<<x满足这个要求 ostream类将operator<<()函数实现为返回一个指向ostream对象的引用。准确的说,它返回一个指向调用对象(这里是cout)的引用。因此,表达式(cout<<x)本身就是ostream对象cout,从此可以位于<<运算符的左侧。
可以对于友元函数采用相同的方法 只要修改operator<<()函数,让他返回ostream对象的引用即可:
ostream & operator<<(ostream &os,const Time &t)
{
os<<t.hours<<"hours "<<t.minutes<<"minutes ";
return os;
}
这样在cout<<"Trip Time: "<<trip<<"xxx"<<endl;的xxx就可以正常输出了。
一般来说,要重载<<运算符来显示c_name的对象,可使用一个友元函数,其定义如下;
ostream & operator<<(ostream &os,const c_name &obj)
{
os<<...;
return os;
}
只有在类声明中的原型中才能使用friend关键字。除非函数定义也是原型,非原则不能在函数定义中使用该关键字。
重载运算符:作为成员函数还是非成员函数
一般来说,非成员函数应是友元函数,这样他才能直接访问类的私有数据。
例如类的加法运算符 加法运算符需要两个操作数。对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显示地传递;对于友元函数来说,两个操作数都作为参数来传递。
非成员版本的重载运算符函数所需的形参与运算符使用的操作数数目相同;而成员版本所需的参数数目少一个,因为其中的一个操作数是被隐式地传递的调用对象。
另外 这两种格式只能选其一,因为同时定义会产生二义性错误。
类的自动转换和强制类型转换
下面的的构造函数用于将double类型的值转换为stomewt类型:
stonewt(double lbs);
也就是说可以编写如下代码
stonewt mycat;
mycat=16.9;
程序将使用构造函数stonewt(double)来创建一个临时stonewt对象,并将16.9作为初始化值。随后,采用逐成员赋值方式将该临时对象的内容复制到mycat中,这一过程称为隐式转换,因为他是自动进行的,而不需要想显式强制类型转换。
只有接受一个参数的构造函数才能作为转换函数。下面的构造函数有两个参数,因此不能用来转换类型:
stonewt(int stn ,double lbs);
然而如果给第二个参数提供默认值,则可以。
stonewt(int stn ,double lbs=1.0);
这种自动特性并非总是合乎需要的,因为这会导致意外的类型转换。因此,C++新增了关键字explicit
用于关闭这种自动特性。也就是说可以这样声明构造函数:
explicit stonewt (double lbs);
转换函数
上文说明 数字可以通过构造函数转换成类 那么可以做相反的转换吗?
可以。
但不是运用构造函数,构造函数只用于某种类型到类类型的转换。要进行相反的转换,必须使用特殊的C++运算符函数转换函数。
转换函数是用户定义的强制类型转换。可以像使用强制类型转换那样使用它们。例如:
stonewt wolfe(222.0);
double host = double (wolfe);
double thinker = (double)wolfe;
也可以让编译器决定怎么做;
stonewt wolfe(222.0);
double star = wolfe;
编译器发现,右侧是stonewt类型,而左侧是double类型,因此他将查看程序员是否定义了与此匹配的转换函数。(如果没找到将出错)
要转换为typename类型需要使用这种格式的转换函数
operator typename();
注意:
1.转换函数必须是类方法;
2.转换函数不能指定返回类型;
3.转换函数不能有参数。
例如要将stonewt对象转换为int类型 则需要将下面原型添加到类声明中
operator int();
函数定义处
stonerwt::operator int() const{
return ...;
}
应谨慎地使用隐式转换函数。通常,最好选择尽在被显式化时才会执行的函数。
(完)
网友评论