第四章 类与对象

Post on 19-Jan-2016

152 views 2 download

description

第四章 类与对象. 封装( Encapsulation ) 是面向对象程序设计最基本的特性,也就是把数据(属性)和函数(操作)合成一个整体,这是用类与对象实现的。 本章重点: 1. 引入 C++ 的类( class )和对象( object )的概念,建立 “ 函数也可以是数据类型的成员 ” 的思想。 2. 运算符重载。. 第四章 类与对象. 4.1 类与对象. 4.6 友元. 4.2 从面向过程到面向对象. 4.7 静态成员. 4.3 构造函数和析构函数. 4.8 结构. 4.4 引用 与复制构造函数. - PowerPoint PPT Presentation

Transcript of 第四章 类与对象

第四章 类与对象

封装( Encapsulation )是面向对象程序设计最基本的特性,也就是把数据(属性)和函数(操作)合成一个整体,这是用类与对象实现的。 本章重点:1. 引入 C++ 的类( class )和对象( object )的概念,建立“函数也可以是数据类型的成员”的思想。2.运算符重载。

第四章 类与对象

4.1 类与对象

4.5 运算符的重载

4.4 引用与复制构造函数

4.2 从面向过程到面向对象

4.9 名字空间域和类域(选读)

4.10 面向对象的程序设计和 Windows 编程

4.8 结构

4.7 静态成员

4.6 友元

4.3 构造函数和析构函数

4.1 类与对象

4.1.3 对象的创建与使用

4.1.1 C++ 类的定义

4.1.2 成员函数的定义

4.1.1 C++ 类的定义 类的引入: 类是一种数据类型。

描述客观事物必须用不同的数据类型来描述不同的方面。如商品: 商品名称(用字符串描述),该商品数量(用整型数描述),该商品单价(用浮点数描述),该商品总价(用浮点数描述)。这里用了属于三种不同数据类型的四个数据成员( data member )来描述一种商品。

4.1.1 C++ 类的定义类的表述:class CGoods{ public : char Name[21] ;

int Amount ; float Price ; float Total_value ;

} ; // 最后的分号不可少,这是一条说明语句关键字 class 是数据类型说明符,指出下面说明的是类。标识符 CGoods 是商品这个类的类型名。花括号中是构成类体的系列成员,关键字 public 是一种访问限定符。

4.1.1 C++ 类的定义访问限定符( access specifier ):public (公共的)说明的成员能从外部进行访问。private (私有的)和 protected (保护的)说明的成员不能从外部进行访问。 每种说明符可在类体中使用多次。 访问限定符的作用域是从该说明符出现开始到下一个说明符之前或类体结束之前结束。 如果在类体起始点无访问说明符,系统默认定义为私有( private )。 访问限定符 private (私有的)和 protected(保护的)体现了类具有封装性( Encapsulation )。

4.1.1 C++ 类的定义类的定义:class 类名 {《《 private :》 成员表 1; 》 《 public: 成员表 2; 》 《 protected: 成员表 3; 》}; // 注意:所有说明都以分号结束其中“ class 类名”称为类头( class head )。花括号中的部分称为类体( class body ),类体中定义了类成员表( class member list ) , 包含数据和函数。

4.1.1 C++ 类的定义成员函数 (member function) :class CGoods{private : char Name[21] ; int Amount ; float Price ; float Total_value ;public : void RegisterGoods(char[],int,float); // 输入数据 void CountTotal(void) ; // 计算商品总价值 void GetName(char[]) ; // 读取商品名 int GetAmount(void) ; // 读取商品数量 float GetPrice(void) ; // 读取商品单价 float GetTotal_value(void) ; }; // 读取商品总价值

4.1.1 C++ 类的定义封装:类把数据(事物的属性)和函数(事物的行为——操作)封装为一个整体。 接口: 通常数据成员被说明成私有的,函数成员被说明成公有的;从外部对数据成员进行操作 , 只能通过公有函数来完成 ,数据受到了良好的保护 , 不易受副作用的影响。公有函数集定义了类的接口( interface )。 成员函数可以直接使用类定义中的任一成员,可以处理数据成员,也可调用函数成员。注意: 类是一种数据类型,定义时系统不为类分配存储空间,所以不能对类的数据成员初始化。类中的任何数据成员也不能使用关键字 extern 、 auto 或 register 限定其存储类型。

4.1.2 成员函数的定义 函数定义:  通常在类定义中,成员函数仅作声明。函数定义通常在类的说明之后进行,其格式如下:返回值类型 类名 :: 函数名 ( 参数表 ) {……}// 函数体其中运算符“ ::” 称为作用域解析运算符 (scope resolution operator) ,它指出该函数是属于哪一个类的成员函数。

类 CGoods 的函数定义

定义对象: 对象是类的实例( instance )。定义一种数据类型只是告诉编译系统该数据类型的构造,并没有预定内存。类只是一个样板,以此样板可以在内存中开辟出同样结构的实例——对象。格式如下:   CGoods Car ;这个定义创建了 CGoods 类的一个对象 Car ,同时为它分配了属于它自己的存储块,用来存放数据和对这些数据实施操作的成员函数(代码)。对象只在定义它的域中有效。

4.1.3 对象的创建与使用

对象存储 :

图 4.1 各对象完全独立地安排内存的方案

图 4.1 是系统为每一个对象分配了全套的内存。数据区安放成员数据,代码区安放成员函数。 注意:区别同一个类的各个不同的对象的属性是由数据成员决定的,不同对象的数据成员的内容是不一样的;而行为(操作)是用函数来描述的,这些操作的代码对所有对象都是一样的。

数据区

代码区

对象 1 对象2

数据区

代码区

对象n

...... 数据区

代码区

图 4.2 各对象的代码区共用的方案

数据区

对象 1

数据区对象2

数据区

对象n

...... 

公共代码区

图 4.2仅为每个对象分配一个数据区,代码区(放成员函数的区域)为各对象类共用。 图 4.1 对应的是在类说明中定义函数,而图 4.2 对应的是在类说明外部定义函数 。

4.1.3 对象的创建与使用内联函数:使用关键字 inline ,系统自动采用内联扩展方法实现,每个对象都有该函数一份独立的代码。如 RegisterGoods() 函数可定义为:inline void CGoods::RegisterGoods(char name[] , int amount , float price){

strcpy(Name,name) ; Amount=amount ; Price=price ; }则每个对象都有 RegisterGoods() 函数一份独立的代码。

注意: inline 只是一个建议,最后由编译器决定是否执行。对象的存储方式是物理的,这是由计算机来完成的,它并不影响类在逻辑上的封装性。

4.1.3 对象的创建与使用

对象使用规则: 只要在对象名后加点号(点操作符,成员访问运算符 (member access oprator) 之一),再加成员数据或成员函数名就可以了。但是这些成员必须是公有的成员,只有公有成员才能在对象的外面对它进行访问。

【例4.1】商品类对象应用实例

【例 4.1 】中对象 car 的 4 个数据成员全是私有的 , 如写:car.Name;car. Amount;car.Price;car.Total_value;是错误的,必须用对象 car 所带的公有函数进行访问。

4.2 从面向过程到面向对象 (阅读)结构化程序设计特点:采用的是“自顶向下,逐步细化( divide and conquer,stepwise refinement )”的思想。具体操作方法是模块化。模块是按功能来分的,所以也称功能块。在 C++ 中称为一个函数,一个函数解决一个问题,即实现一个功能或一个操作。

  在模块化的思想中已经出现了封装的概念,这个封装是把数据封装到模块中,即局部变量。这是很不彻底的,因为模块是功能的抽象,而数据则是具有其个性的,一但发生变化,抽象的功能模块就不再适用了。可维护性差成了制约结构化程序设计的瓶颈。 面向过程程序设计缺点的根源在于数据与数据处理分离。

4.2 从面向过程到面向对象(阅读)结构化程序设计弱点:

  当软件规模过大,采用结构化程序设计,其开发和维护就越来越难控制。其根本的原因就在于面向过程的结构化程序设计的方法与现实世界(包括主观世界和客观世界)往往都不一致,结构化程序设计的思想往往很难贯彻到底。

对象概念:对象的概念是面向对象技术的核心所在。面向对象技术中的对象就是现实世界中某个具体的物理实体在计算机逻辑中的映射和体现。

4.2 从面向过程到面向对象(阅读)

对象

计 算 机世 界

实体

抽象类别

现实世界客观世界

抽象

抽象

实例化

映射

主观世界

图 4.3 对象、实体与类

  现实世界中的实体可以抽象出类别的概念。对应于计算机世界就有一个类( class )的概念。面向对象是计算机世界模拟现实世界。图4.3 表达了计算机世界与现实世界之间的对应关系。

4.2 从面向过程到面向对象(阅读)

对象、类与消息: 面向对象程序设计模拟自然界认识和处理事物的方

法,将数据和对数据的操作方法放在一起,形成一个相对独立的整体——对象( object ),同类对象还可抽象出共性,形成类( class )。一个类中的数据通常只能通过本类提供的方法进行处理,这些方法成为该类与外部的接口。对象之间通过消息( message )进行通讯。

4.2 从面向过程到面向对象(阅读)

属性

行为

表针

旋钮

其他机械机构

调节旋钮

对 象

4.2 从面向过程到面向对象(阅读)

是一个抽象的概念,用来描述某一类对象所共有的、本质的属性和类行为。

类的一个具体实现,称为实例

手表 一块手表

类 对象描述这类对象共有的、本质的属性和行为

手表共有的属性(表针、旋钮、内部结构)和行为(调节旋钮)

具体到一只圆形的或方形的手表

4.2 从面向过程到面向对象(阅读)

我们把对象之间产生相互作用所传递的信息称做消息。

消 息

启 动

发送消息 接收并响应消息

转 向

4.2 从面向过程到面向对象(阅读)

封装性

内 外

机械零件

动作

调节旋钮

读表盘

对象是一个封装体,在其中封装了该对象的属性和操作。通过限制对属性和操作的访问权限,可以将属性“隐藏”在对象内部,对外提供一定的接口,在对象之外只能通过接口对对象进行操作。

C++ 通过建立数据类型——类来支持封装和数据隐藏。封装性增加了对象的独立性,从而保证了数据的可靠性。一个定义完好的类可以作为独立模块使用。

面向对象程序设计的特点:

汽车

客车 货车

小轿车 大客车

载货载人

小,速度快 大,速度慢

4.2 从面向过程到面向对象(阅读)继承与派生

以汽车为例看客观世界描述事物的方式:

当定义了一个类后,又需定义一个新类,这个新类与原来的类相比,只是增加或修改了部分属性和操作,这时可以用原来的类派生出新类,新类中只需描述自己所特有的属性和操作。

面向对象程序设计提供了类似的机制:

继承性大大简化了对问题的描述,大大提高了程序的可重用性,从而提高了程序设计、修改、扩充的效率。

新类称为子类或派生类,原来的类称为基类。派生可以一直进行下去,形成一个派生树。

4.2 从面向过程到面向对象(阅读)

语文、数学、英语、政治、物理、化学、生物

多态性多态性指,同一个消息被不同对象接收时,产

生不同结果,即实现同一接口,不同方法。

高中生计 算平均成绩

大学生高数、英语、计算机、线

性代数

4.2 从面向过程到面向对象(阅读)

继承和多态性组合,可以生成很多相似但又独一无二的对象。继承性使得这些对象可以共享许多相似特性,而多态又使同一个操作对不同对象产生不同表现形式。这样不仅提高了程序设计的灵活性,而且减轻了分别设计的负担。

4.3 构造函数和析构函数  数据成员多为私有的,要对它们进行初始化,必须用一个公有函数来进行。同时这个函数应该在且仅在定义对象时自动执行一次。称为:

构造函数( constructor )

4.3.1 构造函数的定义与使用

4.3.2 析构函数的定义

构造函数特征:1. 函数名与类名相同。2. 构造函数无函数返回类型说明。注意是什么也不写,也不可写 void!3. 在程序运行时,当新的对象被建立,该对象所属的类的构造函数自动被调用,在该对象生存期中也只调用这一次。4. 构造函数可以重载。严格地讲,说明中可以有多个构造函数,它们由不同的参数表区分,系统在自动调用时按一般函数重载的规则选一个执行。

4.3.1  构造函数的定义与使用

4.3.1  构造函数的定义与使用

5. 构造函数可以在类中定义,也可以在类外定义。 6. 如果类说明中没有给出构造函数,则 C++ 编译器自动给出一个默认的构造函数 : 类名 (void) {}但只要我们定义了一个构造函数,系统就不会自动生成默认的构造函数。 只要构造函数是无参的或各参数均有默认值的, C++ 编译器都认为是默认的构造函数,并且默认的构造函数只能有一个 。

4.3.1  构造函数的定义与使用 CGoods 的构造函数:三参数:Cgoods (char* name , int amount , float price){ strcpy(Name,name) ;Amount=amount ;Price=price ; Total_value=price*amount ;   }两参数:货名和单价,Cgoods (char* name , float price){ strcpy(Name,name) ;Price=price ;Amount=0 ; Total_value=0.0 ;}默认的构造函数:CGoods(){Name[0]=‘\0’ ; Price=0.0 ; Amount=0 ; Total_value=0.0 ;}这三个构造函数同时被说明(重载)。

4.3.1  构造函数的定义与使用 构造函数应用:CGoods Car1(“夏利 2000” , 30, 98000.0);调用了 CGoods 中的第一个构造函数,等效于:CGoods Car1= CGoods(“夏利 2000” , 30, 98000.0);CGoods Car2(“桑塔那 2000” , 164000.0) ;调用的是第二个构造函数,参数为两个。

CGoods Car3; 定义时调用不带参数的构造函数但是定义对象时不能加括号。例如: CGoods Car4();Car4() 是不带参数的函数,它的返回值是类 CGoods 的对象。

【例4.1_1】完整商品类对象应用实例

4.3.2 析构函数的定义

析构函数( destructor )特征:   当一个对象的生命周期结束时, C++会自动调用析构函数( destructor )对该对象并进行善后工作,1. 构函数名与类名相同,但在前面加上字符‘ ~’,如~CGoods ()。2. 析构函数无函数返回类型,与构造函数在这方面是一样的。但析构函数不带任何参数。3. 一个类有一个也只有一个析构函数,这与构造函数不同。析构函数可以默认。4. 对象注销时,系统自动调用析构函数。

【例4.2】定义一个矩形类

4.4  引用与复制构造函数

4.4.1 引用

4.4.2 复制构造函数

4.4.3 成员对象与构造函数

4. 4.1   引用

引用的导入:  参数传递的传值方式在函数域中为参数重新分配内存,而把实参的数值传递到新分配的内存中。它的优点是有效避免函数的副作用。

问题:如果要求改变实参的值,怎么办呢?如果实参是一个复杂的对象,重新分配内存会引起程序执行效率大大下降,怎么办呢 ?

有一种导出型数据类型—引用( reference )可以解决上面的难题。引用又称别名( alias )。

4.4.1   引用

引用的定义: 引用是给一个已经定义的变量重新起一个别名,而不是定义一个新的变量,定义的格式为:

类型 &引用变量名 =已定义过的变量名;

例如:double number ;double &newnum=number ;newnum 是新定义的引用类型变量,它是变量 number 的别名。 引用主要用于函数之间的数据传递。

4.4.1   引用 newnum 是变量 number 的别名, C++系统不为引用类型变量分配内存空间。内存分配见下图 :

  number 称为引用 newnum 的关联变量。“ &”( 读作ampersand) 在 这里是 引 用的说明符。必须注意 number 和 newnum 都 是 double类型。如在程序中修改了 newnum也就是修改了 number ,两位一体。

注意:对数组只能引用数组元素,不能引用数组(数组名本身为地址)。

4.4.1   引用

【例4.4】引用作为函数的返回值一般函数返回值时,要生成一个临时变量作为返回值的副本,而用引用作为返回值时,不生成值的副本。

【例4.5】 返回值为引用的函数作为左值(选读)

【例4.3】引用作为函数的参数。采用引用调用时,将对实参进行操作。

注意:采用引用返回方式时,返回的不能是函数中的局部变量,这时返回的局部变量地址已经失效。引用方式返回最常用的是由引用参数传递过来的变量(见例 4.5 ),其次是全局变量,这样返回的变量地址是有效的。

4.4.2 复制构造函数 复制构造函数引入: 同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制是完全可行的。这个复制过程只需要复制数据成员,而函数成员是共用的(只有一份代码)。在建立对象时可用同一类的另一个对象来初始化该对象,这时所用的构造函数称为复制构造函数( Copy Constructor )。CGoods 类,复制构造函数为:CGoods (CGoods & cgd){ Strcpy (Name , cgd.Name); Price= cgd.price; Amount=cgd.Amount; Total_value=cgd.Total_value;}

4.4.2 复制构造函数

复制构造函数特征:1.  复制构造函数的参数必须采用引用。* 在 C++ 中按值传递一个参数时,会在函数中重新分配一块内存建立与参数同类型的变量或对象,再把参数的数据成员赋给新的变量或对象。在建立这个对象时,编译器就会自动为这个对象调用复制构造函数。如果其参数是真实的对象而不是引用,则又会引入新的一轮调用复制构造函数的过程,出现了无穷递归。

4.4.2 复制构造函数

2.  系统会自动提供称为默认的按成员语义支持的复制构造函数,亦称为默认的按成员初始化。按成员作复制是通过依次复制每个数据成员实现的。赋值运算符“ =”称默认的按成员复制赋值操作符( Copy Assignment Operator ) ,同类对象之间可以用“ =”直接复制 。3. 通常按成员语义支持已经足够。但在某些情况下,它对类与对象的安全性和处理的正确性还不够,这时就要求提供特殊的复制构造函数和复制赋值操作符的定义。

4.4.2 复制构造函数 实例:CGood Car1(“夏利 2000” , 30, 98000.00);// 调用三个参数的构造函数CGood Car2= Car1; // 调用复制构造函数CGood Car3 ( Car1); // 调用复制构造函数, Car1 为实参这样三个对象的初始化结果完全一样。

注意: 在类定义中如果没有显式给出构造函数时,并不是不用构造函数,而是由系统自动调用默认的构造函数或默认的复制构造函数。如果有程序设计者定义的构造函数(包括复制构造函数),则按函数重载的规律,调用合适的构造函数。

4.4.2 复制构造函数隐含的复制构造函数使用:1.当函数的形参是类对象,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参复制到新的对象中。

2.当函数的返回值是类对象,函数执行完成返回调用者时使用。理由也是要建立一个临时对象,再返回调用者。

因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以编译系统会在调用函数的表达式中创建一个无名临时对象,该临时对象的生存周期只在函数调用处的表达式中。所谓 return 对象,实际上是调用复制构造函数把该对象的值拷入临时对象。如果返回的是变量,处理过程类似,只是不调用构造函数。

4.4.3 成员对象与构造函数

聚合( aggregation ) : 类中的成员,除了成员数据和成员函数外,还有成员对象,即用其他类的对象作为类的成员。使用成员对象的技术称为聚合。成员对象是实体,系统不仅为它分配内存,而且要进行初始化。

4.4.3 成员对象与构造函数含对象成员的构造函数:类名 :: 构造函数名 ( 参数总表 ): 对象成员 1( 参数名表 1) ,对象成员 2( 参数名表 2) ,……对象成员 n( 参数名表 n){……}冒号后用逗号隔开的为要初始化的对象成员,附在后面的参数名表 1 ,…,参数名表 n依次为调用相应对象成员所属的构造函数时的实参表。这些表中的参数通常来自冒号前的参数总表,但没有类型说明 。 含对象成员的类对象的初始化时,首先依次自动调用各成员对象的构造函数,再执行该类对象自己的构造函数的函数体部分。各成员对象的构造函数调用的次序与类定义中说明的顺序一致,而与它们在构造函数成员初始化列表中的顺序无关。

4.4.3 成员对象与构造函数

【例 4.6 】含有成员对象的类的构造函数

含对象成员的析构函数: 因为析构函数没有参数,所以包含成员对象的类的析构函数形式上并无特殊之处。但是撤销该类对象时,会首先调用自己的析构函数,再调用成员对象的析构函数,调用次序与初始化时的次序相反。

4.4.3 成员对象与构造函数构造函数另一格式:对于不含对象成员的类对象的初始化,也可以套用以上的格式,把部分需要直接赋初值的变量初始化写在冒号的右边:

类名 :: 构造函数名 (参数表 ):变量 1( 初值 1),……,变量 n( 初值 n){……}当然也可以把一部分变量重新放回花括号中的函数体。冒号以后部分实际是函数体的一部分,所以在构造函数的声明中,冒号及冒号以后部分必须略去。

4.4.3 成员对象与构造函数

构造函数和析构函数的调用规则:  1. 对全局定义的对象,当程序进入入口函数 main 之前  对象就已经定义,那时要调用构造函数。整个程序结束时调用析构函数。

  2. 对于局部定义的对象,每当程序控制流到达该对象定义处时,调用构造函数。当程序控制走出该局部域  时,则调用析构函数。

  3. 对于静态局部定义的对象,在程序控制首次到达该对象定义处时,调用构造函数。当整个程序结束时调  用析构函数。

4.4.3 成员对象与构造函数 在正确定义了构造函数和析构函数的前提下,在一个健康的程序中,每个创建的对象必然有一个而且只有一个撤消动作。

注意:先建立的对象后撤销。

【例4.7】演示对象创建和撤消的对应关系

4.5 运算符的重载运算符重载的概念:  运算符的重载是特殊的函数重载,必须定义一个函数,并通知 C++ 编译器,当遇到该重载的运算符时调用此函数。这个函数叫做运算符重载函数,通常为类的成员函数。

运算符重载函数定义:返回值类型 类名 ::operator 重载的运算符 (参数表 ) {……}

operator 是关键字,它与重载的运算符一起构成函数名。因函数名的特殊性, C++ 编译器可以将这类函数识别出来。

细解运算符重载:复数类 + 的重载:Complex Complex::operator+(Complex c){ //显式说明局部对象 Complex Temp(Real+c.Real , Image+c.Image) ; // 注意:直接写对象 c 的私有成员,不用调 c 的公有函数处理 return Temp ;}编译器把表达式 c2+c3解释为:c2.operator+(c3) ;函数 c2.operator 创建一个局部的 Complex 对象 Temp ,把出现在表达式中的两个 Complex 类对象 c2 和 c3 的实部之和及虚部之和暂存其内,然后把这个局部对象返回,赋给 Complex 类对象 c (注意这里调用了复制构造函数生成一个无名临时对象过渡)。参见图 4.8。

4.5 运算符的重载

Temp.Real=Real+ c2.Real;Temp.Image=Image+ c3.Image;c=return(Temp);

Real

Image

c3.Real

c3.Image

= +

局部对象 Temp

当前对象 c2 对象 c3

图 4.8 显式说明临时对象的“ +” 运算符执行过程

隐式返回计算结果: 省略局部的 Complex 对象 Temp

Complex Complex::operator+(double d){ return Complex(Real+d , Image);}//隐式说明局部对象

在 return 后面跟的表达式中调用的是类的构造函数,它为无名对象赋值(初始化),返回值就是该无名对象。

4.5 运算符的重载说明:Complex Complex::operator+(Complex c){ Complex Temp(Real+c.Real , Image+c.Image) ; return Temp ;}

当成员函数的参数为同一类( class )的对象或它的引用,在函数体内使用参数对象的私有数据成员时,可用对象名加成员访问操作符点号进行。

从逻辑上讲,每个对象有自己的成员函数,访问同类其他对象的私有数据成员应通过该对象的公有函数,不能直接访问。但在物理上只有一个成员函数代码,所以直接访问是合理的。仅在成员函数中可以这样做。

【例 4.8 】复数类,应用它进行复数运算

4.5 运算符的重载

引用作为参数:Complex Complex::operator+(const Complex &c){ return Complex(real+c.real , Image+c.Image) ;}注意:参数采用对象的引用而不是对象本身,调用时不再重新分配内存建立一个复制的对象,函数效率会更高。而在引用形式参数类型说明前加 const 关键字,表示被引用的实参是不可改变的,如程序员不当心在函数体中重新赋值了被引用的实参, C++ 编译器会认为出错。

4.5 运算符的重载const 引用进一步说明:引用在内部存放的是被引用对象的地址,不可寻址的值是不能

引用的;当引用作为形参时,实参也不能使用不可寻址的值,更不可能进行类型转换(如:实数转换为整数)。但是 const 引用不同,它是只读的,为了绝对保证不会发生误改,编译器实现 const 引用时,生成一个临时对象,引用实际上指向该临时对象,但用户不能访问它。所以 const 引用可以实现不可寻址的值(包括字面常量)的引用。

例如:double dval=1024; const int &ri=dval;是正确的,编译器将其转换为:double dval=1024; int temp=dval; const int &ri=temp;因有临时对象,引用和类型转换都实现了。当 const 引用作为形参时,实参也能使用不可寻址的值,并

能进行类型转换。

4.5 运算符的重载默认的复数复制赋值操作符:函数声明:

Complex &Complex::operator = (Complex& c);这种默认的赋值操作格式对所有类都是固定的,对复数是合适的。默认的赋值操作返回对象本身的引用,它可以进行连续赋值。如: a=b=c=d ;

本例中重载的赋值运算符“ =”取代了默认的赋值操作,返回一个复数临时变量,尽管该复数生命期仅在使用赋值号的表达式(如 a=b=c )中,却也能进行连续赋值。但它的执行效率和代码简洁性较差。重载的运算符“ +=”标准算法声明是:Complex& Complex::operator +=(Complex & com);

4.5 运算符的重载

  2. 当用类的成员函数实现运算符的重载时,运算符重载函数的参数(当为双目运算符时)为一个或(当为单目运算符时)没有。运算符的左操作数一定是对象,因为重载的运算符是该对象的成员函数,而右操作数是该函数的参数。 3.  单目运算符“ ++” 和“ --” 存在前置与后置问题。前置“ ++” 格式为: 返回类型 类名 ::operator++(){……} 而后置“ ++” 格式为: 返回类型 类名 ::operator++(int){……} 后置“ ++” 中的参数 int 仅用作区分。

小结:  1. 运算符重载函数的函数名必须为关键字 Operator加一个合法的运算符。在调用该函数时,将右操作数作为函数的实参。

4.5 运算符的重载

运算符 运算符名称 禁止重载的理由

? : 三目条件运算符

C++ 中没有定义三目运算符的语法

. 和 .* 成员与成员指针操作符

为保证成员操作符对成员访问的安全性

:: 作用域操作符 该操作符右操作数不是表达式

sizeof 类型字长操作符

该操作符的操作数为类型名,不是表达式

表 5.1 C++ 中不允许重载的运算符

4. C++ 中只有极少数的运算符不允许重载。

4.5 运算符的重载

问题:例 5.7中:

c=c+d;语句,改为

c=d+c;因为 d不是 Complex 的对象, C++ 编译器将无法找到合适的重载的“ +” 运算符对应的函数,最终给出出错信息。怎样解决?

4.6 友元

在 C++ 中友元( friend )函数允许在类外访问该类中的任何成员,就象成员函数一样。友元函数用关键字 friend 说明。

上节答案:用友元函数重载运算符“ +” ,可以实现c=d+c;

4.6 友元class Complex {…… friend Complex operator + (double,Complex); }; //opration+ 说明为类 Complex 类的友元函数, //friend 只用于类说明中,定义时不加 friend Complex operator + (double d , Complex c){ return Complex(d+c.Real , c.Image) ; } // 注意友元不是成员函数,但以直接访问私有成员int main(void){……

c=d+c1; c.print();return 0;}解释:d+c 被 C++ 编译器解释为: operator+(d,c)

4.6 友元友元函数重载运算符形式:+ 有三种形式。另两个的声明为:

friend Complex operator +(Complex , Complex ) ;friend Complex operator + (Complex , double ) ;

涵盖实数与复数,复数与复数,复数与实数相加三种情况。

可以仅使用友元函数

friend complex operator +(complex , complex) ;

实数被默认的构造函数强制转换为虚部为零的复数。 d+c1被解释为: operator+(complex(d) , c1)注意:传值,在函数内是建立了两个复数对象,而把实参的值传进去,进行运算。参见图 5.9 。

4.6 友元

比较:友元函数可以有两个参数,而对应的成员函数只有一个参数,所以友元函数的使用可以更灵活、更方便。改进: Operator+友元函数的声明可使用引用类型变量friend Complex operator+(const Complex & c1, const Complex & c2)

图 5.9 友元函数 operator+执行过程内存分配

【例4.8_1】 用友元函数重载运算符

4.6 友元

单目运算符前“ ++” 的成员函数重载方式如下:Complex Complex::operator++(){ return Complex (++Real , ++Image) ;}采用成员函数方式重载与使用都很方便。

友元函数重载后置“ ++” 如下:friend Complex operator++(Complex & c , int) {// 注意友元方式与前者的区别 return Complex(c.Real++ , c.Image++) ;}采用引用类型,后“ ++” 是直接施加于实参。否则施加于副本,而实参不变。

注意:复制赋值运算符( = )重载必须为成员函数,不可为友元函数。因为默认的复制赋值运算符( = )是成员函数,友元函数不能取代它。

友元函数注意点: 1. 友元函数不是类的成员函数,在函数体中访问对象的成员,必须用对象名加运算符“ .”加对象成员名。但友元函数可以访问类中的所有成员,一般函数只能访问类中的共有成员。 2. 友元函数不受类中的访问权限关键字限制,可以把它放在类的公有、私有、保护部分,但结果一样。 3. 某类的友元函数的作用域并非该类作用域。如果该友元函数是另一类的成员函数,则其作用域为另一类的作用域,否则与一般函数相同。友元类:整个类可以是另一个类的友元。友元类的每个成员函数都是另一个类的友元函数,都可访问另一个类中的保护或私有数据成员。定义方法如下:class A{…… friend class B; //声明 B为 A 的友元类 …… };

4.7 静态成员 由关键字 static修饰说明的类成员,成为静态类成员( static class member )。但与函数中的静态变量有明显差异。类的静态成员为其所有对象共享,不管有多少对象,静态成员只有一份存于公用内存中。

4.7.1 静态数据

4.7.2 静态函数成员(选读)

4.7.1 静态数据静态数据成员定义与使用: 在类定义中,用关键字 static修饰的数据成员为静态数据成员。

该类所有对象共享由系统为静态成员分配的一个存储空间,而这个存储空间是在编译时分配的,在定义对象时不再为静态成员分配空间。

静态数据是该类所有对象所共有的,可提供同一类的所有对象之间信息交换的捷径。

静态数据成员属于整个类,使用时可用以下格式:

类名 :: 静态数据成员名

4.7.1 静态数据【例4.9】用静态数据成员计算由同一类建立的对象的数量执行程序后输出:对象数量 =1 //a[0] 构造函数产生对象数量 =2 //a[1] 构造函数产生对象数量 =3 //a[2] 构造函数产生对象数量 =2 //a[2] 析构函数产生对象数量 =1 //a[1] 析构函数产生对象数量 =0 //a[0] 析构函数产生

4.7.2 静态函数成员(选读)静态函数成员的使用: 函数成员说明为静态,将与该类的不同对象无关。静态函数成员的调用,在对象之外可以采用下面的方式:类名 :: 函数名 ( 对象名 , 参数表 );任一类对象名 :: 函数名 ( 对象名 , 参数表 ); 静态函数成员多为公有的。在例 4.8中的复数类中的函数成员 print(), 改为静态的则可如下表达:static void print(complex & ob){ cout<<”Real=”<<ob.Real<<’\t’ <<”Image=”<<ob.Image<<’\n’ ;}参数是为了告诉 C++ 系统应取哪一个对象的数据。

4.8 结构结构类型的引入:   在 C++ 中结构( structure )与类几乎是完全一样的类型,差别仅仅在于默认情况下结构的成员为公有的。 在 C 语言阶段,结构就已存在,但它只有公有的数据成员。正因为如此, C++ 程序员仍然使用结构,但是只为结构安排公有的数据成员。因为这样程序更易读易懂。在程序设计中常把结构类型的数据作为类的数据成员。 C风格的定义 :struct 结构类型名 { 类型名 变量 1 ;《类型名 变量 2 ;… ; 》}; // 最后的分号不可少

4.8 结构实例:struct inventory{ //库存货物char description[15] ; //货物名称char no[10] ; //货号int quantity ; //库存数量double cost ; // 成本double retail ; } ; //零售价格

struct employee{ // 员工char name[27] ; // 员工姓名char address[30] ; //家庭住址long int zip ; //邮政编码long int telenum ; //联络电话double salary ; } ; //工资

4.8 结构变量定义与初始化: 结构是一种派生数据类型,定义结构时并不分配存储空间,只有定义了结构类型的变量,编译系统才为结构变量分配存储空间。定义变量方法如下:inventory car , motor ; 初始化是用花括号中顺序填入结构中的(公有数据)成员的初始值完成的:employee emp1={“朱明”,“四牌楼 2 号”, 210096, 3792666, 2430.0} ,emp2={“沈俊”,“丁家桥 15号”, 210009, 3273389, 1920.0} ; 结构变量的访问与类一样,可使用成员访问操作符之一:点操作符,对成员一个个进行:变量名 . 成员名

4.8 结构

结构类型使用说明:   (1) 与同类的对象之间可以复制一样,同结构类型的变量之间也可以作为整体相互赋值(复制)。

   (2) 结构变量也可以作为函数的参数和返回值,结构作为参数可以按值(复制)进行传递的,也可以按引用传递。

(3) 在程序文件中强烈推荐将结构类型的定义放在所有函数的外面,这样程序文件中的各个函数可以按需要在各个函数中声明局部的结构变量。在各函数中定义结构类型,即使两个函数中定义的完全一样,系统也完全认为是两种结构类型。

4.8 结构(4) 结构可以嵌套:struct mail{char address[30] ; // 地址long int zip ; //邮政编码long int telenum ; }; //电话号码struct employee{char name[25] ; // 员工姓名mail addinfo ; // 结构作为成员,嵌套double salary ; }; //工资用连续点号来访问结构变量的结构成员中的成员 :employee emp1={“朱明”,“四牌楼 2 号”, 210096, 3792666, 2430.0};cout<<emp1.addinfo.telenum ;输出为 3792666。

4.8 结构联合 (选读 ) : union 共同体变量名 {类型 成员名 1 ;《类型 成员名 2 ;…;》}; 联合( union )与结构的区别是:结构变量的各成员同时被分配了各自独立的内存区,而联合变量的各个成员的存储开始地址都相同,所以在任一时刻联合变量只能存储一个成员。 系统为联合变量分配空间时按需要最大存储量的成员大小分配内存空间。 联合被称为一种特殊的类(它因编译器不能知道成员的类型,而没有构造函数和析构函数,所以联合的对象不是由构造函数生成的。故称特殊的类)。

4.8 结构用途:联合的典型用途是按不同方式访问同一块内存。例如:union num{ int k; char ch[2];}a; 图 4.10 联合变量 a 内存分配

系统为变量 a 分配了 4 个字节的空间。如果以 ch[1]记楼层号,以 ch[0]记同一楼层的房间号,如 15楼 8 号房间则可赋值: a.ch[1]=15; a.ch[0]=8; 见图 4.10 。如果需要把所有房间顺序排号,则可用 a.k来读取一个整型数( 15*256+8 ),1楼 1 号房间排在第一,楼层越高、同一层房间号越大的房间排得越后。

4.9  名字空间域和类域 (选读) 在 C++ 中支持三种域:局部域、名字空间域和类域。

名字空间域声明:  名字空间域相当于一个更加灵活的文件域(全局域),可以用花括号把文件的一部分括起来,并以关键字 namespace开头给它起一个名字:namespace ns1{ float a,b,c; fun1(){……} … }花括号括起来的部分称声明块。声明块中可以包括:类、变量(带有初始化)、函数(带有定义)等。

4.9  名字空间域和类域 (选读)

名字空间域访问: 在域外使用域内的成员时,需加上名字空间名作为前缀,后面加上域操作符“ ::” 。这里添加了名字空间名称的成员名被称为限定修饰名 (qualified name) 。如: ns1::a, ns1::fun1()等等。

4.9  名字空间域和类域 (选读)名字空间域嵌套:  名字空间域可分层嵌套,同样有分层屏蔽作用。例如:namespace cplusplus_primer{ namespace Matrixlib{ // 名字空间嵌套

class matrix{……} // 名字空间类成员 matrix…... }

}访问 matrix ,可写限定修饰名: cplusplus_primer::Matrixlib::matrix 最外层的名字空间域称为全局名字空间域( global namespace scope ),即文件域。

4.9  名字空间域和类域 (选读)

using声明: 使用 using声明可只写一次限定修饰名。 using声明以关键字 using 开头,后面是被限定修饰的( qualified )名字空间成员名:

using cplusplus_primer::Matrixlib::matrix; // 名字空间类成员 matrix 的 using声明

以后在程序中使用 matrix 时,就可以直接使用成员名,而不必使用限定修饰名。

4.9  名字空间域和类域 (选读)

using 指示符 : 使用 using 指示符可以一次性地使名字空间中所有成员都可以直接被使用,比 using声明方便。 using 指示符以关键字 using 开头,后面是关键字 namespace ,然后是名字空间名。 标准 C++库中的所有组件都是在一个被称为 std 的名字空间中声明和定义的。在采用标准 C++ 的平台上使用标准 C++库中的组件,只要写一个 using 指示符:using namespace std;就可以直接使用标准 C++库中的所有成员。这是很方便的。

4.9  名字空间域和类域 (选读)

名字空间域的引入,主要是为了解决全局名字空间污染( global namespace pollution )问题,即防止程序中的全局实体名与 C++ 各种库中声明的全局实体名冲突。

名字空间域补充说明:名字空间可以不连续,分为多段,但它们仍是同一个名字空间。名字空间域不能定义在函数声明、函数定义或类定义的内部。

4.9  名字空间域和类域 (选读)类域:  类体部分称为类域。在类域中说明的标识符仅在该类的类域内有效。必须加上“类名 ::”作限定修饰。

  类的实体——对象中的公有成员也可以在对象之外访问,但必须使用成员访问操作符“ .”,对象名 +“.”+成员名。

  定义类本身的目的就是要实现一个封装性,对外是封闭的,对内是开放的,在程序中并不总是需要用成员访问符之类来引用类成员。多数程序代码本身就在类域中,这些程序可以直接访问类成员。

在类域中类成员在类体中被声明的顺序同样很重要,后声明的成员不能被先声明的成员引用。

4.9  名字空间域和类域 (选读)标识符解析: 编译器对名字(标识符)的解析分两步,第一步查找在声明中用到的名字,包括数据成员和函数成员声明中用到的参数类型,第二步才是函数成员体内的名字。例如:class string{ // 字符串类public: typedef int index_type;// 为易读易懂用下标型命名 char GetstringElement(index_type elem) { return Astring[elem];} // Astring未说明private: char Astring[30]; //Astring 后说明};表面上看是错的;实际上是对的。因为 Astring 名字的解析是在第一步,而函数使用它是在第二步。

4.10 面向对象程序的组织与Windows 下的实现

在本小节中,我们引入怎样实际实现面向对象的程序设计的概念与方法。使读者理解面向对象设计的程序中各对象是怎样协调工作的,以及为什么在 Windows 操作系统下才能真正实现面向对象的程序设计。

4.10 面向对象程序的组织与Windows 下的实现 面向过程的程序结构:程序 = 算法 + 数据结构。算法实际上就是功能抽象。在面向过程的程序设计中程序是模块化的,模块是分层次的,层与层之间是一个从上往下的调用关系。图 4.11给出了这种层次模块的调用关系。

图 4.11 面向过程程序设计的程序组织

4.10 面向对象程序的组织与Windows 下的实现

功能抽象是困难的,而且很难全面,一旦要解决的问题发生一点小变化,功能块就要重编,而一个功能块又被多个上层模块调用(图中称任务块),它们的要求有的变了,有的没变,这就给重编带来极大的困难。

4.10 面向对象程序的组织与Windows 下的实现

面向对象的程序构造:对象 = (算法 + 数据结构),程序 = 对象 + 对象 +……+ 对象 +消息。这里程序是由一个个封装的对象组成,而对象是由紧密结合在一起的算法和数据结构组成,对象中有数据和对数据的操作,它带来了计算机效率的下降和程序员效率的上升及工作难度的下降。

4. 10 面向对象程序的组织与Windows 下的实现 对象与对象之间怎样建立有效的联系,互相调用的思想明显不行。实际上采用的是用消息传递机制来协调各对象的运行,如图 4.12 :

图 4.12 面向对象的程序组织

4.10 面向对象程序的组织与Windows 下的实现消息:  消息是对象之间相互请求或相互协作的途径,是要求某个对象执行其中某个功能操作的规格的说明。消息传递是对象与其外部世界相互关联的唯一途径。对象可以向其他对象发送消息以请求服务,也可以响应其他对象传来的消息,完成自身固有的某些操作,从而服务于其他对象。

4.10 面向对象程序的组织与Windows 下的实现

消息和方法: 因为对象的操作主要用来响应外来消息并为其他对象提供服务,所以它们也被称作“外部服务”。

 消息是客观世界中对象之间通信的信号,是要求某个对象执行其中某个功能操作的规格说明。对象的动作取决于发送给该对象的消息,消息通知对象要求完成的功能。也就是说,消息传递完成的是“做什么”的任务,并认为接受消息的对象知道如何去做,对象激活该功能,完成任务。

4.10 面向对象程序的组织与Windows 下的实现

  方法描述了对象的能力,从程序设计的角度看它是对象实现功能操作的代码段。方法与消息相互对应,每当对象收到一个消息后,除了知道“做什么”外,还必须知道和决定“怎样做”。方法就是对象中决定“怎样做”的操作代码,方法就是实现每条消息具体功能的手段。

面向对象的程序设计并没有给出或指定具体的方法来实现这个“消息传递”机制,只是提出这样一种理念。实际上对 C++而言,这一机制是由操作系统完成的。

4.10 面向对象程序的组织与Windows 下的实现

消息传递,事件驱动:Windows 系统支持多个应用程序同时执行,在界面形式上,它支持多个窗口同时活动。它的运行机制就是“消息传递,事件驱动( message based,event driven )”。

 Windows 系统使用事件驱动的编程模式。所谓事件的含义非常广泛。输入设备的动作,如敲打键盘、按鼠标等会产生一系列的事件(注意不是一个事件)。操作系统所作的一举一动也被当作某种类型的事件,应用程序也会产生各种事件。事件用来标识发生的某件事情。

 Windows 系统对于应用程序环境中发生的每一个事件都会以对应的某种消息的形式标识,并放入相应的Windows 建立的消息队列中,然后由对应的应用程序或窗口函数去处理。

4.10 面向对象程序的组织与Windows 下的实现

图 5.13 windows 操作系统下的应用程序

第五章 类与对象

结束谢谢!

4.1.2 成员函数的定义void CGoods::RegisterGoods(char name[] , int amount , float price){strcpy(Name , name) ; // 字符串复制函数Amount=amount ; Price=price ;}void CGoods::CountTotal(void){ Total_value = Price*Amount;}void CGoods::GetName(char name[]){strcpy(name , Name);}int CGoods::GetAmount(void){return(Amount) ;}float CGoods::GetPrice(dvoi){return(Price) ;}float CGoods::GetTotal_value(void){return(Total_value) ;}

【例 4.1 】商品类对象应用实例【例 4.1】商品类对象应用实例:#include<iostream>#include<iomanip>#include<string>using namespace std;

// 省略了类定义int main( ){CGoods car ;char string[21] ;int number ;float pr ;

成员名Name[21] ;Amount ;Price ;Total_value ; 10

minicar 5 2 10minicar52

string[21]number Pr

minicar52

minicar

cout<<“请输入汽车型号:” ;cin.getline(string , 20) ; //输入串长必须小于 20cout<<“请依次输入汽车数量与单价:” ;cin>>number>>pr ;car.RegisterGoods(string , number , pr) ;car.CountTotal() ;string[0]=’\0’ ; // 字符串 string清零car.GetName(string) ; //string赋值 car.Namecout<<setw(20)<<string<<setw(5) <<car.GetAmount() ; //Acout<<setw(10)<<car.GetPrice()<<setw(20) <<car.GetTotal_value()<<endl ; //Breturn 0;}

【例 4.1_1 】完整商品类对象应用实例 class CGoods{private :

char Name[21] ; int Amount ;float Price ;float Total_value ;

public :CGoods();CGoods(char [],int,float);CGoods(char [],float);void RegisterGoods(char[],int,float) ;void CountTotal(void) ;void GetName(char[]) ;int GetAmount(void) ;float GetPrice(void) ;float GetTotal_value(void) ;};

【例 4.1_1 】完整商品类对象应用实例 int main( ){

char string[21]={'\0'};CGoods Car1("夏利 2000",30,98000.0);CGoods Car2("桑塔那 2000",164000.0);Car1.GetName(string); //string赋值 car.Namecout<<setw(20)<<string<<setw(5)

<<Car1.GetAmount();cout<<setw(10)<<Car1.GetPrice()<<setw(20)

<< Car1.GetTotal_value()<<endl;Car2.GetName(string); //string赋值 car.Namecout<<setw(20)<<string<<setw(5)

<< Car2.GetAmount();cout<<setw(10)<<Car2.GetPrice()<<setw(20)

<< Car2.GetTotal_value()<<endl;return 0;}

【例 4.2 】矩形类【例 4.2 】矩形类。要确定一个矩形(四边都是水平或垂直方向,不能倾斜),只要确定其左上角和右下角的 x 和 y坐标即可,即左右上下四个边界值。class Rectangle {

int left, top, right, bottom;public:

Rectangle(int =0, int =0, int =0, int =0); // 默认构造函数必须在此指定默认实参

~ Rectangle(){} ; //析构函数,在此函数体为空void Assign(int , int , int , int );void SetLeft(int t){ left = t;}

void SetRight( int t ){ right = t;}void SetTop( int t ){ top = t;}void SetBottom( int t ){ bottom = t;}void Show();};

【例 4.2 】矩形类

Rectangle::Rectangle(int l , int t, int r, int b) { left = l; top = t; right = r; bottom = b; }void Rectangle::Assign(int l, int t, int r, int b){ left = l; top = t; right = r; bottom = b; }void Rectangle::Show(){ cout<<”left-top point is (”<<left<<”,”<<top <<”)”<<’\n’; cout<<”right-bottom point is (”<<right<<”,” <<bottom<<”)”<<’\n’; }

【例 4.2 】矩形类#include <iostream>using namespace std;#include “rect.h”int main(){

Rectangle rect; rect.Show();rect.Assign(100,200,300,400);rect.Show();Rectangle rect1(0,0,200,200);rect1.Show();Rectangle rect2 ( rect1 ) ;rect2.Show();return 0;

}

【例 4.3 】引用作为形参

X

y

d1

d2

temp

1.414

2.718

1.414

2.718

1.414

void swap(double & d1, double & d2){ double temp ; temp=d1 ; d1=d2 ; d2=temp ; }int main(void){ double x , y ; cout<<" 请输入 x 和 y 的值 " <<'\n'; cin>>x>>y ; swap(x,y) ; cout<<"x="<<x<<'\t' <<"y="<<y<<'\n'; return 0; }

图 5.5 参数 d1 、 d2 为引用时内存分配示意

【例 4.4 】 引用作为返回值 【例 4.4 】采用不同返回方式的求正方形面积函数的比较。double temp; // 全局变量double fsqr1(double a){ temp=a*a ; return temp;}double & fsqr2(double a){ temp=a*a ; return temp;}int main(){ double x=fsqr1(5.5); // 第一种情况 double y=fsqr2(5.5); // 第二种情况 cout<<"x="<<x<<'\t‘<<"y="<<y<<endl; return 0;}运行结果为:x=30.25 y=30.25运行结果一样,但在内存中的活动却不同。

【例 4.4 】 引用作为返回值

图 4.6 普通返回 图 4.7 引用返回

【例 4.5 】 返回值为引用的函数作为左值(选读)【例 4.4】统计学生成绩,分数在 80分以上的为 A 类, 60分以上,80分以下的为 B类, 60分以下为 C 类。int& level(int grade ,int& typeA ,int& typeB ,int& typeC){ if(grade>=80) return typeA ; else if(grade>=60) return typeB; else return typeC;}void main( ){ int typeA=0,typeB=0,typeC=0,student=9 ; int array[9]={90 , 75 , 83 , 66 , 58 , 40 , 80 , 85 , 71} ; for (int i=0 ; i<student ; i++) level(array[i], typeA, typeB, typeC)++ ; // 函数调用为左值 cout<<"A 类学生数: "<<typeA<<endl ; cout<<"B类学生数: "<<typeB<<endl ; cout<<"C 类学生数: "<<typeC<<endl ;}

【例 4.6 】含有成员对象的类的构造函数class studentID{ long value;public: studentID(long id=0){

value=id;cout<<"赋给学生的学号: "<<value<<endl;}

~studentID(){cout<<"删除学号: "<<value<<endl;} };

class student{private: studentID id; char name[20];public: student (char sname[]="no name",long sid=0):id(sid){ strcpy(name,sname);

cout<<“学生名:” <<name<<endl; }

这样运行结果为:赋给学生的学号: 08002132学生名:朱明删去学生名:朱明删去学号: 08002132在 student 构造函数头部的冒号表示要对对象成员的构造函数进行调用。但在构造函数的声明中,冒号及冒号以后部分必须略去。

【例 4.6 】含有成员对象的类的构造函数 ~studentID(){

cout<<“删除学生名: "<<name<<endl;} };int main(){ student ss("朱明 ",82020132); return 0;}

【例 4.7 】演示对象创建和撤消的对应关系本例目的是总结一下语法,请注意各函数输出的标志:class complex{ private: double real, image; public: complex(){ //默认的构造函数

real=0.0; image=0.0;cout<<"Initializing 0 0"<<endl;}

complex(double r,double i=0.0){ // 带参数的构造函数real=r; image=i;cout<<"Initializing"<<r<<'\t'<<i<<endl;}

complex(complex &com); // 复制的构造函数声明 ~complex(){ // 析构函数

cout <<"Destructor"<<endl; }

【例 4.7 】演示对象创建和撤消的对应关系

void assign(complex com){ real=com.real; //先建立临时对象 com image=com.image; } void print(){ cout<<real<<'+'<<image<<'i'<<endl; }};inline complex::complex(complex &com){ // 复制的构造函数说明 cout<<"Copy"<<com.real<<'\t‘ <<com.image<<endl; real=com.real; image=com.image;}

【例 4.7 】演示对象创建和撤消的对应关系complex fun(complex com){ cout<<"Entering function"<<endl; global.assign(com); cout<<"Exiting function"<<endl; return global; }complex global; //全局对象首先建立int main(){ cout <<"Entering main"<< endl; complex com1, com2(5.6, 7.5); complex com3=com1; com3.print(); global.print(); com1=fun(com2); com1.print(); cout<<"Exiting main"<<endl; return 0;}

【例 4.7 】演示对象创建和撤消的对应关系

运行结果:Initializing 0 0 //全局对象 global 建立,调默认的构造函数Entering main // 进入入口函数 mainInitializing 0 0 // 用默认的构造函数建立 com1Initializing 5.6 7.5 // 用带参数的构造函数建立 com2Copy 0 0 // 用复制的构造函数建立 com30+0i //打印 com30+0i //打印 globalCopy 5.6 7.5 // 调用全局函数 fun() ,调用复制构造函数建立临时对象 comEntering function // 进入全局函数 fun()Copy 5.6 7.5 // 进入 global.assign() , // 调用复制构造函数建立临时对象新 com

【例 4.7 】演示对象创建和撤消的对应关系Destructor

//退出 global.assign() ,调用析构函数,清新 comExiting function //将退出 fun()Copy 5.6 7.5 //返回对象时调用复制构造函数建立临时对象Destructor //退出 fun() ,调用析构函数,清 fun() 的 comDestructor //返回的临时对象赋给 com1 后析构5.6 + 7.5i //打印 com1Exit main   //将退出入口函数 mainDestructor   //退出入口函数前,调用析构函数,清 com3Destructor   //退出入口函数前,调用析构函数,清 com2Destructor   //退出入口函数前,调用析构函数,清 com1Destructor   //退出入口函数前,调用析构函数,清 global本例运行结果应与程序对比,看看程序运行的细节。

【例 4.8 】复数类class Complex{

double Real,Image ;public : Complex(double r=0.0, double i=0.0):Real(r),Image(i){} Complex(Complex &com){

Real=com.Real ; Image=com.Image ; } void Print(){

cout<<"Real="<<Real<<'\t'<<"Image="<<Image<<'\n'; }Complex operator+(Complex); // 重载 + 运算符函数Complex operator+(double); // 重载+运算符函数Complex operator=(Complex); // 重载=运算符函数Complex operator+=(Complex); // 重载+=运算符函数double abs(void); //求绝对值函数Complex operator*(Complex); // 重载*运算符函数Complex operator/(Complex); }; // 重载/运算符函数

Complex Complex::operator+(Complex c){// 重载 +Complex Temp(Real+c.Real , Image+c.Image) ;return Temp ; }验证主函数

【例 4.8 】复数类Complex Complex::operator+(double d){ // 重载 +

return Complex(Real+d , Image); } Complex Complex::operator+=(Complex c){// 重载 + =

Complex temp; // 为了返回 Complex 类型的值,使 += 可以连续使用 temp.Real=Real+c.Real; temp.Image=Image+c.Image;Real=temp.Real; Image=temp.Image;return temp;}

Complex Complex::operator=(Complex c){// 重载 =Complex temp; // 定义 temp 为可返回 Complex 类型值,使 = 可连续使

用 temp.Real=c.Real; temp.Image=c.Image;Real=temp.Real; Image=temp.Image;return temp;}

double Complex::abs(void){//求绝对值函数return sqrt(Real*Real+Image*Image); }

Complex Complex::operator*(Complex c){// 重载 *return Complex(Real*c.Real-Image*c.Image ,

Real*c.Image+c.Real*Image); }

【例 4.8 】复数类Complex Complex::operator/(Complex c){ // 重载 / double d=c.Real*c.Real+c.Image*c.Image ; return Complex((Real*c.Real+Image*c.Image)/d , (Image*c.Real-Real*c.Image)/d) ; }int main(void){

Complex c1(1.0,1.0) , c2(2.0,2.0) , c3(4.0,4.0) , c;double d=0.5 ;c1.Print();c=c2+c3 ; c.Print() ;c+=c1 ; c.Print() ;c=c+d ; c.Print() ; // 可用 0.5 代替 dc=c3*c2 ; c.Print() ;c=c3/c1 ; c.Print() ;cout<<"c3 的模为: "<<c3.abs()<<endl ;}c=c3=c2=c1; c.Print(); //连续赋值c+=c3+=c2+=c1; c.Print(); //连续加赋值 return 0;}

【例 4.8_1 】 用友元函数重载运算符class Complex{

double Real,Image ;public : Complex(double r=0.0, double i=0.0):Real(r),Image(i){} Complex(Complex &com){ Real=com.Real ; Image=com.Image ;} void Print(){ cout<<"Real="<<Real<<'\t'<<"Image="<<Image<<'\n' } friend Complex operator+(const Complex &,const Complex &); friend Complex &operator +=(Complex &,const Complex &); friend double abs(void); friend Complex operator*(const Complex &,const Complex &); friend Complex operator/(const Complex &,const Complex &);};

【例 4.8_1 】 用友元函数重载运算符Complex &operator +=(Complex &c1,const Complex &c2){ // 重载复数 "+="

c1.Real=c1.Real+c2.Real;c1.Image=c1.Image+c2.Image;return c1; } // 返回由引用参数传递过来的变量,函数返回值可为引

用Complex operator+(const Complex & c1,const Complex & c2){

return Complex(c1.Real+c2.Real,c1.Image+c2.Image);} //隐式说明局部对象Complex operator*(const Complex & c1,const Complex & c2){

return Complex(c1.Real*c2.Real-c1.Image*c2.Image , c1.Real*c2.Image+c2.Real*c1.Image);}Complex operator/(const Complex & c1,const Complex & c2){

double d=c2.Real*c2.Real+c2.Image*c2.Image ;return Complex((c1.Real*c2.Real+c1.Image*c2.Image)/d ,

(c1.Image*c2.Real-c1.Real*c2.Image)/d) ;}double abs(Complex &c){

return sqrt(c.Real*c.Real+c.Image*c.Image);}

【例 4.8_1 】 用友元函数重载运算符int main(void){

Complex c1(1.0,1.0) , c2(2.0,2.0) , c3(4.0,4.0) , c;double d=0.5 ;c1.Print();c=c2+c3; c.Print(); //两复数相加c+=c2+=c1; c.Print(); //连续加赋值c=c+d; c.Print(); // 复数加实数c=d+c; c.Print(); // 实数加复数c=c3*c2; c.Print();c=c3/c1; c.Print();c=c3*d; c.Print(); // 复数乘以实数c=c3/d; c.Print() ; // 复数除以实数cout<<"c3 的模为: "<<abs(c3)<<endl ;return 0;}

【例 4.9】静态数据成员#include <iostream.h>class Ctest{private: static int count;// 注意私有public: Ctest(){++count;cout<<"对象数量 ="<<count<<'\n';} ~Ctest(){ --count;cout<<"对象数量 ="<<count<<'\n'; }};int Ctest::count=0; //A 行 对静态数据定义性说明int main(void){Ctest a[3];return 0;}A 行是对静态数据成员数据作定义性说明,必须在文件作用域中作一次并只能做一次说明,只有在这时 C++ 编译器为静态数据成员分配存储空间。