探秘Programming Language:解读解释、编译、动态、静态、强弱类型的奥秘
解释、编译
首先,我们编程都是用的高级语言(写机器语言的大牛们除外),计算机不能直接理解高级语言,只能理解和运行机器语言,所以必须要把高级语言 翻译成机器语言,计算机才能运行高级语言所编写的程序。
根本区别
说到翻译,翻译的方式有两种,一个是编译,一个是解释。
两种方式只是翻译的时间 不同,编译型语言生成完整 的文件后才开始执行,解释型语言边翻译边执行。
- 假设厂里来了两个新工人,一个叫编译,另一个叫解释。厂长(程序员)给他们安排了一项任务(需求),并发放了操作说明(源代码)。
- 编译 这名工人的做法是先完整的看一遍操作说明,遇到错别字或者不明白的地方,就去问厂长,直到操作说明最终成为一个没有错别字且他自己完全能够理解的东西。然后他把操作说明的内容理解消化 (编译),并且变成记忆(可执行程序)。之后每次需要完成任务的时候就靠自己的理解和记忆去执行,不再需要操作说明。
- 我们再看解释 这个工人。他拿到操作说明二话不说直接上手,读一条,操作一步,再读一条,操作下一步,如此重复 。就算是操作说明中有错别字或者他看不懂的地方,在没有读到那一条之前他是不知道的,也不影响他进行前面的操作,等真的读到错别字或者不能理解的条目再去问厂长。而且不管他执行这个任务执行了多少次,每次都是需要看着操作说明一步一步执行。
解释型语言
解释型语言编写的程序不需要编译。解释型语言在运行的时候才翻译,比如Python语言,在执行的时候,专门有一个解释器能够将Python语言翻译成机器语言,每个语句都是执行的时候才翻译,解释型语言每执行一次就要翻译一次.
就是说,你在你Windows上准备好Hello.py文件、对应操作系统解释器,无需编译,直接就可以运行,解释器边将Python码翻译成机器码,边交给操作系统执行,运行一遍程序也就翻译了一遍,但下次运行,还要再翻译一遍。
你在你操作系统上编写好了代码,我这边操作系统想要执行,你只需要发给我你的源代码,但是我需要安装这个语言关于我操作系统的解释器,我这边执行,实际上还是又翻译了一遍。
编译型语言
用编译型语言写的程序在执行之前,需要一个专门的编译过程,通过编译系统(不仅仅只是通过编译器,编译器只是编译系统的一部分,还要依赖于操作系统)把高级语言翻译成机器语言(下图为c语言编译过程),打包成的可执行文件在该操作系统上可直接运行。
也就是说,你在Windows系统上先准备好对应的hello.c源代码、对应编译器,编译生成后的hello.exe文件拷贝到我的Windows电脑上,我的电脑不需要装编译器就可以直接运行hello.exe文件。
动态、静态
有三个名词容易混淆:
Dynamic Programming Language (动态语言或动态编程语言 )
Dynamically Typed Language (动态类型语言 )
Statically Typed Language (静态类型语言)
动态语言
准确地说,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除或在结构上发生变化。比如以下Python的类里的函数被动态的改变了
python
class Student:
def __init__(self, name):
self.name = name
def self_introduce(self):
print(f'I am {self.name}.')
def new_self_introduce(self):
print(f'My name is {self.name}. Nice to meet you!')
a = Student('xiaoming')
a.self_introduce()
# I am xiaoming.
Student.self_introduce = new_self_introduce
a.self_introduce()
# My name is xiaoming. Nice to meet you!
b = Student('xiaohong')
b.self_introduce()
# My name is xiaohong. Nice to meet you!
动态类型语言
变量的类型在执行期间才能确定,以下Python程序先前并未对str变量进行类型定义,但可以直接进行赋值,或者说,变量的类型在执行期间由赋的值决定,变量的类型可以改变的。
ini
str=1
str="world"
上面的代码再Python语言中完全合法(多次赋值,但没有一次定义),但类似的语句在Java、C/C++中不被允许
静态类型语言
变量必须先定义才能赋值、使用,且类型确定后只能赋相应类型的值(其他类型想要对这个类型进行赋值,需要经过显、隐转换)。
注意 :变量的定义 和赋值是不一样的
对于静态语言,变量的赋值可以有多次,但定义只能有一次(除了少部分支持变量遮罩的静态语言,比如Rust)
比如C语言的 extern关键字,其可以在头文件中进行定义一个变量,然后具体的*.c引入头文件后可以进行赋值使用
下面Java语言代码是正确的
ini
int a;
a = 10;
a = 10.0;
下面Java语言代码是错误的
ini
a = 100;
下面Java语言代码是错误的
ini
int a = 10;
a = "hello";
以下代码是正确的(这是因为Java进行了隐式类型转换)
ini
int a = 10;
char b = 'c';
a = b;
System.out.println(a+"1");
// 99hello
对于动态语言
其实根据能否改变程序结构就区分语言是动态语言还是静态语言是不合理的,一个语言,不同的编译器,不同的实现方式的类别可能就是不相同的。
比如C++与Java,可以通过动态织入,来改变类的结构。
对于动态类型语言
有人可能一开始就没搞清楚定义与赋值的区别,所以可能会说我的 Java/C++/C 定义与赋值是同时的呀,比如下面的代码
ini
int a=10;
其实这条语句是分为两步,分别是,其实真实顺序是
ini
int a;
a=10;
变量是先进行的定义后进行的赋值,只不过这样写太繁琐了,所以做出了简化
而有的人也会有疑问,我使用的语言,比如golang、rust赋值、定义变量时也没指定变量的类型啊
比如以下rust的代码
ini
let a =10;
或者
ini
let a;
a=10;
这是因为编译器进行了类型推断,本质还是还是先定义后赋值
强类型、弱类型
强类型语言
当一个对象从调用函数传递到被调用函数时,其类型必须与被调用函数中声明的类型兼容
现代一般的定义为:一旦变量的类型被确定,就不能转化为其它不兼容类型的语言。实际上所谓的貌似转化,都是通过中间变量来达到,原本的变量的类型肯定是没有变化的。
Python是强类型语言,有人会说
ini
a=10
a=10.0
数据类型改变了,a的类型发生了改变,所以其是弱类型语言
但其实不是,因为请注意,此时的变量a已经不是原来的变量a,原来a指向的那块内存地址的类型并没有发生改变,只是被垃圾回收了,下面的代码能直观的体现出这种微妙
代码中,变量a,b都是常量,一开始都指向内存中的相同位置,但在a赋值或变量后,其指向了其他地方
注意,在Java/C/C++中,会有隐式的类型的转换,如下JAVA程序
ini
char c = 90;
int a = c;
System.out.println(a);
// 90
原因也是跟上述一样,变量c的内存地址并没有发生改变,a指向了另一个变量
还有种情况,在JAVA程序中,整型变量可以与字符串变量进行相加(如下代码),这其实是一种语法糖
ini
int a = 100;
String b = "hello";
System.out.println(a + b);
// 100hello
弱类型语言
一个变量的类型是由其应用上下文确定的。比如语言直接支持字符串和整数可以直接用 + 号搞定。
当然,在支持运算符重载的强类型语言中也能通过外部实现的方式在形式上做到这一点,不过这个是完全不一样的内涵 ,
C/C++是典型的弱类型语言,下面C语言代码中,变量a的类型是int,但是其可被float与char *类型赋值,并且内存地址并没有发生改变
perl
int a;
a=1.2;
printf("%d,%p\n",a,&a);
// 1,000000000061FE1C
a="hello";
printf("%d,%p\n",a,&a);
// 4210695,000000000061FE1C
注意,强类型语言在速度上略逊色于弱类型语言(毕竟多了临时变量的开销),使用弱类型语言可节省很多代码量,有更高的开发效率。而对于构建大型项目,使用强类型语言可能会比使用弱类型更加规范可靠
总结
通过顶层设计探寻编程语言的奥秘,让我们以崭新的视角领略编程的魅力。本文旨在帮助读者理解解释、编译、动态、静态、强弱类型的概念,并为那些渴望深入研究的同学提供进一步的指引。
如果你想更深入地了解编程语言,甚至尝试自己设计一门语言,不妨搜索一下『Programming Language』这门课程,让你的编程之旅开启全新的篇章!让我们共同追寻『构建我的编程语言』这个独特成就的目标吧!
相关链接
# 一文彻底弄懂:解释、编译、动态、静态、动态类型、静态类型、强、弱语言
# Coursera IT神课第二弹:Programming Languages,文能吹牛,武能自制编程语言!
参考来自上述文章,特别鸣谢知识分享,如有侵权,联系删除。