C++语言程序设计复习(二)
四、类与对象
抽象
面向对象方法中的抽象,是指对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程。
抽象包括两个方面:数据抽象和行为抽象。
举例一:抽象后的时钟属性描述如下:
// 对时钟进行抽象
// 数据抽象
int hour,int minute,int second
// 功能抽象
showTime(),setTime()
举例二:抽象后的人的属性描述如下:
// 对人进行抽象
// 共同的属性:姓名、性别、年龄等用变量来表示
string name,string sex,int age
// 共同的行为:吃饭、行走等生物性行为,以及工作、学习等社会性行为
eat(),walk(),work(),study()
封装
封装就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的函数代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
举例一:在抽象的基础上,将时钟的数据和功能封装起来构成一个时钟类
// 时钟类的定义
class Clock //class关键字 类名
{ //边界
public: //外部接口
void setTime(int newH, int newM, int newS); //行为,代码成员
void showTime(); //行为,代码成员
private: //特定的访问权限
int hour, minute, second; //属性,数据成员
}; //边界
继承
C++中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更详细、更具体的说明。(例如:人按照职业划分,人又分为学生、教师、工程师、医生等,每一类人又有各自的特殊属性和行为。详细的说,学生具有专业、年级等特殊属性和升级、毕业等特殊行为。)
多态
多态性是指一段程序能够处理多种类型对象的能力。在C++语言中,这种多态性可以通过强制多态、重载多态、类型参数化多态、包含多态4种形式来实现。(详见后章,在此处出现只是为了保持知识体系的完整性。)
类与对象
在面向对象程序设计过程中,程序模块是由类构成的。类是对逻辑上相关的函数与数据的封装,它是对问题的抽象描述。
// 类的定义
class 类名称
{
public:
外部接口
protected:
保护型成员
private:
私有成员
};
类成员的访问控制
访问控制属性可以有以下三种:公有类型(public)、私有类型(private)和保护类型(protect)。
公有类型成员定义了类的外部接口。
私有成员只能被本类的成员函数访问,来自类外部的任何访问都是非法的。
【习惯】:一般情况下,一个类的数据成员都应该声明为私有成员,这样,内部数据结构就不会对该类以外的其余部分造成影响,程序模块之间的相互作用就被降低到最小。
保护类型成员的性质和私有成员的性质相似,其差别在于继承过程中对产生的新类影响不同。
在类的定义中,具有不同属性的成员,可以按任意顺序出现。修饰访问属性的关键字也可以多次出现。但是一个成员只能具有一种访问属性。
【习惯】:在书写时通常习惯将公有类型放在最前面,这样便于阅读,因为他们是外部访问时所要了解的。
对象
声明一个对象和声明一个一般变量相同,采用以下的方式:
类名 对象名;
Clock myclock;
就声明了一个时钟类型的对象myclock.
【注意】:对象所占据的内存空间只是用于存放数据成员,函数成员不在每一个对象种存储副本,每个函数的代码在内存中只占据一份空间。
定义了类及类的对象,就可以访问对象的成员,例如设置和显示对象myclock的时间值。这种访问采用的是操作符“.”。
访问数据成员的一般形式是:
对象名.数据成员名
调用函数成员的一般形式是:
对象名.函数成员名(参数表)
例如:访问类Clock的对象myclock的函数成员showTime()的方式如下:
myclock.showTime()
类的成员函数
1.成员函数的实现
函数的原型声明要写在类体中,原型说明了函数的参数表和返回值类型。而函数的具体实现是写在类定义之外的。与普通函数不同的是,实现成员函数时要指明类的名称,具体形式为:
返回值类型 类名::函数成员名(参数表)
{
函数体
}
例如:
void Clock::setTime(int newH, int mewM, int newS){
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime(){
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
2.成员函数调用中的目的对象
调用一个成员函数与调用普通函数的差异在于,需要使用“.”操作符指出调用所针对的对象,这一对象在本次调用中称为目的对象。
3.带默认形参值的成员函数
类成员函数的默认值,一定要写在类定义中,而不能写在类定义之外的函数实现中。
例如:
class Clock{
public:
void setTime(int newH=0, int newM = 0, int newS = 0);
...
}
4.内联函数成员
内联函数的声明有两种方式:隐式声明和显式声明。
将函数体直接放在类体内,这种方法称之为隐式声明。比如,将时钟类的showTime()函数声明为内联函数,可以写作:
class Clock{
public:
void showTime(){
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
private:
int hour, int minute, int second;
};
为了保证类定义的简洁,可以采用关键字inline显式声明的方式,可以写作:
inline void Clock::showTime(){
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
程序实例
//4_1.cpp
# include<iostream>
using namespace std;
class Clock{
public:
void setTime(int newH=0, int newM=0, int newS=0);
void showTime();
private:
int hour, minute, second;
};
void Clock::setTime(int newH, int newM, int newS){
hour = newH;
minute = newM;
second = newS;
}
inline void showTime(){
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
//主函数
int main(){
Clock myclock;
cout<<"First time set and output:"<<endl;
myclock.setTime();
myclock.showTime();
cout<<"Second time set and output:"<<endl;
myclock.setTime(8,30,30);
myclock.showTime();
return 0;
}
构造函数与析构函数
在定义对象的时候进行的数据成员设置,称为对象的初始化。C++程序中的初始化和清理工作,分别由两个特殊的成员函数来完成,他们就是构造函数和析构函数。
构造函数
构造函数的作用就是在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态。
构造函数也是一个类的成员函数,除了具有一般成员函数的特征之外,还有以下特殊的性质:
1.构造函数的函数名与类名相同。
2.没有返回值
其通常被声明为公有函数。
只要类中有构造函数,编译器就会在建立新对象的地方自动插入对构造函数调用的代码,即构造函数在对象被创建的时候将被自动调用。
class Clock{
public:
Clock(){} /*编译系统生成的隐含的默认构造函数*/
...
};
【提示】:虽然本例中编译系统生成的隐含的构造函数不做任何事情,但有时函数体为空的构造函数并非不做任何事情,因为他还要负责基类的构造和成员对象的构造。
class Clock{
public:
Clock(int newH, int newM, int newS); //构造函数
void showTime();
void setTime(int newH, int newM, int newS);
private:
int newH, int newM, int newS;
};
//构造函数的实现
Clock::Clock(int newH, int newM, int newS){
hour = newH;
minute = newM;
second = newS;
}
//主函数
int main(){
Clock c(0,0,0);
c.showTime();
c.setTime(8,30,30);
return 0;
}
这时,以下写法出错:(无形参)
Clock c2;
构造函数可以是内联函数,可以带有参数表,可以带默认的形参值,也可以重载。重载并内联的情况如下:
class Clock{
public:
Clock(int newH, int newM, int newS){
hour = newH;
minute = newM;
second = newS;
} //构造函数
Clock(){
hour = 0;
minute = 0;
second = 0;
} //构造函数重载
void showTime();
void setTime(int newH, int newM, int newS);
private:
int newH, int newM, int newS;
};
//主函数
int main(){
Clock c(0,0,0);
Clock c2;
c2.showTime();
c.setTime(8,30,30);
return 0;
}
复制构造函数
复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性,其形参是本类对象的引用。其作用是使用一个已经存在的对象(由复制构造函数的参数指定),去初始化同类的一个新对象。
系统自动生成的隐含的复制构造函数的功能是,把初始值对象的每个数据成员的值都复制到新建立的对象中。
复制构造函数的声明和实现的一般方法如下:
class 类名
{
public:
类名(形参表);
类名(类名&对象名);
...
};
类名::类名(类名&对象名){
{
函数体
}
举例如下:
通过水平和垂直两个方向的坐标值X和Y来确定屏幕上的一个点。点(Point)类定义如下:
class Point{
public:
Point(int xx=0, int yy=0){ //构造函数
x=xx;
y=yy;
}
Point(Point &p); //复制构造函数
int getX(){return x;}
int getY(){return y;}
private:
int x,y;
};
Point::Point(Point &p){
x = p.getX();
y = p.getY();
cout<<"Calling the copy constructor"<<endl;
}
//(1)当用类的一个对象去初始化该类的另一个对象时。
int main(){
Point a(1,2);
Point b(a);
Point c=a; //与上式均能调用复制构造函数
cout<<b.getX()<<endl;
return 0;
}
//(2)如果函数的形参是类的对象,调用函数时,进行形参和实参结合时。
void f(Point p){
cout<<p.getX()<<endl;
}
int main(){
Point a(1,2);
f(a);
return 0;
}
//只有把对象用值传递时,才会调用复制构造函数。
//(3)如果函数的返回值是类的对象,函数执行完成返回调用者时。
Point g(){
Point a(1,2);
return a;
}
int main(){
Point b;
b = g();
return 0;
}
析构函数
析构函数用来完成对象被删除前的一些清理工作。析构函数是在对象生存期即将结束时的时刻被自动调用的。
析构函数通常也是类的一个公有函数成员,它的名称是由类名前面加“~”构成,没有返回值,且不接收任何参数。
举例如下:
class Clock{
public:
Clock();
void setTime(int newH, int newM, int newS);
void showTime();
~Clock(){}
private:
int hour, minute, second;
};