3.3 类与封装
类具有封装性。可以从两方面认识类的封装性:其一是类能够把数据和算法(操作数据的函数)组合在一起,构成一个不可分割的整体;其二是类具有信息隐藏的能力,能够有效地把类的内部数据(即私有和受保护成员)隐藏起来,使外部函数只有通过类的公有成员才能访问类的内部数据。
封装使类成为一个具有内部数据的自我隐藏能力、功能独立的软件模块。用private把不想让其他程序访问的数据或函数设置为私有成员,就可以禁止其他程序对这些数据的随意修改;用public设置一些公有成员,让本类之外的其他函数能够通过这些公有成员,按照类允许的方法访问类的私有数据,实现数据保护的目的。
现在来设计一个时钟类,借此理解类的封装和信息隐藏。任何时钟都是一个独立存在的有形实体,在钟面设置有时针、分针、秒针,人们通过这些指针查看时间,如果时间不准确,还可以通过这些指针调整时间。时钟的内部结构和内部运行机制则被封装在钟表内部,人们不知道这些内部结构,也不知道时钟的内部运行机制。(指针怎样移动?是电流驱动还是机械驱动呢?)事实上,这些都是时钟的事情,人们没必要知道。
用类来抽象与封装时钟类,把时、分、秒设置为私有数据成员,把内部运行机制设置成私有成员函数run( ),把时钟提供给人们的操作(如设置时、分、秒)设置成公有成员函数,用这种思想抽象得到的一个时钟类如下:
class Clock{ private: int hour,minute,second; void run(); public: void setHour(int h) { hour=h; } void setMinute(int m) { minute=m; } void setSecond(int s) { second=s; } void dispTime(){ cout<<"Now is: "<<hour<<":"<<minute<<":"<<second<<endl; } }; void Clock ::run(){ while(1){ if(++second>=60){ second=0; if(++minute==60){ minute=0; if(++hour==24) hour=0; } } dispTime(); } }
图3-1是时钟及时钟类的对比图形。时钟类把时钟的属性和行为封装成一个整体,通过访问限定符private将时钟内部的属性(即hour、minute、second)和行为(即时钟运行函数run( ))设置为私有成员,这些私有成员不允许外部函数直接访问,就好像不允许人们直接拨动时钟的指针调整时间一样。
图3-1 时钟和Clock类
通过public限定符将时钟的接口(即显示时间的表面,允许人们操作的时、分、秒调整旋钮)设置成公有成员函数dispTime()、setHour()、setMinute()、setSecond(),外部函数可以调用这些函数进行时间的显示和调整,就好像允许人们通过时钟表面观察时间,通过时针调整旋钮调整时间一样(例如,通过公有成员函数setHour()设置私有成员hour的值,就好像旋转时针旋钮调整小时的数值一样)。
在Clock类中,显露在时钟外部可供人们查看和操作的部分就被抽象成了公有成员,隐藏在时钟内部不为人知的部分和工作原理就被封装成了私有成员。要想访问Clock的私有成员,必须通过公有成员才能做到。这一点与人们只能通过时钟的外部接口(时钟表面的各种开关和时间调节旋钮)才能调整时间的道理完全相同,所以类的公有成员也称为类的接口,其意思就是,外部函数要访问类的内部成员,只有通过类的公有成员才能实现。