1. 面向对象编程

OOP ( Object Oriented Programming) 即面向对象编程。

面向对象编程是一种编码思想,或是一种代码组织方式。如同编辑文章时,可以选择分段、分节的方式让文章看起来有层次、更方便阅读或修改。

编码时可以选择使用 OOP 方案,也可以选择不使用。如同行文一样,使用或不使用都不会对核心逻辑产生影响。

面向对象编程有自己的核心编码理论,对于任何一种计算机语言而言,如果选择支持此理论,则称此计算机语言支持面向对象编程。如 C++、Java、Python……

因每一种计算机语言语法上的差异性,在提供 OOP 实现时的语法规范会有很大的区别。除此之外,对于每一种语言而言,也可以在 OOP 基础理论上进行语法扩展或限制。如 Python 支持多继承。而 Java 语言只支持单根继承……

1.1 OOP 特点

要了解 OOP 的特点,可从 2 个角度进行阐述。

广义角度: 让程序像人类解决问题一样去解决问题,让程序具有人的思维模式。

人类解决问题时,先是要了解问题域中会涉及到哪些对象,然后再深入了解**每一个对象的特性或功能,**最后再做出相应的决策。

比如:为班级选一名班长。

选班长就是现实世界的一个问题域,如何才能选择一名符合要求的班长?

  1. 首先确定此问题中涉及的对象(此处便是班上的所有学生)。
  2. 然后了解每一个学生的兴趣、爱好、性格……以及个人能力等等。
  3. 从了解的群体中匹配一个符合班长标准的学生便可。

面向对象编程中的对象一词,便是借鉴了现实世界中对象概念。

狭义角度:OOP 编码为整个程序维护带来的优势。

OOP 组织的代码可让程序整体上有高度的可阅读性,除此之外,最主要的特点是可提高代码的复用性、安全性、可扩展性。

任何事情都会 2 面性,OOP 会增加代码的理解难度。

1.2 OOP 基本概念

OOP 中有两个很重要的概念,类和对象

对象从何而来?

现实世界中我们很少思考这个问题,在选班长时,不会思考学生是从哪里来的,即使思考这个问题,也会认为那是哲学家的事情。

我们不思考现实世界中的手机、电脑、电视机是怎么来的……因为我们不关心这个,我们关心的是使用它们所提供的功能。

如果我们思考一下手机是怎么出现的,则会发现:

  1. 首先需要工程师设计手机蓝图。
  2. 在工厂里根据手机蓝图进行生产(可能生产很多)。
  3. 用户购买手机,了解手机特性和功能,使用手机。

我们身边的诸如 电视机、洗衣机、电脑……无不例外的需要经过这几个过程后方能来到我们的世界。

即使是人也是女娲按自己的样子创建出来的……

同理,电脑世界里不会突然冒出手机、电脑、学生……如何才能让电脑出现此类对象。一样,先设计一个蓝图,此蓝图在电脑世界我们就称其为**“类”**。

有了**“类”之后才可以创建手机对象**,有了对象后才能在程序代码中使用设计时为手机赋予功能完成程序逻辑。

现实世界设计手机蓝图时,需要设计手机的外观,如大小、形状、体重……需要赋予手机功能、如打电话、播放音乐、播放视频、上网……

在计算机的编码世界里,同样在设计类时需要为 “手机类” 设计外观和功能。OPP 中称外观为属性,称功能为方法。

类是蓝图,具有抽象性特征

对象是根据蓝图创建出来的个体,具有具体性、实用性特征

2. C++ 实现 OOP

如需使用 OOP 理念实现程序逻辑,则需遵循如下流程:

2.1 分析问题

首先需要明确问题:如编写一个程序摸拟小狗的行为。

此问题中的对象便是小狗,所以程序中需要一只小狗。

按上所述,创建小狗之前需要设计“狗类”,因此需要为类的设计提供足够的信息。

分析可得在设计类时需要有小狗属性:姓名、年龄,小狗的行为:尊下、打滚。

2.2 类设计语法

#include <iostream>
using namespace std;
class Dog {
	private:
         //名字
		string name;
        //年龄
		int age;
	public :
        //构造函数
		Dog(string name,int age) {
			this->name=name;
			this->age=age;
		}
        //蹲下
		void sit() {
			cout<<this->name<<" 乖乖的尊下了!";
		}
        //打滚
		void rollOver() {
			cout<<this->name<<" 开始打滚哈!";
		}
};
int main(int argc, char** argv) {
	return 0;
}

 如上为 C++ 中类设计的结构语法:

  • 类中定义的函数称为类的行为,可以是私有,只能在类中访问,也可以是公有,在外部访问。
  • 构造函数是必须的,其名称和类名一样,不得有返回值。此函数会在创建对象时被自动调用,用来初始化对象数据。
  • 类中定义的变量称为类的属于,如 nameage

2.3 创建对象语法

有了类后,方可创建对象,所谓对象,指遵循类结构设计方案存储的数据集合。有了对象后方可激活属性和方法。对象的创建有 2 种语法:

  • 静态语法:变量 myDog里存储的是对象本身。静态创建的对象,使用 .运算符访问对象中的数据和方法。
int main(int argc, char** argv) {
	Dog myDog("小雪", 6);
	myDog.sit();
	myDog.rollOver();
	return 0;
}
  • 动态语法:变量myDog存储对象的指针。动态创建的对象,使用->运算符访问对象中的数据和方法。
int main(int argc, char** argv) {
	Dog* myDog=new Dog("小雪", 6);
	myDog->sit();
	myDog->rollOver();
	return 0;
}

Tips: 创建小狗时,需调用和类名相同的构造函数,如上述的 Dog 函数,需要传递小狗的具体姓名和年龄初始 name 和 age 变量。

有了类之后,可以根据此类的设计方案,创建出多个对象。每一个对象有自己的数据空间,彼此之间的数据是独立且隔离的。

Dog myDog("小雪", 6);
myDog.sit();
myDog.rollOver();
Dog yourDog("小花", 6);
yourDog.sit();
yourDog.rollOver();

 如同现实世界一样。现在有了 2 只小狗,它们是独立的个体。修改其中一只狗的名字,对另一只小狗是没影响的。

3. OOP 的封装性

声明类时,nameage被声明在private中,无法使用下面的语法访问或修改小狗的姓名和年龄。这是封装性的原故。

myDog.name='小花';
myDog.age=4;

封装性可以从 2 个角度上展开讨论:

3.1 广义角度:无处不封装

类就是一个封装体:它把数据以及对数据的相关操作方法封装在了一起。

方法也是一个封装体:封装了代码逻辑

封装的优点!

当我们通过对象使用数据和方法时,不需要了解其中的内部细节,如此实现了设计和使用的分离,和现实世界中我们使用手机一样,不需了解手机的内部结构和细节。

开发者在使用 C++提供的模块时,不需要了解模块中的相关实现细节,直接使用其功能便可。

设计和使用的分离能加速工业软件的开发效率。

3.2 狭义角度:保证内部数据的完整性

设计类时,如果把 age放在public下面。

public :
	int age;

创建一只小狗后,可以编写如下代码修改小狗的年龄。

	Dog myDog("小雪", 6);
	myDog.age= -4;
	cout<<"小狗今年"<<myDog.age<<"岁了"; 

显然这是不符合实际情况的,没有一只小狗的年龄可以是负 4岁。但是,现在程序可以正常运行。

小狗今年-4 岁了

出现这样不合常理的想象,应该追究谁的责任。类的设计者还是对象使用者?

我们应该要追究类设计者的责任,就如同我刚买的手机不能充电一样,是设计者的设计缺陷引起的。

我们应该在设计类的时候提供一种内部安全检查机制,保护变量能被赋予一个正确的、可靠的值。

实施流程:

  1. 把变量的声明放在private 区域。private 的意思是私有的,此区域中声明的变量只能在对象内部使用。
	private:
		string name;
		int age;
  1. 在类中提供对应的 setget 函数实现对内部变量的保护。
int getAge() {
    return this->age;
}
// 对数据进行检查
void setAge(int age) {
    if (age<0) {
        cout<<"小狗的年龄不可能为负数";
        return;
    }
    this->age = age;
}
  1. 测试
Dog myDog ("小雪", 6);
myDog.setAge(-4)

输出结果

小狗的年龄不可能为负数

4 . 总结

面向对象编程可以用《人类简史》中的一句话总结,人类文明的进步不一定能泽福到每一个个体。

类可以设计的很完美,但每一个对象作为个体可以有自己的命运。

封装是面向对象编程理念中最基本也是最重要的特性,没有封装便没有后续的更多。

封装可以让我们把相关联的数据与方法构建成一个逻辑上的整体,也可保护内部数据的安全性,毕竟没有数据安全性的程序是没有意义的。