480 likes | 646 Views
第十章 类. 10. 第二部分 面向对象程序设计. 第十章 目 录. §10.1 类的概念. §10.2 类的定义格式. §10.3 对象的定义和成员表示. §10.4 定义成员函数. §10.5 调用成员函数. §10.6 this 指针. §10.7 屏蔽类的内部实现. §10.8 再论程序结构. 第十章小结. 第十章 类. 类构成了实现 C++ 面向对象程序设计的基础。 类是 C++ 数据封装的基本单元,它把数据和函数封装在一起。
E N D
第十章 类 10 第二部分 面向对象程序设计
第十章 目 录 §10.1 类的概念 §10.2 类的定义格式 §10.3 对象的定义和成员表示 §10.4 定义成员函数 §10.5 调用成员函数 §10.6 this 指针 §10.7 屏蔽类的内部实现 §10.8 再论程序结构 第十章小结
第十章 类 类构成了实现C++面向对象程序设计的基础。 类是C++数据封装的基本单元,它把数据和函数封装在一起。 本章介绍定义类和成员函数的方法,掌握访问成员函数的方法,理解私有数据如何屏蔽外部访问的原理。
§10.1 类的概念 类是对现实世界中的客观事物的抽象,将具有相同属性的一类事物称作类,类的实例称为对象。 类是一种复杂的数据类型,它是将不同类型的数据和与这些数据相关的操作封装在一起的集合体。 类具有对数据的抽象性、隐藏性和封装性。 类对象的行为由类的内部数据结构和相关的操作确定;外部行为通过操作接口实现。人们关心的就是操作接口所能提供的服务。
The aim of the C++ class concept is to provide the programmer with a tool for creating new types that can be used as conveniently as the built-in types. A class is a user-defined type. The classes logically don’t differ much from built-in types. Ideally such types should not differ from built-in types in the way they are used, only in the way they are created.
§10.2 类的定义格式 类定义包括两个部分: 1、说明部分(“做什么”): 数据成员(名称、类型) 成员函数(方法) 2、实现部分(“怎么做”): 成员函数的定义和实现
类定义的一般形式: //说明部分(类体) class <类名> { public: <公有段数据及成员函数> protected: <保护段数据及成员函数> private: <私有段数据及成员函数> } //实现部分 <各成员函数的实现>
简单的类常将说明部分和实现部分合并在一起。下面定义一个为Savings 的类: class Savings { public: unsigned deposit(unsigned amount) //member //function { balance+=amount; return balance; } private: unsigned accountnumber; //member date float balance; };
关键字class表示类; Savings 是自定义的类名,一般首字符用大写字母表示,与对象名相区别; 关键字public、private、protected为访问权限控制符(Access Control),规定成员的访问权限,他们出现的顺序和次数无限制; 公有(public)成员提供了类的接口功能,不仅可以被成员函数访问,而且可以在程序中被访问; 私有(private)成员是被类隐藏的数据,只有该类的成员或友元函数才可以访问,通常将数据成员定义为私有成员; 保护(protected)成员具有公有成员或私有成员的特性。
deposit()是成员函数 Functions declared within a class definition are called member functions. accountnumber和balance 是两个私有数据 private data can be used only by member function. 定义一个类后,在程序中必须定义类的对象,这与定义一种类型后,必须定义该类型的变量的概念是相同的。
§10.3 对象的定义和成员表示 对象是类的实例,任何一个对象都是属于某个已知类的。 对象定义格式如下: <类名> <对象名表>; 例如:定义Savings 类的对象如下: Saving a, b, c[10], *p; 其中a, b为两个一般对象; c[10]是对象数组; p是指向类Savings 对象的指针。 对象的成员与它所属类的成员一样,有数据成员和成员函数。
对象访问成员的方法与结构变量访问成员变量的方法相同。对象访问成员的方法与结构变量访问成员变量的方法相同。 访问一般对象的成员: <对象名>.<数据成员名> <对象名>.<成员函数名>(<参数表>) 访问指向对象的指针的成员: <对象指针名> -> <数据成员名> <对象指针名> -> <成员函数名>(<参数表>)
下面在函数fn()中定义了Savings类的对象,并对成员函数进行访问。下面在函数fn()中定义了Savings类的对象,并对成员函数进行访问。 void fn() { Savings a; Savings b; a.balance=100.5; //error b.balance=200.5; //error a.deposit(100); //right b.deposit(200); //right }
§10.4 定义成员函数 定义类中的成员函数可以采用以下三种方式: 1、成员函数的定义及实现在类体中完成; 2、成员函数的定义及实现在类体外完成; 3、成员函数的定义及实现与类体在不同的文件中完成。 下边举例说明这三种情况: 例1. 下面是一个关于日期的类的定义,并且在类体中定义成员函数的程序。
#include <iostream.h> class Tdate { public: void set(int m,int d, int y) { month=m; day=d; year=y; } int Isleapyear() { return(year%4==0&&year%100!=0) ||(year%400==0); } void print() { cout<<month<<“/”<<day<<“/”<<year<<endl; } private: int month; int day; int year; };
void main() { Tdate a; a.set(10,1,1949); a.print(); if(a.Isleapyear) cout<<“It is a leap year!” else cout<<“It is not a leap year!” } 结果: 10/1/1949 It is not a leap year! 注意:在类中定义成员函数一般适于语句只有1-5句的小规模函数,一般为内联函数中。
例2. 下面是在类体外定义成员函数的例子 在类体外定义成员函数时须按下述格式: <函数类型> <类名>::<成员函数名>(<参数表>) { <函数体> } 其中,作用域运算符(Scope Resolution)“::”是用来标识某个成员函数是属于哪个类的。 #include <iostream.h> class Tdate //说明部分 { public void set(int m,int d,int y); int Isleapyear(); void print();
private: int month; int day; int year; }; //实现部分 void Tdate::set(int m,int d,int y) { month=m; day=d; year=y; } int Tdate::Isleapyear() { return(year%4==0&&year%100!=0)||(year%400==0); } void Tdate::print() { cout<<month<<“/”<<day<<“/”<<year<<endl; }
例3. 类体与成员函数定义在不同文件中(类定义的头文件和类的成员函数定义的文件)。 //tdata.h class Tdate //类的说明部分 { pubilc: void set(int,int,int); int Isleapyear(); void print(); private: int month; int day; int year; }
//tdate.cpp #include <iostream.h> #include “tdate.h” void Tdate::set(int m,int d,int y) { month=m; day=d; year=y; } int Tdate::Isleapyear() { return(year%4==0&&year%100!=0) ||(year%400==0); } void Tdate::print() { cout<<month<<“/”<<day<<“/”<<year<<endl; }
将类体和其成员函数分开定义,是开发大型程序通常采用的方法。将类体和其成员函数分开定义,是开发大型程序通常采用的方法。 类体 外部接口 类的成员函数 内部实现 注意: 在类体外定义成员函数必须使用作用域操作符,在成员函数名前和“::”前加上类名,如果无类名,编译器会认为是普通函数; 在类体内定义成员函数时,实际上是省略了类名; 成员函数也可以重载,但由于类名是成员函数名的一部分,所以一个类的成员函数与另一个类的成员函数即使同名,也不认为是重载。 提供 提供
§10.5 调用成员函数 一个对象要表现其行为,就要调用它的成员函数。前面已经介绍了调用成员函数的方法。以下举例说明常用的几种调用方式。 一、用成员访问符调用 void func() { Tdate oneday; //定义类对象 oneday.set(2,5,1998); oneday.print(); }
二、用指针调用 例如:使用§10.4中的例子 //…mfca.prj mfca.cpp tdate.h //类成员函数定义的文件 //…mfca.cpp #include <iostream.h> #include “tdate.h” //Tdata类定义头文件 void somefunc(Tdate *ps) { ps->print(); //ps是指向Tdate类对象的指针 if(ps->Isleapyear()) cout<<“leap year\n”; else cout<<“not leap year\n”; }
void main( ) { Tdate s; //定义s为类对象 s.set(2,15,1998); somefunc(&s); //传递对象s的地址 } 结果: 2/15/1998 not leap year
三、用引用传递方式调用 用对象的引用调用成员函数,实参为对象自己。例如: //…mfca.prj mfca.cpp tdate.h //…mfca.cpp #include <iostream.h> #include “tdate.h” void somefunc(Tdate& refs) //形参定义为对象的引用 { refs.print(); if(refs.Isleapyear()) cout<<“leap year\n”; else cout<<“not leap leap year\n”; }
void main() { Tdate s; s.set(2,15,1998); somefunc(s); //对象s 传给引用 } 结果: 2/15/1998 not leap year
§10.6 this 指针 成员函数必须使用对象来调用。一个类的所有对象调用的成员函数都是同一个代码段,例如: void Tdata::set(int m,int d,int y) { month=m; day=d; year=y; } 成员函数如何识别month,day 和year 是属于哪个调用对象呢? 实际上,在每个类的成员函数中,都隐含了一个this 指针。该指针指向正在调用成员函数的对象。
当对象s调用 s.set(2,15,1998) 时,成员函数除了接收传递的3个参数外,还接收到正在调用成员函数的对象s 的地址,这个地址放入隐含的形参this 指针中。等同于执行this=&s;语句。所以对成员函数内数据成员的访问都隐含地加上了this 指针。 因此, month=m; 等价于 this->month=m; 或 s.month=m; 所以无论对应哪个对象调用,成员函数从获得的参数(显式的实参和隐含的对象地址)来判断都不会弄错;因此成员函数中访问数据成员无须在前面加对象名。
set()函数还可以表示成: void Tdate::set(int m,int d,int year) { this->month=m; this->day=d; this->year=y; } 注意: 一个类对象所占据的内存空间由它的数据成员所占据的数据空间总和决定。 类的成员函数不占据对象的内存空间。
§10.7 屏蔽类的内部实现 在编写应用程序时,要使用某个已定义的类,所要了解的全部内容是其公共段中的成员,即它们做什么用?参数是什么?并不需要了解其内部的实现。 正如使用电视机一样。 类提供的公共接口,为应用开发提供了方便。 即使由于条件的改变,或者发现了类中的错误,则只需改变类的内部实现代码,而并不要求改变外部应用,因为公共接口未变。
例如:下面的程序实现了一个Point 类,并使用该类计算点的直角坐标和极坐标。 //…ch10_7.cpp #include <iostream.h> #include <math.h> class Point { public: void set(double ix,double iy); //为x、y 坐标赋值 double xoffset(); //取x 轴坐标分量 double yoffset(); //取y 轴坐标分量 double angle(); //取点的极坐标弧角 double radius(); //取点的极坐标半径 private: double x; double y; }
void Point::set(double ix,double iy) { x=ix; y=iy; } double Point::xoffset() { return x; } double Point::yoffset() { return y; } double point::angle() { return(180/3.14159)*atan2(y,x); }
void Point::radius() { return sqrt(x*x+y*y); } void main() { Point p; double x,y; for( ; ; ) { cout<<“Enter x and y:\n”; cin>>x>>y; if(x<0||y<0) break; p.set(x,y);
cout<<“angle=”<<p.angle() <<“,radius=”<<p.radius() <<“,xoffset=”<<p.xoffset() <<“,yoffset=”<<p.yoffset()<<endl; } } 运行结果: Enter x and y: 10 10 <回车> angle=45, radius=14.1421, xoffset=10, yoffset=10 Enter x and y: 50 0 <回车> angle=0, radius=50, xoffset=50, yoffset=0 Enter x and y: -1 -1 <回车>
例中,私有数据存放一个类对象的x,y坐标,一切对x,y的操作都由成员函数完成。例中,私有数据存放一个类对象的x,y坐标,一切对x,y的操作都由成员函数完成。 double atan2(double y,double x);是在“math.h”中定义的坐标函数,其功能是求y/x的反正切。 通常将Point 类的定义从程序中分离,成为独立的头文件point.h,则程序可改为: //…ch11-7.cpp #include <iostream.h> #include <math.h> #include “point.h” void main() { …… }
//…point.h //类定义部分 class Point { //成员函数声明(公共接口) //私有数据 }; //类实现部分 void Point::set(double ix,double iy) { x=ix; y=iy; } //…
由于类很好地屏蔽了内部数据的表示,如果需要把类Point 的私有数据成员从点的直角坐标(x,y) 改为点的极坐标(a,r),尽管修改了相应成员函数的内部实现,但由于接口未变(即公共成员函数的名字、功能、调用方式均未变)。因此以该类为基础的应用程序的开发就不须改变,这就大大地减轻了开发应用程序的难度。 例如,上例中的直角坐标改为极坐标表示,程序改写如下: 包含main()的文件ch11-7.cpp不须改变,point.h文件中类说明部分提供的公共接口也无须改变,仅改变类的成员函数的实现部分。
//…point.h class Point { public: void set(double ix,double iy); //…公共接口未变 double xoffset(); double yoffset(); double angle(); double radius(); private: double a; double r; }
//成员函数实现部分改变 void Point::set(double ix,double iy) { a=atan2(iy,ix); r=sqrt(ix*ix+iy*iy); } double Point::xoffset() { return r*cos(a); } double Point::yoffset() { return r*sin(a); } double Point::angle() { return(180/3.14159)*a } double Point::radius() { return r; }
§10.8 再论程序结构 一、类的作用域 类的作用域简称类域,类域的范围是指在类所定义的类体中,该类的成员局部于该类所属的类域。一个类的任何成员都能访问同一类的任一其他成员。 对类作用域外的一个类的数据成员和成员函数的访问受程序员编写程序的控制。当把成员定义为私有和保护时,外界访问被限制。 类域可以被包含在文件域中,可见类域小于文件域;而类域中又可包含函数域,可见类域又大于函数域。 类域介于文件域和函数域之间。
二、对象的生存期 不同存储类的对象具有不同的生命期。 对象的生存期是指对象从创建开始到被释放为止的存在时间,即该对象的寿命。 按生命期的不同,对象可分为如下三种(与介绍的变量划分情况相似)。 1、局部对象:定义在一个函数体内或程序块内,作用域和生命周期都是局部的; 2、全局对象:定义在某个文件中,作用域为包含该文件的整个程序,生命期是全局的; 3、静态对象:分为内部静态对象和外部静态对象,生命期都是全局的,前者作用域为定义它的函数体和程序块内;后者作用域为定义它的文件。
三、可见性 类名允许与其他变量名或函数名同名,可通过下面方法实现正确的访问: 1、如果一个非类型名隐藏了类型名,则类型名通过加前缀class 访问: class Sample { //… } void func(int Sample) //形参屏蔽了类名 { class Sample a; //类名前加class Sample++; //形参自增运算 //… }
2、如果一个类型名隐藏了一个非类型名,则用一般作用域规则访问 int S=0; void func() { class S { … } //类S 屏蔽了全局变量S S a; //定义类对象a ::S=3; //引用全局变量前加作用域符 } int g=S; //全局变量S 给变量g 初始化
Point 类 封装体 xoffset 内部数据成员 yoffset 访问接口 x y set angle radius 四、类的封装 定义一个类就是实现对创建一个对象的数据结构的描述。 在类中,一些成员是保护的,被有效地屏蔽,以防外界的干扰和误操作。 另一些成员是公共的,作为接口提供外界使用。 右图是对它们采用的图示方法说明类的组成结构。以Point 类为例。
五、类库的构成 一个商业性C++ 类库包括一个类定义和成员函数的定义。 类定义以头文件的方式提供。 成员函数定义则以编译实现的代码方式提供。 六、C++ 程序结构 一个大的C++ 应用程序通常是一个“程序工程”。C++ 程序工程文件中,应包含以下程序文件。 main.cpp //包含主函数的程序文件 class.cpp’s //用户自定义类库的内部实现程序 function.cpp’s //用户自定义函数库的实现程序
其中,主函数的源程序文件应为: main.cpp #include <标准类库头文件>’s #include <标准函数头文件>’s #include “自定义类库头文件”’s #include “自定义函数头文件”’s 函数原形’s 全局数据定义’s void main() { …… } 函数定义’s
第十章 小结 一个类具有数据成员,还具有成员函数,通过成员函数可以对数据成员进行操作,并实现其它的功能。 定义一个类后可以把类名作为一种数据类型,定义其“变量”(即对象)。 程序利用成员操作符“.”或箭头操作符“->”访问类的公共成员。 程序可以在类体的外部或内部定义其成员函数,在类体的外部定义其成员函数时,须指出所属的类名,并用作用域分辨符“::”把类名和函数名连接起来。
类的成员都可以被说明为公有、保护和私有。公有成员可以在程序中任意被访问,而保护或私有成员只能被这个类的成员函数所访问。类的成员都可以被说明为公有、保护和私有。公有成员可以在程序中任意被访问,而保护或私有成员只能被这个类的成员函数所访问。 把成员说明为私有的或保护的,使类的使用者在使用它时,只关心接口,无须关心其内部实现,既方便使用,又保护了内部结构。这就是封装的原理。 含有类的程序结构,充分体现了类的封装和重用,更容易理解。