类和对象(一)
整理自《面向对象程序设计》
1.面向对象程序设计
- 对象
对象是构成系统的基本单位。在面向对象程序设计中是用对象来描述客观事物的一个实体。任何一个对象都应当具有两个要素,即属性(attribute)和行为(behavior),一个对象往往是由一组属性和一组行为构成的。
- 封装与信息隐藏
面向对象程序设计方法的一个重要特点就是“封装性” (encapsulation),顾名思义就是将某事物包装起来,具体来说就是将数据和代码绑定到一起。通过封装将一部分成员作为类与外部的接口,而将其他的成员隐藏起来,以防外界的干扰、误操作以及不确定性,是程序的不同模块之间的相互影响减小到最低限度。
- 抽象
抽象的过程是将有关事物的共性归纳、集中的过程。即忽略事物的非本质特征,只注意那些与当前对象有关的本质特征,从而找出事物的共性,把具有共同性质的事物划分为一类,得出一个抽象的类的概念。面向对象方法中的“类”,是具有相同属性和行为的一组对象的集合。
- 继承与重用
继承实现了更高层次的代码的重用,是面向对象技术能够提高软件开发效率的重要原因之一。通过继承可以实现代码的重用:从已有的类中派生出的新类将自动具有原来那个类的特性,包括类的数据成员和成员函数(代码)。这就是常说的“软件重用”(software reusability)的思想。
- 多态性
在 C++ 中,所谓多态性(polymorphism)是指由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应。多态机制使得具有不同内部结构的类可以共享相同的外部接口,通过这种方式来减小代码的复杂度。
在面向对象程序设计中,设计者的任务包括两个方面:一是设计所需要的各种类和对象,即决定把哪些数据和操作封装在一起;二是考虑怎样向有关对象发送消息,以完成所需要的任务。这时它如同一个总调度,不断地向各个对象发出命令,让这些对象活动起来(或者说激活这些对象),完成自己职责范围内的工作。各个对象的操作完成了,整体任务也就完成了。显然,对一个大型任务来说,面向对象程序设计方法是十分有效的,它能大大降低程序设计人员的工作难度。
2.类和对象
2.1类和对象的关系
现实生活中,一个类表示具有相同性质和功能的事物所构成的集合。在 C++ 中是把具有相同属性和行为的对象看成同一类。把属于某个类的实例称为某个类的对象。
类是对象的抽象,对象是类的特例。类只是一种形式化的定义,而对象是具体存在的。要使用类所提供的功能,就必须使用类的实例,即对象。类与对象的关系类似于普通数据类型与变量的关系,对象也与普通变量一样占用一定的存储空间。
2.2类的概念和定义方法
类作为一种数据类型,在定义时不分配任何内存,它只是为将来类对象(类变量)的内存分配定义一个蓝图或模式。类有两类成员,一类是数据成员,用于表示实体对象的属性;另一类是成员函数,用来描述实体对象的行为。类定义的格式为:
class 类名{ private: //私有成员只能被本类的成员函数访问,类外的任何成员对它的访问都是不允许的。通常是描述该类对象属性的数据成员,这些数据成员用户无法直接访问,只有通过成员函数或某些特殊说明的函数才可访问,体现对象的封装性。 数据成员或成员函数的说明; //当一个成员在声明中缺省访问权限时,默认为私有成员。 protected: //保护成员在类内部和继承类中可以访问。 数据成员或成员函数的说明; public: //公有成员可以被程序中的任何函数访问,它提供了外部程序与类的接口功能。公有成员通常是成员函数。 数据成员或成员函数的说明; }; <各函数的实现代码> //也可放入类内
说明:
(1)类名是用户 自己定义的 C++ 的有效标识符,且一般首字母大写。
(2)声明的类是一个数据结构而不是函数,因而最后的分号不能丢掉,它表示类定义的结束。
(3)程序开发的通常做法是:将类的定义写在一个头文件中,以 .h 为后缀保存;成员函数的定义写在一个程序文件中,以 .cpp 为后缀保存。这样做实现了类的接口(定义)与类的实现(成员函数定义)的分离,提高程序的可维护性。
【定义一个Time类】
#Time.h class Time { private: int hour; int minute; int sec; public: void set(int h, int m, int s); void show(); }; #Time.cpp #include<iostream> using namespace std; #include"Time.h" void Time::set(int h, int m, int s) { hour = h; minute = m; sec = s; } void Time::show() { cout << hour << ":" << minute << ":" << sec << endl; }
2.3对象的概念和定义方法
对象是类的实例,表面上看,对象实例和一般基本类型的变量一样,在定义时分配内存。使用类名定义一个对象实例与使用基本类型定义变量的格式是一样的。但对象不是普通的变量,它是数据和操作的封装体。每个对象的数据成员都占用存储单元,但成员函数是所有对象公有的。
定义对象的格式为:
类名 对象1,对象2,...;
例如,定义Time类的几个对象:
Time time1,time2,*ptime,time[5],&qtime=time2;
2.4对象成员的访问方法
定义了类及其对象之后,就可以访问对象的成员(只能是公有成员)。访问格式有以下3种:
格式1: 对象名 .公有成员名;
格式2: 指向对象的指针 -> 公有成员名;
格式3:(*指向对象的指针).公有成员名;
【访问Time类对象的成员】
#include"Time.h" #include<cstdio> int main() { Time time1, time2; Time *p; p = &time1; p->set(10, 10, 10); time2.set(9, 9, 9); (*p).show(); //与time1.show();或p->show();等价 time2.show(); getchar(); }
如果将语句 time1.hour=10;写入 main() 函数,则会发生错误。因为对于类的私有成员,只能通过其成员函数来访问,而不能在类外直接对私有成员进行访问。
2.5this指针
定义Time类的三个同类对象 t1,t2,t3。那么现在 t1.set() 和 t2.set() 都在同意函数段,系统怎样才能使该函数段分别引用t1或t2的数据成员呢?
在每个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this,它是指向本类对象的指针,它的值是当前被调用的成员函数所在对象的起始地址,编译系统把对象t1的起始地址赋给成员函数 set 的 this指针,于是在成员函数引用数据成员时,就可以按照 this 的指向找到对象t1的数据成员。this 是隐式使用的,它是作为参数被传递给成员函数的。本来,成员函数 set()的定义如下:
void Time::Set(int h,int m,int s){ int hour = h; int minute = m; int sec = s; }
C++ 把它处理为:
void Time::Set(Time*this,int h,int m,int s){ this->hour = h; this->minute = m; this->sec = s; }
在执行t1.set(9,9,9)语句时,C++把它处理为t1.set(&t1,9,9,9);即给 set() 函数新增了一个参数 &t1,等同于执行 this = &t1。执行函数中的 this->hour = h;相当于 t1.hour = h;从而对对象 t1 进行操作。这些都是编译系统自动实现的,不需要在形参中增加 this 指针。
3.构造函数和析构函数
3.1构造函数
构造函数的作用是在创建对象时使用给定的值将对象初始化。析构函数的作用是释放一个对象,在对象被删除前,有它来做一些清理工作。
3.1.1构造函数的定义
构造函数名与类名相同,没有返回类型,也不允许有返回值,其作用是创建对象时使用给定值将对象初始化。
构造函数可以带默认值也可以重载,在创建对象时系统根据提供的参数个数和类型自动隐式地调用构造函数,完成对象的初始化工作。
3.1.2默认构造函数
如果在类中没有定义构造函数,系统将提供一个默认的构造函数。该默认构造函数是一个空函数,只负责创建对象,而不做任何初始化工作。
3.1.3构造函数的重载
C++ 允许同一函数名定义多个函数,但这些函数的参数个数和参数类型不同,这就是函数的重载(function overloading)。即对一个函数名重新赋予它新的含义,使同一个函数名可以多次使用。重载时,要求函数在参数类型和个数上必须有所不同,否则就会出错。
3.1.4带默认参数值的构造函数
当构造函数具有默认值时,称这种构造函数为具有缺省参数的构造函数。构造函数的参数默认值只能出现在类定义的声明部分,而不能出现在类定义的实现部分。如果函数有多个参数,带默认值的参数只能从后向前设置,即所有具有默认值的参数必须放在参数表的最右边。
当无参构造函数Time()和带有全部默认形参值的构造函数Time(int h=0,int m=0,int s=0)同时出现时,系统无法确定是调用无参构造函数还是调用缺省构造函数,这时编译就会出错。
3.1.5构造函数中的初始化列表
参数初始化表的语法如下:
函数构造名(参数列表):成员名(表达式),成员名(表达式),...{};
例如:类Time的构造函数如下:
Time(int h,int m,int s){ hour = h; minute = m; sec = s; } //上述类Time的构造函数可以表示成带有初始化表的构造函数,格式如下: Time(int h,int m,int s):hour(h),minute(m),sec(s){};
带有初始化表的这种写法方便、简练,可以直接在函数首部初始化,但是,带有初始化表的构造函数只可以出现在头文件中。
【3.1示例】
#Time.h class Time { private: int hour; int minute; int sec; public: Time();//默认构造函数 Time(int h, int m=1, int s=0);//带默认参数值的构造函数 Time(int h, int m);//构造函数的重载 void show(); }; #Time.cpp #include<iostream> using namespace std; #include"Time.h" Time::Time() { hour = 0; minute = 0; sec = 0; } Time::Time(int h, int m) { hour = h; minute = m; sec = 0; } Time::Time(int h, int m, int s) { hour = h; minute = m; sec = s; } void Time::show() { cout << hour << ":" << minute << ":" << sec << endl; } #主函数 #include"Time.h" #include<cstdio> int main() { Time time1(10, 10, 10); time1.show();//10:10:10 Time time2; time2.show();//0:0:0 Time time3(10); time3.show();//10:1:0 getchar(); }
3.2析构函数
析构函数的作用与构造函数相反,它的函数名是在类名前面加上“~”。析构函数没有返回值和参数,也没有重载,类中只能有一个析构函数。
在对象消亡前,系统会自动调用析构函数,C++通过析构函数来处理对象的善后工作,释放独享的存储空间。它还可用来完成用户指定的任何操作。所有希望析构函数完成的操作都必须在析构函数中指定。
如果程序不定义,系统会提供一个默认的析构函数,该默认函数只能用来释放对象的数据成员所占用的空间。
【修改Time类,使其包含构造函数和析构函数】
#Time.h class Time { private: int hour; int minute; int sec; public: Time();//默认构造函数 Time(int h, int m=1, int s=0);//带默认参数值的构造函数 Time(int h, int m);//构造函数的重载 ~Time(); void show(); }; #Time.cpp #include<iostream> using namespace std; #include"Time.h" Time::Time() { hour = 0; minute = 0; sec = 0; } Time::Time(int h, int m) { hour = h; minute = m; sec = 0; } Time::Time(int h, int m, int s) { hour = h; minute = m; sec = s; cout << "constructor called." << endl; } Time::~Time() { cout << "destructor called." <<hour<< endl; } void Time::show() { cout << hour << ":" << minute << ":" << sec << endl; } #main.cpp #include"Time.h" #include<cstdio> int main() { Time time1(10, 10, 10); time1.show(); //constructor called. //10:10:10 //destructor called.10. return 0; }
3.3拷贝构造函数
拷贝构造函数用一个已有的对象快速地复制出多个完全相同的对象。定义拷贝构造函数的格式为:
Time(Time &t1);
拷贝构造函数的函数名与类名相同,它的参数就是该类对象的引用。如果程序没有定义,系统也会自动生成一个默认的拷贝构造函数,把成员值一一复制,通过等于号复制对象时,系统会自动调用拷贝构造函数。
【修改Time类,使其包含拷贝构造函数】
#Time.h class Time { private: int hour; int minute; int sec; public: Time();//默认构造函数 Time(int h, int m=1, int s=0);//带默认参数值的构造函数 Time(int h, int m);//构造函数的重载 Time(const Time &t1);//拷贝构造函数 void show(); }; #Time.cpp #include<iostream> using namespace std; #include"Time.h" Time::Time() { hour = 0; minute = 0; sec = 0; } Time::Time(int h, int m) { hour = h; minute = m; sec = 0; } Time::Time(int h, int m, int s) { hour = h; minute = m; sec = s; cout << "constructor called." << endl; } Time::Time(const Time &t1) { hour = t1.hour; minute = t1.minute; sec = t1.sec; } void Time::show() { cout << hour << ":" << minute << ":" << sec << endl; } #main.cpp #include"Time.h" #include<cstdio> int main() { Time time1(10, 10, 10); Time time2(time1); time1.show();//10:10:10 time2.show();//10:10:10 getchar(); }
拷贝构造函数Time(const Time &t1);中的 const 说明 t1 是一个常引用,在本函数执行期间,t1的值不许被修改。