面向对象程序设计-重载
合集下载
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
我们可以认为该运算是一个参与运算的类型的内部函 数,而另一个参与运算的类型是其参数;也可以认为 该运算是一个全局函数,参与运算的数据都是其参数。
复数类的例子
#include <iostream.h> class Complex { private:
float Real; float Imag; public: Complex(); Complex(float R,float I); Complex Add(const Complex &c); void print(); };
class X { public: returnType operator@(Y y1 ); }
<<= >>=
参数和返回值
1) 对于任何函数参数,如果仅需要从参数中读而不改 变它,缺省地应当按c o n s t引用来传递它。普通算术 运算符(像+和-号等)和布尔运算符不会改变参数, 所以以c o n s t引用传递是使用的主要方式。当函数是 一个类成员的时候,就转换为c o n s t成员函数。只是 对于会改变左侧参数的赋值运算符( o p e r a t o r- a s s i g n m e n t,像+ =)和运算符‘=’,左侧参数才不是常 量( c o n s t a n t ),但因为参数将被改变,所以参数仍 然按地址传递。
主函数
main() {
Complex c1(5.0,10.0),c2(8.0,-5.0),c3; c3=c1+c2; c1.print(); c2.print(); c3.print(); }
对运算符重载的一些限制
只能重载c++语言原先定义的运算符
不能重载以下操作符
.
.*
::
?:
不能改变运算符的操作数个数
通构造函数调用(不需要拷贝构造函数), 并且不会调用析构函数。因此,这种方法不需要什么
花费,效率是非常高的。
用友元函数重载运算符
class B { public: friend returnType operator@(参数表); } 第一个参数是左操作数,第二个参数是右操作数
重载赋值运算符
Complex::Complex() {
Real=0; Imag=0; } Complex::Complex(float R,float I) { Real=R; Imag=I; } inline Complex Complex::operator+(const Complex &c) { return Complex(Real+c.Real,Imag+c.Imag ); } void Complex::print() { cout<<Real<<"+"<<Imag<<"i"<<endl; }
参数和返回值
integer tmp(left.i + right.i) ; return tmp ; 相反,“返回临时对象”的方法是完全不同的。看这
样情况时,编译器明白对创建的对象 没有其他需求,只是返回它,所以编译器直接地把这
个对象创建在返回值外面的内存单元。因 为不是真正创建一个局部对象,所以仅需要单个的普
Complex Add(const Complex &c1, const Complex &c2) 来完成这个操作。
所以我们可以在复数类中定义一个加法运算,也可以 定义一个不属于任何类的加法运算。为了说明成 c3=c1+c2的情况,我们需要对“+”运算进行重载, 使得可以接受两个复数参与运算,并返回一个复数结 果。由于要操作其中的私有成员,需要将该函数定义 为友元。
回?如果允许对象被改变,一些人写了表达式(++A).foo( ),则 foo( )作用在A上。但对于表达式(A++).foo( ),foo( )作用在通过后 缀运算符+ +号返回的临时对象上。临时对象自动定为c o n s t,
所以被编译器标记。但为了一致性,使两者都是c o n s t更有意义, 就像这儿所做的。因为想给自增和自减运算符赋予各种意思,所 以它们需要就事论事考虑。
如在复数类中定义,则如下:
#include <iostream.h> class Complex { private:
float Real; float Imag; public: Complex(); Complex(float R,float I); Complex operator+(const Complex &c); void print(); };
参数和返回值
3) 所有赋值运算符改变左值。为了使得赋值结果用于 链式表达式(像A = B = C),应该能够返回一个刚刚 改变了的左值的引用。但这个引用应该是c o n s t还是n o n c o n s t呢?虽然我们是从左向右读表达式A = B = C, 但编译器是从右向左分析这个表达式,所以并非一定 要返回一个n o n c o n s t值来支持链式赋值。然而人们 有时希望能够对刚刚赋值的对象进行运算,例如 (A = B). f o o( ),这是B赋值给A后调用foo( )。因 此所有赋值运算符的返回值对于左值应该是n o n c o n s t引用。
不能改变原有运算符的优先级
不能改变原有运算符的结合性
– 右结合,如[]
– 左结合,如+
不能改变预定义类型的操作方式
可以重载的运算符
一元运算符 + - ~ ! & ++ --
class B { public: returnType operator@(); }
可以重载的运算符
自增和自减 重载的++和--号运算符出现了两难选择的局面,这是 因为希望根据它们出现在它们作用的
– 运算符操作可以看作是全局的,也可以看作是属于参与运算 的某个数据类型的
– 不能对预定义类型的运算进行重载 – 运算符重载也是通过静态联编实现的
重载的好处
参与加法运算的可以是整数、浮点数,也可以是字符, 我们是否可以使实数(即小数)、复数、字符串等自 定义类型也进行加法运算呢?
对于不同类型的加法运算,编译系统需要根据参与加 法的操作数类型的不同来判断。
对象前面(前缀)还是后面(后缀)来调用不同的函 数。解决是很简单的,但一些人在开始时却发现它们 容易令人混淆。例如当编译器看到++a(先自增)时, 它就调用operator++(a);但当编译器看到a++时,它就调 用operator++(a,int)。即编译器通过调用不同的函数区 别这两种形式。
源自文库
主函数
main() {
Complex c1(5.0,10.0),c2(8.0,-5.0),c3; c3=c1.Add(c2); c1.print(); c2.print(); c3.print(); }
改进
根据习惯,我们可以需要采用c3=c1+c2的形式来取代 c3=c1.Add(c2);
刚才实现的时候出类可以在复数类中定义Add方法以外, 还可以定义一个全局函数
Complex::Complex() {
Real=0; Imag=0; } Complex::Complex(float R,float I) { Real=R; Imag=I; } inline Complex Complex::Add(const Complex &c) { return Complex(Real+c.Real,Imag+c.Imag ); } void Complex::print() { cout<<Real<<"+"<<Imag<<"i"<<endl; }
第十五讲 重载
函数重载,相同作用域内,若干个参数特征不同的函 数使用相同的函数名。
– 参数不能存在二义性 – 不能通过运算符重载 – 参数的类型和个数来区分,不能通过参数名来区分
– 函数重载是通过静态联编实现的,编译时将函数的参数类型 按照顺序编如函数名称
运算符重载,相同的运算符可以施加于不同类型(包 括自定义 的类)的操作数上。
参数和返回值
2) 应该选择的返回值取决于运算符所期望的类型。 (可以对参数和返回值做任何想做的事)如果运算符 的效果是产生一个新值,将需要产生一个作为返回值 的新对象。例如,i n t e g e r : : o p e r a t o r +必须生成 一个操作数之和的i n t e g e r对象。这个对象作为一个c o n s t通过传值方式返回,所以作为一个左值结果不会 被改变。
参数和返回值
1. 按c o n s t通过传值方式返回
按c o n s t通过传值方式返回,开始看起来有些微妙, 所以值得多加解释。我们来考虑二元运算符+号。假设 在一个表达式像f ( A + B )中使用它,A + B的结果变为 一个临时对象,这个对象用于f( ) 调用。因为它是临时 的,自动被定为c o n s t,所以无论使返回值为c o n s t 还是不这样做都没有影响。 然而,也可能发送一个消息给A + B的返回值而不 是仅传递给一个函数。例如,可以写表达式(A+B).g( ), 这里g( )是i n t e g e r的成员函数。通过设返回值为c o n s t,规定了对于返回值只有c o n s t成员函数才可以被 调用。用c o n s t是恰当的,这是因为这样可以防止在 很可能丢失的对象中存贮有价值的信息。
String类的copy方法 不定义的话,有一个缺省的赋值运算符 缺省的行为是逐域拷贝 引起的指针悬挂问题 指针悬挂问题的解决,重载赋值运算符 赋值运算符只能使用成员函数重载,不能使用全局友
元函数 operator =
重载运算符的规则
下列运算符只能使用成员函数重载
= -> ()
[]
下列运算符只能使用友元函数重载
参数和返回值
4) 对于逻辑运算符,人们希望至少得到一个i n t返回值, 最好是b o o l返回值。(在大多数编译器支持C + +内 置b o o l类型之前开发的库函数使用i n t或t y p e d e f等 价物)
参数和返回值
5) 因为有前缀和后缀版本,所以自增和自减运算符出现了两难局 面。两个版本都改变对象,所以不能把这个对象看作一个c o n s t。 因此,前缀版本返回这个对象被改变后的值。这样,用前缀版本 我们只需返回* t h i s作为一个引用。因为后缀版本返回改变之前 的值,所以被迫创建一个代表这个值的单个对象并返回它。因此, 如果想保持我们的本意,对于后缀必须通过传值方式返回。(注 意,我们经常会发现自增和自减运算返回一个int 值或b o o l值, 例如用来指示是否有一个循环子( i t e r a t o r )在表的结尾)。现 在问题是:这些应该按c o n s t被返回还是按n o n c o n s t被返
<< >>
如果运算符的操作需要修改当前对象的状态,最好使 用成员函数重载(如+=,-=,*=,/=,++。--等)
如果一元操作符的操作数或者二元操作符的左操作数 是预定义类型,则必须使用友元函数重载
在成员函数版中,如果编译器看到++b,它就产生一个 对B::operator++()的调用;如果编译器看到b++,它就 产生一个对B::operator++(int)的调用。
可以重载的运算符
二元运算符 + - * / & % ^ | << >> += -= *= /= %= ^= &= |= == != > >= < <= && ||
参数和返回值
2. 返回效率 当为通过传值方式返回而创建一个新对象时,要注意使用的形式。
例如用运算符+号:
return integer (left.i + right.i) ; 一开始看起来像是一个“对一个构造函数的调用”,但其实并非
如此。这是临时对象语法,它是这样陈述的:“创建一个临时对 象并返回它”。因为这个原因,我们可能认为如果创建一个命名 的本地对象并返回它结果将会是一样的。其实不然。如果这样表 示,将发生三件事。 首先, t m p对象被创建,与此同时它的构造函数被调用。然后, 拷贝构造函数把t m p拷贝到返回值外部存储单元里。最后,当t m p在作用域的结尾时调用析构函数。
复数类的例子
#include <iostream.h> class Complex { private:
float Real; float Imag; public: Complex(); Complex(float R,float I); Complex Add(const Complex &c); void print(); };
class X { public: returnType operator@(Y y1 ); }
<<= >>=
参数和返回值
1) 对于任何函数参数,如果仅需要从参数中读而不改 变它,缺省地应当按c o n s t引用来传递它。普通算术 运算符(像+和-号等)和布尔运算符不会改变参数, 所以以c o n s t引用传递是使用的主要方式。当函数是 一个类成员的时候,就转换为c o n s t成员函数。只是 对于会改变左侧参数的赋值运算符( o p e r a t o r- a s s i g n m e n t,像+ =)和运算符‘=’,左侧参数才不是常 量( c o n s t a n t ),但因为参数将被改变,所以参数仍 然按地址传递。
主函数
main() {
Complex c1(5.0,10.0),c2(8.0,-5.0),c3; c3=c1+c2; c1.print(); c2.print(); c3.print(); }
对运算符重载的一些限制
只能重载c++语言原先定义的运算符
不能重载以下操作符
.
.*
::
?:
不能改变运算符的操作数个数
通构造函数调用(不需要拷贝构造函数), 并且不会调用析构函数。因此,这种方法不需要什么
花费,效率是非常高的。
用友元函数重载运算符
class B { public: friend returnType operator@(参数表); } 第一个参数是左操作数,第二个参数是右操作数
重载赋值运算符
Complex::Complex() {
Real=0; Imag=0; } Complex::Complex(float R,float I) { Real=R; Imag=I; } inline Complex Complex::operator+(const Complex &c) { return Complex(Real+c.Real,Imag+c.Imag ); } void Complex::print() { cout<<Real<<"+"<<Imag<<"i"<<endl; }
参数和返回值
integer tmp(left.i + right.i) ; return tmp ; 相反,“返回临时对象”的方法是完全不同的。看这
样情况时,编译器明白对创建的对象 没有其他需求,只是返回它,所以编译器直接地把这
个对象创建在返回值外面的内存单元。因 为不是真正创建一个局部对象,所以仅需要单个的普
Complex Add(const Complex &c1, const Complex &c2) 来完成这个操作。
所以我们可以在复数类中定义一个加法运算,也可以 定义一个不属于任何类的加法运算。为了说明成 c3=c1+c2的情况,我们需要对“+”运算进行重载, 使得可以接受两个复数参与运算,并返回一个复数结 果。由于要操作其中的私有成员,需要将该函数定义 为友元。
回?如果允许对象被改变,一些人写了表达式(++A).foo( ),则 foo( )作用在A上。但对于表达式(A++).foo( ),foo( )作用在通过后 缀运算符+ +号返回的临时对象上。临时对象自动定为c o n s t,
所以被编译器标记。但为了一致性,使两者都是c o n s t更有意义, 就像这儿所做的。因为想给自增和自减运算符赋予各种意思,所 以它们需要就事论事考虑。
如在复数类中定义,则如下:
#include <iostream.h> class Complex { private:
float Real; float Imag; public: Complex(); Complex(float R,float I); Complex operator+(const Complex &c); void print(); };
参数和返回值
3) 所有赋值运算符改变左值。为了使得赋值结果用于 链式表达式(像A = B = C),应该能够返回一个刚刚 改变了的左值的引用。但这个引用应该是c o n s t还是n o n c o n s t呢?虽然我们是从左向右读表达式A = B = C, 但编译器是从右向左分析这个表达式,所以并非一定 要返回一个n o n c o n s t值来支持链式赋值。然而人们 有时希望能够对刚刚赋值的对象进行运算,例如 (A = B). f o o( ),这是B赋值给A后调用foo( )。因 此所有赋值运算符的返回值对于左值应该是n o n c o n s t引用。
不能改变原有运算符的优先级
不能改变原有运算符的结合性
– 右结合,如[]
– 左结合,如+
不能改变预定义类型的操作方式
可以重载的运算符
一元运算符 + - ~ ! & ++ --
class B { public: returnType operator@(); }
可以重载的运算符
自增和自减 重载的++和--号运算符出现了两难选择的局面,这是 因为希望根据它们出现在它们作用的
– 运算符操作可以看作是全局的,也可以看作是属于参与运算 的某个数据类型的
– 不能对预定义类型的运算进行重载 – 运算符重载也是通过静态联编实现的
重载的好处
参与加法运算的可以是整数、浮点数,也可以是字符, 我们是否可以使实数(即小数)、复数、字符串等自 定义类型也进行加法运算呢?
对于不同类型的加法运算,编译系统需要根据参与加 法的操作数类型的不同来判断。
对象前面(前缀)还是后面(后缀)来调用不同的函 数。解决是很简单的,但一些人在开始时却发现它们 容易令人混淆。例如当编译器看到++a(先自增)时, 它就调用operator++(a);但当编译器看到a++时,它就调 用operator++(a,int)。即编译器通过调用不同的函数区 别这两种形式。
源自文库
主函数
main() {
Complex c1(5.0,10.0),c2(8.0,-5.0),c3; c3=c1.Add(c2); c1.print(); c2.print(); c3.print(); }
改进
根据习惯,我们可以需要采用c3=c1+c2的形式来取代 c3=c1.Add(c2);
刚才实现的时候出类可以在复数类中定义Add方法以外, 还可以定义一个全局函数
Complex::Complex() {
Real=0; Imag=0; } Complex::Complex(float R,float I) { Real=R; Imag=I; } inline Complex Complex::Add(const Complex &c) { return Complex(Real+c.Real,Imag+c.Imag ); } void Complex::print() { cout<<Real<<"+"<<Imag<<"i"<<endl; }
第十五讲 重载
函数重载,相同作用域内,若干个参数特征不同的函 数使用相同的函数名。
– 参数不能存在二义性 – 不能通过运算符重载 – 参数的类型和个数来区分,不能通过参数名来区分
– 函数重载是通过静态联编实现的,编译时将函数的参数类型 按照顺序编如函数名称
运算符重载,相同的运算符可以施加于不同类型(包 括自定义 的类)的操作数上。
参数和返回值
2) 应该选择的返回值取决于运算符所期望的类型。 (可以对参数和返回值做任何想做的事)如果运算符 的效果是产生一个新值,将需要产生一个作为返回值 的新对象。例如,i n t e g e r : : o p e r a t o r +必须生成 一个操作数之和的i n t e g e r对象。这个对象作为一个c o n s t通过传值方式返回,所以作为一个左值结果不会 被改变。
参数和返回值
1. 按c o n s t通过传值方式返回
按c o n s t通过传值方式返回,开始看起来有些微妙, 所以值得多加解释。我们来考虑二元运算符+号。假设 在一个表达式像f ( A + B )中使用它,A + B的结果变为 一个临时对象,这个对象用于f( ) 调用。因为它是临时 的,自动被定为c o n s t,所以无论使返回值为c o n s t 还是不这样做都没有影响。 然而,也可能发送一个消息给A + B的返回值而不 是仅传递给一个函数。例如,可以写表达式(A+B).g( ), 这里g( )是i n t e g e r的成员函数。通过设返回值为c o n s t,规定了对于返回值只有c o n s t成员函数才可以被 调用。用c o n s t是恰当的,这是因为这样可以防止在 很可能丢失的对象中存贮有价值的信息。
String类的copy方法 不定义的话,有一个缺省的赋值运算符 缺省的行为是逐域拷贝 引起的指针悬挂问题 指针悬挂问题的解决,重载赋值运算符 赋值运算符只能使用成员函数重载,不能使用全局友
元函数 operator =
重载运算符的规则
下列运算符只能使用成员函数重载
= -> ()
[]
下列运算符只能使用友元函数重载
参数和返回值
4) 对于逻辑运算符,人们希望至少得到一个i n t返回值, 最好是b o o l返回值。(在大多数编译器支持C + +内 置b o o l类型之前开发的库函数使用i n t或t y p e d e f等 价物)
参数和返回值
5) 因为有前缀和后缀版本,所以自增和自减运算符出现了两难局 面。两个版本都改变对象,所以不能把这个对象看作一个c o n s t。 因此,前缀版本返回这个对象被改变后的值。这样,用前缀版本 我们只需返回* t h i s作为一个引用。因为后缀版本返回改变之前 的值,所以被迫创建一个代表这个值的单个对象并返回它。因此, 如果想保持我们的本意,对于后缀必须通过传值方式返回。(注 意,我们经常会发现自增和自减运算返回一个int 值或b o o l值, 例如用来指示是否有一个循环子( i t e r a t o r )在表的结尾)。现 在问题是:这些应该按c o n s t被返回还是按n o n c o n s t被返
<< >>
如果运算符的操作需要修改当前对象的状态,最好使 用成员函数重载(如+=,-=,*=,/=,++。--等)
如果一元操作符的操作数或者二元操作符的左操作数 是预定义类型,则必须使用友元函数重载
在成员函数版中,如果编译器看到++b,它就产生一个 对B::operator++()的调用;如果编译器看到b++,它就 产生一个对B::operator++(int)的调用。
可以重载的运算符
二元运算符 + - * / & % ^ | << >> += -= *= /= %= ^= &= |= == != > >= < <= && ||
参数和返回值
2. 返回效率 当为通过传值方式返回而创建一个新对象时,要注意使用的形式。
例如用运算符+号:
return integer (left.i + right.i) ; 一开始看起来像是一个“对一个构造函数的调用”,但其实并非
如此。这是临时对象语法,它是这样陈述的:“创建一个临时对 象并返回它”。因为这个原因,我们可能认为如果创建一个命名 的本地对象并返回它结果将会是一样的。其实不然。如果这样表 示,将发生三件事。 首先, t m p对象被创建,与此同时它的构造函数被调用。然后, 拷贝构造函数把t m p拷贝到返回值外部存储单元里。最后,当t m p在作用域的结尾时调用析构函数。