文章目录
函数的重载
函数重载是指完成不同功能的函数可以具有相同的函数名。
C++编译器根据函数的实参确定应该调用哪一个函数。
1.定义的重载函数必须具有不同的参数个数,或不同的参数类型。编译系统根据不同的参数去调用不同的重载函数,实现不同的功能。
2、仅返回值不同时,不能定义为重载函数。
运算符重载
运算符重载的作用
- 重新定义的运算符称为运算符的重载。
- 运算符重载赋予已有的运算符多重含义或新的功能。C++通过重新定义运算符,使它能够用于特定的对象,执行特定的功能
用一个经典的例子说明这个情况
- 用运算符重载实现两个复数相加
class Complex{
private:
double real,imag;
public:
Complex(double r=0,double i=0):real(r),imag(i){}
void display()const{cout<<real<<" , "<<imag<<"i"<<endl;}
Complex(const Complex &c){real = c.real;imag = c.imag;}
Complex operator+(const Complex &c2);
};
Complex Complex::operator+(const Complex &c2){
Complex c;
c.real = real+c2.real;
c.imag = imag+c2.imag;
return c;
}
/*//也可以利用无名对象
Complex Complex::operator+(const Complex &c2){
return Complex(real+c2.real,imag+c2.imag);
}
*/
int main(){
Complex c1(1,2);
Complex c2(2,5);
Complex c3 = c1+c2;
c3.display();
return 0;
}
为了重载运算符,必须定义一个函数,告诉编译器,遇到这个重载运算符就调用该函数,由这个函数来完成该运算符应该完成的操作,这种函数称为运算符重载函数,它通常是类的成员函数或者是友元函数。运算符的操作数通常是类的对象。
运算符重载对C++有重要的意义:
本来C++提供的运算符只能用于C++的标准数据类型,不能用于用户自己定义的类对象,影响了类和对象的使用。
C++不是为类对象另外定义一批新的运算符,只允许重载现有的运算符,使这些简单易用、众所周知的运算符能够直接用于类对象。
- 运算符重载使C++具有更强大的功能、更好的可扩充性和适应性。
运算符重载的规则
<返回类型> operator<运算符>(<参数表>)
{函数体}
A operator+(A &);
operator是定义运算符重载函数的关键字,它与其后的运算符一起构成函数名。
- 只能对C++中已有的运算符进行重载。
在C++中允许重载的运算符如下
C++中不能重载的运算符只有5个:
.
(成员访问运算符)
.*
(成员指针访问运算符)
∷
(域运算符)
sizeof
(长度运算符)
?:
(条件运算符)
- 重载不能改变运算符的优先级、结合性、运算对象(即操作数)的个数。
- 重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数。
class room{
float Length;
float Wide;
public:
room(float a=0.0,float b=0.0){ Length=a; Wide=b; }
void Show(void) {cout<<"Length="<<Length<<'\t'<<"Wide="<<Wide<<endl;}
void ShowArea(void) { cout<<"Area="<<Length*Wide<<endl; }
room operator+(room &);//重载运算符+,函数原型
};
room room::operator + (room &r) //重载运算符函数
{ room rr;
rr.Length =Length+r.Length;
rr.Wide =Wide+r.Wide ;
return rr;
}
int main(void)
{ room r1(3,2),r2(1,4), r3,r4,r5;
r1.Show (); r2.Show ();
r3=r1+r2;
r3.Show ();
r4=r1+r2+r3; //运算顺序:(r1+r2), (r1+r2)+r3
r4.Show ();
r5=r1+(r2+r3); //报错!!!
r5.Show();
return 0;
}
- 重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象(或类对象的引用)。
- 参数不能全部是C++的标准类型,以防止用户修改用于标准数据类型的运算符。
有两个运算符一般不需要重载:
- ①赋值运算符(=)可以用于每一个类对象,利用它在同类对象之间相互赋值。(也有必须重载的情况)
- ②地址运算符&也不必重载,它能返回类对象在内存中的起始地址。
运算符重载的方式
方式(1) 作为类成员函数
当用成员函数实现运算符的重载时,运算符重载函数的参数只能有二种情况:
没有参数;
带有一个参数。
- 对于只有一个操作数的运算符,在重载这种运算符时,通常不能有参数;
- 对于有二个操作数的运算符,只能带有一个参数。这个参数可以是对象、对象的引用或其它类型的参数。
用成员函数实现运算符的重载时**,运算符的左操作数为当前对象**,这是一个隐含的函数参数,要用到隐含的this指针;
c = a+b ; c = a.operator+(b);
c += a ; c.operator+=(a);
c = ++a ; c = a.operator++();
class A{
public:
int x ;
A(int x):x(x){}
};
a + b ;
实际上为 a.operator+(b);
A operator+(A&b)
{
return(this->x+b.x)
}
运算符重载函数不能定义为静态的成员函数,因为静态的成员函数中没有this指针。
demo
class A
{ int i;
public: A(int a=0){ i=a; }
void Show(void){ cout<<"i="<<i<<endl; }
A operator +(A &a){
A t;
t.i=i+a.i;
return t;
}
A operator+=(A &a){
i=i+a.i;
return *this; //修改这个对象的成员数据后,再返回
}
};
int main(void)
{ A a1(10),a2(20),a3;
a1.Show ();
a2.Show ();
a3=a1+a2;
a3.Show ();
a1+=a2; // int a1,a2; a1+=a2就相当于 a1=a1+a2;
a1.Show ();
return 0;
}
方式2:作为类的友元函数
友元函数是在类外的普通函数,与一般函数的区别是可以调用类中的私有或保护数据。
friend <类的类型> operator<运算符>(<参数表>)
{……}
friend A operator+(A &a,A &b)
{……}
c = a+b; //c = operator+(a,b);
将运算符重载函数定义为友元函数,参与运算的对象全部成为函数参数。
(没有隐含的this指针了)
注意和上面的区别!!!
c = a+b; c = operator+(a,b);
c += a; operator+=(c,a);
c = ++a; c = operator++(a);
对单目运算符,友元函数有一个参数。
对双目运算符,友元函数有2个参数。
还是用上面的复数相加的例子:
将运算符“+”重载为适用于复数加法,重载函数不作为成员函数,而放在类外,作为Complex类的友元函数。
class Complex{
private:
double real,imag;
public:
Complex(double r=0,double i=0):real(r),imag(i){}
void display()const{cout<<real<<" , "<<imag<<"i"<<endl;}
friend Complex operator+(const Complex &,const Complex &);
};
Complex operator+(const Complex &c1,const Complex &c2){
//声明了为友元,可以使用Complex的私有数据
return Complex(c1.real+c2.real,c1.imag+c2.imag);
}
int main(){
Complex c1(1,2),c2(2,5);
Complex c3 = c1+c2;
c3.display();
return 0;
}
方式3:既非类的成员函数也不是友元函数的普通函数
只有在极少的情况下才使用既不是类的成员函数也不是友元函数的普通函数(一般不用),因为普通函数不能直接访问类的私有成员。
仍然以上面的复数类为例
考虑这样的情形:
Complex c3 = 2+c2;// 不合法
只能写作:
Complex c3 = Complex(2,0)+c2;
那如果就像使这种写法可行呢
可以这样重载:
friend Complex operator+(int r,Complex &c);
Complex operator+(int r,Comlex &c)
{
return Complex(r+c.real,c.imag);
}
注意这里的加法交换律并不默认处理,
如果需要,再重载一次就可。
friend Complex operator+(Complex &c,int r);
Complex operator+(Comlex &c,int r)
{
return Complex(r+c.real,c.imag);
}
C++规定,赋值运算符=
、下标运算符[]
、函数调用运算符()
、成员运算符->
必须定义为类的成员函数。
流插入运算符<<
、流提取运算符>>
、类型转换运算符不能定义为类的成员函数,只能作为友元函数。
典型运算符的重载
双目运算符的重载
常用的有:
< > <= >= + - * / %
记双目运算符为 B
- 重载为类的成员函数
a1 B a2 相当于 a1.operatorB(a2);
- 重载为类的友元函数
a1 B a2 相当于 operatorB(a1,a2);
单目运算符重载
常用的有
! - & * ++ --
注意:
b = ++a;
b = a++;
虽然++运算后对象a的值一致,但先自加或后自加的函数的返回值不一致,必须在重载时予以区分。
++
为前置运算时,成员函数重载的一般格式
<type> operator++()
{……}
++
为后置运算时,成员函数重载的一般格式
<type> operator++(int)
{……}
class Complex{
private:
double real,imag;
public:
Complex(double r=0,double i=0):real(r),imag(i){}
void display()const{cout<<real<<" , "<<imag<<"i"<<endl;}
//前置++的重载 ++c;
Complex operator++();
Complex operator++(int);
};
Complex Complex::operator++(){
real++;
imag++;
return *this;
}
Complex Complex::operator++(int){
Complex c(*this); //将当前对象的引用传给复制构造参数
real++;
imag++;
return c;
}
int main(){
Complex c1(1,2),c2(2,5),c3;
c3 = c1++;
c3.display();
c1.display();
c3 = ++c2;
c3.display();
c2.display();
return 0;
}
1 , 2i
2 , 3i
3 , 6i
3 , 6i
++
为前置运算的时候,友元函数重载函数的一般格式
A operator++(A &a)
{……}
++
为后置运算的时候,友元函数重载函数的一般格式
A operator++(A&a,int)
{……}
class Complex{
private:
double real,imag;
public:
Complex(double r=0,double i=0):real(r),imag(i){}
void display()const{cout<<real<<" , "<<imag<<"i"<<endl;}
//前置++的重载 ++c;
friend Complex& operator++(Complex&);
friend Complex operator++(Complex&,int);
};
//当然此处也可以不返回对象引用,但是耗内存和时间。
Complex& operator++(Complex& c){
c.real++;
c.imag++;
return c;
}
//当然此处绝不返回对象引用,因为c1是临时的副本,生命在函数结束之后被析构
Complex operator++(Complex& c,int){
Complex c1(c); //将当前对象的引用传给复制构造参数
c.real++;
c.imag++;
return c1;
}
int main(){
Complex c1(1,2),c2(2,5),c3;
c3 = ++c1;
c1.display();
c3.display();
c3 = c2++;
c2.display();
c3.display();
return 0;
}
2 , 3i
2 , 3i
3 , 6i
2 , 5i
流运算符的重载
cin
和cout
分别是istream类和ostream类的对象。在类库提供的头文件中已经对“<<”和“>>”进行了重载,作为流插入运算符和流提取运算符,能用来输出和输入C++标准类型的数据,用#include 包含到自己的程序文件中。
用户自己定义的类型的数据,不能直接用“<<”和“>>”来输出和输入,必须进行重载。
istream & operator >> (istream &, 自定义类 &);
ostream & operator << (ostream &, 自定义类 &);
只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数。
原因:
一旦将其重载为类的成员函数,那么左操作数就是调用<<
的这个对象,那么operator <<
的第一个参数就是这个对象,隐含的this指针
,那么就只能这样调用
A << cout
。这样显然不是我们要的。
cout<<a 被编译系统解释为
operator<<(cout,a)
class Complex{
private:
double real,imag;
public:
Complex(double r=0,double i=0):real(r),imag(i){}
friend ostream& operator<<(ostream&,Complex&); //重载 << 插入运算符
friend istream& operator>>(istream&,Complex&); //重载 >> 插入运算符
};
ostream& operator<<(ostream& output,Complex& c)
{
if(c.imag>0)
output<<"("<<c.real<<"+"<<c.imag<<"i"<<")"<<endl;
else if(c.imag==0)
output<<c.real<<endl;
else
output<<"("<<c.real<<c.imag<<"i"<<")"<<endl;
return output;
}
istream& operator>>(istream& input,Complex& c)
{
cout<<"input real part and imaginary part of complex number:"<<endl;
input>>c.real>>c.imag;
return input;
}
int main(){
Complex c1;
cin>>c1;
cout<<c1;
return 0;
}
input real part and imaginary part of complex number:
1 1 //从键盘输入
(1+1i)
return output
的作用
返回cout的当前值,**以便连续输出** 。output是ostream类的对象,它是实参cout的引用,也就是cout通过传送地址给output,二者共享同一段存储单元,或者说**output是cout的别名**。return output就是return cout,将输出流cout返回,即保留输出流的现状。
赋值运算符重载 “=”
同类型的对象间可以相互赋值,默认实现是把对象的各个成员一一赋值。注意:与复制构造函数调用方式的不同。
注意:
①②是不一样的,①是调用复制构造函数的简便写法
②是先创建a2对象,再用赋值语句=,这没有调用复制构造函数
①
A a1;
A a2 = a1; //相当于 A a2(a1);
②
A a1,a2;
a2 = a1;
说明:
当对象的成员中使用了new
开辟空间,
不能直接使用默认的赋值运算符“=”,否则在程序的执行期间会出现运行错误。
这个原因和复制构造函数是一致的。
同一空间释放两次,必须重载。
class A{
char *ps;
public:
A( ){ ps=0;}
A(const char *s ){ps =new char [strlen(s)+1]; strcpy(ps, s);}
~A( ){ if (ps) delete [] ps;}
void Show(void) { cout<<ps<<endl;}
};
int main(void )
{ A s1("China!"), s2("Computer!");
s1.Show();
s2.Show();
s2=s1; //相当于 s2.ps=s1.ps;
s1.Show();
s2.Show();
}
前四行如愿输出
China!
Computer!
China!
China!
CppReview(52520,0x1000d5dc0) malloc: *** error for object 0x10056f560: pointer being freed was not allocated
CppReview(52520,0x1000d5dc0) malloc: *** set a breakpoint in malloc_error_break to debug
重载赋值运算符“=”
格式
<函数类型> <className>::operator=(<参数表>)
A& A::operator=(A&a)
b = a; 相当于 b.operator=(a);
demo
class A{
char *ps;
public:
A( ){ ps=0;}
A(const char *s ){ps =new char [strlen(s)+1]; strcpy(ps, s);}
A& operator=(A&);
~A( ){ if (ps) delete [] ps;}
void Show(void) { cout<<ps<<endl;}
};
//返回同类型的引用适合于连续赋值
A& A::operator=(A&a)
{
if(ps) delete []ps; //这块空间如果不主动delete掉,即使此对象析构,也不会释放
if(a.ps){
ps = new char[strlen(a.ps)+1];
strcpy(ps, a.ps); // 必须是深复制
}
else{
ps=0;
}
return *this;
}
int main(void )
{ A s1("China!"), s2("Computer!");
s1.Show();
s2.Show();
s2=s1; //相当于 s2.ps=s1.ps;
s1.Show();
s2.Show();
}
转换构造函数
- 数据类型之间的显式类型转换
类型名(数据)
int(10.5) == 10
转换构造函数是将一个其他类型的数据转换成一个类的对象,属于构造函数的重载。
以上面的Complex类为例
//默认构造函数
Complex()
//初始化的构造函数
Complex(double r,double i)
//用于复制对象的复制构造函数
Complex(const Complex &c)
//转换构造函数
Complex(double r){real=r;imag=0}
class Complex{
private:
double real,imag;
public:
Complex(double r,double i):real(r),imag(i){}
Complex(double r){real = r;imag =0;}
friend ostream& operator<<(ostream&,Complex&); //重载 << 插入运算符
};
ostream& operator<<(ostream& output,Complex& c)
{
output<<"("<<c.real<<"+"<<c.imag<<"i"<<")"<<endl;
return output;
}
int main(){
Complex c1(1,10);
Complex c2 = 3.0;
cout<<c1;
cout<<c2;
return 0;
}
Complex c2 = 3.0; //转换构造函数
//相当于 Complex c2(3.0)
隐式转换常常会带来程序逻辑的错误,而且这种错误一旦发生是很难察觉的,应当尽量避免。
在声明构造函数的时候前面添加上explicit
即可,这样就可以防止这种转换。C++中的explicit关键字只需用于修饰只有一个参数的类构造函数, 表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit
, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式)。
一旦声明为显式的(explicit
)
//Complex c2 = 3.0; //违法
//只能这样写
Complex c2(3.0)
不仅可以将一个标准类型数据转换成类对象,也可以将另一个类的对象转换成类对象。
class B;
class A{
public:
int x;
A(int x):x(x){}
A(B&);
};
class B{
public:
int x;
B(int x):x(x){}
};
A::A(B &b)
{
x = b.x;
}
int main(){
B b(1);
A a = b;
cout<<a.x<<endl;
return 0;
}
类型转换函数
类型转换函数是在类中定义一个成员函数,将这个类的对象转换为另外一种类型的数据。
- ①转换函数必须是类的成员函数,不能是友元函数
- ②转换函数的调用是隐含的,没有参数,操作数是对象。
格式
operator <type>()
{……
return <type>;
}
ClassName:: operator <type>()
{
……
}
仍以Complex类为例
在这里插入代码片
class Complex{
private:
double real,imag;
public:
Complex(double r,double i):real(r),imag(i){}
operator double();
};
Complex::operator double()
{
cout<<"转换为复数的模长"<<endl;
return sqrt(real*real+imag*imag);
}
int main(){
Complex c(3,4);
cout<<c<<endl; //这里就可以直接cout了,因为存在着类型转换
return 0;
}