
类和对象
类和对象(class)是两种以计算机为载体的计算机语言的合称。对象是对客观事物的抽象,类是对对象的抽象。类是一种抽象的数据类型。
它们的关係是,对象是类的实例,类是对象的模板。对象是通过new className产生的,用来调用类的方法;类的构造方法 。
基本介绍
- 中文名:类和对象
- 外文名:class
- 载体:计算机
- 举例:方法和变数的软体模板
- 分类:计算机语言
声明定义
类对象的定义
类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起。
对象是具有类类型的变数。类和对象是面向对象编程技术中的最基本的概念。
类对象的关係
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用记忆体,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变数的软体模板。
类类型的声明
class 类名{ public: 公用的数据和成员函式 protected: 保护的数据和成员函式 private: 私有的数据和成员函式}
定义对象方法
1.先声明类类型,然后再定义对象
举例:Student stud1,stud2; //Student是已经声明的类类型
2.在声明类类型的同时定义对象
class Student//声明类类型{ public://先声明公用部分 void display() { cout<<″num:″<<num<<endl; cout<<″name:″<<name<<endl; cout<<″sex:″<<sex<<endl; } private://后声明私有部分 int num; char name[20]; char sex;}stud1,stud2;//定义了两个Student类的对象//在定义Student类的同时,定义了两个Student类的对象。
3.不出现类名,直接定义对象
class//无类名{ private://声明以下部分为私有的 ┆ public://声明以下部分为公用的 ┆}stud1,stud2;//定义了两个无类名的类对象
直接定义对象,在C++中是合法的、允许的,但却很少用,也不提倡用。在实际的程式开发中,一般都採用上面3种方法中的第1种方法。在小型程式中或所声明的类只用于本程式时,也可以用第2种方法。在定义一个对象时,编译系统会为这个对象分配存储空间,以存放对象中的成员。
类结构体异同
C++增加了class类型后,仍保留了结构体类型(struct ),而且把它的功能也扩展了。C++允许用struct来定义一个类型。如可以将前面用关键字class声明的类类型改为用关键字struct。
为了使结构体类型也具有封装的特徵,C++不是简单地继承C的结构体,而是使它也具有类的特点,以便于用于面向对象程式设计。
用struct声明的结构体类型实际上也就是类。用struct声明的类,如果对其成员不作private或public的声明,系统将其默认为public。
如果想分别指定私有成员和公用成员,则套用private或public作显式声明。
而用class定义的类,如果不作private或public声明,系统将其成员默认为private,在需要时也可以自己用显式声明改变。
如果希望成员是公用的,使用struct比较方便,如果希望部分成员是私有的,宜用class。建议儘量使用class来建立类,写出完全体现C++风格的程式。
成员函式
成员函式性质
类的成员函式(简称类函式)是函式的一种,它的用法和作用和第4章介绍过的函式基本上是一样的,它也有返回值和函式类型,
它与一般函式的区别只是:
它是属于一个类的成员,出现在类体中。
它可以被指定为private(私有的)、public (公用的)或protected(受保护的)。
在使用类函式时,要注意调用它的许可权(它能否被调用)以及它的作用域(函式能使用什幺範围中的数据和函式)。
例如私有的成员函式只能被本类中的其它成员函式所调用,而不能被类外调用。
成员函式可以访问本类中任何成员(包括私有的和公用的),可以引用在本作用域中有效的数据。
一般的做法是将需要被外界调用的成员函式指定为public,它们是类的对外接口。
但应注意,并非要求把所有成员函式都指定为public。有的函式并不是準备为外界调用的,而是为本类中的成员函式所调用的,就应该将它们指定为private。
这种函式的作用是支持其它函式的操作,是类中其它成员的工具函式(utility function),类外用户不能调用这些私有的工具函式。
类的成员函式是类体中十分重要的部分。如果一个类中不包含成员函式,就等同于C语言中的结构体了,体现不出类在面向对象程式设计中的作用。
类外定义函式
classStudent{public:voiddisplay();//公用成员函式原型声明private:intnum;stringname;charsex;//以上3行是私有数据成员};voidStudent∷display()//在类外定义display类函式{cout<<″num:″<<num<<endl;//函式体cout<<″name:″<<name<<endl;cout<<″sex:″<<sex<<endl;}Studentstud1,stud2;//定义两个类对象
注意:在类体中直接定义函式时,不需要在函式名前面加上类名,因为函式属于哪一个类是不言而喻的。
但成员函式在类外定义时,必须在函式名前面加上类名,予以限定(qualifed),“∷”是作用域限定符(field qualifier)或称作用域运算符,用它声明函式是属于哪个类的。
如果在作用域运算符“∷”的前面没有类名,或者函式名前面既无类名又无作用域运算符“∷”,
如 ∷display( ) 或 display( ),则表示display函式不属于任何类,这个函式不是成员函式,而是全局函式,即非成员函式的一般普通函式。
类函式必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函式定义之前,否则编译时会出错。
虽然函式在类的外部定义,但在调用成员函式时会根据在类中声明的函式原型找到函式的定义(函式代码),从而执行该函式。
在类的内部对成员函式作声明,而在类体外定义成员函式,这是程式设计的一种良好习惯。
如果一个函式,其函式体只有2-3行,一般可在声明类时在类体中定义。多于3行的函式,一般在类体内声明,在类外定义。
inline 函式
在类体中定义的成员函式的规模一般都很小,而系统调用函式的过程所花费的时间开销相对是比较大的。调用一个函式的时间开销远远大于小规模函式体中全部语句的执行时间。
为了减少时间开销,如果在类体中定义的成员函式中不包括循环等控制结构,C++系统会自动将它们作为内置(inline )函式来处理。
也就是说,在程式调用这些成员函式时,并不是真正地执行函式的调用过程(如保留返回地址等处理),而是把函式代码嵌入程式的调用点。
这样可以大大减少调用成员函式的时间开销。C++要求对一般的内置函式要用关键字inline声明,但对类内定义的成员函式,可以省略inline,因为这些成员函式已被隐含地指定为内置函式。如
classStudent{public:voiddisplay(){cout<<″num:″<<num<<endl;cout<<″name:″<<name<<endl;cout<<″sex:″<<sex<<endl;}private:intnum;stringname;charsex;};
其中第3行
void display( ) 也可以写成
inline void display( )
将display函式显式地声明为内置函式。
以上两种写法是等效的。对在类体内定义的函式,一般都省写inline。
应该注意的是: 如果成员函式不在类体内定义,而在类体外定义,系统并不把它默认为内置(inline )函式,调用这些成员函式的过程和调用一般函式的过程是相同的。如果想将这些成员函式指定为内置函式,应当用inline作显式声明。如
classStudent{public:inlinevoiddisplay();//声明此成员函式为内置函式private:intnum;stringname;charsex;};inlinevoidStudent∷display()//在类外定义display函式为内置函式{cout<<″num:″<<num<<endl;cout<<″name:″<<name<<endl;cout<<″sex:″<<sex<<endl;}
在函式的声明或函式的定义两者之一作inline声明即可。
值得注意的是: 如果在类体外定义inline函式,则必须将类定义和成员函式的定义都放在同一个头档案中(或者写在同一个源档案中),否则编译时无法进行置换(将函式代码的拷贝嵌入到函式调用点)。
但是这样做,不利于类的接口与类的实现分离,不利于信息隐蔽。虽然程式的执行效率提高了,但从软体工程质量的角度来看,这样做并不是好的办法。
只有在类外定义的成员函式规模很小而调用频率较高时,才将此成员函式指定为内置函式。
成员函式存储
用类去定义对象时,系统会为每一个对象分配存储空间。
如果一个类包括了数据和函式,要分别为数据和函式的代码分配存储空间。
按理说,如果用同一个类定义了10个对象,那幺就需要分别为10个对象的数据和函式代码分配存储单元。
能否只用一段空间来存放这个共同的函式代码段,在调用各对象的函式时,都去调用这个公用的函式代码。
显然,这样做会大大节约存储空间。C++编译系统正是这样做的,因此每个对象所占用的存储空间只是该对象的数据部分所占用的存储空间,而不包括函式代码所占用的存储空间。如果声明了一个类:
classTime{public:inthour;intminute;intsec;voidset(){cin>>a>>b>>c;}};
可以用下面的语句来输出该类对象所占用的位元组数:
cout<<sizeof(Time)<<endl;
输出的值是12。
这就证明了一个对象所占的空间大小只取决于该对象中数据成员所占的空间,而与成员函式无关。
函式代码是存储在对象空间之外的。如果对同一个类定义了10个对象,这些对象的成员函式对应的是同一个函式代码段,而不是10个不同的函式代码段。
需要注意的是: 虽然调用不同对象的成员函式时都是执行同一段函式代码,但是执行结果一般是不相同的。
不同的对象使用的是同一个函式代码段,它怎幺能够分别对不同对象中的数据进行操作呢?
原来C++为此专门设立了一个名为this的指针,用来指向不同的对象。需要说明:
(1) 不论成员函式在类内定义还是在类外定义,成员函式的代码段都用同一种方式存储。
(2) 不要将成员函式的这种存储方式和inline(内置)函式的概念混淆。
(3) 应当说明: 常说的“某某对象的成员函式”,是从逻辑的角度而言的,而成员函式的存储方式,是从物理的角度而言的,二者是不矛盾的。
成员引用
运算访问成员
例如在程式中可以写出以下语句:
stud1.num=1001;//假设num已定义为公用的整型数据成员
表示将整数1001赋给对象stud1中的数据成员num。
其中“.”是成员运算符,用来对成员进行限定,指明所访问的是哪一个对象中的成员。
注意不能只写成员名而忽略对象名。
访问对象中成员的一般形式为
对象名.成员名
不仅可以在类外引用对象的公用数据成员,而且还可以调用对象的公用成员函式,但同样必须指出对象名,如
stud1.display( );//正确,调用对象stud1的公用成员函式
display( );//错误,没有指明是哪一个对象的display函式
由于没有指明对象名,编译时把display作为普通函式处理。
应该注意所访问的成员是公用的(public )还是私有的(private )。只能访问public成员,而不能访问private成员,如果已定义num为私有数据成员,下面的语句是错误的:
stud1.num=10101;//num是私有数据成员,不能被外界引用
在类外只能调用公用的成员函式。在一个类中应当至少有一个公用的成员函式,作为对外的接口,否则就无法对对象进行任何操作。
指向访问成员
class Time
{
public : //数据成员是公用的
int hour;
int minute;
};Time t,*p;//定义对象t和指针变数p
p=&t;//使p指向对象t
cout<<p->hour;//输出p指向的对象中的成员hour
在p指向t的前提下,p->hour,(*p).hour和t.hour三者等价。
对象访问成员
如果为一个对象定义了一个引用变数,它们是共占同一段存储单元的,实际上它们是同一个对象,只是用不同的名字表示而已。
因此完全可以通过引用变数来访问对象中的成员。
如果已声明了Time类,并有以下定义语句:
Time t1; //定义对象t1
Time &t2=t1;//定义Time类引用变数t2,并使之初始化为t1
cout<<t2.hour;//输出对象t1中的成员hour
由于t2与t1共占同一段存储单元(即t2是t1的别名),因此t2.hour就是t1.hour。
套用举例
例1
最简单的例子。
#include<iostream>usingnamespacestd;classTime//定义Time类{public://数据成员为公用的inthour;intminute;intsec;};intmain(){Timet1;//定义t1为Time类对象cin>>t1.hour;//输入设定的时间cin>>t1.minute;cin>>t1.sec;//输出时间:cout<<t1.hour<<″:″<<t1.minute<<″:″<<t1.sec<<endl;return0;}
运行情况如下: 1232 43↙
12:32:43
注意:
(1) 在引用数据成员hour,minute,sec时不要忘记在前面指定对象名。
(2) 不要错写为类名,
如写成
Time.hour,Time.minute,Time.sec是不对的。因为类是一种抽象的数据类型,并不是一个实体,也不占存储空间,而对象是实际存在的实体,是占存储空间的,其数据成员是有值的,可以被引用的。
(3) 如果删去主函式的3个输入语句,即不向这些数据成员赋值,则它们的值是不可预知的。
例2
引用多个对象的成员。
(1)程式(a)#include<iostream>usingnamespacestd;classTime{public:inthour;intminute;intsec;};intmain(){Timet1;//定义对象t1cin>>t1.hour;//向t1的数据成员输入数据cin>>t1.minute;cin>>t1.sec;cout<<t1.hour<<″:″<<t1.minute<<″:″<<t1.sec<<endl;//输出t1中数据成员的值Timet2;//定义对象t2cin>>t2.hour;//向t2的数据成员输入数据cin>>t2.minute;cin>>t2.sec;cout<<t2.hour<<″:″<<t2.minute<<″:″<<t2.sec<<endl;//输出t2中数据成员的值return0;}运行情况如下:103243↙10:32:43223243↙22:32:43程式是清晰易懂的,但是在主函式中对不同的对象一一写出有关操作,会使程式冗长。为了解决这个问题,可以使用函式来进行输入和输出。见程式(b)。(2)程式(b)#include<iostream>usingnamespacestd;classTime{public:inthour;intminute;intsec;};intmain(){voidset_time(Time&);//函式声明voidshow_time(Time&);//函式声明Timet1;//定义t1为Time类对象set_time(t1);//调用set_time函式,向t1对象中的数据成员输入数据show_time(t1);//调用show_time函式,输出t1对象中的数据Timet2;//定义t2为Time类对象set_time(t2);//调用set_time函式,向t2对象中的数据成员输入数据show_time(t2);//调用show_time函式,输出t2对象中的数据return0;}voidset_time(Time&t)//定义函式set_time,形参t是引用变数{cin>>t.hour;//输入设定的时间cin>>t.minute;cin>>t.sec;}voidshow_time(Time&t)//定义函式show_time,形参t是引用变数{cout<<t.hour<<″:″<<t.minute<<″:″<<t.sec<<endl;//输出对象中的数据}
运行情况与程式(a)相同。
(3) 程式(c)
可以对上面的程式作一些修改,数据成员的值不再由键盘输入,而在调用函式时由实参给出,并在函式中使用默认参数。将程式(b)第8行以下部分改为
intmain(){voidset_time(Time&,inthour=0,intminute=0,intsec=0);//函式声明voidshow_time(Time&);//函式声明Timet1;set_time(t1,12,23,34);//通过实参传递时、分、秒的值show_time(t1);Timet2;set_time(t2);//使用默认的时、分、秒的值show_time(t2);return0;}voidset_time(Time&t,inthour,intminute,intsec){t.hour=hour;t.minute=minute;t.sec=sec;}voidshow_time(Time&t){cout<<t.hour<<″:″<<t.minute<<″:″<<t.sec<<endl;}
程式运行时的输出为
12:23:34 (t1中的时、分、秒)
0:0:0 (t2中的时、分、秒)
以上两个程式中定义的类都只有数据成员,没有成员函式,这显然没有体现出使用类的优越性。在下面的例子中,类体中就包含了成员函式。
例3
将例2的程式改用含成员函式的类来处理。
#include<iostream>usingnamespacestd;classTime{public:voidset_time();//公用成员函式voidshow_time();//公用成员函式private://数据成员为私有inthour;intminute;intsec;};intmain(){Timet1;//定义对象t1t1.set_time();//调用对象t1的成员函式set_time,向t1的数据成员输入数据t1.show_time();//调用对象t1的成员函式show_time,输出t1的数据成员的值Timet2;//定义对象t2t2.set_time();//调用对象t2的成员函式set_time,向t2的数据成员输入数据t2.show_time();//调用对象t2的成员函式show_time,输出t2的数据成员的值return0;}voidTime∷set_time()//在类外定义set_time函式{cin>>hour;cin>>minute;cin>>sec;}voidTime∷show_time()//在类外定义show_time函式{cout<<hour<<″:″<<minute<<″:″<<sec<<endl;}
运行情况与例2中的程式(a)相同。
注意:
(1) 在主函式中调用两个成员函式时,应指明对象名(t1,t2)。表示调用的是哪一个对象的成员函式。
(2) 在类外定义函式时,应指明函式的作用域(如void Time∷set_time( ))。在成员函式引用本对象的数据成员时,只需直接写数据成员名,这时C++系统会把它默认为本对象的数据成员。也可以显式地写出类名并使用域运算符。
(3) 应注意区分什幺场合用域运算符“∷”,什幺场合用成员运算符“.”,不要搞混。
例4
找出一个整型数组中的元素的最大值。这个问题可以不用类的方法来解决,用类来处理,读者可以比较不同方法的特点。
#include<iostream>usingnamespacestd;classArray_max//声明类{public://以下3行为成员函式原型声明voidset_value();//对数组元素设定值voidmax_value();//找出数组中的最大元素voidshow_value();//输出最大值private:intarray[10];//整型数组intmax;//max用来存放最大值};voidArray_max∷set_value()//成员函式定义,向数组元素输入数值{inti;for(i=0;i<10;i++)cin>>array[i];}voidArray_max∷max_value()//成员函式定义,找数组元素中的最大值{inti;max=array[0];for(i=1;i<10;i++)if(array[i]>max)max=array[i];}voidArray_max∷show_value()//成员函式定义,输出最大值{cout<<″max=″<<max;}intmain(){Array_maxarrmax;//定义对象arrmaxarrmax.set_value();//调用arrmax的set_value函式,向数组元素输入数值arrmax.max_value();//调用arrmax的max_value函式,找出数组元素中的最大值arrmax.show_value();//调用arrmax的show_value函式,输出数组元素中的最大值return0;}
运行结果如下:
12 12 39 -34 17 134 045 -91 76↙ (输入10个元素的值)
max=134 (输入10个元素中的最大值)
请注意成员函式定义与调用成员函式的关係,定义成员函式只是设计了一组操作代码,并未实际执行,只有在被调用时才真正地执行这一组操作。
可以看出: 主函式很简单,语句很少,只是调用有关对象的成员函式,去完成相应的操作。
在大多数情况下,主函式中甚至不出现控制结构(判断结构和循环结构),而在成员函式中使用控制结构。
在面向对象的程式设计中,最关键的工作是类的设计。所有的数据和对数据的操作都体现在类中。
只要把类定义好,编写程式的工作就显得很简单了。