[C++]面向对象方法

来源:互联网 发布:python爬虫书籍 编辑:程序博客网 时间:2024/05/18 02:25

update:2016/11/5

(一)对象和类

1.对象和类的认识

1.对象

对象(object)是问题域中某些事物的抽象,是具有唯一标识的某些属性、操作和方法的封装体。

  • 对象之间通过消息(message)进行通信。

    消息是从一个对象(发送者)向另一个或几个对象(接收者)发送信号,或者由一个对象(调用者)调用另一个对象(接受者)的操作。某项任务通常需要多个对象协作完成,而多个对象之间的协作是通过消息传递来完成的。称为了完成一个特定任务而在对象间产生的一组消息称为一个消息序列
    在面向对象的程序设计中,消息可以通过函数调用、程序间内部通信、事件的发生等进行实现。

  • 对象具有生命周期(lifetime)。

    生命周期是指对象从创建、活动到删除的过程。在计算机中,对象生命周期中的状态主要由其所占用的存储空间是否有效为标志:
    对象的创建是指在内存中为其对象分配空间;
    对象的活动是指对象能自主地运行,并且可以接收消息加以处理,通过处理外来消息改变自身状态,并且向其他对象发送消息等;
    对象的删除是指当对象的使命完成后,可以将内存中其占用的存储空间置为无效并回收。

2.类

(class)是一组客观对象的抽象,是一组具有共同的静态特征和行为特征的对象集合。

  • 类是对象的本质,对象是类的实例。
    对于同一类的不同实例之间,具有如下特点:①相同的属性;②相同的操作方法;③不同的对象名;④不同的属性值。

3.属性、操作和方法

类和对象都包含了属性、操作和方法。

属性(attribute)是类和对象中的静态特征。在面向对象程序中用一个数据单元来表示属性。属性可以用属性名、类型、可修改性、多重性、可见性描述,属性可以有初始值。对于可以修改的属性,可以改变其属性值。

操作(operation)是对象执行某种功能的规格说明。

方法(method)是操作的实现,是说明生成操作结果的算法或过程,通常用函数体或过程体来表示方法。

消息可以是调用者对被调用者操作的调用。被调用者在接收到调用者传递的消息时,激活操作相应的方法。执行终了,调用者收回控制,并获得方法处理的结果。

2.面向对象方法的基本特征

面向对象(object-orientation programming,OOP)的基本特征是封装、继承和多态。

1.封装

封装(encapsulation)就是将属性和操作封装在对象中并和外部区隔开来。在面向对象编程中,对象是一种自治、封装的实体,对象之间通过接口进行消息传递。
封装给程序设计带来如下的好处:

  • 有效控制某个对象内部发生变化时对其他对象的影响。
  • 通过对象的接口简化对象的使用,保持程序结构的稳定。
  • 便于对数据和功能的复用。

2.泛化和继承

泛化(generalization)是一般类和特殊类之间的层次关系。

  • 一般类也称为基类(base class)或父类(super class);特殊类称为派生类(derived class)或子类(subclass)。派生类建立在基类的基础之上,继承(inherit)基类所有的属性、操作,并对其进行扩展。泛化有时也叫做“is-a”关系,因为派生类的实例也可看成(is a)基类的实例。

继承(inheritance)是类间数据和操作的共享。

  • 继承按照派生类继承基类的个数划分,可以分为单继承和多继承;按照继承的内容来分,有实现继承(继承基类全部的属性、操作和方法)和接口继承(只继承基类的操作)。

泛化和继承是从不同的角度认识类之间层次。
泛化表达了派生类“就是”基类的语义关系,也就是说在程序中可以用派生类来代替基类,这是多态的基础;继承描述了类之间的共享机制,也就是说派生类可以“重用”基类的属性、操作和方法。

在泛化关系之上应用继承机制是OOP最主要的特征,这带来了如下的好处:

  • 可以实现类的分解,从而形成稳定而易于理解的程序结构;
  • 实现代码的共享和重用;
  • 支持多态。

3.多态

多态(polymorphism)是指具有泛化和继承关系的对象接收同一消息时可以有不同行为。多态提供了不同对象的同一类操作的相同接口。

(二)类

我们将要试着编写一个time类,用来实现对于时钟上的时间的抽象,在整篇文章中我们将要不断丰富直至完成time类。

1.类的定义

1.类的设计

由于类是由类的属性、操作及方法组成的,因此对于类的定义就是对类的属性、操作的定义。属性使用变量描述,操作用函数描述。变量称为数据成员,函数则成为成员函数。定义类采用如下的代码:

    class className    {    blablabla……//declare the class member    }

一般来说在定义一个类时,会将其分为类的声明(声明数据成员和成员函数)和类的实现(定义成员函数)两部分,因此,定义类可以细化为如下格式:

class className{    <declaration of data member>    <declaration of member function>};<definition of member function>

2.this指针

2.类的封装

按照上述的定义格式我们完成了time类 。

/* class time 1.0 *  *  */class time{    int h,m,s;    enum method    {        _24Hours,        _12Hours    };    time add(const time&,const time&);    int time2second(const time&);    void iniTime(time*);    void freeTime(time*);    istream &read(istream&,time &);    ostream &print(ostream&,time &);};time time::add(const time&a,const time &b){……}int time::time2second(const time &item){……}void time::iniTime(time* ptr){……}void time::freeTime(time* ptr){……}istream &read(istream& is,time & item){……}ostream &print(ostream& os,time & item){……}

我们可以发现并没有实现对于time类的封装:在我们的class time v1.0中,数据成员和所有的操作都是处于同一层级,与此同时,并没有明确哪些操作是time类的接口,哪些操作是time类的实现。这样一来,我们的time类对于用户来说没有明晰的层次,用户可以深入到类的最底层进行操作而不是使用接口,这是危险的。

1.访问说明符

对于类的成员来说,访问说明符(access specifiers)带来了三级存取,分别是:公共(public)、保护(protected)和私有(private),通过访问说明符可以为类的数据成员和成员函数进行封装。

  • public说明符后的成员可以被任何函数(代码)访问。 使用public的成员定义类的接口。

  • protected说明符后的成员可以被类的成员函数、类的friend函数以及类的派生类对象访问。使用protected的成员实现类的泛化关系。

  • private说明符后的成员可以被类的成员函数或是被声明为类的friend的函数访问,但不能被使用类的代码或是类的派生类访问。使用private的成员封装类的实现细节。

2.使用classstruct关键字

在定义类时可以采用classstruct关键字,二者的区别在于默认访问权限不同:class的默认访问权限为privatestruct的默认访问权限为public

3.友元

在定义类时,常常还需要一些辅助函数用来进行类的输入、输出。考虑到一个类的封装需求,我们尽量少的定义类的成员函数,以增强类的隐蔽性。但是这些函数需要访问private的数据成员,因此采用关键字friend,令这些辅助函数作为类的友元(friend)成为类的非成员接口函数,访问类的非公有成员。

3.构造函数和析构函数

1.构造函数

任何一个类都定义了其对象被初始化的方式:类通过一个或几个特殊的成员函数,我们称之为构造函数(constructor)来完成这一件事。

构造函数(constructor)是在定义函数时被编译器自动调用用来完成对象的创建及初始化的函数。其具有一些特殊性质:

  • 在没有声明任何构造函数时,编译器会自动生成默认构造函数(default constructor);
  • 构造函数的名字和类名相同;
  • 构造函数没有返回类型;
  • 构造函数不能被声明为const的,在构造函数构造一个const对象时可以向其写入值;
  • 构造函数是public函数,但除了在定义函数
  • 一个类可以包含多个构造函数,但要求其必须在参数的类型(或数量)上有所区别。

一个典型的构造函数定义如下:

class className{    pravite:        //类含有两个数据成员        int itemName1=0;        double itemName2=0.0;    //空参数列表,默认构造函数    className()=default;    //两个含参数的构造函数    className(int classItem1):itemName1(classItem1) { }     className(int classItem1,double classItem2):itemName1(classItem1), itemName(classItem2) { }}

1.1 默认构造函数

默认构造函数(default constructor)在类对象执行默认初始化时控制其进程的构造函数。它具有如下特征:

  • 默认构造函数无需任何实参;
  • 如果类没有显式地定义构造函数,则由编译器隐式地定义一个默认构造函数,又称为合成的默认构造函数(synthesized default constructor);
  • 对于大多数类,将按照如下规则初始化类的数据成员:
    • 如果存在类内的初始值,则用其初始化数据成员。
    • 否则,默认初始化该成员,类中的内置类型或复合类型对象在默认初始化后的值仍将是未定义的。
  • 一旦类内显式定义了其它的构造函数,那么除非再定义一个默认构造函数,否则类将不含有默认构造函数。

对于某些类而言,不能依赖于合成的默认构造函数,其原因是多样的:

  • A
  • B
  • C
    如果我们需要定义一个默认构造函数,可以采用如下的语句:

    className()=default;

1.2 构造函数

在类的定义中,除了默认构造函数以外,我们还有另外两个构造函数:

className(int classItem1):itemName1(classItem1){}className(int classItem1,double classItem2):itemName1(classItem1),itemName(classItem2){}

定义中出现了新的部分,也就是在函数名和函数体之间的部分:itemName1,称其为构造函数初始值列表(constructor initialize list),它为新创建的对象的某些数据成员初始化(执行了列表初始化)。
构造函数初始值列表是成员名字的列表,每个名字后紧跟一个括号或花括号内的成员初始值,不同成员的初始化以逗号分隔开来,即:

itemName1(initialValue1),itemName2(initialValue2)itemName3{initialValue3},itemName4{initialValue4}

值得指出的是,当函数初始值列表未包含全部的数据成员时,未包含进去的数据成员将采用与合成默认构造函数相同的方式对其隐式地初始化。
除此之外,上面的两个函数体都是空的, 这是因为构造函数的唯一目的是为数据成员初始化,一旦没有其他的任务需要执行,函数体也就为空了。

2.拷贝、赋值

3.析构函数

1 0