第9章 重载.

Slides:



Advertisements
Similar presentations
移动应用软件开发技术 第二讲:C++编程基础
Advertisements

Tool Command Language --11级ACM班 金天行.
第九讲 类与对象 (I)面向对象基础.
7.2 访问控制 —— 公有继承 公有继承练习 //Point.h #ifndef _POINT_H #define _POINT_H class Point { //基类Point类的定义 public: //公有函数成员 void initPoint(float x = 0, float.
第14章 c++中的代码重用.
C++中的声音处理 在传统Turbo C环境中,如果想用C语言控制电脑发声,可以用Sound函数。在VC6.6环境中如果想控制电脑发声则采用Beep函数。原型为: Beep(频率,持续时间) , 单位毫秒 暂停程序执行使用Sleep函数 Sleep(持续时间), 单位毫秒 引用这两个函数时,必须包含头文件
第10讲 Java面向对象编程基础(4) 教学目标 主要内容.
第九章 字符串.
Using C++ The Weird Way Something about c++11 & OOP tricks
EBNF与操作语义 请用扩展的 BNF 描述 javascript语言里语句的结构;并用操作语义的方法描述对应的语义规则
Object-Oriented Programming in C++ 第四章 运算符重载
4.3函数 4.3.1函数的概念及定义 1、函数的概念: 可以被其它程序调用具有 特定功能的一段相对独立的 程序(模块),称函数。
4.1 概述 4.2 类与对象的实现 4.3 对象的初始化和析构 4.4 类的包含 4.5 类模板
西安交通大学 计算机教学实验中心 大学C++程序设计教程 西安交通大学 计算机教学实验中心
管理信息结构SMI.
走进编程 程序的顺序结构(二).
辅导课程六.
第八章 类与对象 本章要求: 掌握类的含义与定义格式。 掌握类的无参构造函数、带参构造函数、拷贝构造函数和赋值重载函数的定义格式及作用。
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
类类型 C++支持的内置类型和操作,如 int i=10; i=i%6; i=i+4;
第九章 多态性 丘志杰 电子科技大学 计算机学院 软件学院.
第二章 Java语言基础.
第十一讲 运算符重载 与类型转换.
用event class 从input的root文件中,由DmpDataBuffer::ReadObject读取数据的问题
第七章 操作符重载 胡昊 南京大学计算机系软件所.
第一章 函数与极限.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C语言程序设计 主讲教师:陆幼利.
EBNF与操作语义 请用扩展的 BNF 描述 javascript语言里语句的结构;并用操作语义的方法描述对应的语义规则
简单介绍 用C++实现简单的模板数据结构 ArrayList(数组, 类似std::vector)
$9 泛型基础.
内容 构造函数 析构函数 对象数组 对象指针 this指针 const数据保护. 类和对象的使用 潘荣江 山东大学.
第14讲 运算符重载 运算符重载的概念、方法和规则 运算符重载作为类的成员函数 运算符重载作为类的友元函数 特殊运算符的重载 类型转换函数.
C#面向对象程序设计 $6 深入理解类.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
第13讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
第10讲 构造函数和析构函数 构造函数 析构函数 This 指针.
第二章 Java基本语法 讲师:复凡.
C语言程序设计 第一章 数据类型, 运算符与表达式 第二章 顺序程序设计 第三章 选择结构程序设计 第四章 循环控制 第五章 数组.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
第4章 Excel电子表格制作软件 4.4 函数(一).
本节内容 类成员的访问控制 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++大学基础教程 第10章 运算符重载 北京科技大学 2019/5/7 北京科技大学.
第九节 赋值运算符和赋值表达式.
iSIGHT 基本培训 使用 Excel的栅栏问题
3.16 枚举算法及其程序实现 ——数组的作用.
C++程序设计基础 主讲人:谢昕 华东交通大学信息工程学院 第十~十二讲 多态性和虚函数 2005年春季学期.
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
学习任务三 偏导数 结合一元函数的导数学习二元函数的偏导数是非常有用的. 要求了解二元函数的偏导数的定义, 掌握二元函数偏导数的计算.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
第 9 章 建構函式與解構函式.
第7章 模板 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
本节内容 C语言的汇编表示 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第二章 Java基本语法 讲师:复凡.
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
C++与数据结构简明教程 第五章 类和对象.
Class 2005/05/25.
基于列存储的RDF数据管理 朱敏
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
第三章 高级函数特性.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本节内容 this指针 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
§2 自由代数 定义19.7:设X是集合,G是一个T-代数,为X到G的函数,若对每个T-代数A和X到A的函数,都存在唯一的G到A的同态映射,使得=,则称G(更严格的说是(G,))是生成集X上的自由T-代数。X中的元素称为生成元。 A变, 变 变, 也变 对给定的 和A,是唯一的.
Presentation transcript:

第9章 重载

1.重载的概念 (1) int abs(int x){ return (x>=0 ? x: -x ); } C++语言提供了一种机制,使得对于不同的输入参数,使用统一的函数名(如例9.1的abs())定义各种不同输入参数时的实现版本,由编译程序根据函数参数的不同而选择使用相应的实现代码。这就叫做函数的重载。 1.重载的概念 (1) 例9.1 求绝对值函数abs( )的重载 int abs(int x){ return (x>=0 ? x: -x ); } double abs(double x){ lone abs(lone x){ 表面上,似乎这三个函数的代码相同。实际上编译时产生的代码是不同的,程序运行时在内存中的存放位置也是不同的。 另外,C++中由于引入了大量扩展数据类型,特别是通过类的定义引入的各种外部类型,希望能够用比较简洁的、统一的形式来书写这些自定义类型的表达式。比如,对于一个复数类Complex的对象C1和C2,若想不用Complex_ADD(C1,C2), 而用C1+C2的形式来书写这两个对象的求和式,这就需要使“+”运算符重载复数Complex类求和运算。

重载的概念 (2) 重载的概念 (2) 重载分为函数重载和运算符重载。 函数重载由参数形式区分同名函数的不同版本。 构造函数重载是函数重载的主要应用之一,它为对象创建提供了灵活方便的内部状态初始化手段。 运算符重载使自定义抽象数据类型能像基本数据类型一样用系统已定义的运算符来书写表达式,这就使得对象的使用像内部变量一样自然和方便,因而带来了更加自然的系统可扩展性。 重载的概念 (2)

2.函数重载 函数重载就是用同一个标识符命名语义相近的一组函数。函数重载作为一种符号抽象工具。程序员可以利用这个工具定义一个抽象函数,其函数名描述一组函数功能的共同点,以减少由于大量符号定义带来的记忆负担。值得注意的是,C++语言并没有提供重载函数之间的任何约束,如果用相同的名字定义互不相关的函数,只会产生增加程序复杂性的负面效果。

2.1 函数重载的区分机制 重载函数靠参数不同而区分不同调用版本。决定参数是否相同有参数个数、参数顺序、参数类型等要素。 2.1 函数重载的区分机制 重载函数靠参数不同而区分不同调用版本。决定参数是否相同有参数个数、参数顺序、参数类型等要素。 如length ()函数的重载说明: int length (int x); int length (int x, int y); double length (double x1, double x2, double y1, double y2); 这三个函数,由于它们的函数名相同,参数不同而成为重载函数,与它们返回值类型相同并无关系。 1.用参数区分函数重载的不同调用版本 int sum (int x, int y);和 double sum (int a, int b); 这两个函数不是重载函数,因为它们参数个数和类型都相同,尽管它们的返回值类型不同、形式参数名也不同。编译程序认为它们是对一个函数的重新定义(override) 另外不同的参数传递方式也无法区分重载函数,如: int func1(int value); int func1(int &value); 不能作为重载函数。

例9.2 试分析下面的程序及调用结果,并说明get_area()函数是否属于函数重载。 void main() { point p1(3,7); circle c1(2,3,4),c2(5,1,4); cout<<c1.get_area()<<endl; cout<<p1.get_area()<<endl; cout<<c2.get_area()<<endl; cout<<c2.point::get_area()<<endl; } #include<iostream.h> class point{ double x,y; public: point(double a, double b){ x=a; y=b; } double get_area() { return 0; } };  class circle: public point{ int r; circle(int rad, int a, int b):point(a,b) { r=rad; } {return 3.1416*r*r;} 运行结果: 12.5664 78.54 本例实际上也是派生类中同名函数的重定义。可以简单地认为,加上对象名或类名前缀后,c1.get_area()和p1.get_area()不再被看作同名函数。

2.const关键字可以被用于参与对重载函数的区分 常成员函数: 在一个类中使用const关键字说明的成员函数,称为常成员函数。常成员函数说明格式如下: 函数类型 函数名(<参数表>)const ; 其中,const是加在函数说明后面的类型修饰符,它是函数说明的一个组成部分,因此,在函数的实现部分也要带const关键字。常成员函数不更新对象的数据成员,也不能调用该类中其它没有用const修饰的成员函数。

例9.3 常成员函数举例。 class M{ int m1,m2; public: M(int x,int y){ m1 = x ; m2 = y ;} void print ( ) { cout<<m1<<”,”<<m2<<endl; } void print ( ) const ; }; void M::print ( ) const { cout<<m1+m2<<endl; } void main ( ){ M a (5,4); a.print ( ) ; const M b(8,7); b.print ( ) ; } 程序运行结果: 5,4 15 在类M的成员中,说明了两个同名的成员函数print(),但它们的类型不同,如下所示: void print ( ) ; void print ( ) const ; 这是重载的成员函数。表达式:a.print()将调用成员函数:void print ( ); 而表达式:b.print ( ) 将调用成员函数:void print ( ) const ;

3.函数重载的二义性 函数缺省参数是函数重载的一种特殊形式。但带缺省参数的函数重载时有可能发生二义性错误。 例:#include(iostream.h) #include(math.h) int main( ){ int function1(int x, int y){ return sqrl(x*x+y*y); } int function1(int x=3, int y=5){return x*y;} 则调用:function1(1,2) 、function1(7)和function1( )都能分别得到正确的结果。 但是若定义: int function2(int x){return x*x;} int function2(int x, int y=5){return x*y;} 调用 funtion2(3),将产生二义性,编译时出错

2..2 构造函数重载 构造函数重载是C++应用函数重载最多的地方。C++允许为一个类定义多个构造函数。除了保证类数据成员有效性以外,多构造函数为用不同形式初始化对象带来了极大的方便。

例9.4 时间类的构造函数重载。1 #include<iostream.h> #include<stdlib.h> class Time{ int hh, mm, ss; public: Time(); Time(int hour, int inute, nt second); Time(int second); Time(char *str); void print() { cout<<hh<<":"<<mm<<":"<<ss<<"\n"; } };

例9.4 时间类的构造函数重载。2 Time::Time(){ hh=0; mm=0; ss=0;} Time::Time(int hour, int minute, int second) { if (hour<0||minute<0||second<0) { hh=0; mm=0; ss=0; cout<<"Illegal initical value! Time variant don't allowed under zero."<<endl; } else{ if (second>59) { minute=minute+ second/60; second= second%60; } if (minute >59) { hour = hour + minute /60; minute = minute %60; } if (hour >23) hour = hour%24; hh=hour; mm=minute; ss=second; }

例9.4 时间类的构造函数重载。3 Time::Time(int second) { hh=0;mm=0; if (second<0) { ss=0; cout<<"Illegal input value! Time variant don't allowed under zero. ”; cout<<endl; } else{ if (second>59) mm=second/60; ss=second%60; if (mm>59) hh=mm/60; mm=mm%60; if (hh>23) hh=hh%24; }

例9.4 时间类的构造函数重载。4 Time::Time(char *string) { char *str=new char[3]; str[0]=string[0]; str[1]=string[1]; str[2]='\0'; hh=atoi(str); str[0]=string[3]; str[1]=string[4]; mm=atoi(str); str[0]=string[6]; str[1]=string[7]; ss=atoi(str); delete str; } void main(){ Time t1,t2(100,100,100),t3(3690),t4("18:56:34"); cout<<"t1 : ";t1.print(); cout<<"t2 : ";t2.print(); cout<<"t3 : ";t3.print(); cout<<"t4 : ";t4.print(); } 程序运行结果: t1 : 0:0:0 t2 : 5:41:40 t3 : 1:1:30 t4 : 18:56:34 在本例中构造函数重载了四次。

拷贝初始化构造函数是一种特殊的构造函数,具有一般构造函数的所有特性,其形参是本类对象的引用,功能是用一个已知的对象去初始化一个被创建的同类对象。典型情况是,将参数代表的对象的数据成员的值拷贝给正在创建的另一个同类的对象。 用户可以根据自己实际问题的需要定义特定的拷贝初始化构造函数,以实现同类对象之间数据成员的传递。 C++规定每个类都必须有一个拷贝初始化构造函数,如果用户没有为一个类定义拷贝初始化构造函数,则系统就会为该类自动产生一个缺省拷贝初始化构造函数。缺省拷贝初始化构造函数将一个已定义对象的所有数据成员拷贝给新对象。 当被拷贝对象的数据成员中含有指向其它类对象的指针时,这种拷贝方式形成的对象在释放时有产生内存垃圾及指针别名等问题,此时就应该由用户定义拷贝构造函数来处理。 拷贝初始化构造函数的定义格式如下: 类名::构造函数名(const 类名 &引用名) { … // 函数体 } 拷贝构造函数名的参数是一个本类对象的常引用。所谓常引用就是用const定义的引用。const是一个类型修饰符,被它修饰的对象是一个不能被更新的常量。作为常引用的实参对象的成员,在函数调用过程中不会无意中被修改。 2.3 拷贝初始化构造函数

例9.5 拷贝初始化构造函数的定义和调用。1 # include<iostream.h> class Point{ double x , y ; public: Point (double a , double b) {x = a ; y = b ; cout<< “Constructor called . “<<endl ;} // 普通构造函数 Point (const Point &p); // 拷贝初始化构造函数 ~Point ( ) {cout<< “The destructer is called . “<<endl ;} // 析构函数 double GetX ( ) {return x ;} double GetY ( ) {return y ;} void Print ( ) {cout<<”The point is : (“<< x <<”, ”<< y<<”)” <<endl;} };

例9.5 拷贝初始化构造函数的定义和调用。 2 Point :: Point (const Point &p ) // 拷贝初始化构造函数的定义 { x = p.x ; y = p.y ; cout<<”Copy initialization Constructor called .”<<endl ; } void main ( ) { Point p1(3.14 , 4.2) ; // 定义对象point1,并指定初始化参数为:3.14 , 4.2 Point p2(p1); // 定义对象p2,指定初始化参数为对象p1 p1.Print( ) ; p2.Print( ); 程序输出: Constructor called. Copy initialization Constructor called . The point is : (3.14, 4.2) The destructer is called .

拷贝初始化构造函数在以下三种情况下都会被调用: 当用类的一个对象去初始化该类的另一个对象时。如例9.5中:用对象p1去初始化对象p2。 (2) 如果函数的形参是类的对象,调用函数进行形参和实参结合时。例如: void fun( Point p) { cout<<p.GetX( )<<”,”<<p.GetY( )<<endl ; } void main( ) { Point px (2.8, 3.5); fun (px ) ; } 在调用fun()函数时,对象px是实参,要用它来初始化被调用函数的形参p,这时需要调用拷贝构造函数,完成实参对象与形参对象进行数据传递的工作 (3) 当对象作为函数的返回值时。例如: Point g( ) { Point A(2.8, 3.5); return A ; } 执行返回语句return A;时,系统将用对象A来初始化一个匿名对象,这时需要调用拷贝构造函数。 }

例9.6点类及拷贝初始化构造函数测试程序1 # include<iostream.h> class Point{ double x,y; public: Point (double a , double b) {x = a ; y = b ; cout<< "Constructor called."<<endl ;} Point ( const Point &p); // 拷贝初始化构造函数 ~Point ( ) {cout<< "The destructor is called. "<<endl ;} double GetX( ) {return x;} double GetY( ) {return y;} void Print ( ) {cout<<"The point is : ("<< x <<","<< y<<")" <<endl;} };

例9.6点类及拷贝初始化构造函数测试程序2 Point :: Point ( const Point &p ) // 拷贝初始化构造函数的定义 { x = p.x ; y = p.y ; cout<<"Copy initialization Constructor called."<<endl; } Point fun( Point p) { double a , b ; a = p.GetX( ) + 5 ; b = p.GetY( ) + 10 ; Point R(a,b); return R; } void main ( ) { Point p1(3.14 , 4.28) , p2(0,0); Point p3(p1); // 定义对象p3, //并指定初始化参数为对象p1 p2 = fun(p3); p1.Print( ) ; p2.Print( ); p3.Print( ); } 程序执行结果如下: Constructor called. Copy-initialization Constructor called. The destructor is called. The point is : (3.14,4.28) The point is : (8.14,14.28)

3 运算符重载 3.1 运算符重载的概念 一个数据类型的定义包括一个值集和一个作用于该值集的操作集。C++在提供内部基本数据类型的同时提供了许多预定义的运算符,用于各种表达式中。如算术运算符、逻辑运算符、比较运算符、取值、取地址、括号等等。 基本数据类型上的运算符,其语义等价为一个函数,如声明x, y为int或double等基本类型变量,那么表达式x+y可以理解为 函数add(x, y) 或+(x, y) ,显然,使用前一种形式比使用后两种形式使表达式的书写更简洁、更符合日常习惯。 在一个类中定义的运算符成员函数称类成员运算符,在类之外定义的运算符函数通常使用类的友元函数的形式,称为友元运算符。

3.2类成员运算符重载 运算符重载的基本语法形式: 类型 类名::operator 运算符(<参数表>) //运算符函数头 { ……; //运算符函数体 } 若将定义头中“operator 运算符”部分用普通函数名替代,则与普通函数定义头形式完全相同。operator 是专门用于定义重载运算符的系统保留字。在下面的例子中还可以看到,重载过的运算符还可以再重载。

类成员运算符重载的实现 重载一元运算符时参数表为空,当前对象作为运算符的单操作数(在函数体中用this指针访问); 重载二元运算符时参数表中有一个操作数,当前对象作为运算符的左操作数,参数表中的操作数为运算符的右操作数。注意左、右操作数的位置理论上是不能随意互换的,比如,对于减法,操作数位置直接关系到结果的正确性。 由于函数add(x,y){x=x+y;}改变操作数的值,而在表达式x+y求值过程中改变x的值不符合日常习惯,通常的作法是在运算符重载函数中声明一个临时变量(对象)来返回运算结果。

例9.7矢量类加法、减法及反向运算符重载 1 #include<iostream.h> class vector{ double x,y; public: vector(double vx, double vy) {x=vx; y=vy;} vector() {x=0; y=0;} vector operator +(vector v1 ); //加法(二元)运算符重载 vector operator -(vector v1 ); //减法(二元)运算符重载 vector operator -( ); //反向(一元)运算符重载 void print( ){ cout<<x<<" "<<y<<"\n";} };

例9.7矢量类加法、减法及反向运算符重载 2 vector vector:: operator + (vector v1 ) v.x=x+v1.x; v.y=y+v1.y; return v; } vector vector:: operator - (vector v1 ) v.x=x-v1.x; v.y=y-v1.y; vector vector:: operator - ( ) v.x= -x; v.y= -y; void main() { vector v1(4.5,-7.8), v2(-1.5,1.2); v1.print(); v2.print(); v1=v1+v2; v2= -v1; } 运行后输出: 4.5 -7.8 -1.5 1.2 3 -6.6 -3 6.6

在C++语言中,运算符重载更多使用友元函数实现。这主要基于以下两个原因:第一,C++语言是混合型的面向对象语言,用友元提供了一种有限制地打破类的封装的机制,其目的是增加编程的灵活性和提高函数的访问效率。第二,用成员运算符重载二元运算时,两个操作数必须同为本类型操作数。如果两个操作数类型不同时,特别是左操作数不属于本类型,因而不能隐含使用this指针时,无法实现重载。友元运算符重载提供了解决这个问题的一个手段。 友元运算符重载语法形式: 声明:friend 返回值类型 operator 运算符(<参数表>);//在某类中声明的原型 定义:返回值类型 operator 运算符(<参数表>) //运算符函数头 { ……; //运算符函数体 } 3.3 友元运算符重载 说明:因为友元函数不是某个类的成员,在友元函数体中不能出现this保留字,也没有隐含的操作数。所以一元友元运算符参数表中必须显式声明一个参数,二元友元运算符参数表中必须显式声明两个参数。

将例9.7中的成员运算符重载改造成友元运算符重载 1 #include<iostream.h> class vector{ double x,y; public: vector(double vx, double vy) {x=vx; y=vy;} vector() {x=0; y=0;} friend vector operator +(vector v1, vector v2); //加法(二元)运算符重载 friend vector operator -(vector v1, vector v2); //减法(二元)运算符重载 friend vector operator -(vector v1); //反向(一元)运算符重载 void print( ){ cout<<x<<" "<<y<<"\n";} };

例9.7矢量类加法、减法及反向运算符重载 2 vector operator + (vector v1, vector v2 ) v.x=v1.x+v2.x; v.y=v1.y+v2.y; return v; } vector operator - (vector v1, vector v2 ) v.x=v1.x-v2.x; v.y=v1.y-v2.y; vector operator - (vector v1) v.x= -v1.x; v.y= -v1.y;

3.4 运算符重载举例 1.++运算符的重载 ++运算符有两种使用方式:前缀方式++x和后缀方式x++。作为表达式++x 返回 x 加 1 以后的值,x++ 返回 x 加 1 以前的值。++运算符重载时用不同的参数形式区分这两种形式。前缀方式用 oprerate ++( )定义,是一个普通一元成员运算符重载,后缀方式用 oprerate ++( int )定义,用的是二元成员运算符重载的形式。

例9.9 ++运算符重载 void main() #include<iostream.h> 例9.9 ++运算符重载 #include<iostream.h> class integer { long i; public: integer(long a=0){ i=a; } integer operator ++(); integer operator ++(int); void print(){ cout<<i<<endl; } }; integer integer::operator ++() { i++; return *this; } integer integer::operator ++(int) { integer j; j.i=i++; return j; } void main() { integer x(100000),y(200000),z; z=++x; x.print(); z.print(); z=y++; y.print(); } 程序运行结果: 100001 200001 200000

2.下标运算符[ ]的重载 下标运算符operator[ ]被看作是一个二元运算符,必须重载为某一个类的成员函数。其一般使用格式为: 对象名[<整数表达式>] 其中对象名为左操作数,<整数表达式>为右操作数。 若obj是类class1的对象,且class1中重载了函数调用运算符operator[ ],则函数调用 obj[n]; 被解释为:obj.operator[ ] (n); 下标运算符重载用于动态构造对象数组。在对象数组的创建及访问时必须保证数组不越界。为了防止数组越界,应该事先作出预防性安排。在C++中,这是通过异常处理技术实现的。

例 下标运算符[ ]的重载 class String{ char *contents; int length; public: 例 下标运算符[ ]的重载 class String{ char *contents; int length; public: String(char *str) { length=strlen(str); contents=new char[length+1]; strcpy(contents,str); } char& operator [](int j) { if(j>=0 && j<length) return contents[j]; void print(String &s) { int j; for(j=0;j< length;j++) cout<<s[j]<<" "; cout<<endl; };

3.函数调用运算符()的重载 函数调用运算符operator ()被看作是一个二元运算符,必须重载为某一个类的成员函数。其一般使用格式为: 对象名(<参数表>) 其中对象名为左操作数,<参数表>为右操作数。 若obj是类class1的对象,且class1中重载了函数调用运算符operator (),则函数调用 obj(arg1,arg2,…); 被解释为:obj.operator () (arg1,arg2,…); 对于比较复杂的类中频繁使用的复杂算法使用重载函数调用运算符能够有效地简化程序行的书写。

例9.10 通过重载函数调用运算符求解一元二次方程。 #include<iostream.h> #include<math.h> class ax2bxc{ //定义一元二次方程类 double a,b,c; public: ax2bxc(){a=1;b=1;c=1;} void ax2bxc::operator ()(double A,double B,double C); // 重载函数调用运算符求解一元二次方程 }; 例9.10 通过重载函数调用运算符求解一元二次方程。 void main() { ax2bxc solution; solution (4,4,1); //重载运算符“()”的调用, ///可解释为solution.operator(4,4,1) solution (2,-3,-1); solution (1,1,1); solution (0,1,2); solution (2,3,1); } 程序运行结果: The solution of equation (4)X^2 +(4)X +(1)=0 : x1=x2=-0.5 The solution of equation (2)X^2 +(-3)X +(-1)=0 : x1=1.78078 x2=-0.280776 The solution of equation (1)X^2 +(1)X +(1)=0 : No solutionf for this equation! Error!!! The value of (a) can not be 0!   The solution of equation (2)X^2 +(3)X +(1)=0 : x1=-0.5 x2=-1 void ax2bxc::operator ()(double A,double B,double C) { a=A;b=B;c=C; if(a==0) cout<<"Error!!! The value of (a) can not be 0!"<<endl<<endl; else{ cout<<"The solution of equation (“ <<a<<")X^2 +("<<b<<")X +("<<c<<")=0 : "<<endl; if ((b*b-4*a*c)<0) cout<<" No solutionf for this equation!"<<endl; else if ((b*b-4*a*c)==0) cout<<"x1=x2="<<(-b/(2*a))<<endl; else { cout<<"x1="<<(-b+sqrt(b*b-4*a*c))/(2*a)<<endl; cout<<"x2="<<(-b-sqrt(b*b-4*a*c))/(2*a)<<endl;} }

4.赋值运算符重载 C++为每一个生成了一个缺省的赋值运算符。其功能是将赋值号右边的对象的数据成员逐个拷贝到赋值号右边的类对象中。缺省的赋值运算符采用的这种复制对象的方法称为浅复制,在对象成员含有指针或数组的情况下,浅复制会出问题。此时就应该为类编写用户定义的赋值运算符重载函数,实现正确的(深)复制。

缺省赋值运算与对象浅复制可能发生的问题 #include<string.h> #include<iostream.h> class str{ char *pc; public: str(const char* s) { int n=strlen(s); pc=new char[n+1]; strcpy(pc,s); } void print(){cout<<pc<<endl;} }; void main() { str a1("first"); str a2("second"); a3.print(); a1=a2; a1.print(); 在这个例子中,语句a1=a2;调用缺省赋值运算操作,将对象a2的数据成员逐一复制给a1的各数据成员。当a2的字符串指针变量pc的值赋给a1的指针pc之后,a1的pc将与a2的pc指向同一个字符串,致使a1原来指向的字符串变成一块无法访问和释放的内存垃圾。同时,在释放对象a1和a2时,a2原来指向的字符串将被释放两次。这些问题都需要通过用户定义赋值运算符重载的深复制策略来解决。

用户定义的深复制策略赋值运算符重载 1 #include<string.h> #include<iostream.h> class str{ char *pc; public: str(const char* s) { int n=strlen(s); pc=new char[n+1]; strcpy(pc,s); cout<<”Constractor called!”<<endl; } void str::operator = ( const str& a); //赋值运算符重载定义。 void print(){cout<<pc<<endl;} ~str(){cout<<"Destrctor called!"<<endl;} };

用户定义的深复制策略赋值运算符重载 2 void str::operator = ( const str& a) { 说明:(1)深复制方法是将原来的指针释放后重新分配,然后再进行数据复制。避免了内存垃圾和同一内存块重复释放的问题。 (2)赋值运算目的就是改变左操作数的值,因此函数体中不需要引入临时对象。 注意:对象初始化时的赋值和已创建对象之间的赋值虽然都涉及对象的复制,但它们是两个不同的操作。对象初始化首先是为新对象分配存储空间,然后执行对象复制。 如:Point p1, p2(3.14, 4.06); Point p3=p2; //调用拷贝构造初始化函数 p1=p2; //调用赋值运算符重载 所以,对于应该采用深复制的对象,用户应该同时为它的类定义拷贝初始化构造函数。 void str::operator = ( const str& a) { if (this==&a) return; delete pc; //释放原来的字符串 int n=strlen(a.pc); pc=new char[n]; //按新长度为字符串分配内存 strcpy( pc, a.pc); //字符串拷贝 } void main() str a1("first"); str a2("second"); a1.print(); a1=a2; 程序输出: Constrct called! first second Destrctor called!

5.复数类中的运算符重载 例9.11一个完整复数类定义及测试程序 1 #include<iostream.h> class Complex { double real, image; public: Complex(double r=0, double i=0); Complex(const Complex& other); void print(); Complex operator + ( const Complex& other); Complex operator - ( const Complex& other); //减法, 二元 Complex operator - ( ); //求负,一元 Complex operator = ( const Complex& other); //赋值, 二元 };

例9.11一个完整复数类定义及测试程序 2 Complex::Complex(double r, double i) { real=r; image=i; } Complex::Complex(const Complex& other) { //此拷贝构造函数与缺省拷贝函数相同,可以省略。 real=other.real; image=other.image; void Complex::print() { cout<<real; if (image>0) cout<<"+"<<image<<"i"; else if (image<0) cout<<image<<"i"; cout<<"\n"; Complex Complex:: operator + (const Complex& other) { Complex temp; temp.real=real+other.real; //相当于temp.real=this->real+other.real; temp.image= image +other.image; return temp;

例9.11一个完整复数类定义及测试程序 3 Complex Complex:: operator - (const Complex& other) { Complex temp; temp.real=real-other.real; temp.image= image -other.image; return temp; } Complex Complex:: operator - ( ) temp.real= - real; temp.image= - image; Complex Complex:: operator = (const Complex& other) { //此赋值运算符的重载与缺省赋值运算相同,可以省略。 real= other.real; image= other.image; return *this; void main() { Complex c1(1,2); Complex c2(2); Complex c3(c1); c3.print(); c1=c1+c2+c3; c2= - c3; c3=c2-c1; } 程序输出: 1+2i -5-6i