⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 c++10.dat

📁 C++指令字典 一个方便的软件 用于C++指令的查询
💻 DAT
📖 第 1 页 / 共 3 页
字号:
第十章 类与对象

第一节 类与对象的概述

我们的周围是一个真实的世界,不论在何处,我们所见到的东西都可以看成是对象.人、动物、工厂、汽车、植物、建筑物、割草机、计算机等等都是对象,现实世界是由对象组成的.
  对象多种多样,各种对象的属性也不相同.有的对象有固定的形状,有的对象没有固定的形状,有的对象有生命,有的对象没有生命,有的对象可见,有的对象不可见,有的对象会飞,有的对象会跑,有的对象很高级,而有的对象很原始,….各个对象也有自己的行为,例如:球的滚动、弹跳和缩小,婴儿的啼哭、睡眠、走路和眨眼,汽车的加速、刹车和转弯,等等.但是,各个对象可能也有一些共同之处,至少它们都是现实世界的组成部分. 
  人们是通用过研究对象的属性和观察它们的行为而认识对象的.我们可以把对象分成很多类,每一大类中又可分成若干小类,也就是说,类是分层的.同一类的对象具有许多相同的属性和行为,不同类的对象可具有许多相同的属性和类似的行为,例如:婴儿和成人,人和猩猩,小汽车和卡车、四轮马车、冰鞋等等都有共同之处,类是对对象的抽象.
  在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 + -