#P2232. 面向对象编程

面向对象编程

1. 什么是面向对象编程?

面向对象编程 (OOP) 是一种编码设计,它使用数据来表示一组指令。OOP 设计围绕可实例化为对象的专用类展开。

与过程式或函数式编程不同,OOP 为我们提供了更全面地表达代码的余地。虽然以前的范式通常没有结构,但 OOP 鼓励使用称为类的专用结构。

方法是在类中执行特定任务的函数。属性就像描述类特征或特性的变量。方法可以独立运行,也常常基于类的属性。最终,两者共同作用,实现了OOP的概念。

2. 面向对象编程的优点

那么面向对象编程如何帮助你编写更好的程序呢?

OOP 降低了代码库的复杂性。

它可以帮助我们清楚地表达我们的代码,使其更具可读性。

用 OOP 编写的程序通常更具可扩展性。

它简化了代码测试和调试。

OOP消除了代码重复,建立了DRY(不要重复自己)原则。

OOP 代码通常更加模块化,鼓励关注点分离。

类组合和继承使代码更易于重用。

抽象提高了代码库的安全性。

3. 面向对象编程的缺点

虽然OOP的优点大于缺点,但缺点也不能忽视:

它可能比函数式编程慢。

OOP陡峭的学习曲线很陡峭。

脚本文件夹和文件随着应用程序的扩展而增加。

4. 面向对象的编程结构

OOP 围绕严格的架构展开。以下是我们将了解的一些术语:

4.1 类

类是作为执行类似操作的数据表示的代码集合。我们可以将类视为对象处理程序,因为我们可以使用对象处理程序来实例化对象。

4.2 方法

方法定义类如何完成其任务。一个类可以包含一个或多个方法。我们可以将方法视为类在其内部分担职责的方式。

例如,单位转换器类可能包含将摄氏度转换为华氏度的方法。它可能包括另一种将克更改为千克的方法。

4.3 属性

属性是描述类的要素或属性。例如,单位转换器类可能包含转换单位等属性。我们可以定义作用于这些属性的方法。

与方法一样,我们可以从类实例访问(某些)属性。

4.4 对象

简单地说,对象是一个类的实例。实例化类时,生成的对象将使用该类作为其属性和方法的蓝图。 面向对象编程的原则

面向对象编程为编程表带来了一些原则。这些中的每一个都使其领先于传统编程。

4.5 抽象化

OOP的抽象概念认为,你不需要知道某些东西是如何工作的。它允许我们用简单的语言包装代码,而无需担心程序幕后的复杂性。

例如,我们无需担心提交操作背后的逻辑、过滤算法或函数。作为用户,我们看到和关心的只是发送按钮。

面向对象的编程通过将单个任务呈现为单个调用来帮助我们抽象逻辑。例如,虽然单位转换器类可能会在后台进行大量计算,但我们可以通过调用单个方法来运行其千克到克转换器:

class_instance.convert_gram()

其中class_instance是对象,convert_gram是转换器类的方法。

4.6 封装

封装是面向对象编程创建抽象的方式之一。每个对象都是被视为实体的数据集合。对象中的数据包括隐藏在全局空间中的属性和方法。

通常,封装允许我们将类数据私下包装在对象中。因此,一个对象的内容不会干扰另一个对象。只有对象的固有方法和属性才能改变它。

例如,单位转换器对象中的方法不应在没有继承或组合的情况下更改另一个对象的属性。

封装允许我们更改对象的内容或结构,不用而无需担心公共接口。

4.7 继承

继承允许我们在另一个称为子类的类中重用类(称为超类)的内容。当类继承超类时,它会自动获取其属性和方法。

除了从超类继承的属性外,子类还可以具有自己的属性和方法。

例如,如果我们希望类使用外部模块中的数据,继承将派上用场。它还确保我们在编写代码时不会重复自己。

因此,创建子类还可以节省大量时间。我们可以创建一个基类并将其扩展到新的子类,借用现有功能,而不是为所有内容创建新类。

继承是有用的,但知道何时使用组合是一个基本的编程原则。 多态

多态是继承的结果。它允许您在不同的对象中维护方法或属性名称,您可以在其中根据需要使用它们。

此概念可确保可以在不同的类中动态使用类方法,并从基类继承它。

例如,通用游戏对象可以定义移动方法。子类可以准确定义其特定运动的发生方式。然后,控制代码不需要知道单独的类是如何移动的,只需知道它们都可以通过通用方法移动。

5. OOP 与函数式编程

OOP很流行,但程序员仍然使用许多其他编程范式,这取决于他们的需求。函数式编程与OOP完全不同:

函数式编程处理专用函数中的指令。另一方面,OOP将指令呈现为存储在对象中的数据。

在真正的函数式编程中,函数的输出总是相同且不可变的。OOP 允许多态性,并且返回的数据在其他类中是可变的。

OOP 更具可扩展性,因为您可以轻松扩展对象。

抽象使 OOP 更安全,因为局外人不知道幕后的操作。函数式编程可以公开某些层。

虽然 OOP 可能很复杂,但它比函数式编程更易于维护。函数式编程仍然保留一些过程属性。

函数式编程可以更快,因为程序在不考虑对象层次结构的情况下访问指令。在使用面向对象的程序时,对象层次结构是必不可少的。

6. 具体又简单的例子

题目

贝贝的国王

题目描述

传说古代印度有个喜欢下棋的国王叫舍罕,而宰相贝贝是个聪明的大臣,发明了国际象棋。国王玩得爱不释手,决定奖赏宰相。贝贝说:陛下,我别无他求,请你在这张棋盘的第 1 个格子里赏我 1 粒麦子;在第 2 个格子里赏我 2 粒麦子;在第 3 个格子里赏我 4 粒麦子;在第 4 个格子里赏我 8 粒麦子......依此类推直到 100 个格子,按这张棋盘上各格应赏的麦子全赏给我吧。

国王听了,觉得贝贝的要求并不高,说道:你能如愿以偿的。然而,国王却不知道这个数字是多么巨大啊!你能帮助国王算算第 n 个格子的麦粒数量吗?

输入格式

一行,一个正整数 n(n < 101)

输出格式

第 n 个格子的麦粒数量,注意不能以科学记数法表示。

样例

5
16
40
549755813888

参考程序

#include<bits/stdc++.h>
using namespace std;
class hp{

public:
	int a[500];// 数组定义得越大,耗的内存越多
	hp(){
		memset(a,0,sizeof a); // 初始化为 0 很重要 
	}
	hp(string s){
		memset(a,0,sizeof a);
		
		// 反向存储,个位放在 a[0] 
		for(int j=0,i=s.size()-1;i>=0;i--,j++)
			a[j] = s[i] - '0';
	}
	void show(){
		//要从高位开始输出,前面的 0 跳过不输出
		bool f = true;
		for(int i=499;i>=0;i--){
			if(f&&a[i]==0) continue;
			f = false;
			printf("%d",a[i]);
		}
		if(f) printf("0"); // 意味着整个数组都是 0 
		printf("\n");
	}
	hp operator * (int k){ // 高精度数乘 int 
		hp tmp;// 定义临时变量,加运算的结果放在 tmp
		for(int i=0;i<500;i++){
			tmp.a[i] += a[i]*k;
			tmp.a[i+1] += tmp.a[i]/10;
			tmp.a[i] %= 10;
		}
		return tmp;
	}
}ans[101];
int main(){
	int n;
	cin>>n;
	
	ans[1] = hp("1"); // 递推的起点 
	
	for(int i=2;i<=n;i++)
		ans[i] = ans[i-1]*2;

	ans[n].show(); // 调用 成员函数 输出 高精度数据 
	return 0;
}
  1. hp 是一个类,简单理解,就是高精度类

  2. show 是 hp 类下面的一个方法

  3. hp 类下有一个 int a[500] 的数组,a[500] 是 hp 类的一个成员

  4. hp 类有 2 个构造函数。构造函数的名字和类的名字相同,一个是没有参数的,一个是有参数的。在定义一个实例的时候,构造函数会被自动调用,调用哪一个构造函数取决于定义实例的方式。

  5. ans[101] 是定义了 hp 类的对象实例数组,每一个数组成员都是 hp 类的一个实例。每个实例都有自己的成员变量。例如 ans[1] 有自己的 a[500],ans[2] 有自己的 a[500],彼此独立。在定义 ans[101] 的时候,无参数的构造函数会被自动调用,每一个数组元素通过构造函数被初始化。

  6. hp 类还重载了运算符 * 函数,当一个 hp 实例 * 一个 int 实例的时候,就会调用这个运算符函数。

更多的面向对象编程技术和特性会在后续其它课件中进一步介绍