类的声明和对象的定义
类和对象的关系
- 类是对象的抽象,而对象是类的具体实例。
- 类是抽象的,不占内存,而对象是具体的,占用存储空间。
声明类类型
- 类是用户建立的类型。如果程序中要用到类类型,必须自己根据需要进行声明,或者使用别人已设计好的类。C++中声明类类型的方法和声明一个结构体类型是相似的。
例:声明一个结构体类型
struct Student //声明了一个名为Student的结构体类型
{
int num;
char name[20];
char sex;
};
Student stud1, stud2; //定义了两个结构体变量stud1和stud2
例:声明一个类
class Student //以class开头,类名为Student
{
int num;
char name[20];
char sex;
void display() //这是成员函数
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl; //以上3行是函数中的操作语句
}
};
Student stud1, stud2; //定义了两个Student类的对象stud1和stud2
程序分析:
- 可以看到,声明类的方法是由声明结构体类型的方法发展而来的。第一行(class Student)是类头,由关键字class与类名Student组成,class是声明类时必须使用的关键字,相当于声明结构体类型时必须使用struct一样。从第2行开头的左花括号起到倒数第2行的右花括号是类体。也就是说,类体是一对花括号括起来的。类的声明以分号结束。
- 在类体中是类的成员表,列出类中的全部成员。可以看到除了数据部分以外,还包括了对这些数据操作的函数。这就体现了把数据和操作封装在一起。display是一个函数,用来对本对象中的数据进行操作。
- 现在封装在类对象stud1和stud2中的成员对外界都是隐藏的,外界不能调用它们。只有本地对象中的函数display可以引用本对象中的数据进行操作。也就是说,在类外不能直接调用类中的成员。
- 类类型声明的一般形式:
class 类名
{ //类体
public: //公有访问权限
公有的数据成员和成员函数
protected: //保护访问权限
保护的数据成员和成员函数
private: //私有访问权限
私有的数据成员和成员函数
};
- 无论数据成员还是函数成员,类的每个成员都有访问控制属性,由以下三种访问标号说明:public(公有的)、private(私有的)和protected(保护的)。
- 公有成员用public标号声明,类成员和类用户都可以访问公有成员,任何一个来自类外部的类用户都必须通过公有成员来访问。显然,public实现了类的外部接口。
- 私有成员用private标号声明,只有类成员可以访问私有成员,类用户的访问是不允许的。显然,private实现了私有成员的隐蔽。
- 保护成员用protected标号声明,在不考虑继承的情况下,protected的性质和private的性质一致,但保护成员可以被派生类的类成员访问。
说明:
- 在定义类时,声明为public、private或protected的成员的次序任意。
- 在一个类体中不一定都包含public、private或protected部分,可以只有public、private、protected部分或任意组合。
- 关键字public、private、protected可以分别出现多次,即一个类体可以包含多个public、private或protected部分。但更通用的做法是将相同访问控制属性的成员集中在一起来写。
- 实际编程中,为了使程序清晰,每一种成员访问限定符在类体中只出现一次,且按照public、protected、private顺序组织,形成访问权限层次分明的结构。
- 如果在类的定义中既不指定private,也不指定public,则系统就默认为是私有的。
定义对象的方法
- 定义对象的方法有3种:
- 先声明类类型,然后再定义对象
- C++中,在声明了类类型后,定义对象有两种形式
①class 类名 对象名;
如:class Student stud1,stud2;
把class和Student合起来作为一个类名,用来定义对象
②类名 对象名;
如:Student stud1,stud2;
可以不必写class,而直接用类名定义对象
- 第1种方法是从C语言继承下来的,第2种方法是C++的特色,显然第2种方法使用更简捷方便。
2.在声明类的同时定义对象
class Student //声明类类型
{
public: //先声明公共部分
void display() //这是成员函数
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl; //以上3行是函数中的操作语句
}
private: //后声明私有部分
int num;
char name[20];
char sex;
}stud1,stud2; //定义了两个Student类的对象
- 不出现类名,直接定义对象
class //无类名
{
private: //声明以下部分为私有的
...
public: //声明以下部分为公用的
...
}stud1,stud2; //定义了两个无类名的类对象
- 直接定义对象在C++是合法的、允许的,但却很少用,也不提倡使用。因为在面向对象程序设计和C++程序中,类的声明和类的使用是分开的,类并不只为一个程序服务,人们常把一些常用的功能封装成类,并放在类库中。
类的数据成员
在类中声明数据成员
- 类的数据成员的声明类似于普通变量的声明。如果一个类具有多个同一类型的数据成员,则这些成员可以在一个成员声明中指定。
- 类的数据成员可以是基本类型、数组、指针、引用、共用体、枚举类型、void指针、const限定等数据类型。
例:
class ADT //类成员数据类型
{
... //成员函数
long color;
double x, y, z, side; //基本类型
int a[10]; //数组
char* s; //指针
char& r; //引用
void* p; //void指针
};
- 类的数据成员还可以是成员对象,即类类型或结构体类型的对象。若类A中嵌入了类B的对象,称这个对象为子对象。
例:类Line嵌入了类Point的子对象start、end
class Point //点类
{
public:
void set(int a, int b);
int x, y;
};
class Line //线类
{
void set(Point a, Point b);
Point start, end; //成员对象
};
在类中定义或声明数据类型
- 除了定义数据成员和成员函数之外,类还可以定义自己的局部类型,并且使用类型别名来简化。
- 在类中定义或声明的数据类型的作用是类内部,因此,它们不能在类外使用。
- 在类定义中,可以定义结构体和共用体类型、嵌套的类定义,声明枚举类型。
例:
class ADT //类定义
{
struct Point { int x, y; }; //定义结构体
union UData { Point p; long color }; //定义共用体
enum COLORS{ RED,GREEN,BLUE,BLACK,WHITE }; //定义枚举类型
class Nested //嵌套类定义
{
... //成员函数
Point start; //数据成员
UData end; //数据成员
COLORS color; //数据成员
};
typedef Point* LPPOINT; //声明类型别名
... //成员函数
... //数据成员
}; //定义结束
类的成员函数
成员函数的性质
- 类的成员函数(简称类函数)是函数的一种,它也有返回值和函数类型,它与一般函数的区别只是:它是属于一个类的成员,出现在类体中。它可以被指定为private(私有的)、public (公用的)或protected(受保护的)。
- 在使用类函数时,要注意调用它的权限(它能否被调用)以及它的作用域(函数能使用什么范围中的数据和函数)。例如私有的成员函数只能被本类中的其它成员函数所调用,而不能被类外调用。成员函数可以访问本类中任何成员(包括私有的和公用的),可以引用在本作用域中有效的数据。
- 一般的做法是将需要被外界调用的成员函数指定为public,它们是类的对外接口。但应注意,并非要求把所有成员函数都指定为public。有的函数并不是准备为外界调用的,而是为本类中的成员函数所调用的,就应该将它们指定为private。这种函数的作用是支持其它函数的操作,是类中其它成员的工具函数,类外用户不能调用这些私有的工具函数。类的成员函数是类体中十分重要的部分。如果一个类中不包含成员函数,就等同于C语言中的结构体了,体现不出类在面向对象程序设计中的作用。
在类外定义成员函数
- 成员函数可以在类体中定义,也可以在类体中只对成员函数进行声明,而在类的外面进行函数定义。
例:
class Student //声明类类型
{
public:
void display(); //公用成员函数原型声明
private:
int num;
char name[20];
char sex; //以上3行是私有数据成员
};
void Student::display() //在类外定义display类函数
{ //函数体
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl; //以上3行是函数中的操作语句
}
Student stud1, stud2; //定义两个类对象
程序分析:
- 注意:在类体中直接定义函数时,不需要在函数名前面加上类名,因为函数属于哪一个类是不言而喻的。但成员函数在类外定义时,必须在函数名前面加上类名,予以限定,"∷"是作用域限定符或称作用域运算符,用它声明函数是属于哪个类的。Student∷display()表示Student类的作用域中的display函数,也就是Student类中的display函数。如果没有"Student∷"的限定,则不是Student类中的display函数。由于不同的类中可能有同名的函数(但功能可能不同),用作用域限定符加以限定,就明确地指明了是哪一个作用域的函数,也就是哪一个类的函数。
- 如果在作用域运算符∷的前面没有类名,或者函数名前面既无类名又无作用域运算符∷,如∷display()或display(),则表示display函数不属于任何类,这个函数不是成员函数,而是全局函数,即非成员函数的一般普通函数。此时
∷display()
的∷不是类作用域限定符的含义,而是命名空间域定义限定符的含义。 - 类函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前,否则编译时会出错。
- 虽然函数在类的外部定义,但在调用成员函数时会根据在类中声明的函数原型找到函数的定义(函数代码),从而执行该函数。
- 在类的内部对成员函数作声明,而在类体外定义成员函数,这是程序设计的一种良好习惯。如果一个函数,其函数体只有2-3行,一般可在声明类时在类体中定义。多于3行的函数,一般在类体内声明,在类外定义。这样不仅可以减少类体的长度,使类体清晰,便于阅读,而且能使类的接口种类和类的实现细节分离。
内置成员函数
- 类的成员函数也可以指定为内置函数。
- 在类体中定义的成员函数的规模一般都很小,而系统调用函数的过程所花费的时间开销相对是比较大的。调用一个函数的时间开销远远大于小规模函数体中全部语句的执行时间。为了减少时间开销,如果在类体中定义的成员函数中不包括循环等控制结构,C++系统会自动将它们作为内置(inline)函数来处理。
- 也就是说,在程序调用这些成员函数时,并不是真正地执行函数的调用过程(如保留返回地址等处理),而是把函数代码嵌入程序的调用点。这样可以大大减少调用成员函数的时间开销。C++要求对一般的内置函数要用关键字inline声明,但对类内定义的成员函数,可以省略inline,因为这些成员函数已被隐含地指定为内置函数。
例:
class Student //声明类类型
{
public:
void display() //等价于inline void display()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
}
private:
int num;
char name[20];
char sex;
};
- 其中第3行
void display( )
也可以写成inline void display( )
,将display函数显式地声明为内置函数。
以上两种写法是等效的。对在类体内定义的函数,一般都省写inline。 - 应该注意的是,如果成员函数不在类体内定义,而在类体外定义,系统并不把它默认为内置(inline)函数,调用这些成员函数的过程和调用一般函数的过程是相同的。如果想将这些成员函数指定为内置函数,应当用inline作显式声明。
- 在函数的声明时或函数的定义时作inline声明均可(二者有其一即可)。值得注意的是,如果在类体外定义inline函数,则必须将类定义和成员函数的定义都放在同一个头文件中(或者写在同一个源文件中),否则编译时无法进行置换(将函数代码的拷贝嵌入到函数调用点)。但是这样做,不利于类的接口与类的实现分离,不利于信息隐蔽。虽然程序的执行效率提高了,但从软件工程质量的角度来看,这样做并不是好的办法。
- 只有在类外定义的成员函数规模很小而调用频率较高时,才将此成员函数指定为内置函数
成员函数的存储方式
- 同一类的不同对象中的数据成员的值一般是不相同的,而不同对象的函数的代码是相同的,不论调用哪一个对象的函数的代码,其实调用的都是同样内容的代码
- 为了节约存储空间,C++编译系统中,每个对象所占用的存储空间只是该对象的数据部分所占用的存储空间,而不包括函数代码所占用的存储空间。因此,一个对象所占的空间大小只取决于该对象中数据成员所占的空间,而与成员函数无关
- 需要注意的是:虽然调用不同对象的成员函数时都是执行同一段函数代码,但是执行结果一般是不相同的。C++为此专门设了一个名为this的指针,用来指向不同的对象
说明:
- 不论成员函数在类内定义还是类外定义,成员函数的代码段的存储方式是相同的,都不占用对象的存储空间。
- 不论是否用inline声明,成员函数的代码段都不占用对象的存储空间(inline函数只影响程序的执行效率而与成员函数是否占用对象的存储空间无关)
- 虽然成员函数并没有放在对象的存储空间中,但从逻辑角度,成员函数是和数据封装在一个对象中的,只允许本对象中成员的函数访问同一对象中的私有数据
对象成员的引用
访问对象中的成员可以有3种方法:
- 通过对象名和成员运算符访问对象中的成员(" . ")
- 通过指向对象的指针访问对象中的成员(" -> ")
- 通过对象的引用访问对象中的成员(" & ")
类的封装性和信息屏蔽
公共接口与私有实现的分离
- 类的作用是把数据和算法封装在用户声明的抽象数据类型中。在面向对象的程序设计中,在声明类时,一般都是把所有的数据指定为私有的,使它们与外界隔离。把需要让外界调用的成员函数指定为公用的。
- 在一个类中应当至少有一个公用的成员函数,作为对外的接口,否则就无法对对象进行任何操作。
- 通过成员函数对数据成员进行操作称为类的功能的实现,为了防止用户任意修改公用成员函数,改变对数据进行的操作,往往不让用户看到公用成员函数的源代码,显然更不能修改,用户只能接触到公用成员函数的目标代码。可以看到:类中被操作的数据是私有的,类的功能的实现细节对用户是隐蔽的,这种实现称为私有实现。这种"类的公用接口与私有实现的分离"形成了信息隐蔽。
软件工程的一个最基本的原则就是将接口与实现分离,信息隐蔽是软件工程的重要概念。它的好处在于:
- 如果想修改或扩充类的功能,只需修改该类中有关的数据成员和它有关的成员函数,程序中类以外的部分可以不必修改。
当接口与实现(对数据的操作)分离时,只要类的接口没有改变,对私有实现的修改不会引起程序的其他部分的修改。- 如果在编译时发现类中的数据读写有错,不必检查整个程序,只须检查本类中访问这些数据的少数成员函数。
类声明和成员函数定义的分离
- 一个类如果只被一个程序使用,那么类的声明和成员函数的定义可以直接写在程序的开头,但是如果一个类被多个程序使用,这样做的重复工作量就很大了,效率就太低了。在面向对象的程序开发这,往往把类的声明(其中包含成员函数的声明)放在指定的头文件中,用户如果想用该类,只需要把有关的头文件包含进来即可,不必在程序中重复书写类的声明,以减少工作量,节省篇幅,提高编程效率
- 为了实现信息隐蔽,对类成员函数的定义一般不放在头文件中,而另外放在一个文件中。包含成员函数定义的文件就是类的实现。特别注意:类声明和函数定义是分别放在两个文件中的。
- 实际上,一个C++程序是由3个部分组成的:
- 类声明的头文件(后缀为.h)
- 类实现文件(后缀为.cpp),它包含类成员函数的定义
- 类的使用的文件(后缀为.cpp),即主文件
- 在实际工作中,并不是将一个类声明做成一个头文件,而是将若干个常用的功能相近的类声明集中在一起,形成各种类库。 类库有两种:一种是C++编译系统提供的标准类库;另一种是用户根据自己的需要做成的用户类库,提供给自己和自己授权的人使用,这称为自定义类库。
- 类库包括两个组成部分:
1. 包括类声明的头文件
2. 已经过编译的成员函数的定义,它是目标文件