第九章:运算符重载
第九章:运算符重载
第九章:运算符重载
学习目标
· 运算符重载的一般概念
· 双目运算符重载
· 类型转换运算符重载
· 流运算符重载
· 单目运算符重载
· 赋值运算符重载
· 下标运算符重载
· New/delete运算符重载
· 指针运算符重载
运算符重载一般概念
C++内部定义的数据类型(int , float, …)的数据操作可以用运算符号来
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
示,其使用形式是表达式,用户自定义的类型的数据的操作则用函数表示,其使用形式是函数调用。为了对用户自定义数据类型的数据的操作与内定义的数据类型的数据的操作形式一致,C++提供了运算符的重载,通过把C++中预定义的运算符重载为类的成员函数或者友员函数,使得对用户的自定义数据类型的数据—对象的操作形式与C++内部定义的类型的数据一致。
运算符重载即对C++内部的基本数据类型所支持的运算符赋予新的含义,让其可操作自定义的数据类型。运算符重载指对已知的运算符,在新的场合,通过程序实现新的行为。
运算符重载规则
· 允许重载的运算符
标准
excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载
C++中提供的运算符很多,但允许重载的运算符只能是下表中所列。参见下表。
(允许重载的运算符列表)
双目运算符
+ - * / %
关系运算符
== != < > <= >=
逻辑运算符
|| && +
单目运算符
+ - * &
自增自减运算符
++ --
位运算符
| & ~ ^ << >>
赋值运算符
= += -= *= /= %= &= |= ^= <<= >>=
空间申请和释放
New delete new[] delete[]
其他运算符
() -> ->* , []
· 不允许重载的运算符
不允许重载的运算符只有5个:
· .
(成员访问符)
· .*
(成员指针访问运算符)
· ::
(域运算符)
· Sizeof (长度运算符) typeof typedef
· ?:
(条件运算符号)
· 其他规则
1) 不允许自己定义新的运算符,只能对已有的运算符号进行重载;
2) 重载不能改变运算符运算对象的个数,如>和<是双目运算符,重载后仍为双目运算符,需要两个参数;
3) 重载不能改变运算符的结合性,如=是从右至左,重载后仍然为从右至左;
4) 重载不能改变运算符的优先级别,例如* / 优先于+ -,那么重载后也是同样的优先级别;
5) 重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数,与第2条矛盾;
6) 重载的运算符必须和用户的自定义数据类型一起使用,其参数至少应有一个是类对象(或者类对象的引用),或者说参数不能全部是C++的标准类型;
7) 运算符重载函数可以是类的成员函数,也可以是类的友员函数,也可以使普通函数。
运算符重载的三种方式
对同一个运算符号,往往可以通过普通函数、友员函数和类成员函数这三种方式实现重载,以完成同样第功能。通过普通函数实现运算符重载的特点是自定义类即数据类型不得将其数据成员公开,以便让普通函数可以访问类的数据成员,这破坏了类的封装性,所以这种重载方式要少用或不用,C++语言之所以提供此种方式,是为了与C语言的兼容性。通过友员函数重载运算符的特点可以不破坏类的封装性,类的数据成员可以是私有的,但这种方式需要在类中定义友员函数,以允许友员函数可以操作类的私有数据成员,这在实际使用中也很不方便,所以不在必要时不要使用,这量的必要指的是C++中有一些运算符不能用类成员函数的方式实现重载,比如用于cout和cin的流提取符最好使用友员函数实现重载。
通过类成员函数重载运算符是我们推鉴使用的,运算符重载函数是类的成员函数,正好满足了类的封装性要求。这种运算符重载的特点是类本身就是一个运算符参数,参见实例。以下我们分别对一些主要的运算符重载的具体情况展开讨论。
单目运算符、双目运算符重载
我们知道单目运算符是指该运算符只操作一个数据,由此产生另一个数据,如正数运算符和负数运算符。双目运算符则是要操作两个数据,由此产生另一个数据,如一般的算术运算符和比较运算符都双目运算符。
1) 在一个类中重载一元运算符@为类运算符的形式为:
返回值类型 类名::operator@()
{
//…
}
2) 在一个类中重载一元运算符@为友员运算符的形式为:
返回值类型 operator@(const 类名 &obj)
{
//…
}
3) 在一个类中重载二元运算符@为类运算符的形式为:
返回值类型 类名::operator@(参数1)
{
//…
}
双目运算符需要两个运算符,这量的函数原型为何仅列出了一个参数?这是因为类本身也是一个参数,存取该参数可以通过this关键字来实现。
4) 在一个类中重载二元运算符@为友员运算符的形式为:
返回值类型 operator@(参数1,参数2)
{
//…
}
关于双目运算符的例子参见例1。
转换构造函数和类型转换运算符(函数)
类的转换构造函数只带一个参数,它把参数类型的数据转换成相应类型的类的对象。这与拷贝构造函数不同,拷贝构造函数的参数是同类型对象的引用。
例如
class CComplex
{
public:
double m_fReal;
double m_fImag;
CComplex(){};
CComplex(double fReal, double fImag=0)
{
m_fReal=fReal;
m_fImag=fImag;
};
};
int main()
{
CComplex cpx;
cpx=9;
return 0;
}
“cpx=9”一句将数值9转换成一个临时的Ccomplex对象,并拷贝给cpx。
与之相对应,可以通过转换运算符,将一个类的对象转换成其他类型的数据。对于类X,转换后的类型为T,则类型转换运算符的形式为:
X:operator T()
{
//…
}
如
class CComplex
{
public:
double m_fReal;
double m_fImag;
CComplex(){};
CComplex(double fReal, double fImag=0)
{
m_fReal=fReal;
m_fImag=fImag;
};
operator double()
{
return m_fReal;
}
};
int main()
{
CComplex cpx(2,3);
double f=double(cpx);
return 0;
}
类型转换运算符的特点是:
1) 待转换类型是类自己
2) 转换后的目标类型要和operator构成了运算符重载的函数名称即operator double
3) 类型转换运算符没有返回类型,但必须要写return语句返回目标类型的值。
· 下面是一个类型转换函数的例子,将复数类的转换为一个字符串。
operator char*()
{
char* s = (char*)malloc(100);
sprintf(s,"Value:(%.2f,%.2f)", real,imag);
return s;
}
· 同类型的转换构造函数和类型转换函数同时存在时,会发生二义性,例子:
class Complex
{
public:
Complex(){real=0;imag=0;}
Complex(double r,double i){real=r;imag=i;}
Complex operator+(const CComplex &cpx1);
//转换构造函数
Complex(double c){real = c; imag = 0;}
//类型转换函数
operator double()
{
return real;
}
public:
double real;
double imag;
};
int main()
{
Complex a,b,c;
c = a+b;
c = a+2.5;
c = 2.5+a;
return 0;
}
其中语句a+b不会发生二义性,C++编译器会定位到加的双目运算符函数并调用,函数返回值赋给变量c。语句a+2.5和2.5+a则要发生二义性,因为编译器可以变量a调用转换构造函数产生一个double数据类型,进行两个double数据类型的加法,也可以将常数2.5调用转换构造函数生成一个复数类型的变量,调用两个复相加的运算符函数,函数返回一个复数类变量,并赋给变量c。
流运算符的重载<< >>
流运算符的最大特点是其第一个参数类型一定是类ostream或istream,并且返回值也是类ostream或istream,这样才能将自定义类的变量输出或输入到流中。显然,流运算符不能通过类的成员函数重载,因为通过类的成员函数重载必须是运算符的第一个参数是类自己。一般我们用友员来实现流运算符的重载,不用普通函数来实现。参见例2。
前加++/--和后加++/--运算符的重载
前加++/--和后加++/--运算符是一种单目运算符的重载,正数和负数单目运算符的重载与此类似。其由于通过类的成员函数实现运算符的重载,第一个参数一定得是类自己,所以前加++的函数原型中没的参数。如下:
Complex Complex ::operator++()
{
return Complex(++real,++imag);
}
后加++也是单目运算符,C++编译器如何区分后加运算符的情况呢?C++编译器将后加运算符后加++/--强行加一个参数类型int,该参数的作用是仅仅区分之用。
Complex Complex ::operator++(int) //after ++
{
Complex temp(*this);
temp.real++;
temp.imag++;
return temp;
}
赋值运算符的重载
重载
方法
快递客服问题件处理详细方法山木方法pdf计算方法pdf华与华方法下载八字理论方法下载
缺省的赋值运算符是实行对象间的按位拷贝,如果类成员中含有指针类型的数据成员,一般应该将该类的赋值运算符重载,因为这时调用缺省的赋值运算符没有意义。如:
class CMyString
{
private:
char *m_pszData;
public:
CMyString(char *pszData);
//构造函数
CMyString(CMyString &objStr);
//拷贝构造函数
CMyString &operator=(CMyString &objStr);
//重载=操作符
CMyString &operator=(char *pszData);
//重载=操作符
~CMyString()
{
delete []m_pszData;
}
};
CMyString::CMyString(char *pszData)
{
m_pszData=new char[strlen(pszData)+1];
strcpy(m_pszData,pszData);
}
CMyString::CMyString(CMyString &objStr)
{
m_pszData=new char[strlen(objStr.m_pszData)+1];
strcpy(m_pszData,objStr.m_pszData);
}
CMyString &CMyString::operator=(CMyString &objStr)
{
if(this==&objStr)
return *this;
delete []m_pszData;
m_pszData=new char[strlen(objStr.m_pszData)+1];
strcpy(m_pszData,objStr.m_pszData);
return *this;
}
CMyString &CMyString::operator=(char *pszData)
{
delete []m_pszData;
m_pszData=new char[strlen(pszData)+1];
strcpy(m_pszData,pszData);
return *this;
}
int main()
{
CMyString s1="abc";
CMyString s2="xyz";
s1="123";
s1=s2;
return 0;
}
赋值运算符和拷贝构造函数的区别和联系
相同点:都是将一个对象COPY到另一个中去
不同点:拷贝构造函数涉及到要新建立一个对象。在以下三种情况下要调用拷贝构造函数:
1) 形如 A a = b;由于对象a是新建的对象,所以这种情况并不调用赋值运算符,而是要调用拷贝构造函数
2) 对象做函数参数时,由于要在堆栈中建立新对象,所以要调拷贝构造函数
3) 对象是函数的返回值,而赋值运算符不涉及到对象的建立。
在一个对象中如果不定义赋值运算符和拷贝构造函数,编译器将为之生成默认的赋值运算符和拷贝构造函数,它只进行按位COPY,来实现对象的COPY或赋值。一般来讲当一个类定义有指针,默认的赋值运算符和拷贝构造函数将不能完成对象的赋值和COPY,因为按位COPY的结果是将一对象的指针值COPY 到另一个对象中,这会引起相同的内存被多个对象引用的错误。
这时要注意一个问题:引用或指针做函数参数或返回值时,函数调用时会不会调用类的拷贝构造函数呢?是不会的,因为函数在调用时不会涉及到新对象的建立,仅仅是将原对象的地址进行了COPY。
[]下标运算符重载
标准情况下,[]运算符用于访问数组的元素。我们可以通过重载下标运算符为类运算符。使得可以象访问数组元素一样的访问对象中的数据成员。C++只允许把下标运算符重载为非静态的成员函数。
下标运算符的定义形式为:
T1 T::operator[] (T2);
其中T1为希望返回的数据类型,T为类名,T2为下标,它可以是任意类型。如需访问第5节中的CMyString的某个字符的话,在类中可声明重载[]运算符:
char operator[](int iIndex);
在外部定义该运算符重载函数
char CMyString::operator[](int iIndex)
{
if(iIndex
的重载
class CDataSetPtr
{
private:
CDataSet *m_pDataSet;
public:
CDataSetPtr()
{
m_pDataSet=new CDataSet;
}
~CDataSetPtr()
{
delete m_pDataSet;
}
CDataSet * operator->()
{
return m_pDataSet;
}
};
void main()
{
CDataSetPtr pDataSet;
int iValue;
pDataSet->GetField(iValue,"Title");
}
例1通过复数类的相加运算符演示运算符重载的几种实现方式
如有下列复数类:
class CComplex
{
public:
double m_fReal;
double m_fImag;
char m_szStatus[32];
CComplex(){
m_fReal=0;
m_fImag=0;
}
CComplex(double fReal,double fImage){
m_fReal=fReal;
m_fImag=fImage;
}
};
如果要实现将该类的两个对象的实部间相加、虚部间相加生成一个新的对象,即:对象3=对象2+对象1;
1) 如果用普通函数的实现运算符+的重载,可以用如下形式
CComplex operator+(const CComplex &cpx1,const CComplex &cpx2)
{
CComplex cpxRet;
cpxRet.m_fReal=cpx1.m_fReal+cpx2.m_fReal;
cpxRet.m_fImag=cpx1.m_fImag+cpx2.m_fImag;
return cpxRet;
}
通过普通函数重载来实现,必须将复数类的私有数据成员公开,这破坏了类的封装性
2) 由于为类重载运算符函数往往要访问类的成员,而类为了封装性的需要,往往将成员声明为非公有的,因此往往将一个运算符函数重载为类的成员函数或者友员函数。如果CComplex的m_fReal,m_fImag是类的私有或者保护成员,那么普通函数无法访问,在此情况下就不能实现上述重载功能,此时,可以通过友员函数的方式实现运算符+的重载。
如下声明方式,可以在类中声明友员函数上面的普通函数为友员函数
class CComplex
{
private:
double m_fReal;
double m_fImag;
char m_szStatus[32];
public:
CComplex()
{
m_fReal=0;
m_fImag=0;
}
CComplex(double fReal,double fImage)
{
m_fReal=fReal;
m_fImag=fImage;
}
friend CComplex operator+(const CComplex &cpx1,const CComplex &cpx2);
};
函数的实现与普通函数相同。
3) 也可以将运算符重载为类的成员函数,如下:
class CComplex
{
private:
double m_fReal;
double m_fImag;
char m_szStatus[32];
public:
CComplex(){
m_fReal=0;
m_fImag=0;
}
CComplex(double fReal,double fImage){
m_fReal=fReal;
m_fImag=fImage;
}
CComplex operator+(const CComplex &cpx);
};
并在类外定义该成员函数:
CComplex CComplex::operator+(const CComplex &cpx2)
{
CComplex cpxRet;
cpxRet.m_fReal=this->m_fReal+cpx2.m_fReal;
cpxRet.m_fImag=this->m_fImag+cpx2.m_fImag;
return cpxRet;
}
4) 运算符重载函数如何调用呢?
可以象调用一般的函数一样调用运算符重载函数,运算符重载函数的函数名称是operator+
例如:
void main()
{
CComplex c1,c2,c3;
C3 = c1. operator+(c2);
}
但这样
书
关于书的成语关于读书的排比句社区图书漂流公约怎么写关于读书的小报汉书pdf
写很不直观,也不方便。C++编译提供了一种简写的方式,如下:
c3 = c1+c2
例2通过友员函数来实现复数类的流运算符重载
class Complex;
ostream operator<<(ostream& os, Complex& c);
istream operator>>(istream& is, Complex& c);
Complex operator+(Complex &first, Complex &second );
class Complex
{
public:
Complex(){real=0;imag=0;}
Complex(double r,double i){real=r;imag=i;}
//转换构造函数
Complex(double c){real = c; imag = 0;}
//类型转换函数
operator double()
{
return real;
}
operator char*()
{
char* s = (char*)malloc(100);
sprintf(s,"Value:(%.2f,%.2f)", real,imag);
return s;
}
friend Complex operator+(Complex &first, Complex &second );
friend ostream operator<<(ostream& os, Complex& c);
friend istream operator>>(istream& is, Complex& c);
void display()
{
cout<<"("<>(istream& is, Complex& c)
{
cout<<"input a complex :\n"<>c.real>>c.imag ;
return is;
}
ostream operator<<(ostream& os, Complex& c)
{
os<<"complex value is:("<>cc>>ccc;
cout<>cc>>ccc的理解与输出的流提取符的理解是完全一样的
内容总结
运算符重载的基本概念,算符重载是有规则的,不能够自随意定义一个特殊的符号对其进行重载。必须是对C++语言已有的运算符进行重载,而且还不是全部的预定义运算符都可以重载。在重载运算符时不能改变该运算符原有的操作数的个数,及其运算的结合性和优先级。在重载运算符时,运算符函数不能定义默认参数,而且函数的参数要至少有一个自定义的数据类型,有三种运算符重载的实现
方案
气瓶 现场处置方案 .pdf气瓶 现场处置方案 .doc见习基地管理方案.doc关于群访事件的化解方案建筑工地扬尘治理专项方案下载
:普通函数、友员函数和类成员函数。我们要学会常用的单目和双目运算符重载,流运算符的重载,赋值和下标运算符的重载。new和delete运算符及指针运算符在实际使用中较少。
独立实践
实践1
开发一个显示时间的程序,要求时间类可以进行加,减,前加和后加,并且可以通过下标运算符来取出时间的时分秒。
实践2
在上述时间类显示程序上加一个功能,通过流运算符可以从键盘上输入时间,并显示之。
实践3
思考:什么时候必须要定义自己的拷贝构造函数和赋值函数?
实践4
编写函数实现两个静态整型数据的相加运算,并且是指能够使用流运算符进行输入输出。
实践5
编写函数实现两个动态整型数据的相加运算,并且是指能够使用流运算符进行输入输出。注意类的成员变量就是一个整型变量的指针。
class CMatrix
{
protected:
int *m_pi;
…
};
4
3