编程语言世代
第一代和第二代语言又称低级语言(Low-level language),其余被视为高级语言(High-level language)
第一代编程语言
即机器语言,由0和1构成,通过面板、打孔带或者打孔卡输入。
第二代编程语言
即汇编语言,是机器语言的简单替换,目前在内核/驱动中会被用到。
第三代编程语言
架构无关的语言,更加面向程序员,而非机器。第三代语言将很多细节交由计算机(编译器)把控,同时变得更加抽象。从Fortran、ALGOL、COBOL到C、C++、C#、Java、Basic和PASCAL都是第三代语言。大多数第三代语言支持结构化编程,不少支持面向对象编程。
第四代编程语言
是一个比较模糊而不断变化的概念,本质上说,任何在第三代语言的概念上有所改进的语言都可被视为第四代语言。
第四代语言可能包括对数据库处理、报告生成、GUI开发、网络开发等的支持。某些人认为第四代语言是domain-specific语言的子集。而且这些支持在第三代语言中都有以库的方式体现,这模糊了第三代和第四代的区别。
第五代编程语言
第五代语言被认为可以让计算机在不需要程序员的情况下独立解决一个问题,也就是说计算机只需要了解这个问题的约束,而无需程序员写出算法。
广义上来说,大部分constraint-based和logic-programming和某些其他的declarative语言都是第五代语言。第五代语言通常被用作在人工智能的研究上,一些这样的语言(比如OPS5)基于Lisp。
狭义上来说,第五代语言尚未诞生。因为其特点,第五代语言曾被认为是未来的语言,将取代所有之前的高级语言,日本曾在第五代语言方面投入了大量的研究和资金,但事实上因为第五代语言和Strong AI息息相关,其难度非常之高。
编译型和解释型
所有语言最终都需要转变为机器码,基于其转换为机器码的方式,高级语言可大致分为编译型和解释型两类(汇编语言无须编译或解释,仅需汇编成机器码)。
通常来说,编译型语言的运行速度更快(因为已经预先编译好,运行时无须执行解释这一步骤),而因此,编译型语言的开发/调试时间也较长,因为每次调试之前都需要编译一次。而解释型语言则可以快速的测试和调试。
理论上来说,任何语言都可以是编译型的或者解释型的。许多语言同时采用编译器和解释器来实现:先将代码编译为(虚拟机一致的)字节码,在运行时再用解释器解释执行,即所谓的JIT(Just-in-time,即时编译)。
编译型语言(Compiled language)
编译型语言利用编译器先将代码编译为机器码,再加以运行。比如C、C++、C#(编译成字节码)、Java(编译成字节码)等
解释型语言(Interpreted language)
解释型语言利用解释器,在运行期间,动态将代码逐行解释(Interpret)为机器代码并执行。比如Python、BASIC、JavaScript等。
编程范式
Programming Paradigms,或叫编程范型、编程典范,基于编程语言的特点而进行分类的方式。一种语言可以可以支持超过一种编程范型。
命令式和声明式
这是两个相对/并列的范式,命令式编程描述过程 ,声明式编程描述目标。
命令式编程(Imperative programming)
命令式编程描述计算所需作出的行为。几乎所有的计算机硬件都是命令式的。
子范式:过程式和面向对象式,过程式更靠近机器,面向对象式更贴近程序员。
过程式编程(Procedural programming)
来源于结构化编程,其概念基于过程(procedure、routine、subroutine、function),过程由一系列可执行可计算的步骤构成。
Fortran、ALGOL、COBOL、BASIC、Pascal和C等语言采用过程式编程。
面向对象式编程(Object-oriented programming)
具有对象概念的编程范式。
重要的面向对象编程语言包括Common Lisp、Python、C++、Java、C#、Perl、PHP、Ruby、Swift等。
声明式编程(Declarative programming)
声明式编程描述目标的性质,让计算机明白目标,而非流程。声明式编程通常被定义为所有的“非命令式编程”。
声明式编程包括数据库查询语言(SQL)、正则表达式、逻辑式编程、函数式编程和configuration management。声明式编程通常用作解决人工智能和约束满足问题。
子范型:函数式编程、逻辑式编程、约束式编程、数据流式编程
函数式编程(Functional programming)
又称泛函编程,它将计算视为数学上的函数运算,避免变量或状态(只有函数及其参数)。其最重要的基础是λ演算(lambda calculus),λ演算的函数可以接受函数当作输入和输出。
分为纯函数式编程(Purely functional programming)和函数逻辑式编程(Functional logic programming,函数式编程和逻辑式编程的组合)
逻辑式编程(Logic programming)
逻辑式编程基于数理逻辑,它设置答案所须匹配的规则来解决问题,而非设置步骤来解决问题。过程为:事实+规则=结果。
最常用的逻辑式编程语言是Prolog,Mercury则较适用于大型方案。
约束式编程(Constraint programming)
在这种范式中,变量之间的关系是以约束的形式陈述的,它们并非明确说明了要去执行步骤的某一步,而是规范其解的一些属性。
数据流式编程(Dataflow programming)
将程序建模为一个描述数据流的有向图。例如BLODI。
结构化和非结构化
这是两个相对的范式,非结构化编程是最早的编程范式,现今的计算机科学家都同意结构化编程的好处。
非结构化编程(Non-structured programming)
是最早的编程范式,相对于结构化编程,特点是其控制流是通过(容易引起混乱的)goto语句跳转实现的。非结构化编程包括机器语言、汇编语言、MS-DOS batch、以及早期的BASIC及Fortran等等。
结构化编程(Structured programming)
通过子程序、代码块、for循环、while循环等结构来取代之前的goto语句,以提高代码的清晰程度,获得更好的可读性。现今的大部分高级语言都是结构化的。
结构化编程的流程包括顺序、选择(if, else, switch)、循环(for, while)几类。
元编程(Metaprogramming)
元编程是一种让程序视其他程序为数据的编程技术。也即是说一个程序可以被设计为读取、产生、分析、改变其他程序,甚至在运行时改变自己。元编程所用的语言叫做元语言。
最著名的元语言是Lisp。
元编程的子范型:自动式编程、反射式编程
自动式编程(Automatic programming)
自动式编程的定义并不清晰,而且随时间在演变。
反射式编程(Reflection programming)
反射式编程指的是程序具有在运行时检查/修改它自身的结构及行为的能力。
泛型编程(Generic programming)
泛型允许程序员在用强类型语言编写代码时使用一些以后才指定的类型。
Ada、Delphi、C#、Java、Swift、http://VB.NET称之为泛型(generics),Scala和Haskell称之为参数多态(parametric polymorphism),C++称之为模板()
动态语言(Dynamic programming language)
动态语言在运行时可以改变其结构:新的函数、对象甚至代码可以被引进,已有的函数可以被删除或有其他结构上的变化。JavaScript、PHP、Python、Ruby属于动态语言,而C和C++则不属于动态语言。
大部分动态语言都使用动态类型,但也有些不是。
类型系统(Type system)
动态和静态类型检查
对类型的检查可能发生在编译时(静态检查),或者程序运行时(动态检查)进行。动态类型检查经常出现在脚本语言和解释型语言中,编译语言则通常使用静态类型检查。
大部分动态语言都使用动态类型,但也有些不是。
强弱类型
强类型(Strongly typed)和弱类型(Weakly/Loosely typed)并没有非常明确地定义。主要用于描述编程语言对于混入不同数据类型的值进行运算时的处理方式。强类型的语言遇到函数形参和实参的类型不匹配时通常会失败,而弱类型的语言常常会进行隐式的转换(并可能造成不可知的后果)。
类型安全和内存安全
第三种对语言的类型系统进行分类的方法,就是类型运算和转换的安全性。如果它不允许导致不正确的情况的运算或转换,计算机科学就认为该语言是类型安全的。
内存安全则指的是程序不被允许访问没有分配给它的内存,比如:内存安全语言会做数组边界检查。通常来说,类型安全和内存安全是同时存在的。
比如以下例子:
var x:= 5
var y:= “37”
var z:= x + y
上例中的z的值为42,不管编写者有没有这个意图,该语言定义了明确的结果,且程序不会就此崩溃,或将不明定义的值赋给z。就这方面而言,这样的语言就是类型安全的。
再比如:
int x = 5
char y[] = “37”
char* z = x + y
在这个例子中,z将会指向一个超过y地址5个字节的存储器地址,相当于指向y字符串的指针之后的两个空字符之处。这个地址的内容尚未定义,且有可能超出存储器的定址界线,这就是一个类型不安全/内存不安全的语言。
显式声明和隐式暗示
许多静态类型系统,如C和Java,要求变量声明类型:编写者必须以指定类型明确地关系到每一个变量上。其它的,如Haskell,则进行类型推断:编译器根据编写者如何运用这些变量,以草拟出关于这个变量的类型的结论。
例如,给定一个函数f(x,y),它将x和y加起来,编译器可以推断出x和y必须是数字——因为加法仅定义给数字。因此,任何在其它地方以非数值类型(如字符串或链表)作为参数来调用f的话,将会发出一个错误。
在代码中数值、字符串常量以及表达式,经常可以在详细的前后文中暗示类型。例如,一个表达式3.14可暗示浮点数类型;而[1, 2, 3]则可暗示一个整数的链表;通常是一个数组。