📄 c++10.dat
字号:
第十章 类与对象
第一节 类与对象的概述
我们的周围是一个真实的世界,不论在何处,我们所见到的东西都可以看成是对象.人、动物、工厂、汽车、植物、建筑物、割草机、计算机等等都是对象,现实世界是由对象组成的.
对象多种多样,各种对象的属性也不相同.有的对象有固定的形状,有的对象没有固定的形状,有的对象有生命,有的对象没有生命,有的对象可见,有的对象不可见,有的对象会飞,有的对象会跑,有的对象很高级,而有的对象很原始,….各个对象也有自己的行为,例如:球的滚动、弹跳和缩小,婴儿的啼哭、睡眠、走路和眨眼,汽车的加速、刹车和转弯,等等.但是,各个对象可能也有一些共同之处,至少它们都是现实世界的组成部分.
人们是通用过研究对象的属性和观察它们的行为而认识对象的.我们可以把对象分成很多类,每一大类中又可分成若干小类,也就是说,类是分层的.同一类的对象具有许多相同的属性和行为,不同类的对象可具有许多相同的属性和类似的行为,例如:婴儿和成人,人和猩猩,小汽车和卡车、四轮马车、冰鞋等等都有共同之处,类是对对象的抽象.
在C++中,就是用类来描述对象的,类是对现实世界的抽象得到的.例如,在真实世界中,同是人类的张三和李四,有许多共同点,但肯定也有许多不同点.当用C++描述时,相同类的对象具有相同的属性和行为,它把对象分为两个部分:数据(相当于属性)和对数据的操作(相当于行为).我们刻画张三和李四的数据可能用姓名、性别、年龄、职业、住址等,而对数据的操作可能是读或设置它们他们的名字、年龄等.
从程序设计的观点来说,类就是数据类型,是用户定义的数据类型.这种类型的使用虽然与C++内置的数据类型类似,但是也有很大的区别.例如,C++内置的浮点类型并不针对任何具体问题,仅仅与机器的存储单元相对应,而类是用户根据具体问题的需要而定义的,也就是说,类与具体问题相适应.我们可以通过定义所需要的类,来扩展程序设计语言解决问题的能力.
当我们把现实世界分解为一个个的对象,解决现实世界问题的计算机程序也与此相对应,由一个个对象组成,这些程序就称为面向对象的程序,编写面向对象程序的过程就称为面向对象的程序设计(Object-Oriented Programming,简称为OOP).OOP技术能够将许多现实的问题归纳成为一个简单解,支持OOP的语言也很多,C++是应用最广泛的、支持OOP的语言,第一个成功的支持OOP的语言是Smalltalk.
面向对象的程序设计(OOP)使用软件的方法模拟真实世界的对象,它利用了类的关系,即同一对象(如同一类运载工具)具有相同的特点;还利用了继承甚至多重继承的关系,即新建的对象类是通过继承现有类的特点而派生出来的,但是又包含了其自身特有的特点,如子女有父母的许多特点,但是矮个子父母的子女也可能是高个子.
面向对象的程序设计(OOP)使程序设计过程更自然和直观.也就是说,面向对象的程序设计模拟了真实世界的对象(它们的属性和行为).OOP还模拟了对象之间的通信,就像人们之间互送消息一样(如军官命令部队立正),对象也是通过消息进行通信的.
C++语言是当今应用最广泛的程序设计语言,它与C语言兼容,既支持面向对象的程序设计,也支持面向对象的程序设计方法.在前面的章节中,我们编写的程序是由一个个函数组成的,可以说是结构化的程序.从本章开始,我们编写的程序是由对象组成的,也就是说,将要学习用C++语言进行面向对象的程序设计.
什么叫类,什么叫对象?我们已经知道什么叫变量.假定我们在main函数中定义了一个整型变量nInteger:
void main()
{
int nInteger;
…
}
则在main函数中为nInteger分配栈内存,保存变量nInteger的值,并在main返回时,释放该内存.在面向对象的程序设计中,nInteger也称之为对象.所谓对象就是一个内存区,它存储某种类型的数值,变量就是有名的对象.对象除可以用上述定义的方法来创建外,也可以用new表达式创建,也可能是应用程序运行时临时创建的,例如,在函数调用和返回时,均会创建临时对象.
对象是有类型的,例如,我们上面定义的nInteger对象就是整型的.一个类型可以定义许多对象,一个对象有一个确定的类型,可以这么说:int型变量是int类型的实例.以后,我们也常说:对象是类的实例,那么int是不是一个类呢?
实际上,我们所说的类,并非指C++中的那些基本的数据类型.C++中引入了class关键字来定义类,它也是一种数据类型.类是C++支持面向对象的程序设计的基础,它支持数据的封装、隐藏等.类与我们前面学习过的结构类似,实际上C++中也可以用struct关键字来定义类(虽然很少使用).
我们前面学习的结构中,只有数据成员.实际上,类中除可以定义数据成员外,还可以定义对这些数据成员(或对象)操作的函数,也正是这些函数限制了对对象的操作,即不能对对象进行这些操作函数之外的其它操作,类的成员也有不同的访问权限.下面,我们将要介绍怎样定义类及类的成员.
1.1 类的定义
类定义的一般形式如下:
class Name
{
细节
};
类的定义由头和体两个部分组成.类头由关键字class开头,然后是类名,其命名规则与一般标识符的命名规则一致,有时可能有附加的命名规则,例如美国微软公司的MFC类库中的所有类均是以大写字母'C'开头的.类体包括所有的细节,并放在一对花括号中.类的定义也是一个语句,所以要有分号结尾,否则,会产生难以理解的编译错误.
类体定义类的成员,它支持两种类型的成员:
1. 数据成员,它们指定了该类对象的内部表示.
2. 成员函数,它们指定该类的操作.
类成员有三种不同的访问权限:
1. 公有(public)成员可以在类外访问.
2. 私有(private)成员只能被该类的成员函数访问.
3. 保护(protected)成员只能被该类的成员函数或派生类(有关基类和派生类的概念我们在下一章介绍)的成员函数访问.
数据成员通常是私有的,成员函数通常有一部分是公有的,一部分是私有的.公有的成员函数可在类外被访问,也称之为类的接口.我们可以为各个数据成员和成员函数指定合适的访问权限,类定义常有下面的形式:
class Name {
public:
类的公有接口
private:
私有的成员函数
私有的数据成员定义
};
私有的成员与公有的成员的先后次序无关紧要.不过公有的接口函数放在前面更好,因为,有时我们可能只想知道怎样使用一个类的对象,那只要知道类的公有接口就行了,不必阅读private关键字以下的部分.
1.2 类成员函数的定义
类的成员函数通常在类外定义,一般形式如下:
返回类型 类名::函数名(形参表)
{
函数体
}
双冒号::是域运算符,它主要用于类的成员函数的定义.
1.3 使用对象
定义了类以后,就可以定义类类型的变量(或对象),例如:
void DrawLine(Point& p1, Point& p2)
{
Point MidPoint;
…
}
函数DrawLine中定义了一个Point对象MidPoint.
象结构一样,类类型的变量也能够作为函数参数,以值或引用的方式传递,也可以作为函数的返回值,以及在赋值语句中被复制.
我们已经知道:结构变量通过"."运算符访问其数据成员,对象数据要通过其成员函数进行修改或读出.
Point thePoint;
…
thePoint.SetPt(5, 10);
…
if(thePoint.GetX() < 0) …
可以看到,调用成员函数语法与结构变量访问其数据成员的语法相同:
对象名. 成员函数名 (实参表)
成员函数也可以通过指向对象的指针,调用形式为:
指向对象的指针->成员函数名 (实参表)
注意:
(1) 对私有数据成员的访问只能通过成员函数,下面的语句是非法的:
thePoint.xVal = 5; // 非法
(2) 不要混淆了类与对象的概念.类是用户定义的数据类型(不占内存),对象是类的实例(占内存单元),例如:
Point pt1, pt2, pt3;
定义了三个Point对象pt1、pt2和pt3.
例如:
动态对象创建
有时我们知道程序中需要创建多少对象,但是多数情况下,我们不能预知所需对象的确切数量.比如公路交通管理系统必须同时处理多少辆汽车?一个三维建筑设计系统需要处理多少个模型?解决这些编程问题的方法,是要能够支持在运行时动态创建和销毁对象.
一个对象被动态创建时,依次发生两件事情:
1. 为对象分配内存;
2. 调用构造函数来初始化这块内存.
同样,一个对象被动态销毁时,按照顺序发生了下面两件事情:
1. 调用析构函数清除对象;
2. 释放对象的内存;
C++提供了两个运算符new和delete,分别用来完成动态对象的创建和销毁.当我们用new创建一个对象时,就在堆里为对象分配内存并调用相应的构造函数.new返回一个指向刚刚创建的对象的指针;当我们用delete销毁一个对象时,就调用相应的析构函数,释放掉分配的堆内存,delete运算符的操作数是指向对象的指针.需要注意的是:用new创建的对象必须用delete销毁,否则,会出现内存泄漏.
举个例子说明利用new和delete动态创建和销毁对象的过程:
#include <iostream.h>
class Tree
{
public:
Tree(int height)
{
cout<<"tree object is creating"<<endl;
this->height = height;
}
~Tree()
{
cout<<"tree object is deleting"<<endl;
}
void display()
{
cout<<"this tree is "<<height<<" meters high"<<endl;
}
private:
int height;
};
void main()
{
Tree* tree = new Tree (100);
tree->display();
delete tree;
}
程序的输出结果如下:
tree object is creating
this tree is 100 meters high
tree object is deleting
main( )函数中的第一个语句,是用new运算符动态创建一个Tree类对象,new后面括号中的100,实际上是new创建对象时,传给构造函数的参数.main( )函数的第二个语句是调用对象的显示函数,打印出的结果显示树高为100米,可见new操作符确实调用了类的构造函数display.main( )函数的最后一个语句用delete运算符销毁用new创建的对象.对象一旦被销毁后,就不再存在.如果继续访问对象tree的数据成员或成员函数,则程序会产生错误.
1.4 this指针
C++中,定义了一个this指针,用它指向调用非静态成员函数的对象.也就是说,this指针仅能在类的成员函数中访问,它指向调用该函数的对象,在后面我们要介绍的静态成员函数没有this指针.
当一个非静态的成员函数被一个对象调用时,对象的地址作为一个隐含的参数传给被调用的函数.例如:
myDate.setMonth( 3 );
可被解释为:
setMonth( &myDate, 3 );
下面的成员函数setMonth,可用两种方法实现:
void Date::setMonth( int mn )//使用隐含的this指针
{
month = mn;
}
void Date::setMonth( int mn )//显式使用this指针
{
this->month = mn;
}
虽然显式使用this指针的情况并不是很多,但是,this指针有时是有用的.例如,下面的赋值是不允许的:
void Date::setMonth( int month)
{
month = month;
}
但可以用this指针来解决:
void Date::setMonth( int month)
{
this->month = month;
}
类的每个成员函数都访问特殊的指针-this.This指针保留了激活成员函数的对象地址(也就是说,this总是指向目标对象).This指针仅仅在成员函数内部是合法的,而且名称this是C++的保留字.
每个成员函数所收到的第一个参数就是this指针.程序员没有必要明确定义这个this指针,但是,它总是存在的.This指针通常是每个成员函数(非静态)隐含的第一个参数.编译器在每个成员函数的声明中插入这个隐含参数.当成员函数使用类的成员的绝对名称的任何时候,它隐式使用this指针.编译器在引用成员函数内部的每个表达式中插入this指针(如果用户并没有这样做的话).
第二节 构造函数与析构函数
在C++中,有两种特殊的成员函数,即是构造函数和析构函数,下面分别予以介绍.
2.1 构造函数
变量应该被初始化,我们已经知道了简单变量的初始化、数组的初始化、结构和结构数组的初始化.对象也需要初始化,应该怎样初始化一个对象呢?
C++中定义了一种特殊的初始化函数,称之为构造函数.当对象被创建时,构造函数自动被调用.构造函数有一些独特的地方:函数的名字与类名相同,它也没有返回类型和返回值.
例如:
class Point {
public:
Point(int x = 0, int y = 0);
…
};
类Point的构造函数有两个参数,它们是赋给xVal、yVal的初始值,该构造函数的原型也为实参指定了缺省值0.
Point::Point(double x, double y)
{
xVal = x;
yVal = y;
}
我们也可以把该构造函数定义为内联函数的形式:
class Point {
Point (int x=0, int y=0) {xVal = x; yVal = y;} // 构造函数
…
};
如果不采用Point构造函数,我们将不得不调用SetPt函数初始化.可见,使用Point构造函数方便了编程,简化了程序代码.
现在,我们可以定义Point类对象并立即初始化它们:
Point pt1(10, 20); // xVal和yVal的初值分别为10和20
Point pt2; // xVal和yVal的初值均为0
构造函数也可以重载:
class Point {
int xVal, yVal;
public:
Point (int x, int y) { xVal = x; yVal = y; }
Point (float, float); //极坐标
Point (void) { xVal = yVal = 0; }
…
};
Point::Point (float len, float angle) //极坐标
{
xVal = (int) (len * cos(angle));
yVal = (int) (len * sin(angle));
}
创建Point对象时,可以使用这三种构造函数中的任一个.
Point pt1(10, 20); // 笛卡儿坐标
Point pt2(60.3, 3.14); // 极坐标
Point pt3; // 原点
2.2 成员初始化表
一、常量成员
二、引用成员
三、类对象成员
类中数据成员可以通过构造函数来初始化,例如:
程序1
class Image {
public:
Image(const int w, const int h);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -