编程语言概述
整理一下对编程语言的整体理解,内容具有主观性,部分内容取自网络资料和GPT,仅作为学习整理,不保证正确性。事实上部分内容在网上的中文资料很混乱,很多都是相互矛盾的。
主流编程语言
列举并简要介绍了常用的编程语言,评价具有强烈的主观性,顺序参考TIOBE 排行榜,但是删除了作者从未使用过的部分语言。
主要关注通用编程语言(GPL,general-purpose language),不包括大部分领域特定语言(DSL,domain-specific language),例如在 bash,powershell 中使用的脚本语言,或者 CMake 其实也能算一门语言。当然,这里的数值计算领域的 MATLAB,Mathematica 严格来说也应该被归类为 DSL。
Python
Python 是一种著名的胶水语言,通用型的脚本语言,学习门槛低,代码可读性很高。
Python 的运算效率很低,尤其是原生数组的运算和循环等。通常科学计算为了达到高效率,都会调用 Numpy 等库进行向量化操作,这些库主要基于 C/C++/Fortran。
Python 是机器学习,人工智能时代的宠儿,深度学习主要都是使用 Python
来搭建神经网络。(在投入实际生产,对速度有要求时可能会使用 C++重写)
1
print("hello,world!")
C
C 是最经典的语言,主要用于系统开发工作,特别是组成操作系统的程序。
C 语言最接近底层,语法和汇编的对应最为直接,效率也几乎一样。
操作系统的接口也主要是通过 C 语言函数的形式对外提供,例如 Windows
平台的windows.h
。 1
2
3
4
5
6
int main(int argc, char *argv[]) {
printf("hello,world!\n");
return 0;
}
C++
C++(读作 C plus plus)约等于 C 的超集(C++ 并不是完全兼容 C 的,不兼容的差异很复杂,但都是一些细节问题),在 C 的基础上添加了面向对象,以及很多复杂的组件,C++的目标是在保留面向底层,运行高效率的前提下拥有更多丰富的功能。
1 |
|
没有人可以精通 C++,C++的语法比其它语言都更加复杂。
Java
Java 是一种彻底的面向对象的编程语言,它的关键词仍然是 C/C++系列的风格,但是语法上选择完全拥抱对象,甚至 main 函数也必须从属于一个类。
Java 具有跨平台,可移植性强的特点,因为它基于 JVM
来运行,不需要直接面向底层(例如使用垃圾收集机制,无法直接操作内存)。
由于 JVM 和 GC,Java 在运行效率上很难和 C/C++相比,高性能计算并不是 Java
的应用方向,Java 在互联网行业中应用非常广泛。 1
2
3
4
5class HelloWorld {
public static void main(String[] args) {
System.out.println("hello,world!");
}
}
C#
C#(读作 C sharp)是微软用来对标 Java 的一款语言,基于.net 平台,背靠 Windows 系统和宇宙第一 IDE——VS。C#的语言特点也和 Java 差不多,但是市场份额比 Java 小,可能是考虑到微软完全掌握 C#发展的原因。
这个语言的名字也是意味深长的:C, C++, C++++ = C#。 1
2
3
4
5
6
7
8
9
10namespace Helloworld
{
class Hello
{
static void Main(string[] args)
{
System.Console.WriteLine("hello,world!");
}
}
}
JavaScript
JavaScript 是为浏览器和网页定制的脚本语言,网页主要依靠的就是
html+css+js,在浏览器中任何页面直接按F12
就可以进入控制台,控制台可以输入并执行简单的
js 语句。当然 js 已经可以独立在浏览器之外,比如 node.js 就是服务端的 js
运行时,使得 js 可以进行全栈开发。
常用的 json 数据格式同时也是 js 的语法。
JavaScript 的名字和 Java 没有任何关系,只是蹭热度。TypeScript 是
JavaScript 的改进版,对类型和语法的要求更严格。 1
console.log("hello,world!");
js 的语法设计中的坑是非常混乱的,对 js 的乱七八糟的吐槽数不胜数。但是话又说回来,世界上只有两种语言:一种被人诟病,一种压根没人用。
MATLAB
MATLAB 是著名的科学计算商业软件,同时 MATLAB 也是在这个软件上使用的编程语言(和其它的通用编程语言不太一样)。
MATLAB 的名字源自 Matrix Laboratory 的缩写,MATLAB 软件的图标是一个二维几何区域的某个特征函数。 这个语言对于矩阵、线性代数以及数值计算相关的语法支持非常友好,计算比较高效。
在科学计算的运行效率方面:
- MATLAB 较容易写出比 Python 更高效的代码(MATLAB 作为商业软件加上长期的代码优化,而 Python 只是开源产品)。但是 MATLAB 也有 Python 类似的问题,比如原始的 for 循环很慢,应当用向量化运算代替循环;
- 和 C/C++/Fortran 相比,MATLAB 的效率也并不是注定低一等,主要取决于程序员的编程技巧,因为 MATLAB 的核心计算也是基于 C/C++/Fortran 的,并且已经经过了长期的代码优化。
MATLAB 在科学计算以及工业界都有广泛应用,在工业界的应用主要是体现在 MATLAB 具有功能丰富的扩展工具箱。
作为 MATLAB 的开源替代,Octave 是一个尽量模仿 MATLAB
语法的开源语言/软件,更加轻量级,支持核心的语法内容。 1
disp('hello,world!')
Fortran
Fortran 是过程式编程的活化石,比 C 语言还早,主要用于科学计算中,名称就是 Formula Translation 的缩写。
C/C++/Fortran 通常代表着运行效率的上限,它们是目前可以达到最高的运行效率的一类语言,但是值得注意的是:
- Cpp的下限很低,程序员需要非常熟悉 C++的各种语法技巧,才能让程序获得很高的效率,编译器面对复杂的语法无法进行很好的优化;
- Fortran 的下限很高,因为语法非常简单直接,不需要程序员拥有较高的水准,最简单无脑的计算方案也可以接受,编译器可以让程序达到很高的效率。
当然以上仅仅是从科学计算的运行效率比较,在其它方面 Fortran
简直一无是处,语法仍然是上个世纪的活化石(比如面向对象的语法支持非常瘸腿),
应用也远没有 C/C++ 那么广泛,Fortran 的存在感主要体现在各种祖传代码中。
1
2
3
4program main
implicit none
write(*,*) "hello,world!"
end program main
Fortran用起来实在是太难受了,远没有主流编程语言那么方便,可以体验到几十年前没有智能提示,语法高亮的复古流编程体验,而且毫无疑问,现代的AI辅助对于 Fortran 的支持也没有其它主流语言那么给力,毕竟网上相关的讨论和公开资料不是一个量级的。
Julia
Julia 是一个非常新的正在发展中的编程语言(2009),设计者的野心非常大:希望 Julia 可以同时拥有 Python/MATLAB 一样的易用性和 C/C++/Fortran 一样的运算效率,从而解决在科学计算中动态语言必须和静态语言搭配使用的问题。
Julia 也是 Jupyter notebook 名称来源之一(Jupyter = Julia + Python +
R)。 1
print("hello,world!")
Python/MATLAB 作为动态语言,自身的效率比较低,但是在关键部分使用向量化(基于底层语言实现)就可以让程序整体获得可观的效率。一旦涉及复杂逻辑或控制结构,并且是通过动态语言自身直接实现的,就会导致性能迅速衰减,唯一的方案是再次借助底层语言实现这部分复杂逻辑,然后提供给动态语言调用。
Julia 的主要优势在于,它可以完全用动态语言的语法直接写出接近 C 性能的代码,这得益于它使用的基于 LLVM 的 JIT 编译器,但是这也带来了冷启动时间较长的问题(实际就是在后台编译),尤其在加载模块或函数首次调用时表现明显。与 Python/MATLAB 不同,Julia 的向量化改写并不会带来明显的性能提升。
至少到目前为止,“偷偷编译”所带来的运算效率仍然弥补不了生态的缺失和部分语法的不完善。
Mathematica
和 MATLAB 定位类似,它并不适合被称为一个通用的编程语言,主要是在 Mathematica 软件中使用,但以符号计算为特色。
它的生态位比较独特,擅长的内容恰恰是其它编程语言所不擅长的,比如符号求解方程组和复杂表达式的化简等,符号计算本身比数值计算更复杂,更耗内存,计算也更慢。
1
Print["hello,world!"]
轻度使用时,Mathematica 看起来就像一个超强的计算器/草稿本;重度使用则会感觉到它是一门函数式语言。
Lua
Lua 是一种用 C 语言编写的轻量级的脚本语言,一开始的定位就是便于嵌入到
C/C++代码中执行,常见用法是在 C++编写的游戏中嵌入 Lua 虚拟机,使用 Lua
脚本实现部分频繁改动的逻辑。 1
print("hello,world!")
编程范式的历史与演变
简单回顾一下编程范式的历史与演变。
顺序编程
在早期编程中,人们采用的编程方式被称为顺序编程(非结构化编程)——程序按照上下文顺序执行指令,GOTO
语句在其中发挥了重要作用,
它可以无条件地跳转到程序的任意部分。
GOTO
语句的滥用导致了“意大利面条式代码”,使得程序变得难以理解和维护。为了克服GOTO
语句带来的代码混乱,20世纪60年代Edsger
Dijkstra等人提出了结构化编程。
结构化编程
结构化编程的核心是控制结构,例如顺序、选择(如if
、switch
)、循环(如for
、while
)等结构,通过这些控制结构来组织代码,尽可能避免GOTO
语句的使用。控制结构提高了代码的可读性和维护性,是现代编程语言的重要基础。
面向过程编程
面向过程编程是基于函数调用的编程范式,通过一系列过程(函数)的调用来组织代码,C语言是其中的典型代表。 面向过程编程的特点是数据与操作分离,这对于较小规模的程序没有问题,但是对于更大型的项目,就会遇到维护困难:难以通过函数管理复杂的数据和状态。
面向对象编程
为了应对面向过程编程在大型项目中的局限性,20世纪70年代提出了面向对象编程, 面向对象可以将数据和操作数据的方法封装到一起,通过类的继承可以实现代码的复用和扩展,允许不同的子类通过相同接口提供不同的方法。 Java是典型的面向对象语言。
函数式编程
上面的这一套发展脉络可以统称为命令式编程,程序通过执行具体命令来完成对应的功能,与之相对的是函数式编程(或者称为声明式编程,一个比函数式编程更大一点的概念,直接表明需要做什么,除了函数式语言还包括SQL等), 命令式编程和函数式编程是编程思想的两个“极端”:
命令式编程立足于现实世界中的计算模型,即计算机能够做什么。冯诺伊曼结构的计算机本质上通过顺序地执行指令来完成计算,这种执行方式与命令式编程高度契合。Fortran 是最早的命令式编程语言。
函数式编程则立足于我们希望实现的目标。它起初源于数理逻辑、数学证明等形式化推理的需求,其核心在于符号演算和函数组合。这些思想的产生早于现代计算机的发明。Lisp 是最早的函数式编程语言。
函数这个词在编程语言中有多种含义:
- 在命令式编程中的“函数”称为子过程更合适,它是一段命令的封装,目的是减少代码重复,早期会根据有没有返回值来区分函数和子过程,但是后来出现了无返回值的函数类型,就没有必要使用子过程这个特殊概念,统一称为函数。
- 在函数式编程中的“函数”则与数学中的函数和映射更加契合,它代表的是一种对应关系,具有不可变性和无副作用的特点:相同的输入必然得到相同的输出。
函数式编程的难度远大于命令式编程,学习曲线陡峭,应用很少,看起来有点像编译器语法处理中的中间语言,代表是Lisp和Haskell。
注:现在主流的编程语言都是属于命令式编程风格的,但是它们在发展过程中也不断吸收函数式编程的思想,两者之间并不是泾渭分明的。新语言例如rust和Julia在一开始就很大程度上参考了函数式编程的思想,传统语言例如C++则是在发展中不断吸纳函数式编程的语法。
编程模型的历史发展
1. 静态编译(1950s–1960s)
最早的高级编程语言(如 Fortran)采用了静态编译(Static Compilation)模式。 程序员编写源代码后,使用编译器一次性将其翻译成特定平台的机器代码,生成独立的可执行文件。 这种模式充分利用了当时极其有限的计算资源,保证了程序的运行效率。
典型代表:
- Fortran(1957)
- C(1972)
特点:编译生成机器码,运行速度快,但可移植性差。
2. 解释执行(1960s–1970s)
随着计算机应用领域的扩展,对开发效率和跨平台能力的需求上升,解释执行(Interpreted Execution)逐渐出现。 解释器可以直接读取源程序并执行每一条指令,无需预先编译,极大地方便了交互式开发与实验。
典型代表:
- BASIC(1964)
- Lisp(1958, 主要以解释方式实现)
特点:开发方便、交互式,但执行效率较低。
3. 虚拟机模型(1980s–1990s)
为兼顾编译执行的效率与解释执行的灵活性,出现了虚拟机(Virtual Machine)概念。 源代码首先编译成平台无关的中间代码(如字节码),然后在各平台的虚拟机上运行。 虚拟机可以根据目标硬件进行适配,极大地增强了可移植性。
典型代表:
- Smalltalk(1980年代)
- Java(1995,广泛普及虚拟机概念)
特点:一次编写,到处运行(Write Once, Run Anywhere)。
4. 即时编译技术(1990s–2000s)
解释执行与虚拟机执行虽然可移植,但运行效率仍不如本地编译程序。 为解决这一问题,引入了即时编译(Just-In-Time Compilation, JIT)技术:
- 在程序运行过程中,动态监测热点代码,并将其编译为本地机器码,从而大幅提升性能。
典型代表:
- Java HotSpot JVM(1999)
- .NET CLR(2002)
特点:结合了解释的灵活性与编译的高效性。
5. 多样化混合模式(2000s–至今)
现代编程语言往往采用混合执行模型(Hybrid Execution Models),根据不同应用场景灵活选择优化策略。 例如:
- Python:解释执行为主,模块加载时生成字节码(
.pyc
),并发展出 JIT 优化(PyPy)。 - Julia:函数粒度的即时编译(JIT),结合 LLVM 提供接近 C 的性能。
- MATLAB:最初为解释执行,后引入了局部 JIT 加速。
此外,出现了新的执行策略,如:
- AOT(Ahead-of-Time Compilation):运行前预编译热点模块(如 GraalVM、PyInstaller)。
- 增量编译(Incremental Compilation):仅重新编译修改过的部分(如 Rust 的 Cargo)。
特点:强调灵活性、可扩展性、易部署性。
常见语言的模型
C/C++:采用的是标准的静态编译方式,源代码通过编译器(如 gcc、clang)一次性编译为特定平台的机器码,生成可独立运行的可执行文件。运行时不依赖源代码或编译器。现代的发展主要关注静态编译环节,例如预编译加速,链接时优化等。
Python:采用的是标准的解释执行方式(如 CPython 解释器),直接逐行解析并执行源代码。在 REPL 模式或直接运行脚本时,源代码被即时解释执行,无预编译。但是后续引入了各种优化措施,例如引入了字节码虚拟机(CPython VM),当模块被导入时,源文件会编译为字节码(
.pyc
文件),加快后续加载速度。 除了 CPython,还出现了 PyPy(基于 JIT 技术的 Python 解释器),可以大幅提升运行效率。Java:经典模型是字节码编译 + 虚拟机执行,Java 源代码被编译为平台无关的字节码(
.class
文件),由 Java 虚拟机(JVM)负责解释执行或编译执行。现代的发展包括采用即时编译器(JIT Compiler),将热点方法编译为本地机器码。Julia:采用的模型是即时编译,源代码经过解析、类型推断,直接由 LLVM 后端编译为本地机器码执行。
MATLAB:最初采用的模型是解释执行,逐行读取源代码并执行。后续的执行模型已经发生了一些变化,包括引入即时编译器等。
类型系统分类
1. 强类型 vs 弱类型
强类型和弱类型主要体现了编程语言对数据的类型转换的态度:
- 强类型(Strong Typing):
- 定义:强类型语言要求数据在运算时必须符合其类型要求,如果存在类型不匹配的运算,就会抛出错误,不能隐式地转换数据类型。
- 特点:不允许不同类型的数据直接操作,编译时或运行时进行严格的类型检查,减少了类型错误和不一致性。
- 典型语言:Java、Python
- 弱类型(Weak Typing):
- 定义:弱类型语言允许不同类型的数据之间进行隐式转换,可以在不同类型之间进行操作而不会抛出错误,这可能导致预期之外的行为。
- 特点:允许不同类型之间进行隐式转换,灵活性较强,但可能导致错误或意外行为。
- 典型语言:JavaScript
2. 静态类型 vs 动态类型
数据必然有类型,但是变量却未必,静态类型和动态类型主要体现在变量和数据之间的关系:
- 静态类型(Static Typing):(变量严格绑定数据)
- 定义:静态类型语言要求在编译阶段就确定所有变量的类型(显式声明或通过类型推断),变量的类型在程序运行之前就已经确定,编译时进行类型检查。
- 特点:类型在编译时就已确定,如果通过编译就不会在运行时发生类型错误。类型的确定性使得程序可以优化运行效率。
- 典型语言:C/C++、Java、Rust
- 动态类型(Dynamic Typing):(变量可以被理解为数据的引用或指针)
- 定义:动态类型语言的变量的类型是在运行时因为绑定值而确定的,变量的类型是可以动态变化的,由它当时指向的值决定。
- 特点:语法灵活性高,不需要类型声明,适合快速开发。运行时类型检查必然带来性能开销,导致运行效率偏低,而且容易在运行时遇到类型错误。
- 典型语言:Python、JavaScript
静态语言的开发效率很低,但是更加稳健;动态语言的开发效率高,但是大规模程序的可维护性不够。
动态语言都有静态化的发展趋势,Python 和 JavaScript 采用了不同的策略:
- Python 在语言层面提供类型注解,虽然类型注解不是强制性的,但是可以为静态检查工具提供充分支持
- JavaScript 选择直接发展出了变体:TypeScript
常见语言分类
按照这两个维度进行分类,列举各个类别的代表性的编程语言:
- 强类型静态语言:Java
- 强类型动态语言:Python
- 弱类型静态语言:C/C++
- 弱类型动态语言:JavaScript
注:关于 C++ 是弱类型还是强类型其实是有争议的,本质上是因为强弱类型的定义并不清晰,wiki 上的观点认为现代 C++ 属于强类型。