第4章 类与对象 数学与统计科学学院
目 录 面向对象程序设计的基本特点 4.1 类和对象 4.2 4.3 构造函数和析构函数 类的组合** 4.4 UML图形标识** 4.5 目 录 面向对象程序设计的基本特点 4.1 类和对象 4.2 4.3 构造函数和析构函数 类的组合** 4.4 UML图形标识** 4.5 结构体和联合体 4.6 4.7 综合实例——个人银行账户管理程序
4.1 面向对象程序设计的基本特点 面向对象的方法 目的: 实现软件设计的产业化。 观点: 自然界是由实体(对象)所组成。 程序设计方法: 3 4.1 面向对象程序设计的基本特点 面向对象的方法 目的: 实现软件设计的产业化。 观点: 自然界是由实体(对象)所组成。 程序设计方法: 使用面向对象的观点来描述模仿并处理现实问题。 要求: 高度概括、分类、和抽象。
4.1 面向对象程序设计的基本特点 4.1.1 抽象 抽象是对具体对象(问题)进行概括,抽出这一类对象的公共性质并加以描述的过程。 先注意问题的本质及描述,其次是实现过程或细节。 数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。 代码抽象:描述某类对象的共有的行为特征或具有的功能。 抽象的实现:通过类的声明。
4.1 面向对象程序设计的基本特点 4.1.1 抽象 抽象实例——钟表 数据抽象: 5 4.1 面向对象程序设计的基本特点 4.1.1 抽象 抽象实例——钟表 数据抽象: int hour,int minute,int second 代码抽象: setTime(),showTime() class Clock { public: void setTime(int newH, int newM, int newS); void showTime(); private: int hour, minute, second; };
4.1 面向对象程序设计的基本特点 4.1.1 抽象 抽象实例——人 数据抽象: 6 4.1 面向对象程序设计的基本特点 4.1.1 抽象 抽象实例——人 数据抽象: string name, string gender, int age, int id 代码抽象: 生物属性角度: getCloth(), eat(), step(),… 社会属性角度: work(), promote() ,…
4.1 面向对象程序设计的基本特点 4.1.2 封装 将抽象出的数据成员、代码成员相结合,将它们视为一个整体。 7 4.1 面向对象程序设计的基本特点 4.1.2 封装 将抽象出的数据成员、代码成员相结合,将它们视为一个整体。 目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。 实现封装:类声明中的{ }
实例: 4.1 面向对象程序设计的基本特点 4.1.2 封装 class Clock { 8 4.1 面向对象程序设计的基本特点 4.1.2 封装 实例: class Clock { public: void setTime(int newH, int newM, int newS); void showTime(); private: int hour, minute, second; }; 外部接口 特定的访问权限
9 4.1 面向对象程序设计的基本特点 4.1.3 继承 是C++中支持层次分类的一种机制,允许程序员在保持原 有类特性的基础上,进行更具体的说明。 实现:声明派生类——见第7章
4.1 面向对象程序设计的基本特点 4.1.4 多态 多态:同一名称,不同的功能实现方式。 目的:达到行为标识统一,减少程序中标识符的个数。 10 4.1 面向对象程序设计的基本特点 4.1.4 多态 多态:同一名称,不同的功能实现方式。 目的:达到行为标识统一,减少程序中标识符的个数。 实现:重载函数和虚函数——见第8章
4.2 类和对象—补充较多 类是一种构造数据类型,类是对客观事物的抽象,类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分。 利用类可以实现数据的封装、隐藏、继承与派生。
4.2.1 类的定义格式 类的定义格式一般分为说明部分和实现部分。 说明部分:用来说明类中的数据成员和成员函数。 成员函数是用来对数据成员进行操作的。 实现部分:用来对成员函数定义。 说明部分的一般格式为: class 类类型名{ private: 私有成员说明 public: 公有成员说明 }; 除本类中的成员函数及友元外,其他类外函数不能访问。 私有访问权限,为默认值,可缺省 程序中的任何函数都可访问 公有访问权限
4.2.1 类的定义格式 class Tperson { private: char name[10]; int age; 例如:定义一个Tperson类,设其具有的特征:属性(姓名、年龄、性别),方法(输出一个人的属性);则对应4个成员:3个数据成员,1个成员函数。数据成员一般不能由外界直接访问,随意修改。而只能通过成员函数进行访问。所以3个数据成员定义为私有成员,print成员函数定义为公有成员。则有: class Tperson { private: char name[10]; int age; char sex; public: void print( ); }; 说明部分 数据成员的说明形式与变量的定义形式相似 类名通常用大写字母T开始,以区别其他标识符 成员函数的说明形式与一般函数的说明形式一致
4.2.1 类的定义格式 类的实现部分:即定义它的成员函数。 若在类体外定义成员函数,应用作用域限定符::指明该函数是哪个类中的成员函数。 格式为: 函数返回值类型 类名::成员函数名(参数表) {函数体} 如: void Tperson::print( ) { cout<<name<<age<<sex<< endl; } 除特殊指明外,成员函数操作的是同一对象中的数据成员。其中如name等。 若在成员函数中调用非成员函数(没有类名的函数,如全局函数),则可用不带类名的::来表示。 类的实现部分
void Set(int m, int d, int y) // 设置日期值 { month=m; day=d; year=y; } 4.2.1 类的定义格式 当成员函数的规模较小时,语句只有1-5行,符合内联函数条件,则可在类中定义成员函数,成为内联成员函数。 如: class TDate { public: void Set(int m, int d, int y) // 设置日期值 { month=m; day=d; year=y; } void Print( ); // 打印输出 private: int month; int day; int year; }; 内联成员函数 若将内联成员函数定义在类说明之外则要加上inline。 由于类名是成员函数名的一部分,所以一个类的成员函数与另一个类的成员函数可同名。
void SetPoint(int x, int y) // 置坐标值 { X=x; Y=y; //给数据成员赋值 } 4.2.1 类的定义格式 如定义一个类: class TPoint { public: void SetPoint(int x, int y) // 置坐标值 { X=x; Y=y; //给数据成员赋值 } int Xc ( ) {return X;} //提供X坐标值 int Yc ( ) {return Y;} //提供Y坐标值 void Move(int x1,int y1 ); // 移动点 private: int X; int Y; }; void Tpoint::Move(int x1,int y1) { X=X+x1; Y=Y+y1; } 3个内联成员函数的定义 2个私有数据成员的定义 类体外成员函数的定义
注意事项: 关于类定义的6点说明: ① 定义类的关键字通常用class,也可以用struct等。 使用class定义的类,默认的访问权限是private,用struct定义的类,默认的访问权限是public。 ② 类的定义由两大部分构成:说明部分和实现部分。 说明部分包括类头和类体。实现部分是对类中说明的成员函数的定义,如果类中说明的成员函数都是定义在类体内,则实现部分可省。
注意事项: ③ 类的成员分为数据成员和成员函数两种。 类中的数据成员的类型可以为任意的。 包含整型、浮点型、字符型、数组、指针和引用等; 当另一个类的对象作为该类的成员时,如果另一个类的定义在后,需要提前说明; class N; class M; {public: . private: N n; } class N void f(); }
× √ √ 注意事项: ③ 类的成员分为数据成员和成员函数两种。 类中的数据成员的类型可以为任意的。 自身类的对象不可以作该类的成员;自身类的指针或引用,可以作该类的成员; × √ √ ; ; ;
注意事项: 错误 ③ 类的成员分为数据成员和成员函数两种。 在类体中不允许对所定义的数据成员进行初始化。 class TDate { public:… private: int year(1998),month(4); … }; 成员函数的说明是函数原型,定义在类体内的为内联函数。 错误
注意事项: ④ 类的任何成员都具有访问权。访问权限与修饰符出现 的先后次序无关,并且允许多次出现。 public(公有的):公有成员不仅在类体内是可见的,而且在类体外也是可见的。 private(私有的):私有成员仅在类体内是可见的,在类体外是被隐藏的。 protected(保护的):保护成员对于定义它的类来讲,相当于私有成员;对于该类的派生类来讲,相当于公有成员,派生类中是可见的。 数据成员和成员函数都可以说明公有成员或私有成员,通常较多的成员函数被说明为公有成员,它们成为该类对外服务的接口;较多的数据成员被说明为私有成员,它们被隐蔽在封装体内。
注意事项: ⑤ 成员函数可以定义在类体内,也可以定义在类体外。定义在类体外的成员函数在定义时必须加上类名限定。 格式: <类名>::<函数名>(<参数表>) 这说明所定义的函数是属于指定类的成员函数。成员函数可以都定义在类体内,这时类的实现部分可以省略。 例:定义一个关于日期的类。 说明部分: class TDate { public: void SetDate(int y,int m,int d); int IsLeapYear( ); void Print( ); private: int year,month,day; }; 类中说明了:3个成员函数,3个数据成员
例:定义一个关于日期的类。 实现部分: 作用域运算符 :: 作用:标识某个成员属于哪个类; 作用域运算符 :: 作用:标识某个成员属于哪个类; 格式:<类名>::<函数名>(<参数表>)
上例中类的另一种实现方式: 成员函数定义 成员函数定义 成员函数定义 ;
注意事项: ⑥习惯将类的定义代码放到一个头文件中,以后若要使用则用文件包含命令包含。 例如,可将之前定义的TDate类放到名为tdate.h的头文件中。
如何定义一个类? 对象的特征 类是具有相同属性的对象的集合,需要实现对这些对象的描述和抽象。 抽象 描述 静态特征 对象的属性 类的数据成员 动态特征 对象的操作 类的成员函数 例:定义一个关于学生的类。 要求: 1、类中包含三个数据成员:分别描述学生的学号、姓名及成绩信息; 2、类中包含是三个成员函数:分别用来对数据成员赋值、更改学生成绩及输出学生的信息。
说明部分: 三个成员函数:分别用来对数据成员赋值、更改学生成绩及输出学生的信息 三个数据成员:分别描述学生的学号、姓名及成绩信息
实现部分: 三个成员函数: 对数据成员赋值; 更改学生成绩; 输出学生的信息。
4.2.2 对象的定义方法 定义了一个类只是定义了一种类型,它并不分配空间,不能进行操作。只有用该类创建对象后,系统才为对象分配存储空间。 对象是类的实例,它是具体的实体,任何对象都是属于某个类的对象,对象具有属于它的类的所有成员。 定义对象之前要先定义好类。 对象中仅仅存放类中的(非静态)数据成员,类中成员函数不存放在每一个对象中,它们被存放在一个可以被对象访问的公共区中。
10月23日补课
void SetDate(int y,int m,int d); int IsLeapYear( ); void Print( ); 4.2.2 对象的定义方法 (1)先定义类类型,再定义对象。 (2)定义类类型同时定义对象 对象d1 year month day p (3)使用无名类直接定义对象 class { public: void SetDate(int y,int m,int d); int IsLeapYear( ); void Print( ); private: int year,month,day; }; Ts d1,d2[10],*p=&d1; Ts d1,d2[10],*p=&d1;
4.2.3 对象成员的表示 对象的成员就是该对象所属类的成员。 (1)一般对象的成员表示成员选择运算符 . 。 4.2.3 对象成员的表示 对象的成员就是该对象所属类的成员。 (1)一般对象的成员表示成员选择运算符 . 。 <对象名>.<数据成员名> <对象名>.<成员函数名>(<参数表>) 通过对象可以访问类中的公有数据成员和成员函数。 例: Tdate d; d.year //设成员year和SetDate如果是public访问权限时 d.SetDate(y, m, d) class Tdate { public: void SetDate(int y,int m,int d); int IsLeapYear( ); void Print( ); private: int year,month,day; };
4.2.3 对象成员的表示 对象d month day year 对象的成员就是该对象所属类的成员。 4.2.3 对象成员的表示 对象的成员就是该对象所属类的成员。 (2)用指向对象的指针变量来引用成员时可用成员选择运算符 -> <对象指针名> -> <数据成员名> <对象指针名> -> <成员函数名>(<参数表>) 方式①: 通过指向类类型对象的指针访问类的公有数据成员和成员函数时。 例:Tdate d; Tdate *P=&d; //指针变量P指向对象d P->day=30; P->Print( ); 对象d month day year P class Tdate { public: void SetDate(int y,int m,int d); int IsLeapYear( ); void Print( ); private: int year,month,day; };
4.2.3 对象成员的表示 对象d month day year 对象的成员就是该对象所属类的成员。 4.2.3 对象成员的表示 对象的成员就是该对象所属类的成员。 (2)指向对象指针的成员表示用成员选择运算符 . 和取内容运算符* 。 方式②: (﹡<对象指针名>) · <成员名> (﹡<对象指针名>) · <成员名>(<参数表>) 例:Tdate d; Tdate *P=&d; //指针变量P指向对象d (﹡P) · month (﹡P) · SetDate(y,m,d) class Tdate { public: void SetDate(int y,int m,int d); int IsLeapYear( ); void Print( ); private: int year,month,day; }; 对象d month day year P
4.2.3 对象成员的表示 对象d month day year 对象的成员就是该对象所属类的成员。 (3)对象引用: 4.2.3 对象成员的表示 对象的成员就是该对象所属类的成员。 (3)对象引用: <对象引用名> · <成员名> <对象引用名> · <成员名>(<参数表>) 与一般对象的成员表示相同; 例: TDate d,&rd=d; rd.year rd.month rd.day rd.SetDate(y, m, d) class Tdate { public: void SetDate(int y,int m,int d); int IsLeapYear( ); void Print( ); private: int year,month,day; }; 对象d month day year 别名rd
4.2.3 对象成员的表示 对象的成员就是该对象所属类的成员。 (4)对象数组元素的成员表示同一般对象 4.2.3 对象成员的表示 对象数组d 对象的成员就是该对象所属类的成员。 d[0] (4)对象数组元素的成员表示同一般对象 <数组名>[<下标>]. <成员名> 例如: Tdata d[4]; d[0].SetDate(y, m, d); year month day d[1] year month day class Tdate { public: void SetDate(int y,int m,int d); int IsLeapYear( ); void Print( ); private: int year,month,day; }; d[2] year month day d[3] year month day
4.2.3 对象成员的表示 例:分析程序的输出结果,并熟悉对象的定义和成员的表示。 pd d1 d2 year month day 24 6 4.2.3 对象成员的表示 例:分析程序的输出结果,并熟悉对象的定义和成员的表示。 24 6 2005 8 2 2000 year month day d1 d2 pd
上例微改! 注意:在成员函数中访问成员无须加对象名来作为前缀。 编译出错!因为: 1、在程序中,可以访问类的公有成员; 2、类的成员函数只能通过该类定义的对象来调用; 3、类的私有成员不能被程序所访问,只能被类的成员函数所访问, 上例微改! 注意:在成员函数中访问成员无须加对象名来作为前缀。
4.2.3 对象成员的表示 例:分析程序的输出结果,并熟悉对象的定义和成员的表示。 pd d1 d2 year month day 24 6 4.2.3 对象成员的表示 例:分析程序的输出结果,并熟悉对象的定义和成员的表示。 24 6 2005 8 2 2000 year month day d1 d2 pd 原来在对象调用成员函数时,除了传递对应的实参外,还将对象的地址传给成员函数。 如: void SetData(TDate *this, int y, int m, int d) 目标对象的地址被隐含的形参this指针保存,等同于执行了this=&对象名。 成员函数中所有对数据成员的访问都隐含加上了前缀this->。 所以:year 实为 this-> year 一个类中所有对象调用的成员函数都是在同一代码段。那么成员函数如何区分调用的成员来自哪个对象呢?
4.2 类和对象 4.2.4 类的成员函数 可以直接在类中给出函数体,形成内联成员函数。 40 4.2 类和对象 4.2.4 类的成员函数 可以直接在类中给出函数体,形成内联成员函数。 也可以在类中说明函数原型,在类外给出函数体实现, 在函数名前使用类名加以限定,还可以使用inline定义 成内联函数。 允许声明重载函数和带形参默认值的函数。
4.2 类和对象 4.2.4 类的成员函数--内联成员函数 内联成员函数举例(一) 41 4.2 类和对象 4.2.4 类的成员函数--内联成员函数 内联成员函数举例(一) class Point { public: void init(int initX, int initY) { //内联函数的隐式声明-不要有复杂结构 x = initX; y = initY; } int getX() { return x; } //内联函数的隐式声明 int getY() { return y; } //内联函数的隐式声明 private: int x, y; };
42 42 4.2 类和对象 4.2.4 类的成员函数--内联成员函数 内联成员函数举例(二) //内联函数的显式声明
4.2 类和对象 4.2.4 类的成员函数--带默认形式参数的成员函数 class Point { public: void init(int initX=5, int initY=6) { //内联函数的隐式声明 x = initX; y = initY; } int getX() { return x; } //内联函数的隐式声明 int getY() { return y; } //内联函数的隐式声明 private: int x, y; }; //带默认形式参数的成员函数
4.2.5 例4-1 时钟类的完整程序 44 #include<iostream> using namespace std; class Clock { public: void setTime(int newH, int newM, int newS); void showTime(); private: int hour, minute, second; };//类的实现 void Clock::setTime(int newH, int newM,int newS) { hour = newH; minute = newM; second = newS;} void Clock::showTime() { cout << hour << ":" << minute << ":" << second; } int main() { Clock myClock; myClock.setTime(8, 30, 30); myClock.showTime(); return 0; } 对象myClock hour minute second 30 8
4.3构造函数和析构函数
4.3 构造函数和析构函数 构造函数和析构函数是在类体中说明的两种特殊成员函数。 4.3.1 构造函数的功能、种类和特征 1.构造函数的功能 在创建对象时给对象分配内存空间 ,并可使用给定值来初始化对象,还可以定义一些与初始化对象无关的语句。
4.3 构造函数和析构函数 2.构造函数的种类 特点:不带参数。 3种: (1)默认构造函数 特点:不带参数。 如果在一个类中,用户没有定义任何构造函数时,系统会自动创建一个默认的构造函数,给对象初始化。 #include <iostream> using namespace std; class Date1 { public: Date1(int y,int m,int d){ year=y; month=m; day=d; cout<<"Constructor called.\n"; // Constructor构造函数 } Date1( )//默认构造函数,不带参数 { cout<<"Default constructor called.\n"; }// Default缺省 void Print( ); private: int year,month,day; }; 其他代码省略
4.3 构造函数和析构函数 2.构造函数的种类 可以带有一个或多个参数。 3种: (2)带参数的构造函数 创建对象时,如果被创建的对象带有实参时,系统将根据实参的个数,调用相应参数的构造函数给对象进行初始化。 #include <iostream> using namespace std; class Date1 { public: Date1(int y,int m,int d){ //带参数的构造函数 year=y; month=m; day=d; cout<<"Constructor called.\n"; // Constructor构造函数 } Date1( )//默认构造函数.,不带参数 { cout<<"Default constructor called.\n"; }// Default缺省 void Print( ); private: int year,month,day; }; 其他代码省略
4.3 构造函数和析构函数 2.构造函数的种类 (3)拷贝(复制)构造函数 3种: (3)拷贝(复制)构造函数 是使用已知对象给所创建对象进行初始化时所用的构造函数。拷贝构造函数带有一个参数,该参数是该类的对象引用。 格式: <构造函数名> (<类名> & <对象引用名>) { <函数体> } 如果一个类中,用户没有定义拷贝构造函数时,系统自动创建一个默认的拷贝构造函数。
补充 拷贝构造函数举例 p1 p2 p3 p4 6 9 6 9 6 9 Point::Point(Point &rp) #include <iostream> using namespace std; class Point { public: Point(int i,int j) { X=i;Y=j; cout<<"执行了两参构造函数.\n";} Point(Point &rp); ~Point( ) { cout<<"执行了析构函数.\n"; } int Xcood( ) { return X; } int Ycood( ) { return Y; } private: int X,Y; }; Point::Point(Point &rp) { X=rp.X; Y=rp.Y; cout<<"执行了拷贝构造函数.\n"; } void main( ) { Point p1(6,9); Point p2(p1); cout<<"跟踪1\n"; Point p3=p2,p4(0,0); cout<<"跟踪2\n"; p4=p1; //没有调用拷贝构造函数 cout<<"p3=("<<p3.Xcood( )<<','<<p3.Ycood( )<<")\n"; cout<<"p4=("<<p4.Xcood( )<<','<<p4.Ycood( )<<")\n"; p1 p2 p3 p4 6 9 6 9 6 9
4.3 构造函数和析构函数 注意: 1、构造函数的函数名同类名,且无函数返回值,通常声明为公有的成员函数; 自动生成隐含的拷贝构造函数
4.3 构造函数和析构函数 注意: 2、如果类中没有声明拷贝(复制)构造函数,编译器会自动生成隐含的拷贝构造函数;如果类中声明了构造函数,编译器不会再生成隐含的默认的构造函数; 自动生成隐含的拷贝构造函数
如果类中声明了构造函数,编译器不会再生成隐含的默认的构造函数;
4.3 构造函数和析构函数 3、函数体为空的构造函数不一定不做任何的事,在继承中会有所了解; 注意: 3、函数体为空的构造函数不一定不做任何的事,在继承中会有所了解; 4、构造函数可以直接访问本类的所有数据成员,构造函数可以是内联函数、可以带有参数表、可以带默认的形参值,也可以重载。 #include <iostream> using namespace std; class Date1 { public: Date1(int y=2016,int m=10,int d=21){ year=y; month=m; day=d; cout<<"Constructor called.\n"; // Constructor构造函数 } Date1( )//默认构造函数.,不带参数 { } void Print( ); private: int year,month,day; }; 其他代码省略
4.3 构造函数和析构函数 5、普通构造函数在对象创建时调用,拷贝构造函数在以下3种情况会被调用: 注意: 5、普通构造函数在对象创建时调用,拷贝构造函数在以下3种情况会被调用: 当用类的一个对象去初始化该类的另一个对象时。
例如:前面的 拷贝构造函数举例 当用类的一个对象去初始化该类的另一个对象时。 Point::Point(Point &rp) #include <iostream> using namespace std; class Point { public: Point(int i,int j) { X=i;Y=j; cout<<"执行了两参构造函数.\n";} Point(Point &rp); ~Point( ) { cout<<"执行了析构函数.\n"; } int Xcood( ) { return X; } int Ycood( ) { return Y; } private: int X,Y; }; Point::Point(Point &rp) { X=rp.X; Y=rp.Y; cout<<"执行了拷贝构造函数.\n"; } void main( ) { Point p1(6,9); Point p2(p1); cout<<"跟踪1\n"; Point p3=p2,p4(0,0); cout<<"跟踪2\n"; p4=p1; //没有调用拷贝构造函数 cout<<"p3=("<<p3.Xcood( )<<','<<p3.Ycood( )<<")\n"; cout<<"p4=("<<p4.Xcood( )<<','<<p4.Ycood( )<<")\n"; 当用类的一个对象去初始化该类的另一个对象时。
4.3 构造函数和析构函数 5、普通构造函数在对象创建时调用,拷贝构造函数在以下3种情况会被调用: 注意: 5、普通构造函数在对象创建时调用,拷贝构造函数在以下3种情况会被调用: 当用类的一个对象去初始化该类的另一个对象时。 如果函数的形参是类的对象,调用函数时,把对象值传递时,才会调用拷贝构造函数,如果传递引用,则不会调用拷贝构造函数。 课本p101举例 //假设Point是已经定义好的类名,getX是其成员函数 //f不是成员函数,是一个一般的函数 void f(Point p) {cout<<p.getX()<<endl; } int main() {Point a(1,2); f(a); return 0;
4.3 构造函数和析构函数 5、普通构造函数在对象创建时调用,拷贝构造函数在以下3种情况会被调用: 注意: 5、普通构造函数在对象创建时调用,拷贝构造函数在以下3种情况会被调用: 当用类的一个对象去初始化该类的另一个对象时。 如果函数的形参是类的对象,调用函数时,把对象值传递时,才会调用拷贝构造函数,如果传递引用,则不会调用拷贝构造函数。 如果函数的返回值是类的对象,函数执行完成返回调用者时。 课本p101举例 //假设Point是已经定义好的类名,g是一个一般函数 Point g() { Point a(1,2); //2: 调用2参构造函数 return a; //3: 调用拷贝构造函数 } int main( ) { Point b;// 1:调用默认的构造函数 b=g(); //创建的无名临时对象为b赋值 return 0; }
4.3 构造函数和析构函数 6、编译器会对拷贝构造函数的调用进行优化,避免不必要的拷贝构造函数的调用,故不同编译系统输出结果有区别。 注意: 6、编译器会对拷贝构造函数的调用进行优化,避免不必要的拷贝构造函数的调用,故不同编译系统输出结果有区别。 7、如果被初始化的对象是auto自动存储类的,则初始化的值是无意义的;如果被初始化的对象是静态存储类static的,则初始化的值为默认值。
4.3 构造函数和析构函数 本例创建对象p1,p2时,系统将自动调用默认构造函数给对象初始化。如果被初始化的对象是auto自动存储类的,则初始化的值是无意义的;如果被初始化的对象是静态存储类static的,则初始化的值为默认值。
例4-2 Point类的完整程序 64 #include <iostream.h> class Point { //Point 类的定义 public: Point(int xx=0, int yy=0) { x = xx; y = yy; } //构造函数 Point(Point& p); //拷贝构造函数 int getX() { return x; } int getY() { return y; } private: int x, y; //私有数据 }; //成员函数的实现 Point::Point (Point &p) { x = p.x; y = p.y; cout << "Calling the copy constructor " << endl; } //形参为Point类对象的函数 void fun1(Point p) { cout << p.getX() << endl; //返回值为Point类对象的函数 Point fun2() { Point a(1, 2); return a; } //主程序 int main() { Point a(4, 5); Point b = a; /*情况一,用a初始化b。第一次调用拷贝构造函数*/ cout << b.getX() << endl; fun1(b);/*情况二,对象b作为fun1的实参。第二次调用拷贝构造函数*/ b = fun2(); /*情况三,函数的返回值是类对象,函数返回时调用拷贝构造函数*/ return 0;
#include <iostream.h> class Point { //Point 类的定义 public: Point(int xx=0, int yy=0) { x = xx; y = yy; cout << "执行两参构造函数\n";} //构造函数 Point(Point& p); //拷贝构造函数 int getX() { cout << "执行getX函数\n";return x; } int getY() { cout << "执行getY函数\n";return y; } private: int x, y; //私有数据 }; //成员函数的实现 Point::Point (Point &p) { x = p.x; y = p.y; cout << "执行拷贝构造函数\n";} //形参为Point类对象的函数 void fun1(Point p) { cout << "执行fun1函数\n"; cout << p.getX() << endl;} //返回值为Point类对象的函数 Point fun2() { Point a(1, 2); return a;} //主程序 int main() { Point a(4, 5); //第一个对象a Point b = a; /*情况一,用a初始化b。第一次调用拷贝构造函数*/ cout << b.getX() << endl; fun1(b);/*情况二,对象b作为fun1的实参。第二次调用拷贝构造函数*/ b = fun2(); /*情况三,函数的返回值是类对象,函数返回时调用拷贝构造函数*/ return 0;}
4.3 构造函数和析构函数 4.3.2 析构函数的功能和特点 析构函数的功能:是用来释放所创建的对象的。 一个对象在它的寿命结束时,系统将会自动调用析构函数将释放该对象已分配的空间。
析构函数 例1:分析下列程序的输出结果,熟悉拷贝构造函数的用法及对象赋值的含义。 ① 析构函数名:类名前加按位求反符号“~” 。 ② 析构函数定义时不必给出类型,也无返回值,并且无参数。 ③析构函数可以被调用,也可以由系统调用。 被自动调用的情况有两种: 在一个函数体内定义的一个对象,当函数结束时。 用new运算符动态创建的一个对象,在使用delete释放时。 ④一个类中只能定义一个析构函数,析构函数由于没有参数,它不能被重载。 ⑤如果一个类体内,用户没有定义析构函数时,系统会自动创建一个默认的析构函数。 ⑥使用构造函数创建对象的顺序与使用析构函数释放对象的顺序正好相反。 后面再进行完整分析
2019/7/8 68 4.3 构造函数和析构函数 4.3.4 程序实例——例4-3 一圆形游泳池如图所示,现在需在其周围建一圆形过道,并在其四周围上栅栏。栅栏价格为35元/米,过道造价为20元/平方米。过道宽度为3米,游泳池半径由键盘输入。要求编程计算并输出过道和栅栏的造价。 游泳池 过道 先讲此例巩固
69 4.3.4 程序实例——例4-3 #include <iostream> using namespace std; const float PI = 3.141593; //给出p的值 const float FENCE_PRICE = 35; //栅栏的单价 const float CONCRETE_PRICE = 20;//过道水泥单价 class Circle { //声明定义类Circle 及其数据和方法 public://外部接口 Circle(float r);//构造函数 float circumference(); //计算圆的周长 float area(); //计算圆的面积 private://私有数据成员 float radius; //圆半径 }; //类的实现 //构造函数初始化数据成员radius Circle::Circle(float r) { radius = r; } float Circle::circumference() {//计算圆的周长 return 2 * PI * radius; } float Circle::area() {//计算圆的面积 return PI * radius * radius; int main () { float radius; cout << "Enter the radius of the pool: "; // 提示用户输入半径 cin >> radius; Circle pool(radius); //游泳池边界 Circle poolRim(radius + 3);//栅栏 //计算栅栏造价并输出 float fenceCost = poolRim.circumference() * FENCE_PRICE; cout << "Fencing Cost is $" << fenceCost << endl; 计算栅栏圆的周长 构造泳池圆和栅栏圆
70 4.3.4 程序实例——例4-3 //计算过道造价并输出 float concreteCost = (poolRim.area() - pool.area()) * CONCRETE_PRICE; cout << "Concrete Cost is $" << concreteCost << endl; return 0; } #include <iostream> using namespace std; const float PI = 3.141593; //给出p的值 const float FENCE_PRICE = 35; //栅栏的单价 const float CONCRETE_PRICE = 20;//过道水泥单价 class Circle { //声明定义类Circle 及其数据和方法 public://外部接口 Circle(float r);//构造函数 float circumference(); //计算圆的周长 float area(); //计算圆的面积 private://私有数据成员 float radius; //圆半径 }; //类的实现 //构造函数初始化数据成员radius Circle::Circle(float r) { radius = r; } float Circle::circumference() {//计算圆的周长 return 2 * PI * radius; } float Circle::area() {//计算圆的面积 return PI * radius * radius; int main () { float radius; cout << "Enter the radius of the pool: "; // 提示用户输入半径 cin >> radius; Circle pool(radius); //游泳池边界 Circle poolRim(radius + 3);//栅栏 //计算栅栏造价并输出 float fenceCost = poolRim.circumference() * FENCE_PRICE; cout << "Fencing Cost is $" << fenceCost << endl; 计算泳池圆的面积 计算栅栏圆的面积 主函数结束时执行默认的析构函数先释放对象poolRim和释放对象pool。
4.4 类的组合 4.4.1组合 类中的数据成员可以是另一个类的对象。 2019/7/8 4.4 类的组合 71 4.4.1组合 类中的数据成员可以是另一个类的对象。 利用已有的类的对象来构造新的类,可以在已有抽象的基础上实现更复杂的抽象。 类的组合描述的是一个类内的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。 暂不讲【10.23讲了】
4.4 类的组合 4.4.1组合 当创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象将首先被自动创建。 2019/7/8 4.4 类的组合 72 4.4.1组合 当创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象将首先被自动创建。 在创建对象时既要对本类的基本类型数据进行初始化,又要对内嵌对象成员进行初始化。 关键是理解这些对象的构造函数被调用的顺序。 暂不讲【10.23讲了】
73 类组合的构造函数设计 声明形式: 初始化列表 对内嵌对象初始化 对基本类型数据成 员也可以初始化 类名::类名(对象成员所需形参,本类成员形参) :对象1(参数),对象2(参数),...... { 本类初始化 } 初始化列表 对内嵌对象初始化 对基本类型数据成 员也可以初始化
在创建组合类的对象时,不仅它自身类的构造函数要被调用,其内嵌对象的构造函数也要被调用。 类组合的构造函数调用 2019/7/8 74 在创建组合类的对象时,不仅它自身类的构造函数要被调用,其内嵌对象的构造函数也要被调用。 构造函数调用顺序:先调用内嵌对象的构造函数(按内嵌时的声明顺序,先声明者先构造)。然后调用本类的构造函数。(析构函数的调用顺序相反) 初始化列表中未出现的内嵌对象,用默认构造函数(即无形参的)初始化。 系统自动生成的隐含的默认构造函数中,内嵌对象全部用默认构造函数初始化 普通变量型的成员也可以在初始化列表中进行初始化 74
1 5 4 p1 p2 len 6 对象myp1 对象myp2 x y 对象line 对象line2 #include <iostream> #include <cmath> using namespace std; class Point {//Point类定义 public: Point(int xx = 0, int yy = 0) {cout << "执行Point类的两参构造函数" << endl; x = xx; y = yy; } Point(Point &p); int getX() { return x; } int getY() { return y; } private: int x, y; }; Point::Point(Point &p) { //拷贝构造函数的实现 cout << "执行Point类的拷贝构造函数" << endl; x = p.x; y = p.y; } class Line {//Line类的定义类的组合 public://外部接口 Line(Point xp1, Point xp2); Line(Line &l); double getLen() { return len; } private://私有数据成员 Point p1, p2; //Point类的对象p1,p2 double len;}; //组合类的构造函数 Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) { cout << "执行Line类的两参构造函数" << endl; double x = (double)(p1.getX() - p2.getX()); double y = (double)(p1.getY() - p2.getY()); len = sqrt(x * x + y * y); cout << "结束Line类的两参构造函数" << endl;} Line::Line (Line &l): p1(l.p1), p2(l.p2) {//组合类的拷贝构造函数 cout << "执行Line类的拷贝构造函数" << endl; len = l.len; cout << "结束Line类的拷贝构造函数" << endl;} int main() {//主函数 Point myp1(1, 1), myp2(4, 5); cout << "跟踪1" << endl; Line line(myp1, myp2); cout << "跟踪2" << endl; Line line2(line); cout << "跟踪3" << endl; cout << "The length of the line is: "; cout << line.getLen() << endl; cout << "The length of the line2 is: "; cout << line2.getLen() << endl; return 0;} 类中的数据成员可以是另一个类的对象 对象myp1 对象myp2 1 5 4 x y 6 对象line 对象line2 p1 p2 len
int main() {//主函数 Point myp1(1, 1), myp2(4, 5); //建立Point类的对象 Line line(myp1, myp2); //建立Line类的对象 Line line2(line); //利用拷贝构造函数建立一个新对象 cout << "The length of the line is: "; cout << line.getLen() << endl; cout << "The length of the line2 is: "; cout << line2.getLen() << endl; return 0;} ① ② //例4-4 类的组合,线段(Line)类 #include <iostream> #include <cmath> using namespace std; class Point {//Point类定义 public: Point(int xx = 0, int yy = 0) { x = xx; y = yy;} Point(Point &p); int getX() { return x; } int getY() { return y; } private: int x, y; }; Point::Point(Point &p) { //拷贝构造函数的实现 x = p.x; y = p.y; cout << "Calling the copy constructor of Point" << endl; } 对象 myp1 对象 myp2 x x y y
//类的组合 class Line {//Line类的定义 public://外部接口 Line(Point xp1, Point xp2); Line(Line &l); double getLen() { return len; } private://私有数据成员 Point p1, p2; //Point类的对象p1,p2 double len;}; //组合类的构造函数 Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) { cout << "Calling constructor of Line" << endl; double x = static_cast<double>(p1.getX() - p2.getX()); double y = static_cast<double>(p1.getY() - p2.getY()); len = sqrt(x * x + y * y); } Line::Line (Line &l): p1(l.p1), p2(l.p2) {//组合类的拷贝构造函数 cout << "Calling the copy constructor of Line" << endl; len = l.len; int main() {//主函数 Point myp1(1, 1), myp2(4, 5); //建立Point类的对象 Line line(myp1, myp2); //建立Line类的对象 Line line2(line); //利用拷贝构造函数建立一个新对象 cout << "The length of the line is: "; cout << line.getLen() << endl; cout << "The length of the line2 is: "; cout << line2.getLen() << endl; return 0;} 77 Point::Point(Point &p) { //拷贝构造函数的实现 x = p.x; y = p.y; cout << "Calling the copy constructor of Point" << endl; } ④ ⑦ ⑥ ⑤ ③ ⑧ ⑨ ⑩
4.4.2 前向引用声明 类应该先声明,后使用。 如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。 2019/7/8 78 4.4.2 前向引用声明 类应该先声明,后使用。 如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。 前向引用声明只为程序引入一个标识符,但具体声明在其他地方。 举例: class B; //前向引用声明 class A { public: void f(B b); }; class B { void g(A a); 暂不讲 78
编译通过
前向引用声明注意事项: class F; //前向引用声明 class B { F x; //错误:类F的声明尚不完善 }; 80 需要注意:即便是使用前向引用声明,在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。 请看下面的程序段例1: class F; //前向引用声明 class B { F x; //错误:类F的声明尚不完善 }; class F {//类F的完整声明 B y;
前向引用声明注意事项:
前向引用声明注意事项: 82 举例2: class F; //前向引用声明 class B { public: …… void m() { x.print(); //错误:F类的对象在定义之前被使用 } private: F &x; //正确,经过前向引用声明,可以声明F类的对象引用 }; class F{ //类F的完整声明 void print(); B &y; 注意:当使用前向引用声明时,只能使用被声明的符号,而不能涉及对象所属类的任何细节。 不能声明该类的对象,也不能在内联成员函数中使用该类的对象
4.6 结构体和联合体 4.6.1 结构体 结构体的定义和初始化 结构体是一种特殊形态的类 2019/7/8 83 4.6 结构体和联合体 4.6.1 结构体 结构体的定义和初始化 结构体是一种特殊形态的类 与类的唯一区别:类的缺省访问权限是private,结构体的缺省访问权限是public。 结构体存在的主要原因:与C语言保持兼容。 什么时候用结构体而不用类 定义主要用来保存数据、而没有什么操作的类型。 人们习惯将结构体的数据成员设为公有,因此这时用结构体更方便。 结构体定义 struct 结构体名称 { //可以省略 public: 公有成员 protected: 保护型成员 private: 私有成员 }; 用类的观点来看结构体 83
C语言中的结构体 4.6 结构体和联合体 4.6.1 结构体——结构的声明 结构的概念 结构是由不同数据类型的数据组成的集合体。 2019/7/8 4.6 结构体和联合体 4.6.1 结构体——结构的声明 C语言中的结构体 结构的概念 结构是由不同数据类型的数据组成的集合体。 声明结构类型 struct 结构名 { 数据类型 成员名 1; 数据类型 成员名 2; …… 数据类型 成员名 n; }; 举例: struct student //学生信息结构体 { int num; //学号 char name[20]; //姓名 char gender; //性别 int age; //年龄 float score; //成绩 char addr[30]; //住址 } 此5页待整理! 84
法1 4.6 结构体和联合体 4.6.1 结构体——结构变量说明 变量说明形式 结构名 结构变量名; 2019/7/8 4.6 结构体和联合体 4.6.1 结构体——结构变量说明 定义结构体变量有以下三种方法。 法1 变量说明形式 结构名 结构变量名; 例如: struct student a; 举例: struct student //学生信息结构体 { int num; //学号 char name[20]; //姓名 char gender; //性别 int age; //年龄 float score; //成绩 char addr[30]; //住址 } 数组还没讲 85
法2 4.6 结构体和联合体 4.6.1 结构体——结构变量说明 举例: struct student //学生信息结构体 { 2019/7/8 4.6 结构体和联合体 4.6.1 结构体——结构变量说明 定义结构体变量有以下三种方法。 法2 举例: struct student //学生信息结构体 { int num; //学号 char name[20]; //姓名 char gender; //性别 int age; //年龄 float score; //成绩 char addr[30]; //住址 }a; 数组还没讲 86
法3 4.6 结构体和联合体 4.6.1 结构体——结构变量说明 举例: struct //学生信息结构体 { int num; //学号 2019/7/8 4.6 结构体和联合体 4.6.1 结构体——结构变量说明 定义结构体变量有以下三种方法。 法3 举例: struct //学生信息结构体 { int num; //学号 char name[20]; //姓名 char gender; //性别 int age; //年龄 float score; //成绩 char addr[30]; //住址 }a; 数组还没讲 87
4.6.1 结构体 三种方法都可以说明boy1,boy2变量, boy1,boy2都具有下图所示的结构。 例如:struct stu { int num; char name[20]; char sex; float score; }boy1,boy2; 说明boy1,boy2变量为stu类型后,即可向这两个变量中的各个成员赋值。
成员也可以又是一个结构体类型数据,即构成了嵌套的结构。 成员也可以又是一个结构体类型数据,即构成了嵌套的结构。 例:有如下结构体定义: struct date { int month; int day; int year; }; struct student { int num; char name[20]; char sex; struct date birthday; float score; }boy1,boy2; struct student中的成员birthday被说明为struct data结构体类型。 成员名可与程序中其它变量同名,互不干扰。 结构变量占内存大小可用 sizeof 运算求出: sizeof(类型名或变量名) month day year
4.6.1 结构体 结构体变量成员的表示方法 一般对结构体变量的使用,包括赋值、输入、输出、运算等都是通过结构体变量的成员来实现的,往往不把结构体变量作为一个整体来使用。 结构体成员的引用形式: 结构变量名.成员名 例如:boy1.num 即第一个人的学号;boy2.score 即第二个人的性别. boy1 boy2 如果成员本身又是一个结构体,则必须逐级找到最低级的成员才能使用。 例如:boy1.birthday.month 即第一个人出生的月份成员可以在程序中单独使用,与普通变量完全相同。
4.6.1 结构体 结构体变量成员的初始化 补充例题:结构体变量的初始化和使用 #include <iostream> 2019/7/8 结构体变量成员的初始化 一些结构体变量的初始化可以用以下形式 类型名 变量名 = { 成员数据1初值, 成员数据2初值, …… }; 补充例题:结构体变量的初始化和使用 #include <iostream> #include <iomanip> using namespace std; struct student //学生信息结构体 { int num; //学号 char name[20]; //姓名 char sex; //性别 int age; //年龄 }stu={97001,"Lin Lin",'F',19}; void main() { cout<<setw(7)<<stu.num<<setw(20)<<stu.name<<setw(3)<<stu.sex<<setw(3)<<stu.age; } 说明结构变量的同时可以直接设置初值。 91
92 例4-7用结构体表示学生的基本信息 #include <iostream> #include <iomanip> //#include <string> using namespace std; struct Student{ //学生信息结构体 int num; //学号 char name[20]; //string name; 姓名,字符串对象,第6章 char sex; //性别 int age; //年龄 }; int main() { Student stu={97001,"Lin Lin",'F',19}; //struct Student stu={97001,“Lin Lin”,‘F’,19}; 也正确! cout << "Num: " << stu.num << endl; cout << "Name: " << stu.name << endl; cout << "Sex: " << stu.sex << endl; cout << "Age: " << stu.age << endl; return 0; }
如果是C语言源程序,结构体类型必须写关键字struct 结构名。
4.6.2 联合体 声明形式 union 联合体名称 { 公有成员 protected: 保护型成员 private: 私有成员 }; 2019/7/8 4.6.2 联合体 声明形式 union 联合体名称 { 公有成员 protected: 保护型成员 private: 私有成员 }; 特点: 成员共用相同的内存单元。 任何两个成员不会同时有效。 简介。常与结构体一起使用 94
联合体的内存分配 Mark grade percent pass union Mark { //表示成绩的联合体 char grade; //等级制的成绩 bool pass; //只记是否通过课程的成绩 int percent; //百分制的成绩 }; Mark grade percent pass
无名联合 无名联合没有标记名,只是声明一个成员项的集合,这些成员项具有相同的内存地址,可以由成员项的名字直接访问。 96 2019/7/8 无名联合 无名联合没有标记名,只是声明一个成员项的集合,这些成员项具有相同的内存地址,可以由成员项的名字直接访问。 例: union { int i; float f; } 在程序中可以这样使用: i = 10; f = 2.2; 10.16 96
例4-8使用联合体保存成绩信息,并且输出。 三个两参构造函数的函数重载 2019/7/8 #include <string> #include <iostream> using namespace std; class ExamInfo { private: string name; //课程名称 enum { GRADE, PASS, PERCENTAGE } mode; //采用何种计分方式 union { char grade; //等级制的成绩 bool pass; //只记是否通过课程的成绩 int percent; //百分制的成绩 }; public: //三种构造函数,分别用等级、是否通过和百分初始化 ExamInfo(string name, char grade) : name(name), mode(GRADE), grade(grade) { } ExamInfo(string name, bool pass) : name(name), mode(PASS), pass(pass) { } ExamInfo(string name, int percent) : name(name), mode(PERCENTAGE), percent(percent) { } void show(); }; 三个两参构造函数的函数重载 三个数据成员,第三个成员是无名联合 暂不讲 97
void ExamInfo::show() { cout << name << ": "; switch (mode) { case GRADE: cout << grade; break; case PASS: cout << (pass ? "PASS" : "FAIL"); break; case PERCENTAGE: cout << percent; break; } cout << endl; 枚举常量 无名联合的成员 int main() { ExamInfo course1("English", 'B'); ExamInfo course2("Calculus", true); ExamInfo course3("C++ Programming", 85); course1.show(); course2.show(); course3.show(); return 0; }
*4.7 综合实例——个人银行账户管理程序 我们以一个面向个人的银行账户管理程序为例,说明类及 成员函数的设计。 2019/7/8 *4.7 综合实例——个人银行账户管理程序 我们以一个面向个人的银行账户管理程序为例,说明类及 成员函数的设计。 例4-9:一个人可以有多个活期储蓄账户。 一个活期储蓄账户包括账号(id)、余额(balance)、 年利率(rate)等信息,还包括显示账户信息(show)、 存款(deposit)、取款(withdraw)、结算利息 (settle)等操作。 未讲
一个活期储蓄账户包括账号(id)、余额(balance)、年利率(rate)等信息,还包 括显示账户信息(show)、存款(deposit)、取款(withdraw)、结算利息 (settle)等操作。 //4_9.cpp #include <iostream> #include <cmath> using namespace std; class SavingsAccount { //储蓄账户类 private: int id; //账号 double balance; //余额 double rate; //存款的年利率 int lastDate; //上次变更余额的时期 double accumulation; //余额按日累加之和 //记录一笔帐,date为日期,amount为金额,desc为说明 void record(int date, double amount); //获得到指定日期为止的存款金额按日累积值 double accumulate(int date) const { return accumulation + balance * (date - lastDate); }
public: //构造函数 SavingsAccount(int date, int id, double rate); int getId() { return id; } //内联函数 double getBalance() { return balance; } //内联函数 double getRate() { return rate; } //内联函数 void deposit(int date, double amount); //存入现金 void withdraw(int date, double amount); //取出现金 //结算利息,每年1月1日调用一次该函数 void settle(int date); void show(); //显示账户信息 }; //SavingsAccount类相关成员函数的实现 SavingsAccount::SavingsAccount(int date, int id, double rate) : id(id), balance(0), rate(rate), lastDate(date), accumulation(0) { cout << date << "\t#" << id << " is created" << endl; }
void SavingsAccount::record(int date, double amount) { accumulation = accumulate(date); lastDate = date; amount = floor(amount * 100 + 0.5) / 100; //保留小数点后两位 balance += amount; cout << date << "\t#" << id << "\t" << amount << "\t" << balance << endl; } void SavingsAccount::deposit(int date, double amount) { record(date, amount); } void SavingsAccount::withdraw(int date, double amount) { if (amount > getBalance()) cout << "Error: not enough money" << endl; else record(date, -amount); } void SavingsAccount::settle(int date) { double interest = accumulate(date) * rate / 365; //计算年息 if (interest != 0) record(date, interest); accumulation = 0; } 102
void SavingsAccount::show() { cout << "#" << id << "\tBalance: " << balance; } int main() { //建立几个账户 SavingsAccount sa0(1, 21325302, 0.015); SavingsAccount sa1(1, 58320212, 0.015); //几笔账目 sa0.deposit(5, 5000); sa1.deposit(25, 10000); sa0.deposit(45, 5500); sa1.withdraw(60, 4000); //开户后第90天到了银行的计息日,结算所有账户的年息 sa0.settle(90); sa1.settle(90); //输出各个账户信息 sa0.show(); cout << endl; sa1.show(); cout << endl; return 0; } 103
104
结 束!