59
第第第 第第第第第第第第 第第第

第三章 关于类和对象的进一步讨论

  • Upload
    dudley

  • View
    173

  • Download
    0

Embed Size (px)

DESCRIPTION

第三章 关于类和对象的进一步讨论. 3.1 构造函数. 构造函数和析构函数是在类体中说明的两种 特殊的成员函数 。 构造函数是在创建对象时,使用给定的值来将对象初始化。 析构函数的功能正好相反,是在系统释放对象前,对对象做一些善后工作. 类与对象. 基本数据类型与变量. 一般与特殊. 新课导入. 每个对象区别于其他对象的两个方面 外:对象名称 内:对象自身的属性值,即数据成员的值 在声明对象时进行数据成员的设置(给数据成员赋初值),称为 对象的初始化。(构造函数) 在特定对象使用结束时,需要进行清理工作( 析构函数 ). - PowerPoint PPT Presentation

Citation preview

Page 1: 第三章        关于类和对象的进一步讨论

第三章 关于类和对象的进一步讨论

Page 2: 第三章        关于类和对象的进一步讨论

3.1 构造函数

构造函数和析构函数是在类体中说明的两种特殊的成员函数。

构造函数是在创建对象时,使用给定的值来将对象初始化。

析构函数的功能正好相反,是在系统释放对象前,对对象做一些善后工作

Page 3: 第三章        关于类和对象的进一步讨论

新课导入

每个对象区别于其他对象的两个方面 外:对象名称 内:对象自身的属性值,即数据成员的值

在声明对象时进行数据成员的设置(给数据成员赋初值),称为对象的初始化。(构造函数)

在特定对象使用结束时,需要进行清理工作(析构函数)

类与对象 基本数据类型与变量 一般与特殊

Page 4: 第三章        关于类和对象的进一步讨论

3.1.1 为什么需要构造函数初始化对象? 同一类, N 个对象。为每个对象分配空间存

放数据成员的值,不单独为每个对象分配函数成员空间。

在声明类时,类的数据成员不能直接赋值,进行初始化。例:教材 P69

但是,声明对象时必须对其数据成员赋值(初始化),否则值是不可预知的,因为被分配的内存中保留了前状。

Page 5: 第三章        关于类和对象的进一步讨论

构造函数 构造函数的作用 : 是在对象被创建时使用特

定的值构造对象,或者说将对象初始化为一个特定的状态。

构造函数也是类的一个成员函数,但有特殊性质:

1 、构造函数在对象创建时,由系统自动调用

2 、构造函数的函数名与类名相同,且没有返回值

3 、构造函数通常被声明为 public

4 、若没有显式声明构造函数,编译器自动生成默认形式的构造函数(无参数,无功能)

Page 6: 第三章        关于类和对象的进一步讨论

3.1.2 构造函数的作用 构造函数是类的一种特殊的成员函数,构造函数的

主要作用是完成初始化对象的数据成员以及其它的初始化工作。在建立对象时自动执行,不需要用户显式调用。

系统约定构造函数名必须与类名相同。 构造函数没有返回值。所以,不能指定函数返回值

的类型,也不能指定为 void 类型。 一个类可以定义若干个构造函数。当定义多个构造

函数时,同时必须满足函数重载的原则。即:构造函数可以带参数、可以重载,

Page 7: 第三章        关于类和对象的进一步讨论

3.1.2 构造函数的作用 构造函数的写法: P70 例 3.1

可以与普通成员函数一样,在类内声明构造函数,在类外定义。

在构造函数的函数体内(定义时),对对象的数据成员赋初值。符合通过 public 型的函数成员访问 private 型的数据成员的规则。

构造函数的使用: P71 main() 函数体。 注意对象 t2 :自动调用构造函数。

Page 8: 第三章        关于类和对象的进一步讨论

构造函数举例class Clock{public:

Clock(int NewH,int NewM,int NewS);// 构造函数void SetTime(int NewH,int NewM,int NewS);void ShowTime();

private:int Hour,Minute,Second;

};

定义了构造函数,所以编译系统就不会再为其生成默认构造函数

Page 9: 第三章        关于类和对象的进一步讨论

构造函数的实现:

Clock::Clock(int NewH, int NewM, int NewS)

{

Hour= NewH;

Minute= NewM;

Second= NewS;

}

建立对象时构造函数的作用:

int main()

{ Clock c(0,0,0); // 隐含调用构造函数,将初始值作为实参。

c.ShowTime();

}

9

Clock c2; 语法错误,没有给出必要的实参

Page 10: 第三章        关于类和对象的进一步讨论

有关构造函数使用说明( p71)

第一条:何时?在对象的生命周期开始时,系统自动调用构造函数;在生命结束时,自动调用析构函数。 对象(类的对象或变量)从产生到结束的这段时间就是

它的生存期。在对象生存期内,对象将保持它的状态(数据成员的值),直到被更新为止。

第四条:一般不提倡在构造函数中加入与初始化无关的内容。

第五条:总存在构造函数

Page 11: 第三章        关于类和对象的进一步讨论

小结及导入

构造函数是类的成员函数,可以直接访问类的所有数据成员,可以是内联函数,可以带有参数表,可以带默认的形参值,也可以重载。

对一个类,可以建立多个对象,但注意对象所占据的内存控件只是用于存放数据成员,函数成员不在每一个对象中存储副本。

Page 12: 第三章        关于类和对象的进一步讨论

3.1.3 带参数的构造函数 前:例 3.1 ,构造函数无形参,在函数体中对各数据成员赋

初值。该类的每个对象都得到同一组初值。 现:对不同的对象赋予不同的初值。 书写形式:

例 3.2

3.1.4 用参数初始化表对数据成员初始化。方便 &简练 结论:

带参数的构造函数中的形参,其对应的实参在定义对象时给定。 用这种方法可以对不同对象进行不同的初始化。

Page 13: 第三章        关于类和对象的进一步讨论

class A{float x,y;

public:A(float a,float b){ x=a; y=b;}// 构造函数,初始化对象float Sum(void) { return x+y; }void Set(float a,float b) { x=a; y=b;}Print(void) { cout<<"x="<<x<<'\t'<<"y="<<y<<endl;}};void main(void){ A a1(2.0, 3.0);// 定义时调用构造函数初始化

A a2(1.0,2.0);a2.Set(10.0, 20.0); //利用成员函数重新为对象赋值

a1.Print();a2.Print();

}

Page 14: 第三章        关于类和对象的进一步讨论

3.1.5 构造函数的重载 P73: 第一章中的函数重载知识也适用于构造函数 例 3.3

编译系统根据函数调用的形式去确定对应的函数。 默认构造函数:不必给出实参的构造函数。如:用

户定义的无参构造函数,系统自动给出的构造函数,全部参数都指定了默认值的构造函数。

其他说明: p75

Page 15: 第三章        关于类和对象的进一步讨论

重载的构造函数class Clock

{

public:

Clock(int NewH,int NewM,int NewS);

Clock()

{Hour=0;Minute=0;Second=0;}

void SetTime(int NewH,int NewM,int NewS);

void ShowTime();

private:

int Hour,Minute,Second;

};

Int main( )

{ Clock c1(0,0,0); // 系统自动调用带有参数的构造函数 Clock c2; // 系统自动调用无参数的构造}

重载构造函数,同名但形参类型或个数不同

Page 16: 第三章        关于类和对象的进一步讨论

3.1.6 使用默认参数的构造函数 构造函数中参数的值既可以通过实参传递,

也可以指定为某些默认值。 例 3.4

优点: P77

说明: P77 , 4项

Page 17: 第三章        关于类和对象的进一步讨论

3.2 析构函数 析构函数的作用与构造函数正好相反,是在对象的生命期结束

时,释放系统为对象所分配的空间,即要撤消一个对象。

析构函数也是类的成员函数,定义析构函数的格式为:

ClassName::~ClassName( )

{

......

// 函数体 ;

}

Page 18: 第三章        关于类和对象的进一步讨论

析构函数 完成对象被删除前的一些清理工作。

在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。

如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数。

Page 19: 第三章        关于类和对象的进一步讨论

构造函数和析构函数举例#include<iostream>using namespace std;class Point{ public: Point(int xx,int yy); ~Point(); //... 其他函数原型 private: int X,int Y;};

Page 20: 第三章        关于类和对象的进一步讨论

Point::Point(int xx,int yy)

{ X=xx; Y=yy;

}

Point::~Point()

{

}

//... 其他函数的实现略

例: 3.5, P79

20

Page 21: 第三章        关于类和对象的进一步讨论

析构 函数的特点如下:

1 、析构函数是成员函数,函数体可写在类体内,也可写在类体外。

2 、析构函数是一个特殊的成员函数,函数名必须与类名相同,并在其前面加上字符“ ~” ,以便和构造函数名相区别。3 、析构函数不能带有任何参数,不能有返回值,不指定函数类型。

Page 22: 第三章        关于类和对象的进一步讨论

在程序的执行过程中,当遇到某一对象的生存期结束时,系统自动调用析构函数,然后再收回为对象分配的存储空间。

4 、一个类中,只能定义一个析构函数,析构函数不允许重载。

5 、析构函数是在撤消对象时由系统自动调用的。

Page 23: 第三章        关于类和对象的进一步讨论

class A{float x,y;

public: A(float a,float b) { x=a;y=b;cout<<" 调用非缺省的构造函数 \n";} A() { x=0; y=0; cout<<" 调用缺省的构造函数 \n" ;} ~A() { cout<<" 调用析构函数 \n";} void Print(void) { cout<<x<<'\t'<<y<<endl; }};void main(void){ A a1;

A a2(3.0,30.0);cout<<"退出主函数 \n";

}

调用缺省的构造函数调用非缺省的构造函数退出主函数调用析构函数调用析构函数

Page 24: 第三章        关于类和对象的进一步讨论

补充材料:对象的生存期

对象(类的对象或变量)从产生到结束的这段时间就是它的生存期。在对象生存期内,对象将保持它的状态(数据成员的值),直到被更新为止。

分类:静态生存期、动态生存期

Page 25: 第三章        关于类和对象的进一步讨论

静态生存期静态生存期与程序的运行期相同。 两种情况下对象具有静态生存期:

在文件作用域中声明的对象具有这种生存期。(全局) 在函数内部声明静态生存期对象,要冠以关键字 static 。

static 在内存中是以固定地址存放的,在整个程序运行期间都

有效。 static int I;

注意与作用域和可见性的区别(例:全局寿命,局部可见)

Page 26: 第三章        关于类和对象的进一步讨论

#include<iostream>

using namespace std;

int i=5; //文件作用域int main()

{

cout<<"i="<<i<<endl;

return 0;

}

i具有静态生存期,在文件作用域,全局寿命

Page 27: 第三章        关于类和对象的进一步讨论

动态生存期

块作用域中声明的,没有用 static修饰的对象是动态生存期的对象(习惯称局部生存期对象)。

开始于程序执行到声明点时,结束于命名该标识符的作用域结束处。

Page 28: 第三章        关于类和对象的进一步讨论

#include<iostream>

using namespace std;

void fun();

void main()

{ fun();

fun();

}

void fun()

{ static int a=1;

int i=5;

a++;

i++;

cout<<"i="<<i<<",a="<<a<<endl;

}

运行结果:

i=6, a=2

i=6, a=3

i是动态生存期

a是静态生存期

Page 29: 第三章        关于类和对象的进一步讨论

例 5-2 变量的生存期与可见性#include<iostream>

using namespace std;

int i=1; // i 为全局变量,具有静态生存期。void main(void)

{ static int a; // 静态局部变量,有全局寿命,局部可见。 int b=-10; // b, c 为局部变量,具有动态生存期。 int c=0;

void other(void); // 声明函数 cout<<"---MAIN---\n";

cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl;

c=c+8; other();

cout<<"---MAIN---\n";

cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl;

i=i+10; other();

}

Page 30: 第三章        关于类和对象的进一步讨论

void other(void)

{

static int a=2;

static int b;

// a,b 为静态局部变量,具有全局寿命,局部可见。

//只第一次进入函数时被初始化。

int c=10; // C 为局部变量,具有动态生存期,

// 每次进入函数时都初始化。

a=a+2; i=i+32; c=c+5;

cout<<"---OTHER---\n";

cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl;

b=a;

}

17

Page 31: 第三章        关于类和对象的进一步讨论

运行结果:

---MAIN---

i: 1 a: 0 b: -10 c: 0

---OTHER---

i: 33 a: 4 b: 0 c: 15

---MAIN---

i: 33 a: 0 b: -10 c: 8

---OTHER---

i: 75 a: 6 b: 4 c: 15

18

Page 32: 第三章        关于类和对象的进一步讨论

例 5-3 具有静态、动态生存期对象的时钟程序

#include<iostream>

using namespace std;

class Clock // 时钟类声明{public: // 外部接口

Clock();

void SetTime(int NewH, int NewM, int NewS); // 三个形参均具有函数原型作用域void ShowTime();

~Clock(){}

private: //私有数据成员int Hour,Minute,Second;

};

Page 33: 第三章        关于类和对象的进一步讨论

// 时钟类成员函数实现

Clock::Clock() // 构造函数

{ Hour=0;

Minute=0;

Second=0;

}

void Clock::SetTime(int NewH, int NewM, int NewS)

{ Hour=NewH;

Minute=NewM;

Second=NewS;

}

void Clock::ShowTime()

{ cout<<Hour<<":"<<Minute<<":"<<Second<<endl;

}20

Page 34: 第三章        关于类和对象的进一步讨论

Clock globClock; // 声明对象 globClock ,

//具有静态生存期,文件作用域

void main() // 主函数

{

cout<<"First time output:"<<endl;

//引用具有文件作用域的对象:

globClock.ShowTime(); // 对象的成员函数具有类作用域

globClock.SetTime(8,30,30);

Clock myClock(globClock);

// 声明具有块作用域的对象 myClock

cout<<"Second time output:"<<endl;

myClock.ShowTime(); //引用具有块作用域的对象

}

21

Page 35: 第三章        关于类和对象的进一步讨论

程序的运行结果为:

First time output:

0:0:0

Second time output:

8:30:30

22

Page 36: 第三章        关于类和对象的进一步讨论

3.3 调用构造函数和析构函数的顺序先构造的后析构,后构造的先析构 对象在生命周期开始时,调用构造函数;生

命周期结束时,调用析构函数。 因此,很多时候,当函数多次被调用时,在

函数体内建立的对象,都要调用构造函数。

Page 37: 第三章        关于类和对象的进一步讨论

3.4 对象数组

数组不仅可以由简单变量组成,也可以由对象组成。

如建立 Student 类,全班 50 个学生就是这个类的 50 个对象。每个学生包括: 属性:姓名、年龄、性别、成绩 行为:选课、交费、考试

Page 38: 第三章        关于类和对象的进一步讨论

数组的初始化 数组的初始化就是在声明数组时给部分或全部元素赋初值。

对于基本类型的数组,初始化过程就是给数组元素赋值;

对于对象数组,每个元素都是某类的一个对象,初始化就是调用该对象的构造函数。若有 50 个元素,则需调用 50次构造函数。

Page 39: 第三章        关于类和对象的进一步讨论

对象数组 数组元素可是基本数据类型,也可是自定义类型。

对象数组的元素是对象,具有数据成员和函数成员

声明:

类名 数组名 [元素个数 ]; Point A[2] ;

访问方法:

与基本类型数组一样,在使用对象数组时,只能引用单个数组元素。通过下标访问: 数组名 [下标 ]. 成员名

Page 40: 第三章        关于类和对象的进一步讨论

对象数组初始化 数组中每一个元素对象被创建时,系统都会调

用类构造函数初始化该对象。 Location A[2]={Location(1,2), Location(3,4)} Location A[2]={Location(1,2)}; 如果没有为数组元素指定显式初始值,数组元素调

用默认构造函数初始化。 各元素对象的初值要求为相同的值时,可以声明具

有默认形参值的构造函数。 各元素对象的初值要求为不同的值时,需要声明带

形参的构造函数。

Page 41: 第三章        关于类和对象的进一步讨论

数组元素所属类的构造函数 当数组中每一个对象被删除时,系统都

要调用一次析构函数。 例 1 :教材 P83 例 3.6

Page 42: 第三章        关于类和对象的进一步讨论

例 2 对象数组应用举例//Point.h#if !defined(_POINT_H)#define _POINT_Hclass Point{ public: Point(); Point(int xx,int yy); ~Point(); void Move(int x,int y); int GetX() {return X;} int GetY() {return Y;} private: int X,Y;};#endif

Page 43: 第三章        关于类和对象的进一步讨论

//Point.cpp#include<iostream>using namespace std;#include "Point.h"Point::Point(){ X=Y=0; cout<<"Default Constructor called."<<endl;}Point::Point(int xx,int yy){ X=xx; Y=yy; cout<< "Constructor called."<<endl;}Point ::~Point(){ cout<<"Destructor called."<<endl; }void Point ::Move(int x,int y){ X=x; Y=y; }

43

Page 44: 第三章        关于类和对象的进一步讨论

//app.cpp#include<iostream>#include "Point.h"using namespace std;int main(){ cout<<"Entering main..."<<endl; Point A[2]; for(int i=0;i<2;i++) A[i].Move(i+10,i+20); cout<<"Exiting main..."<<endl; return 0;}

44

Page 45: 第三章        关于类和对象的进一步讨论

运行结果:

Entering main...

Default Constructor called.

Default Constructor called.

Exiting main...

Destructor called.

Destructor called.

45

Page 46: 第三章        关于类和对象的进一步讨论

3.5 对象指针:对象指针的一般概念 和基本类型的变量一样,每个对象在初始化之后都会在内存中占有

空间,存放数据成员。因此,既可以通过对象名,也可以通过对象地址来访问一个对象。对象空间的起始地址就是对象的指针

声明形式

类名 *对象指针名;

Point A(5,10);

Piont *ptr;

ptr=&A; 通过指针访问对象成员

对象指针名 ->成员名

ptr->getx() 相当于 (*ptr).getx();

Page 47: 第三章        关于类和对象的进一步讨论

对象指针应用举例例 1:int main(){ Point A(5,10); // 对象名 A Point *ptr; // 类指针 ptr=&A; // 和基本数据类型用法类似

int x; x=ptr->GetX(); // 访问形式 cout<<x<<endl;

return 0;}例 2: P84

Page 48: 第三章        关于类和对象的进一步讨论

3.5 对象指针 指向对象的指针 指向对象数据成员的指针 指向对象函数成员的指针

Page 49: 第三章        关于类和对象的进一步讨论

指向类的非静态成员的指针 类的成员自身也是一些变量、函数或对象。因此,可

以使指针直接指向对象的成员,将它们的地址放到指针变量中,通过指针访问对象的成员。

通过指向成员的指针只能访问公有成员

Page 50: 第三章        关于类和对象的进一步讨论

指向对象数据成员的指针 int *p;

p1=&t1.hour; // 将对象 t1 的数据成员 hour的地址赋给 p1 , p1 指向 t1.hour;

cout<<*p1<<endl;

Page 51: 第三章        关于类和对象的进一步讨论

指向对象成员函数的指针 声明指向公有函数成员的指针

类型说明符 ( 类名 ::*指针名 )( 参数表 );void (Time::*p2) ( ); // 函数指针,指向函数的入口地址。定义 P2为指向 Time 类中公

有成员函数的指针变量。

原因:指针变量的类型必须与赋值号右侧函数的类型相匹配: 函数参数的类型和个数; 函数返回值的类型 所属的类

Page 52: 第三章        关于类和对象的进一步讨论

补充:指向类的静态成员的指针 对类的静态成员的访问不依赖于对象 可以用普通的指针来指向和访问静态成员 例 6-14

通过指针访问类的静态数据成员 例 6-15

通过指针访问类的静态函数成员

Page 53: 第三章        关于类和对象的进一步讨论

例 6-14 通过指针访问类的静态数据成员#include <iostream>using namespace std;class Point //Point 类声明{public: // 外部接口Point(int xx=0, int yy=0) {X=xx;Y=yy;countP++;}// 构造函数Point(Point &p); //拷贝构造函数int GetX() {return X;}int GetY() {return Y;}static int countP; //静态数据成员引用性说明

private: //私有数据成员int X,Y;

};Point::Point(Point &p){ X=p.X; Y=p.Y; countP++; }

int Point::countP=0; //静态数据成员定义性说明

Page 54: 第三章        关于类和对象的进一步讨论

int main() // 主函数

{ // 声明一个 int 型指针,指向类的静态成员

int *count=&Point::countP;

Point A(4,5); // 声明对象 A

cout<<"Point A,"<<A.GetX()<<","<<A.GetY();

// 直接通过指针访问静态数据成员

cout<<" Object id="<<*count<<endl;

Point B(A); // 声明对象 B

cout<<"Point B,"<<B.GetX()

<<","<<B.GetY();

// 直接通过指针访问静态数据成员

cout<<" Object id="<<*count<<endl;

} 54

Page 55: 第三章        关于类和对象的进一步讨论

例 6-15 通过指针访问类的静态函数成员

#include <iostream>

using namespace std;

class Point //Point 类声明

{ public: // 外部接口

// 其他函数略

static void GetC() //静态函数成员

{ cout<<" Object id="<<countP<<endl; }

private: //私有数据成员

int X,Y;

static int countP; //静态数据成员引用性说明

};

// 函数实现略

int Point::countP=0; //静态数据成员定义性说明

Page 56: 第三章        关于类和对象的进一步讨论

int main() // 主函数

{

// 指向函数的指针,指向类的静态成员函数

void (*gc)()=Point::GetC;

Point A(4,5); // 声明对象 A

cout<<"Point A,"<<A.GetX()<<","<<A.GetY();

gc();//输出对象序号,通过指针访问静态函数成员

Point B(A);// 声明对象 B

cout<<"Point B,"<<B.GetX()<<","<<B.GetY();

gc();//输出对象序号,通过指针访问静态函数成员

} 56

Page 57: 第三章        关于类和对象的进一步讨论

3.5.3 this 指针不同对象占据内存中的不同区域,它们所保存的数据各不相同,但对成员数据进行操作的成员函数的程序代码均是一样的。class A{

int x,y;

public:

void Setxy(int a, int b)

{ x=a; y=b;}

};

A a1, a2;

a1.x

a1.y

a2.x

a2.y

......x=a ;y=b ;......

a1. Setxy() a2. Setxy()

a1.Setxy(1,2);

a2.Setxy(3,4);

this->x=a;

this->y=b;

系统自动将对象的指针带到成员函数中

Page 58: 第三章        关于类和对象的进一步讨论

当对一个对象调用成员函数时,编译程序先将对象的地址赋给 this 指针,然后调用成员函数,每次成员函数存取数据成员时,也隐含使用 this 指针。

this 指针具有如下形式的缺省说明:

Stu *const this; 类名

即 this 指针里的地址是一个常量

Page 59: 第三章        关于类和对象的进一步讨论

this 指针例 1: Point 类的构造函数体中的语句:

X=xx;

Y=yy;

相当于: this->X=xx;

this->Y=yy;

例 2:显式书写

void setID(string ID){ id=ID ; }

用 ID 表示参数,以避免与数据成员 id冲突

void setID(string id){ this->id=id; }