C++系列:对象和类(一)

背景

面向对象编程

首先,面向对象编程(Object-Oriented Programming, OOP)是一种编程风格/程序设计思想/编程范式。它强调以对象(数据+方法)为中心,而不是以过程为中心(即面向过程编程)。

由此可见,从某种意义上讲,编程语言可以在不同程度上支持这种风格。即使 C 语言也可以实现这种风格,只不过实现起来难度更大、更为曲折。

C++ 就是对 OOP 特性支持的比较好的编程语言。

抽象

抽象(abstraction)是一种 OOP 特性。处理复杂问题的方法之一就是提供简单的抽象。那么,抽象究竟表示什么意思?

这里的抽象表示去除无关信息或隐藏细节信息,以减少问题复杂度的过程

一个典型的例子是:对于老司机开车这件事,我们只需要关心速度、油量等指标(数据表示);以及启动、换挡、踩油门、刹车、转方向盘、开闪光灯等操作过程(数据操作)。我们不需要知道发动机究竟怎么启动的,电路和管路是怎么布局的等问题。

因此,这里使用抽象的一个关键就是:尝试从用户的角度去考虑对象的构建,先去定义用户和对象的交互(即接口),再去想对象的内部数据和接口实现。

封装

封装(encapsulation)是一种 OOP 特性。封装有多种含义,OOP 中的封装的语义是:将数据表示和数据操作,绑定到一个对象中,不能直接访问对象的数据,只能通过方法来访问或修改对象的数据。

C++ 中的类(class)就是一种提供抽象的方式,它也相当于自定义类型

定义类需要确定两个部分:一种称为类声明;一种称为类方法定义

类声明用于声明数据成员和成员函数(类的蓝图,常常放在头文件),类方法定义用于定义成员函数(类的细节,常常放在库文件)。

类声明部分  

C++ 提供了访问控制关键字:public 和 private。这两个关键字可以限定对象中数据和方法的访问权限,private 提供了数据隐藏,进而加强了类的封装特性。

由于这只是声明,所以一般情况下,普通的成员函数只需要提供函数头。但是也有例外,有些成员函数可以直接提供函数定义,这种成员函数会隐式地定义为内联函数,又叫内联方法。内联函数一般用于短小的函数,编译器会进行优化,避免函数调用的栈开销,例如下面示例中的 SetGrade。

示例如下:

// student.h
#pragma once
#include <iostream>
#include <string>

class Student
{
public:
    void InitializeData(const std::string& name, int score);
    void SetGrade(int val) { grade = val; };
    void ShowGrade();

private:
    int grade;
    std::string name;
};

这 Student 类的声明,放在头文件。它声明了类的数据成员成员函数,但是没有给出普通成员函数(即非内联的)的定义。

从类的设计上来看,声明部分把数据成员放在了私有部分,而把成员函数放在了公有部分。公有部分构成了公共接口,而私有部分提供了数据隐藏

类方法定义部分

在类方法定义部分,使用 :: 来为成员函数指定类名,它的术语叫作用域解析运算符(scope-resolution operator)

类方法定义部分提供了实现细节。

// student.cpp
#include "student.h"

void Student::InitializeData(const std::string& name_val, int grade_val)
{
    name = name_val;
    grade = grade_val;
}

void Student::ShowGrade()
{
    using namespace std;
    cout << name << " has grade of " << grade << "." << endl;
}

我们看到,成员函数可以访问私有数据成员(name 和 grade)。

类的简单使用

student.h 和 student.cpp 构成了 Student 类的完整定义,接下来是 Student 类的简单使用。

// main.cpp
#include "student.h"

int main()
{
    Student s1;
    Student s2;
    s1.InitializeData("zhangsan", 80);
    s2.InitializeData("lisi", 59);
    s1.ShowGrade();
    s2.ShowGrade();
    s1.SetGrade(99);
    s1.ShowGrade();
    s2.ShowGrade();
}

输出:

zhangsan has grade of 80.
lisi has grade of 59.
zhangsan has grade of 99.
lisi has grade of 59.

这里的要点是,对象 s1 和 s2 都是基于同一个 Student 类创建的,遵循 Student 类提供的相同的抽象。但是对象 s1 和 s2 有自己单独的存储空间,互相不受影响。

但在 C++ 编译器的实现中,不同对象的成员函数/函数实际上是共享的,只有一个副本。但它显然可以找到调用对象的数据成员,因此可以看起来每个对象拥有一个自己的成员函数。

这可以提升编译效率,而不会影响 OOP 特性,即数据表示和数据操作都封装到一个对象里面。

参考