C++OOP对象和类

本文将从入门的角度介绍C++OOP中的类和对象,事实上C++的类和对象远远不止本文所介绍的内容。

不过如果你感兴趣,我会把其他的内容逐一通过文章的形式介绍给你。

1.预备知识

面向对象编程(OOP)是一种特殊的、设计程序的概念性方法,C++通过一些特性改进了C语言,使得应用这种方法更加容易。下面是最重要的OOP特性:

  • 抽象
  • 封装和数据隐藏
  • 多态
  • 继承
  • 代码可重用性

(来自C++Primer Plus 第六版,人民邮电出版社)

为了实现上述特性并把它们结合在一起,C++提供了类和对象。

我们首先来讨论一下OOP。虽然C++偶尔讨论了OOP,但更多的还是诸如C、Pascal和BASIC等语言的标准过程性方法。下面看一个例子,它将揭示OOP的核心观点与过程性编程的差别。

一般来说一场球赛的进球数量可以通过计算机来辅助计算例如每个选手的投球次数、命中率等等。如果这些由C++等利用OOP编写程序,我们可以通过在一个函数中调用另一个函数,可以通过构建一个球队的类,或者建立一个球员的对象,通过调用函数的方式完成计算。而反观过程性编程,程序员需要用函数调用另一个函数来计算,用数组或变量等记录结果等等。(来自《C++语言程序设计》,清华大学出版社)

总之,如果采用过程性编程,首先考虑的是遵循的步骤,然后再考虑这些数据。

对于OOP程序员,我们需要跟踪的就是球员,需要用一个对象表示整个选手的各个方面的表现。

采用OOP,首先要从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作。完成接口描述后,需要确定如何实现接口和数据存储。最后,使用新的设计方案创建出程序。

2.抽象和类

生活中充满了复杂性,处理它们的方法就是简化和抽象。

2.1 数据抽象

数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。

数据抽象是一种依赖于接口和实现分离的编程(设计)技术。

让我们举一个现实生活中的真实例子,比如一台电视机,您可以打开和关闭、切换频道、调整音量、添加外部组件(如喇叭、录像机、DVD 播放器),但是您不知道它的内部实现细节,也就是说,您并不知道它是如何通过缆线接收信号,如何转换信号,并最终显示在屏幕上。

因此,我们可以说电视把它的内部实现和外部接口分离开了,您无需知道它的内部实现原理,直接通过它的外部接口(比如电源按钮、遥控器、声量控制器)就可以操控电视。

现在,让我们言归正传,就 C++ 编程而言,C++ 类为数据抽象提供了可能。它们向外界提供了大量用于操作对象数据的公共方法,也就是说,外界实际上并不清楚类的内部实现。

例如,您的程序可以调用 sort() 函数,而不需要知道函数中排序数据所用到的算法。实际上,函数排序的底层实现会因库的版本不同而有所差异,只要接口不变,函数调用就可以照常工作。

2.2 类

类是一种将抽象转换为用户定义的C++工具,它将数据表示和操控数据的方法组合成一个整洁的包。下面我们来看看一个表示股票的类。

首先我们需要将股票的一股表示为一个基本单元,定义一个表示一股股票的类,然而,这意味着需要100个股票的单元才能构成100股。这将使工作量超标。相反,我们可以通过将某人持有的某种股票作为一个基本单元,数据表示中包含他所持有的股票数量。

具体地说,我们可以将执行的操作简化和限制为:

  • 获得股票
  • 增持
  • 卖出股票
  • 更新股票价格
  • 显示持股信息

根据上述操作的需求来定义stock类的公共接口,我们可以通过调用接口来完成这些操作。而对于需要储存的信息,我们将它再次简化,我们最终将储存以下信息:

  • 公司名称
  • 所持股票数量
  • 每股的价格
  • 股票总值

2.3 接口

接口是一个共享的框架,共两个系统交互时使用。例如张三想要把银行里存的钱取走,需要在银行或者ATM机取走他在银行系统里存的钱。

对于类,我们称为公共接口。在公共接口里,公共(public)是使用类的程序,而接口由程序员提供的调用类方法组成。例如,想要计算string对象包含多少个字符,我们无需打开对象,只需要使用其提供的size方法。方法size()便是用户与string类之间的公共接口。

3.C++中的类和对象

C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。

类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。

3.1 C++类的定义

定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。

C++类的结构

类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。例如,我们使用关键字 class 定义 Box 数据类型,如下所示:

class Box {   
   public:      
   double length;   // 盒子的长度      
   double breadth;  // 盒子的宽度      
   double height;   // 盒子的高度
};

关键字public确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。您也可以指定类的成员为 privateprotected

3.2 C++对象的定义

类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Box 的两个对象:

Box Box1;          // 声明 Box1,类型为 Box
Box Box2;          // 声明 Box2,类型为 Box

对象 Box1 和 Box2 都有它们各自的数据成员。

3.3 C++访问数据成员

类的对象的公共数据成员可以使用直接成员访问运算符.来访问。

下面这个例子能帮助你理解上述概念

#include <iostream>

using namespace std;

class Box
{
  public:
     double length;   // 长度
     double breadth;  // 宽度
     double height;   // 高度
     // 成员函数声明
     double get(void);
     void set( double len, double bre, double hei );
};
// 成员函数定义
double Box::get(void)
{
   return length * breadth * height;
}

void Box::set( double len, double bre, double hei)
{
   length = len;
   breadth = bre;
   height = hei;
}
int main( )
{
  Box Box1;        // 声明 Box1,类型为 Box
  Box Box2;        // 声明 Box2,类型为 Box
  Box Box3;        // 声明 Box3,类型为 Box
  double volume = 0.0;     // 用于存储体积

  // box 1 详述
  Box1.height = 5.0;
  Box1.length = 6.0;
  Box1.breadth = 7.0;

  // box 2 详述
  Box2.height = 10.0;
  Box2.length = 12.0;
  Box2.breadth = 13.0;

  // box 1 的体积
  volume = Box1.height * Box1.length * Box1.breadth;
  cout << "Box1 的体积:" << volume <<endl;

  // box 2 的体积
  volume = Box2.height * Box2.length * Box2.breadth;
  cout << "Box2 的体积:" << volume <<endl;


  // box 3 详述
  Box3.set(16.0, 8.0, 12.0);
  volume = Box3.get();
  cout << "Box3 的体积:" << volume <<endl;
  return 0;
}

3.4 C++类成员函数

类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。

让我们看看之前定义的类 Box,现在我们要使用成员函数来访问类的成员,而不是直接访问这些类的成员:

class Box
{
  public:
     double length;         // 长度
     double breadth;        // 宽度
     double height;         // 高度
     double getVolume(void);// 返回体积
};

成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。所以您可以按照如下方式定义 getVolume() 函数:

class Box
{
  public:
     double length;      // 长度
     double breadth;     // 宽度
     double height;      // 高度
 
     double getVolume(void)
    {
        return length * breadth * height;
    }
};

您也可以在类的外部使用范围解析运算符 :: 定义该函数,如下所示:

double Box::getVolume(void)
{
   return length * breadth * height;
}

在这里,需要强调一点,在 :: 运算符之前必须使用类名。调用成员函数是在对象上使用点运算符(.),这样它就能操作与该对象相关的数据。

这里需要注意的是,定义在类中的成员函数缺省都是内联的,如果在类定义时就在类内给出函数定义,那当然最好。如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上 inline,否则就认为不是内联的。例如:

class A
{
   public:void Foo(int x, int y) { } // 自动地成为内联函数
}

将成员函数的定义体放在类声明之中虽然能带来书写上的方便,但不是一种良好的编程风格,上例应该改成:

// 头文件
class A
{
   public:
   void Foo(int x, int y);
}
// 定义文件
inline void A::Foo(int x, int y){}

inline 是一种用于实现的关键字

关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。

如下风格的函数 Foo 不能成为内联函数:

inline void Foo(int x, int y); // inline 仅与函数声明放在一起
void Foo(int x, int y){}

而如下风格的函数Foo 则成为内联函数:

void Foo(int x, int y);
inline void Foo(int x, int y) {} // inline 与函数定义体放在一起

现在我们来梳理一下上面所说的程序,如下所示

#include <iostream>

using namespace std;

class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度

// 成员函数声明
double getVolume(void);
void setLength( double len );
void setBreadth( double bre );
void setHeight( double hei );
};

// 成员函数定义
double Box::getVolume(void)
{
return length * breadth * height;
}

void Box::setLength( double len )
{
length = len;
}

void Box::setBreadth( double bre )
{
breadth = bre;
}

void Box::setHeight( double hei )
{
height = hei;
}

// 程序的主函数
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
double volume = 0.0; // 用于存储体积

// box 1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);

// box 2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);

// box 1 的体积
volume = Box1.getVolume();
cout << "Box1 的体积:" << volume <<endl;

// box 2 的体积
volume = Box2.getVolume();
cout << "Box2 的体积:" << volume <<endl;
return 0;
}

该程序被编译后执行,会得到下列输出

Box1 的体积: 210
Box2 的体积: 1560