编程范式一

来源

编程范式的英语是 Programming Paradigm,范即模范之意,范式即模式、方法,是一类典型的编程风格,是指从事软件工程的一类典型的风格(可以对照"方法学"一词)。

编程语言发展到今天,出现了好多不同的代码编写方式,但不同的方式解决的都是同一个问题,那就是如何写出更为通用、更具可重用性的代码或模块。

C语言

因为 C 语言历史悠久,而现在看到的所有编程语言几乎都是以 C 语言为基础来拓展的,不管是 C++、Java、C#、Go、Python、PHP、Perl、JavaScript、Lua,还是 Shell。

自 C 语言问世 40 多年以来,其影响了太多太多的编程语言,到现在还一直被广泛使用,不得不佩服它的生命力。但是,我们也要清楚地知道,大多数 C Like 编程语言其实都是在改善 C 语言带来的问题。

那 C 语言有哪些特性呢?

  • C 语言是一个静态弱类型语言,在使用变量时需要声明变量类型,但是类型间可以有隐式转换;
  • 不同的变量类型可以用结构体(struct)组合在一起,以此来声明新的数据类型;
  • C 语言可以用 typedef 关键字来定义类型的别名,以此来达到变量类型的抽象;
  • C 语言是一个有结构化程序设计、具有变量作用域以及递归功能的过程式语言;
  • C 语言传递参数一般是以值传递,也可以传递指针;
  • 通过指针,C 语言可以容易地对内存进行低级控制,然而这加大了编程复杂度;
  • 编译预处理让 C 语言的编译更具有弹性,比如跨平台。

C 语言的这些特性,可以让程序员在微观层面写出非常精细和精确的编程操作,让程序员可以在底层和系统细节上非常自由、灵活和精准地控制代码。

如果说,程序 = 算法 + 数据,C 语言会有这几个问题:

  • 一个通用的算法,需要对所处理数据的数据类型进行适配。但在适配数据类型的过程中,C 语言只能使用 void* 或 宏替换的方式,这两种方式导致了类型过于宽松,并带来很多其它问题。
  • 适配数据类型,需要 C 语言在泛型中加入一个类型的 size,这是因为我们识别不了被泛型后的数据类型,而 C 语言没有运行时的类型识别,所以,只能将这个工作抛给调用泛型算法的程序员来做了。
  • 算法其实是在操作数据结构,而数据则是放到数据结构中的,所以,真正的泛型除了适配数据类型外,还要适配数据结构,最后这个事情导致泛型算法的复杂急剧上升。
  • 在实现泛型算法的时候,你会发现自己在纠结哪些东西应该抛给调用者处理,哪些又是可以封装起来。如何平衡和选择,并没有定论,也不好解决。

C 语言设计目标是提供一种能以简易的方式编译、处理底层内存、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C 语言也很适合搭配汇编语言来使用。C 语言把非常底层的控制权交给了程序员,它设计的理念是:

  • 相信程序员;
  • 不会阻止程序员做任何底层的事;
  • 保持语言的最小和最简的特性;
  • 保证 C 语言的最快的运行速度,哪怕牺牲移值性。

C 语言诞生于 1972 年,到现在已经有 51 年的历史,在它之后,C++、Java、C# 等语言前仆后继,一浪高过一浪,都在试图解决那个时代的那个特定问题。

泛型编程

1980 年,AT&T 贝尔实验室的 Bjarne Stroustrup 创建的 C++ 语言横空出世,它既可以全面兼容 C 语言,又巧妙揉合了一些面向对象的编程理念。

C++ 很大程度就是用来解决 C 语言中的各种问题和各种不方便的。比如: 用引用来解决指针的问题。

  • 用 namespace 来解决名字空间冲突的问题。
  • 通过 try-catch 来解决检查返回值编程的问题。
  • 用 class 来解决对象的创建、复制、销毁的问题,从而可以达到在结构体嵌套时可以深度复制的内存安全问题。
  • 通过重载操作符来达到操作上的泛型。
  • 通过模板 template 和虚函数的多态以及运行时识别来达到更高层次的泛型和多态。
  • 用 RAII、智能指针的方式,解决了 C 语言中因为需要释放资源而出现的那些非常 ugly 也很容易出错的代码的问题。
  • 用 STL 解决了 C 语言中算法和数据结构的 N 多种坑。

C++ 泛型编程

C++ 是支持编程范式最多的一门语言,它虽然解决了很多 C 语言的问题,它最大的意义是解决了 C 语言泛型编程的问题。我们可以看到一些 C++ 的标准规格说明书里,有一半以上都在说明 STL 的标准规格应该是什么样的,这说明泛型编程是 C++ 重点中的重点。

算法应是和数据结构以及类型无关的,各种特殊的数据类型理应做好自己分内的工作,算法只关心一个标准的实现。而对于泛型的抽象,我们需要回答的问题是,如果我们的数据类型符合通用算法,那么对数据类型的最小需求又是什么呢?

C++ 是如何有效解决程序泛型问题的?

  • 第一,它通过类的方式来解决。这样可以让一个用户自定义的数据类型和内建的那些数据类型就很一致了。
  • 第二,通过模板达到类型和算法的妥协。模板很好地取代了 C 时代宏定义带来的问题。
  • 第三,通过虚函数和运行时类型识别。虚函数带来的多态在语义上可以支持"同一类"的类型泛型,运行时类型识别技术可以做到在泛型时对具体类型的特殊处理。

泛型的本质

要了解泛型的本质,就需要了解类型的本质。

  • 类型是对内存的一种抽象。不同的类型,会有不同的内存布局和内存分配的策略。
  • 不同的类型,有不同的操作。所以,对于特定的类型,也有特定的一组操作。

要做到泛型,我们需要做下面的事情:

  • 标准化类型的内存分配、释放和访问。
  • 标准化类型的操作。比如:比较操作,I/O 操作,复制操作......
  • 标准化数据容器的操作。比如:查找算法、过滤算法、聚合算法......
  • 标准化类型上特有的操作。需要有标准化的接口来回调不同类型的具体操作......

C++ 动用了非常繁多和复杂的技术来达到泛型编程的目标。

  • 通过类中的构造、析构、拷贝构造,重载赋值操作符,标准化(隐藏)了类型的内存分配、释放和复制的操作。
  • 通过重载操作符,可以标准化类型的比较等操作。通过 iostream,标准化了类型的输入、输出控制。
  • 通过模板技术(包括模板的特化),来为不同的类型生成类型专属的代码。
  • 通过迭代器来标准化数据容器的遍历操作。
  • 通过面向对象的接口依赖(虚函数技术),来标准化了特定类型在特定算法上的操作。
  • 通过函数式(函数对象),来标准化对于不同类型的特定操作。

屏蔽掉数据和操作数据的细节,让算法更为通用,让编程者更多地关注算法的结构,而不是在算法中处理不同的数据类型。

函数式编程

函数式编程其实是一个非常古老的概念。函数式编程的基础模型来源于 λ 演算,而 λ 演算并没有被设计在计算机上执行。它是由 Alonzo Church 和 Stephen Cole Kleene 在 20 世纪 30 年代引入的一套用于研究函数定义、函数应用和递归的形式系统。

对于函数式编程来说,它只关心定义输入数据和输出数据相关的关系,数学表达式里面其实是在做一种映射(mapping),输入的数据和输出的数据关系是什么样的,是用函数来定义的。

函数式编程有以下特点:

特征

  • stateless:函数不维护任何状态。函数式编程的核心精神是 stateless,简而言之就是它不能存在状态,打个比方,你给我数据我处理完扔出来。里面的数据是不变的。
  • immutable:输入数据是不能动的,动了输入数据就有危险,所以要返回新的数据集。

优势

  • 没有状态就没有伤害。
  • 并行执行无伤害。
  • Copy-Paste 重构代码无伤害。
  • 函数的执行没有顺序上的问题。

劣势

  • 数据复制比较严重。
    注:有一些人可能会觉得这会对性能造成影响。其实,这个劣势不见得会导致性能不好。因为没有状态,所以代码在并行上根本不需要锁(不需要对状态修改的锁),所以可以拼命地并发,反而可以让性能很不错。比如:Erlang 就是其中的代表。
相关推荐
Asthenia041236 分钟前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom1 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide1 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9652 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04122 小时前
Spring 启动流程:比喻表达
后端
Asthenia04123 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua3 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫