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 特性,即数据表示和数据操作都封装到一个对象里面。
参考
- Abstraction in C++
《C++ Primer Plus Sixth Edition》 by Stephen Prata