课程设计安排
This presentation is the property of its rightful owner.
Sponsored Links
1 / 131

课程设计安排 PowerPoint PPT Presentation


  • 118 Views
  • Uploaded on
  • Presentation posted in: General

课程设计安排. 时间 :2014年1月15-19日 下午13:00-18:00 地点:综合楼315. 第四章 类与对象. C++: C with Classes. 封装( Encapsulation ) 是面向对象程序设计最基本的特性,也就是把数据(属性)和函数(操作)合成一个整体,这是用类与对象实现的。接口(类设计)和实现(编程)分离。 本章重点: 1. 引入 C++ 的类( class )和对象( object )的概念,建立 “ 函数也可以是数据类型的成员 ” 的思想。 2. 运算符重载。. 第四章 类与对象. 4.1 类与对象.

Download Presentation

课程设计安排

An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript


4054818

课程设计安排

  • 时间 :2014年1月15-19日下午13:00-18:00

  • 地点:综合楼315


4054818

第四章 类与对象

C++: C with Classes

封装(Encapsulation)是面向对象程序设计最基本的特性,也就是把数据(属性)和函数(操作)合成一个整体,这是用类与对象实现的。接口(类设计)和实现(编程)分离。

本章重点:

1.引入C++的类(class)和对象(object)的概念,建立“函数也可以是数据类型的成员”的思想。

2.运算符重载。


4054818

第四章 类与对象

4.1 类与对象

4.6 友元

4.2 从面向过程到面向对象

4.7 静态成员

4.3 构造函数和析构函数

4.8 结构

4.4 引用与复制构造函数

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

4.5 运算符的重载

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


4054818

4.1类与对象

4.1.1 C++类的定义

4.1.2成员函数的定义

4.1.3对象的创建与使用


4 1 1 c

4.1.1 C++类的定义

类的引入:

类是一种数据类型。

描述客观事物必须用不同的数据类型来描述不同的方面。如商品:

商品名称(用字符串描述),该商品数量(用整型数描述),该商品单价(用浮点数描述),该商品总价(用浮点数描述)。

这里用了属于三种不同数据类型的四个数据成员(data member)来描述一种商品。


4 1 1 c1

4.1.1 C++类的定义

类的表述:

class CGoods{

public:

char Name[21] ;

int Amount ;

float Price ;

float Total_value ;

};//最后的分号不可少,这是一条说明语句

关键字class是数据类型说明符,指出下面说明的是类。标识符CGoods是商品这个类的类型名。花括号中是构成类体的系列成员,关键字public是一种访问限定符。


4 1 1 c2

4.1.1 C++类的定义

访问限定符(access specifier):

public(公共的)说明的成员能从外部(类代码外面)进行访问。

private(私有的)和protected(保护的)说明的成员不能从外部进行访问。

每种说明符可在类体中使用多次。

访问限定符的作用域是从该说明符出现开始到下一个说明符之前或类体结束之前结束。

如果在类体起始点无访问说明符,系统默认定义为私有(private用关键字class定义类)。

访问限定符private(私有的)和protected(保护的)体现了类具有封装性(Encapsulation)。


4 1 1 c3

4.1.1 C++类的定义

类的定义:

class类名{

《《private:》

成员表1;》

《public:

成员表2;》

《protected:

成员表3;》

}; //注意:所有说明都以分号结束

其中“class 类名”称为类头(class head)。花括号中的部分称为类体(class body),类体中定义了类成员表(class member list),包含数据和函数。


4 1 1 c4

4.1.1 C++类的定义

成员函数(member function):

class(struct) 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) ;

};//读取商品总价值

课下查找:利用关键字class和struct定义类的区别?


4 1 1 c5

4.1.1 C++类的定义

封装:

类把数据(事物的属性)和函数(事物的行为——操作)封装为一个整体。

接口:

通常数据成员被说明成私有的,函数成员被说明成公有的;从外部对数据成员进行操作,只能通过公有函数来完成,数据受到了良好的保护,不易受副作用的影响。公有函数集定义了类的接口(interface)。

成员函数可以直接使用类定义中的任一成员,可以处理数据成员,也可调用函数成员。


4054818

  • 注意:

    类是一种数据类型,定义时系统不为类分配存储空间,所以不能对类的数据成员初始化。类中的任何数据成员也不能使用关键字extern、auto或register限定其存储类型。

    class CGoods; //类声明,未定义之前


4 1 2

4.1.2成员函数的定义

函数定义:

通常在类定义中,成员函数仅作声明。函数定义通常在类的定义之后进行,其格式如下:

返回值类型类名::函数名(参数表)

{……}//函数体

其中运算符“::”称为作用域解析运算符(scope resolution operator),它指出该函数是属于哪一个类的成员函数。

类CGoods的函数定义


4054818

成员函数

inline声明

class CGoods{

float GetPrice(void) {//类内部定义,默认inline

return price;

}

};

或者

inlinefloat Cgoods:: GetPrice(){//建议此声明方式

return price;

}


4054818

成员函数

const 函数声明: 数据成员只可以读取不可以修改

class CGoods{

float GetPrice(void) const;

};

而且

float Cgoods:: GetPrice() const{

return price;

}


4 1 3

4.1.3对象的创建与使用

定义对象:

对象是类的实例(instance)。定义一种数据类型只是告诉编译系统该数据类型的构造,并没有预定内存。类只是一个样板,以此样板可以在内存中开辟出同样结构的实例——对象。格式如下:

CGoods Car;

这个定义创建了CGoods类的一个对象Car,同时为它分配了属于它自己的存储块,用来存放数据和对这些数据实施操作的成员函数(代码)。对象只在定义它的域中有效。


4054818

对象1

对象n

对象2

数据区

代码区

数据区

代码区

数据区

代码区

...... 

对象存储:

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

图4.1是系统为每一个对象分配了全套的内存。数据区安放成员数据,代码区安放成员函数。

注意:区别同一个类的各个不同的对象的属性是由数据成员决定的,不同对象的数据成员的内容是不一样的;而行为(操作)是用函数来描述的,这些操作的代码对所有对象都是一样的。


4054818

对象1

对象n

对象2

数据区

数据区

数据区

...... 

公共代码区

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

图4.2仅为每个对象分配一个数据区,代码区(放成员函数的区域)为各对象类共用。

图4.1对应的是在类说明中定义函数,而图4.2对应的是在类说明外部定义函数 。


4 1 31

4.1.3对象的创建与使用

内联函数:

使用关键字inline,系统自动采用内联扩展方法实现,每个对象都有该函数一份独立的代码。

如RegisterGoods()函数可定义为:

inlinevoid CGoods::RegisterGoods(char name[] ,

int amount , float price){

strcpy(Name,name) ; Amount=amount ; Price=price ;

}

则每个对象都有RegisterGoods()函数一份独立的代码。


4 1 32

4.1.3对象的创建与使用

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

对象使用规则:

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

【例4.1】中对象car的4个数据成员全是私有的,如写:

car.Name;car. Amount;

car.Price;car.Total_value;

是错误的,必须用对象car所带的公有函数进行访问。


4054818

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

结构化程序设计特点:

采用的是“自顶向下,逐步细化(divide and conquer,stepwise refinement)”的思想。具体操作方法是模块化。模块是按功能来分的,所以也称功能块。在C++中称为一个函数,一个函数解决一个问题,即实现一个功能或一个操作。

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

面向过程程序设计缺点的根源在于数据与数据处理分离。


4054818

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

结构化程序设计弱点:

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

对象概念:

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


4054818

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

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

现实世界

客观世界

计算机世 界

对象

实体

实例化

映射

抽象

抽象

主观世界

抽象类别

图4.3对象、实体与类


4054818

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

  • 对象、类与消息:

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


4054818

表针

旋钮

其他机械机构

属性

行为

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

对 象

调节旋钮


4054818

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

类 对象

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

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

手表 一块手表

手表共有的属性(表针、旋钮、内部结构)

和行为(调节旋钮)

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

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


4054818

发送消息

接收并响应消息

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

消 息

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

启 动

转 向


4054818

机械零件

动作

调节旋钮

读表盘

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

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

封装性

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

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


4054818

汽车

载人

载货

客车

货车

小,速度快

大,速度慢

小轿车

大客车

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

继承与派生

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

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

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

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

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


4054818

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

多态性

多态性指,同一个消息被不同对象接收时,产生不同结果,即实现同一接口,不同方法。

高中生

大学生

计 算平均成绩

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

高数、英语、计算机、线性代数


4054818

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

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


4054818

4.3构造函数和析构函数

  特殊的成员函数,只要创建类类型的对象,都要执行构造函数。功能:为数据成员分配存储空间并初始化每个对象的数据成员。

构造函数(constructor)

4.3.1构造函数的定义与使用

4.3.2析构函数的定义


4 3 1

4.3.1 构造函数的定义与使用

构造函数特征:

1.函数名与类名相同。

class CGoods{

public:

CGoods (char* name , int amount , float price);

CGoods (int amount , float price) const; //error

};

2.构造函数无函数返回类型说明。注意是什么也不写,也不可写void!形式参数可以有也可以没有. 不能声明为const


4054818

3.在程序运行时,当新的对象被建立,该对象所属的类的构造函数自动被调用,在该对象生存期中也只调用这一次。

CGoods book1;

4.构造函数可以重载。说明中可以有多个构造函数,它们由不同的参数表区分;

class CGoods{

public:

CGoods (char* name , int amount , float price);

CGoods (char* name , float price);

CGoods();

};

思考:多个构造函数,调用时选择哪一个?重载函数调用规则来思考。


4 3 11

4.3.1构造函数的定义与使用

5.构造函数可以在类中定义,也可以在类外定义。

class CGoods{

public:

CGoods (char* name , int amount , float price){//类内部定义

strcpy(Name,name) ;Amount=amount ;Price=price ;

Total_value=price*amount ;}

};

或者

class CGoods{

public:

CGoods (char* name , int amount , float price);//类内部声明

};

CGoods::CGoods (char* name , int amount , float price){//类外部定义

strcpy(Name,name) ;Amount=amount ;Price=price ;

Total_value=price*amount ; 

}


4054818

  • 6. 如果类说明中没有给出构造函数,则C++编译器自动创建一个默认的构造函数:

    类名(void) {}

    注意:只要我们定义了一个构造函数,系统就不会自动生成默认的构造函数。

    只要构造函数是无参的或各参数均有默认值的,C++编译器都认为是默认的构造函数,并且默认的构造函数只能有一个 。


4 3 12

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 13

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】完整商品类对象应用实例


4054818

练习

  • 选择一下一个抽象,确定类中需要什么数据,并提供适当的构造函数。并解释你的决定。

    1. Book 2. Date 3. Student 4. Vehicle

    5. Tree 6. Computer 7. Program


4054818

构造函数初始化式

  • 与普通函数一样,构造函数具有名字,形参列表和函数体。不同:定义时可以包含一个初始化列表(不能在声明处指出)

    CGoods ::CGoods (char* name , int amount , float price):Amount(amont),Price(price){

    strcpy(Name,name);

    }

    注意:const,没有默认构造函数的类类型的成员或引用数据成员

    class A{

    private: const int i;

    public:

    A(int var){var=i;} //error

    A(int var):i(var){ }

    };


4054818

成员初始化的次序:

成员被定义的次序决定了初始化的次序.初始化列表只是仅初始化成员的值,而不指定初始化的顺序。

class X{

int i;

int j;

public:

//run-time error: i is initialized before j

X(int val):j(val),i(j){ }

};


4 3 2

4.3.2析构函数的定义

析构函数(destructor)特征:

当一个对象的生命周期结束时,C++会自动调用析构函数(destructor)对该对象并进行善后工作(不严谨)。

1.构函数名与类名相同,但在前面加上字符‘~’,如

~CGoods()。

2.析构函数无函数返回类型,与构造函数在这方面是一样的。但析构函数不带任何参数。

3. 一个类有一个也只有一个析构函数,这与构造函数不同。析构函数可以默认。

4. 对象注销时,系统自动调用析构函数,按成员在类中声明的次序逆序撤销成员(释放存储空间)。

想一想:什么工作?

有些情况,不需要显示定义

析构函数。查找资料,什么

情况下需要什么情况下不需要?

能否举例说明

【例4.2】定义一个矩形类


4054818

4.4引用与复制构造函数

4.4.1 引用

4.4.2 复制构造函数

4.4.3 成员对象与构造函数


4 4 1

4. 4.1引用

引用的导入:

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

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

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


4 4 11

4.4.1引用

引用的定义:

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

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

例如:

double number ;

double &newnum=number ;

newnum是新定义的引用类型变量,它是变量number的别名。

引用主要用于函数之间的数据传递。


4 4 12

4.4.1引用

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

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

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


4054818

  • const 引用(不严谨):指向const 对象的引用

    const int val=1024;

    const int &refval=val;

    int &ref2=val; // error: nonconst reference to a const object

课下查找,以下语句是否合法,并解释:

int i=34; double j=65.14;

const int &ref1=34; int &ref3=24;

const int &ref2=ref1+i; int &ref4=ref1+i;

const int &ref5=j;

可参考课本 p.133


4 4 13

4.4.1引用

【例4.3】引用作为函数的参数。

采用引用调用时,将对实参进行操作。

【例4.4】引用作为函数的返回值

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

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

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


4 4 2

4.4.2 复制构造函数

复制构造函数引入:

同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制是完全可行的。这个复制过程只需要复制数据成员,而函数成员是共用的(只有一份代码)。在建立对象时可用同一类的另一个对象来初始化该对象,这时所用的构造函数称为复制构造函数(Copy Constructor)。

CGoods类,复制构造函数为:

CGoods (CGoods & cgd){

Strcpy (Name , cgd.Name);

Price= cgd.price;

Amount=cgd.Amount;

Total_value=cgd.Total_value;}

复制构造函数形参通常声明为const:

CGoods (const CGoods & cgd)

{....}

思考: 为什么?


4 4 21

4.4.2 复制构造函数

复制构造函数特征:

1.复制构造函数的参数必须采用引用。

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

CGoods (CGoods cgd){

...

...

}


4 4 22

4.4.2 复制构造函数

2.系统会自动提供称为默认的复制构造函数(无显式定义),亦称为默认的按成员初始化。按成员作复制是通过依次复制每个数据成员实现的。

4.在某些情况下,它对类与对象的安全性和处理的正确性还不够(有指针数据成员或为成员分配资源?),这时就要求提供显式的复制构造函数和复制赋值操作符的定义。

3. 赋值运算符“=”称默认的按成员复制赋值操作符(Overloaded Assignment Operator),同类对象之间可以用“=”直接复制 。


4 4 23

4.4.2 复制构造函数

实例:

CGood Car1(“夏利2000”,30,98000.00);

//调用三个参数的构造函数

CGood Car2= Car1; //调用复制构造函数

Car2=Car1; //调用重载的赋值函数(复制赋值操作)

CGood Car3 ( Car1);

//调用复制构造函数,Car1为实参

这样三个对象的初始化结果完全一样。

注意:

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


4 4 24

4.4.2 复制构造函数

隐含的复制构造函数使用:

当函数的形参是非引用(p.125)类型,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参复制到新的对象中。

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

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


4 4 3

4.4.3 成员对象与构造函数

聚合(aggregation ) :

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

class studentID{ .... };

class student{

studentID m_id; char name[20];

student(char *pname, long pid=0):m_id(pid){ ... }

};


4 4 31

4.4.3 成员对象与构造函数

含对象成员的析构函数:

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

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


4 4 32

4.4.3 成员对象与构造函数

构造函数和析构函数的调用规则:

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

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

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


4 4 33

4.4.3 成员对象与构造函数

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

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

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


4054818

int a(1),b(2),c;

c=a+b;

classX x,y,z;

z=x+y;

4.5运算符的重载

运算符重载的概念:

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

运算符重载函数定义:

返回值类型 类名::operator重载的运算符(参数表)

{……}

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


4054818

4.5运算符的重载

class Complex{

double Real,Image;

...

};

细解运算符重载:

复数类+的重载:

Complex Complex::operator+(Complex c){

//显式说明局部对象

Complex Temp(Real+c.Real , Image+c.Image) ;

//注意:直接写对象c的私有成员,不用调c的公有函数处理

return Temp ;}

Complex c2,c3; c2+c3; --->c2.operator+(c3) ;

函数c2.operator创建一个局部的Complex对象Temp,把出现在表达式中的两个Complex类对象c2和c3的实部之和及虚部之和暂存其内,然后把这个局部对象返回,赋给Complex类对象c(注意这里调用了复制构造函数生成一个无名临时对象过渡)。参见图4.8。

思考:此语句被调用到执行完毕,执行了几次构造函数?


4054818

局部对象Temp

当前对象c2

形参对象c

Temp.Real=Real+

c2.Real;

Temp.Image=Image+

c3.Image;

c=return(Temp);

Real

Image

c.Real

c.Image

=

+

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

隐式返回计算结果:

省略局部的Complex对象Temp

Complex Complex::operator+(double d){

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

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

调用两次构造函数


4054818

4.5运算符的重载

思考:

1. 这样的函数设计有没有问题?

2. 可否改为:

Complex& Complex::operator+(Complex c){

Complex Temp(Real+c.Real , Image+c.Image) ;

return Temp ;

}

  • 说明:

  • Complex Complex::operator+(Complex c){

  • Complex Temp(Real+c.Real , Image+c.Image) ;

  • return Temp ;

  • }

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

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

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


4054818

4.5运算符的重载

引用作为参数:

Complex Complex::operator+(const Complex &c){

return Complex(real+c.real , Image+c.Image) ;

}

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

调用一次构造函数


4054818

4.5运算符的重载

  • const引用(指向const对象的引用)进一步说明:

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

  • 例如:

  • double dval=1024; const int &ri=dval;

  • 是正确的,编译器将其转换为:

  • double dval=1024; int temp=dval;

  • const int &ri=temp;

  • 因有临时对象,引用和类型转换都实现了。

  • 当const引用作为形参时,实参也能使用不可寻址的值,并能进行类型转换。


4054818

4.5运算符的重载

默认的复数复制赋值操作符:

函数声明:

Complex &Complex::operator = (const Complex& c){

Real=c.Real; Image=c.Image;

return *this;

}

默认的赋值操作返回对象本身的引用,它可以进行连续赋值。

Complex a,b,c;

a=b;

c=a=b;


4054818

4.5运算符的重载

课本的例子:

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;

}

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


4054818

4.5运算符的重载

小结:

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

2.当用类的成员函数实现运算符的重载时,运算符重载函数的参数(当为双目运算符时)为一个或(当为单目运算符时)没有。运算符的左操作数一定是对象,因为重载的运算符是该对象的成员函数,而右操作数是该函数的参数。(隐含的*this)

3.  单目运算符“++”和“--”存在前置与后置问题。

前置“++”格式为: 返回类型 类名::operator++(){……}

而后置“++”格式为: 返回类型 类名::operator++(int){……}

后置“++”中的参数int仅用作区分。


4054818

4.5运算符的重载

  • 4. 优先级和结合性是固定的

    x==y+z;

    x.operator==(y.operator+(z))


4054818

成员与成员指针操作符

类型字长操作符

sizeof

该操作符右操作数不是表达式

作用域操作符

::

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

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

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

三目条件运算符

? :

禁止重载的理由

运算符名称

运算符

.和.*

4.5运算符的重载

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

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


4054818

4.5运算符的重载

问题:double d=05; Complex c;

例5.7中:

c=c+d;

语句,改为

c=d+c;

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

怎样解决?


4054818

4.6友元

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

上节答案:

用友元函数重载运算符“+”,可以实现

c=d+c;


4054818

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)


4054818

4.6友元

友元函数重载运算符形式:

+有三种形式。另两个的声明为:

friend Complex operator +(Complex , Complex ) ;

friend Complex operator + (Complex , double ) ;

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

可以仅使用友元函数

friendcomplex operator +(complex , complex) ;

实数被默认的构造函数强制转换为虚部为零的复数。d+c1被解释为:operator+(complex(d) , c1)

注意:传值,在函数内是建立了两个复数对象,而把实参的值传进去,进行运算。参见图5.9。


4054818

4.6友元

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

比较:友元函数可以有两个参数,而对应的成员函数只有一个参数,所以友元函数的使用可以更灵活、更方便。

改进:Operator+友元函数的声明可使用引用类型变量

friend Complex operator+(const Complex & c1,

const Complex & c2)

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


4054818

4.6友元

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

单目运算符前“++”的成员函数重载方式如下:

Complex Complex::operator++(){

return Complex(++Real , ++Image) ;}

采用成员函数方式重载与使用都很方便。

友元函数重载后置“++”如下:

friendComplex operator++(Complex & c , int){

//注意友元方式与前者的区别

return Complex(c.Real++ , c.Image++) ;}

采用引用类型,后“++”是直接施加于实参。否则施加于副本,而实参不变。


4054818

友元函数注意点:

1.友元函数不是类的成员函数,在函数体中访问对象的成员,必须用对象名加运算符“.”加对象成员名。但友元函数可以访问类中的所有成员,一般函数只能访问类中的共有成员。

2. 友元函数不受类中的访问权限关键字限制,可以把它放在类的公有、私有、保护部分,但结果一样。

3. 某类的友元函数的作用域并非该类作用域。如果该友元函数是另一类的成员函数,则其作用域为另一类的作用域,否则与一般函数相同。

建议:万不得已不要使用友元

友元类:整个类可以是另一个类的友元。友元类的每个成员函数都是另一个类的友元函数,都可访问另一个类中的保护或私有数据成员。定义方法如下:

class A{……

friend class B; //声明B为A的友元类

…… };


4054818

4.7 静态成员

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

Student{

static long classID;

};

int Student::classID=191131;

Student s1,s2,s3;

Student{

long classID;

};

Student s1,s2,s3;

4.7.1 静态数据

4.7.2 静态函数成员(选读)


4 7 1

4.7.1 静态数据

静态数据成员定义与使用:

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

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

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

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

类名::静态数据成员名


4 7 11

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.7.2 静态函数成员(选读)

静态函数成员的使用:

函数成员说明为静态,将与该类的不同对象无关。静态函数成员的调用,在对象之外可以采用下面的方式:

类名::函数名(对象名,参数表);

任一类对象名.函数名(对象名,参数表);

静态函数成员多为公有的。在例4.8中的复数类中的函数成员print(), 改为静态的则可如下表达:

staticvoid print(complex& ob){

cout<<”Real=”<<ob.Real<<’\t’

<<”Image=”<<ob.Image<<’\n’ ;}

参数是为了告诉C++系统应取哪一个对象的数据。


4054818

4.8 结构

结构类型的引入:

在C++中结构(structure)与类几乎是完全一样的类型,差别仅仅在于默认情况下结构的成员为公有的。

在C语言阶段,结构就已存在,但它只有公有的数据成员。正因为如此,C++程序员仍然使用结构,但是只为结构安排公有的数据成员。因为这样程序更易读易懂。在程序设计中常把结构类型的数据作为类的数据成员。

C风格的定义:

struct 结构类型名{

类型名 变量1;

《类型名 变量2;…;》

}; //最后的分号不可少


4054818

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 ; }; //工资


4054818

4.8 结构

变量定义与初始化:

结构是一种派生数据类型,定义结构时并不分配存储空间,只有定义了结构类型的变量,编译系统才为结构变量分配存储空间。定义变量方法如下:

inventory car , motor ;

初始化是用花括号中顺序填入结构中的(公有数据)成员的初始值完成的:

employee emp1={“朱明”,“四牌楼2号”,210096,3792666,2430.0},

emp2={“沈俊”,“丁家桥15号”,210009,3273389,1920.0};

结构变量的访问与类一样,可使用成员访问操作符之一:点操作符,对成员一个个进行:

变量名 . 成员名


4054818

4.8 结构

结构类型使用说明:

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

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

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


4054818

4.8 结构

(4)结构可以嵌套:

struct mail{

char address[30] ; //地址

long int zip ; //邮政编码

long int telenum ; };//电话号码

structemployee{

charname[25] ; //员工姓名

mail addinfo ; //结构作为成员,嵌套

double salary ; };//工资

用连续点号来访问结构变量的结构成员中的成员:

employee emp1={“朱明”,“四牌楼2号”,210096,

3792666,2430.0};

cout<<emp1.addinfo.telenum ;

输出为3792666。


4054818

4.8 结构

联合(选读):

union共同体变量名{

类型 成员名1;

《类型 成员名2;…;》

};

联合(union)与结构的区别是:结构变量的各成员同时被分配了各自独立的内存区,而联合变量的各个成员的存储开始地址都相同,所以在任一时刻联合变量只能存储一个成员。

系统为联合变量分配空间时按需要最大存储量的成员大小分配内存空间。

联合被称为一种特殊的类(它因编译器不能知道成员的类型,而没有构造函数和析构函数,所以联合的对象不是由构造函数生成的。故称特殊的类)。


4054818

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号房间排在第一,楼层越高、同一层房间号越大的房间排得越后。


4054818

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

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

名字空间域声明:

名字空间域相当于一个更加灵活的文件域(全局域),可以用花括号把文件的一部分括起来,并以关键字namespace开头给它起一个名字:

namespace ns1{

float a,b,c;

fun1(){……}

… }

花括号括起来的部分称声明块。声明块中可以包括:类、变量(带有初始化)、函数(带有定义)等。


4054818

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

名字空间域访问:

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


4054818

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

名字空间域嵌套:

名字空间域可分层嵌套,同样有分层屏蔽作用。例如:

namespace cplusplus_primer{

namespace Matrixlib{//名字空间嵌套

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

…... }

}

访问matrix,可写限定修饰名:cplusplus_primer::Matrixlib::matrix

最外层的名字空间域称为全局名字空间域(global namespace scope),即文件域。


4054818

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

using声明:

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

using cplusplus_primer::Matrixlib::matrix;

//名字空间类成员matrix的using声明

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


4054818

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

using指示符:

使用using指示符可以一次性地使名字空间中所有成员都可以直接被使用,比using声明方便。using指示符以关键字using开头,后面是关键字namespace,然后是名字空间名。

标准C++库中的所有组件都是在一个被称为std的名字空间中声明和定义的。在采用标准C++的平台上使用标准C++库中的组件,只要写一个using指示符:

using namespace std;

就可以直接使用标准C++库中的所有成员。这是很方便的。


4054818

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

名字空间域补充说明:

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

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


4054818

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

类域:

类体部分称为类域。在类域中说明的标识符仅在该类的类域内有效。必须加上“类名::”作限定修饰。

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

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

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


4054818

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

标识符解析:

编译器对名字(标识符)的解析分两步,第一步查找在声明中用到的名字,包括数据成员和函数成员声明中用到的参数类型,第二步才是函数成员体内的名字。例如:

class string{//字符串类

public:

typedefint index_type;//为易读易懂用下标型命名

char GetstringElement(index_type elem) {

return Astring[elem];}// Astring未说明

private:

char Astring[30];//Astring后说明

};

表面上看是错的;实际上是对的。因为Astring名字的解析是在第一步,而函数使用它是在第二步。


4 10 windows

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

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


4 10 windows1

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

面向过程的程序结构:

程序=算法+数据结构。

算法实际上就是功能抽象。在面向过程的程序设计中程序是模块化的,模块是分层次的,层与层之间是一个从上往下的调用关系。图4.11给出了这种层次模块的调用关系。

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


4 10 windows2

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

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


4 10 windows3

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

面向对象的程序构造:

对象=(算法+数据结构),

程序=对象+对象+……+对象+消息。

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


4 10 windows4

4. 10面向对象程序的组织与Windows下的实现

对象与对象之间怎样建立有效的联系,互相调用的思想明显不行。实际上采用的是用消息传递机制来协调各对象的运行,如图4.12:

图4.12 面向对象的程序组织


4 10 windows5

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

消息:

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


4 10 windows6

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

消息和方法:

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

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


4 10 windows7

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

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

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


4 10 windows8

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

消息传递,事件驱动:

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

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

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


4 10 windows9

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

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


4054818

第五章 类与对象

结束

谢谢!


4 1 21

4.1.2成员函数的定义

voidCGoods::RegisterGoods(char name[] , int amount , float price){

strcpy(Name , name) ; //字符串复制函数

Amount=amount ; Price=price ;

}

voidCGoods::CountTotal(void){

Total_value = Price*Amount;}

voidCGoods::GetName(char name[]){

strcpy(name , Name);}

intCGoods::GetAmount(void){return(Amount) ;}

floatCGoods::GetPrice(dvoi){return(Price) ;}

floatCGoods::GetTotal_value(void){return(Total_value) ;}


4054818

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

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

#include<iostream>

#include<iomanip>

#include<string>

using namespace std;

//省略了类定义

intmain( ){

CGoodscar ;

char string[21] ;

intnumber ;

float pr ;


4054818

cout<<“请输入汽车型号:”;

cin.getline(string , 20) ; //输入串长必须小于20

cout<<“请依次输入汽车数量与单价:”;

cin>>number>>pr ;

car.RegisterGoods(string , number , pr) ;

car.CountTotal() ;

string[0]=’\0’ ; //字符串string清零

car.GetName(string) ; //string赋值car.Name

cout<<setw(20)<<string<<setw(5)

<<car.GetAmount() ; //A

cout<<setw(10)<<car.GetPrice()<<setw(20)

<<car.GetTotal_value()<<endl ; //B

return 0;}

成员名

Name[21] ;

Amount ;

Price ;

Total_value ;

minicar

5

2

string[21]

number

Pr

minicar

minicar

minicar

5

2

10

5

2

10


4 1 1

【例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 11

【例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.Name

cout<<setw(20)<<string<<setw(5)

<<Car1.GetAmount();

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

<< Car1.GetTotal_value()<<endl;

Car2.GetName(string); //string赋值car.Name

cout<<setw(20)<<string<<setw(5)

<< Car2.GetAmount();

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

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


4054818

【例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();};


4054818

【例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’; }


4054818

【例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;

}


4054818

【例4.3】引用作为形参

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为引用时内存分配示意

X

y

d1

d2

temp

2.718

1.414

1.414


4054818

【例4.4】引用作为返回值

【例4.4】采用不同返回方式的求正方形面积函数的比较。

doubletemp; //全局变量

double fsqr1(double a){

temp=a*a ; returntemp;}

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

运行结果一样,但在内存中的活动却不同。

double & fsqr2(double a){

double temp=a*a ;

return temp;} 可以吗?


4054818

【例4.4】引用作为返回值

图4.7 引用返回

图4.6 普通返回


4054818

【例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 ;

elseif(grade>=60) return typeB;

elsereturn 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 ;}


4054818

【例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; }


4054818

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

~studentID(){

cout<<“删除学生名:"<<name<<endl;} };

int main(){

student ss("朱明",82020132);

return 0;

}

这样运行结果为:

赋给学生的学号:08002132

学生名:朱明

删去学生名:朱明

删去学号:08002132

在student构造函数头部的冒号表示要对对象成员的构造函数进行调用。但在构造函数的声明中,冒号及冒号以后部分必须略去。


4054818

【例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;}


4054818

【例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;

}


4054818

【例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;}


4054818

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 //进入入口函数main

Initializing 0 0 //用默认的构造函数建立com1

Initializing 5.6 7.5 //用带参数的构造函数建立com2

Copy 0 0 //用复制的构造函数建立com3

0+0i //打印com3

0+0i //打印global

Copy 5.6 7.5

//调用全局函数fun(),调用复制构造函数建立临时对象com

Entering function //进入全局函数fun()

Copy 5.6 7.5 //进入global.assign(),

//调用复制构造函数建立临时对象新com


4054818

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】演示对象创建和撤消的对应关系

Destructor

//退出global.assign(),调用析构函数,清新com

Exiting function //将退出fun()

Copy 5.6 7.5//返回对象时调用复制构造函数建立临时对象

Destructor //退出fun(),调用析构函数,清fun()的com

Destructor //返回的临时对象赋给com1后析构

5.6 + 7.5i //打印com1

Exit main//将退出入口函数main

Destructor//退出入口函数前,调用析构函数,清com3

Destructor//退出入口函数前,调用析构函数,清com2

Destructor//退出入口函数前,调用析构函数,清com1

Destructor//退出入口函数前,调用析构函数,清global

本例运行结果应与程序对比,看看程序运行的细节。


4054818

【例4.8】复数类

class Complex{

doubleReal,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 ; }

验证主函数


4054818

【例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); }


4054818

【例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代替d

c=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

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

class Complex{

doubleReal,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 &);

friendComplex &operator +=(Complex &,const Complex &);

frienddouble abs(Complex &);

friend Complex operator*(const Complex &,const Complex &);

friend Complex operator/(const Complex &,const Complex &);

};


4 8 11

【例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 12

【例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;}


4054818

【例4.9】静态数据成员

课下查找:

类数据成员:

const static int count; 在哪里初始化?

#include <iostream.h>

class Ctest{

private:

staticint 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++编译器为静态数据成员分配存储空间。


4054818

总结

  • 1. 类和对象

  • 2. 定义一个类,编译器定义的默认成员函数

    class X{};

    1). X::X(){};

    2).~X::X(){};

    3).X::X(const X&){}

    4). X& Xoperator=(const X&){};

    3. 形式参数用引用&;4.不建议使用友元


  • Login