多态性
发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为。
运算符重载
必要性
C++中预定义的运算符的操作对象只能是基本数据类型。
int x , y;
y = x + y ;
对于许多用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作
运算符重载的实质
运算符重载:对已有的运算符赋予多重含义,实质是函数重载,它提供了C++的可扩展性。
实现机制
转化:
运算表达式转化为对运算符函数的调用,
运算对象转化为运算符函数的实参。
编译系统对重载运算符的选择,遵循函数重载的
选择原则:目前可以简单地理解为通过函数的参数表判断调用的是重载中的哪个函数
运算符重载规则
重载运算符的限制
不能重载的运算符
- 成员访问运算符 .
‘.’
在类中对任何成员都有意义,已经成为标准用法。 - 作用域运算符::
- 成员指针访问运算符.*
为了处理特殊情况而设计,在一般程序设计中通常不需要用到 - 条件运算符?:
这个运算符对于类对象来说没有实际意义,相反还会引起歧义
5.Sizeof
不要当作普通的运算符
可以重载的运算符
算术运算符:+,-,*,/,%,++,--;
位操作运算符:&,|,~,^(位异或),<<(左移),>>(右移);
逻辑运算符:!,&&,||;
比较运算符:<,>,>=,<=,==,!=;
赋值运算符:=,+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=;
其他运算符:[],(),->,,(逗号运算符),new,delete,new[],delete[],->*
重载运算符函数可以对运算符作出新的解释,但原有基本语义不变:
1.不改变运算符的优先级
2.不改变运算符的结合性
3.不改变运算符所需要的操作数个数。
只能重载C++语言中已有的运算符,不能创建新的运算符。 经重载的运算符,其操作数中至少应该有一个是自定义类型。
一元运算符
Object op 或 op Object
1.重载为成员函数,解释为:
Object . operator op ()
操作数由对象Object通过this指针隐含传递
2.重载为友元函数,解释为:
operator op (Object)
操作数由参数表的参数Object提供
二元运算符
ObjectL op ObjectR
1.重载为成员函数,解释为:
ObjectL . operator op ( ObjectR )
左操作数由ObjectL通过this指针传递,右操作数由参数
ObjectR传递
2.重载为友元函数,解释为:
operator op ( ObjectL, ObjectR )
左右操作数都由参数传递
重载为类的成员函数时
参数个数=原操作数个数-1 (后置++、–除外)
重载为友元函数时
参数个数=原操作数个数,且至少应该有一个自定义类型的形参。
用成员函数重载运算符
当一元运算符的操作数,或者二元运算符的左操作数 是类的对象时,定义重载算符函数为成员函数。
一元运算符 -a 二元运算符 a+b
成员运算符函数的原型在类的内部声明格式如下:
class X
{
//…
返回类型 operator运算符(形参表);//运算符是重载的运算符,形参表是运算符的操作数
//…
}
在类外定义成员运算符函数的格式如下:
返回类型 X::operator运算符(形参表)
{
函数体
}
运算符成员函数的设计
二元运算符 op
如果要重载 op 为类的成员函数,使之能够实
现表达式 oprd1 op oprd2,其中
oprd1 为A 类对象,则 op 应被重载为 A 类的成员函数,
形参类型应该是 oprd2 所属的类型。
经重载后,表达式 oprd1 op oprd2 相当于
oprd1.operator op(oprd2)
前置一元运算符 op
如果要重载 op 为类成员函数,使之能够实现表达式 op oprd,其中
oprd 为A类对象,则 op 应被重载为 A 类的成员函数,
无形参。
经重载后,
表达式 op oprd 相当于 oprd.operator op()
增1减1运算符是一元运算符。它们又有前置和后置两种。为了区分这两种运算,将后置运算视为二元运算符。表达式 obj++或obj–
被看作为: obj++0或obj–0
后置一元运算符 ++和–
如果要重载 ++或–为类成员函数,使之能够实现表达式 oprd++ 或 oprd-- ,其中
oprd 为A类对象,则 ++或-- 应被重载为 A 类的成员函数, 且
具有一个 int 类型形参。
经重载后,表达式 oprd++ 相当于 oprd.operator ++(0)
运算符前置++和后置++重载为时钟类的成员函数
- 前置一元运算符,重载函数没有形参
- 后置一元运算符,重载函数需要有一个整型形参
用友元函数重载运算符
运算符重载为类的友元函数
1.如果需要重载一个运算符,使之能够用于操作某类对象的私有成员,可以此将运算符重载为该类的友元函数。
2.函数的形参代表依自左至右次序排列的各操作数
class Complex
{ int Real ; int Imag ;
public :
Complex ( int a ) { Real = a ; Imag = 0 ; }
Complex ( int a , int b ) { Real = a ; Imag = b ; }
Complex operator + ( Complex ) ; //重载为成员函数
…...
} ;
int f ( )
{ Complex z ( 2 , 3 ) , k ( 3 , 4 ) ;
z = z + 27 ;//调用构造函数将27 转换为Complex 类常量 可以!!需要隐式转换的情形下,使用友元函数重载运算符是正确的选择
z = 27 + z ;//27 不是Complex对象,不能调用函数
…...
}
成员函数重载的“ + ”运算符不支持交换律
友元函数没有 this 指针,所需操作数都必须在参数表显式声明,很容易实现类型的隐式转换
C++中***不能***用友元函数重载的运算符有= () [] ->
#include<iostream>
using namespace std;
class Complex
{
public:
Complex(double r=0, double i=0 ) {R=r;I=i;}
Complex(int a) {R=a ;I=0;}
void print() const;
friend Complex operator+(const Complex &c1,const Complex &c2);
friend Complex operator-(const Complex &c1,const Complex &c2);
friend Complex operator- ( const Complex &c);
private:
double R,I;
};
Complex operator+(const Complex &c1, const Complex &c2)
{
double r=c1.R+c2.R;
double i=c1.I+c2.I;
return Complex (r,i);
}
Complex operator-(const Complex &c1,const Complex &c2)
{
double r=c1.R-c2.R;
double i=c1.I-c2.I;
return Complex(r,i);
}
Complex operator-(const Complex &c){return Complex(-c.R,-c.I);}
void Complex::print()const{cout <<'('<<R<<","<<I<<')'<<endl;}
int main()
{
Complex c1(0.5,7.7),c2(4.5,3.5);
Complex c;
c =c1-c2;
c.print();
c=25+c2;//1.若为引用,则必为常引用,若非引用,则可为常参数,也可不是常参数。
c.print() ;
c=c2+25;//2.但是执行时的效率不同——参数传递时的不复制或复制
c.print();
c=-c1;
c.print();
}
friend Complex operator+(const Complex &c1, const Complex &c2 ) ;
friend Complex operator-(const Complex &c1, const Complex &c2 ) ;
friend Complex operator- ( const Complex & c ) ;//有const,函数不能改变形参的值
friend Complex operator+(const Complex c1, const Complex c2 ) ;
friend Complex operator-(const Complex c1, const Complex c2 ) ;
friend Complex operator- ( const Complex c ) ;
friend Complex operator+(Complex c1, Complex c2 ) ;
friend Complex operator-(Complex c1, Complex c2 ) ;
friend Complex operator- (Complex c ) ;无const,函数可能改变形参的值
friend Complex operator+(const Complex c1, const Complex c2 ) ;
friend Complex operator-(Complex c1, Complex c2 ) ;
friend Complex operator- ( const Complex & c ) ;
若非引用,则可为常参数,也可不是常参数。
但是执行时的效率不同——参数传递时的不复制或复制。
成员运算符函数与友元运算符函数的比较
二元运算符:
成员运算符函数有一个参数,友元运算符函数有两个参数;
一元运算符:
成员运算符函数无参数,友元运算符函数有一个参数。
(后置的++、–需要增加一个形参)
2.二元运算符一般可以被重载为友元运算符函数或成员运算符函数,但当操作数类型不相同时,必须使用友元函数。
3.成员运算符函数和友元运算符函数可以用习惯方式调用,也可以用它们专用的方式调用,下表列出了一般情况下运算符函数的调用形式。
4. C++的大部分运算符既可说明为成员运算符函数,又可说明为友元运算符函数。究竟选择哪一种运算符好一些,没有定论,这主要取决于实际情况和程序员的习惯。
运算符函数既可以重载为成员函数,也可以重载为友元函数或普通函数。
当一元运算符的操作数,或者二元运算符的左操作数是类的一个对象时,以成员函数重载;当一个运算符的操作需要修改类对象状态时,应该以成员函数重载。如果以成友元函数重载,则使用引用参数修改对象。
当运算符的操作数(尤其是第一个操作数)希望有隐式转换,则重载算符时必须用友元函数。
几个典型运算符重载
重载 ++ 与 –
A Aobject ;
运算符 ++和 - - 有两种方式:
前置方式: ++Aobject --Aobject
成员函数 重载 A :: A operator++ () ;
解释为: Aobject . operator ++( ) ;
友元函数 重载 friend A operator++ (A &) ;
解释为: operator ++( Aobject ) ;
后置方式: Aobject ++ Aobject --
成员函数 重载 A :: A operator++ (int) ;
解释为: Aobject . operator ++( 0 ) ;//0为伪参数
友元函数 重载: friend A operator++ (A &, int) ;
解释为: operator++(Aobject, 0)
重载赋值运算符
赋值运算符重载用于对象数据的复制
operator= 必须重载为成员函数
重载函数原型为:
类名 & 类名 :: operator= (const 类名 & ) ;
重载运算符[]和( )
下标运算符 [ ] 和函数调用符 ( )
1.是二元运算符
2.只能用成员函数重载,不能用友元函数重载
1.重载下标运算符[ ] :
由于数组中并没有保存其大小,因此,不能对数组元素进行存取范围的检查,无法保证给数组动态赋值不会越界。利用C++的类可以定义一种更安全、功能强的数组类型。为此,为该类定义重载运算符[]。在重载下标运算符函数时应该注意:
(1) 该函数只能带一个参数,不可带多个参数。
(2) 不得重载为友元函数,必须是非static的成员函数。
[] 运算符用于访问数据对象的元素
重载格式
类型 类 :: operator[ ] ( 类型 /*右操作数为符合原语义,用 int*/) ;
int & operator [ ] ( int i ) //当重载运算符函数调用需要做左值时,应该返回引用
{
return v [ i ] ;
}
例 设 x 是类 X 的一个对象,则表达式
x [ y ]
可解释为x . operator [ ] ( y )
2.重载函数调用符 ( )
重载格式:
类型 类 :: operator( ) ( 参数表 ) ;//注意,函数调用运算符适用于对象的名称,而不是函数的名称。
例设 x 是类 X 的一个对象,则表达式
x ( arg1, arg2, … )
可被解释为
x . operator ( ) (arg1, arg2, … )
一般重载操作符的形参数目(包括成员函数的隐式 this 指针)与操作符的操作数数目相同。而函数调用操作符可以接受任意数目的操作数。
重载函数调用操作符的类,因为可以像使用函数名一样使用此类的对象名,其对象常称为函数对象(function object),即它们是行为类似函数的对象。
函数调用操作符通常结合函数对象一起使用。
#include <iostream>
using namespace std;
class Area
{
public:
int operator()(int l1,int w) {return l1*w;}
};
void printArea(int l2,int w,Area &area)
{
cout << "Area is " << area(l2,w);
}
int main()
{
Area s;
printArea(3, 4, s);
return 0;
}
/*
输出
Area is 12
*/
•定义函数对象的类一般不需要数据成员,也没有定义的构造函数,因此创建和使用函数对象的开销是最小的。
•函数对象类通常也定义为模板,因为这会增加灵活性。
重载流插入和流提取运算符
istream 和 ostream 是 C++ 的预定义流类
cin 是 istream 的对象,cout 是 ostream 的对象
运算符 << 由ostream 重载为插入操作,用于输出基本类型数据
运算符 >> 由 istream 重载为提取操作,用于输入基本类型数据
用友元函数重载 << 和 >> ,输出和输入用户自定义的数据类型
重载输出运算符“<<”
因为运算符“<<”的左右操作数一般是不同的,
例如,cout<<“China”;
//左操作数cout 是 ostream 的对象,右操作数是字符串常量
只能被重载为友元函数,不能重载为成员函数
定义输出运算符“<<”重载函数的一般格式如下:
ostream& operator<<(ostream& out,class_name& obj)
/*这里可以用引用,也可以不用,
即const class_name& obj
或者:class_name& obj
或者:class_name obj
或者:const class_name obj*/
{
out<<obj.item1;
out<<obj.item2;
.. .
out<<obj.itemn;
return out;
}
重载输入运算符“>>”
只能被重载成友元函数
定义输入运算符函数 “>>”重载函数的一般格式如下:
istream& operator>>(istream& in,class_name& obj)//这里必须用引用否则obj的值没有改变
{
in>>obj.item1;
in>>obj.item2;
. . .
in>>obj.itemn;
return in;
}
重载关系运算符
class Distance
{ private:
int feet, inches;
public:
Distance(int f=0, int i=0){ feet = f; inches = i; }
void displayDistance() { cout << feet << inches <<endl; }
};
bool operator <(const Distance& d) { //重载小于运算符( < )
if(feet < d.feet) { return true; }
if(feet == d.feet && inches < d.inches) { return true; }
return false;
}
构造函数进行类类型转换
转换构造函数(conversion constructor function) 的作用是将一个其他类型的数据转换成一个类的对象。 转换构造函数通常只有一个形参,
如
Complex(int a) { Real = a ; Imag = 0 ; }
其作用是将int型的参数a转换成Complex类的对象,将a作为复数的实部,虚部为0。
用户可以根据需要定义转换构造函数,在函数体中告诉编译系统怎样去进行转换。
对比Complex(2)和float(2)。
二者形式类似, float(2)是强制类型转换,将整数2转换为float型2.0,float ()是强制类型转换运算符。可以认为Complex(2)的作用也是强制类型 转换,将整数2转换为Complex类对象。
这种类型转换可以是隐式的,转换可以发生在算术运算、赋值运算及参数传递中。
void funx(Complex x); //函数原型
Complex x=Complex(3); //显式转换
x=12; //赋值的类型转换
Complex y=5; //对5做类型转换
funx(27); //参数传递的类型转换
执行时,调用带参数的构造函数实现类型转换
归纳起来,使用转换构造函数将一个指定的数据转换为类
对象的方法如下:
- 先声明一个类。
- 在这个类中定义一个只有一个参数的构造函数,参数的类型是需
要转换的类型,在函数体中指定转换的方法。 - 在该类的作用域内可以用以下形式进行类型转换:
类名(指定类型的数据)
就可以将指定类型的数据转换为此类的对象
类 ClassX 具有以下形式的转换构造函数:
ClassX :: ClassX ( arg [,arg1 = E1 ,…, argn = En ]) ;
这是包含默认参数的转换构造函数,其中:
ClassX是用户定义的类类型名;
arg是基本类型或类类型参数,是将被转换成ClassX类的
参数;
arg1- argn是默认参数;
E1- En是默认参数的默认值。
说明了一种从参数 arg 的类型到该类类型ClassX的转换
class X
{ // ……
public :
X ( int ) ; X ( const char * , int = 0 ) ;
};
void f ( X arg ) ;
:
X a = X( 1 ) ; // a = 1 调用构造函数 X ( int ) 把 1 转换为类类型 X 后赋给对象 a也称 X ( 1 ) 为 X 类的类型常量
X b = "Jessie" ; // b = X ( "Jessie" , 0 ) 调用构造函数X ( const char * , int = 0 ) 把字符串转换为类类型 X 后赋给对象 b
a = 2 ; // a = X ( 2 ) 隐式调用构造函数 X ( int ) 把 2 转换为类类型 X 后赋给对象
f ( 3 ) ; // f ( X ( 3 ) ) 隐式调用构造函数 X ( int ) 对实参作类类型转换,然后做参数结合
f ( 10 , 20 ) ;// error 当找不到匹配的构造函数时 转换失败
这样的隐式类型转换由系统自动完成
类型转换函数
1.带参数的构造函数不能把一个类类型转换成基本类型
2. 类类型转换函数是一种特殊的成员函数,提供类对象之间显式类型转换的机制
ClassX :: operator Type ( )
{ ……
return Type 类型的对象
}
功能:将类型 ClassX 的对象转换为类型 Type 的对象
Type可以是预定义类型,也可以是用户定义类型
函数没有参数,没有返回类型,但必须有一条 return 语句,返回Type类型的对象
该函数只能为成员函数,不能为友元函数。
class X
{ ……
public :
operator int ( ) ;
……
} ;
void f ( X a )
{ int i = int ( a ) ;
i = ( int ) a ;
i = a ;
}
除了赋值和初始化,类型转换函数还可以这样使用:
void g ( X a , X b )
{ int i = ( a ) ? 1 + a : 0 ;
int j = ( a && b ) ? a + b : i ;
if ( a ) { …… } ;
}
对象 a 、b 可用在整型变量出现的地方
类型转换函数有两种使用方式:
隐式使用 i = a ;
显式使用 i = a . operator int ( ) // int ( a ) ( int ) a
使用不同函数作类型转换函数:
int i = a ; // 用类型转换函数进行转换
X i = a ; // 用构造函数进行转换