aoximin

前言

学习一下go 语言,也不完全是go,几乎是所以语言通用的部分,主要在于巩固一下基础,几乎不会涉及到语法相关的东西。

正文

前置内容

说起语言,很多人喜欢谈论解释型语言和编译型语言,其实对语言谈论编译型还是解释型语言是没有意义的,也不知道当时是谁提出这个概念的,是图啥呢?

c语言也有解释器(http://www.softintegration.com/),难道就可以把c语言定义为既可以是编译型也是解释型语言。

同样python 难道就不能被编译吗? 参考.net平台,可以编译成IL语言。同样c# 也有自己的解析器啊,以此为例的例子很多。

以此为衍生的问题就会有编译型语言就是要编译成可执行的语言,然后另外一个人又出来反驳,编译型可以编译成另外一种中间语言不一定要是直接可执行的。然后解释型的争论又来了,就是解释一句执行,不需要编译,然后另外一个人提出。。。。

至此无休无止,所以忘却这个概念吧,如果有人谈论到这个话题,就尽量不要参和,因为这是一个语言开发者都不会关心的问题,人家只关心解决什么样的问题或者什么样的痛点。

抛开这个话题,那么有一些概念倒是实打实存在的。

编译器:

一个编译器就是一个程序,他可以阅读某一种语言编写的程序,并把程序翻译成一个等价的、用另外一种语言编写的程序。

这里面有几个关键字,等价的、另一种语言。其实这就是一个翻译过程,比如说英文翻译成中文,那么是由一种语言转换为另外一种语言。

那么这个等价的是什么意思呢? 其实它指的是这样的,比如说英文说你好,那么转换中文的意思也应该是你好,这就是等价的,这里而不是完全相等的意思。

为什么不是完全相等的呢?其实是这样的,比如说英文的hello,其实是打招呼的意思,而中文的你好也是打招呼的意思,所以是等价的。

同样在计算机中,翻译过程中执行逻辑可能不一样,比如说我们的c = a - b,我们理解的是a=10,b=20,然后c=-10,编写高级语言的时候一般是这样理解,人类思维嘛。

但是呢,我们知道计算机中二进制没有减法这个概念的,所以呢,计算机的处理逻辑和我们的逻辑不一样,但是其还是会输出-10,这其实就是等价的意思,结果相同,并不是说翻译后逻辑会一致,而是结果会一致。

解释器:

它并不会通过翻译的方式生成目标程序。从用户的角度上看,解释器直接利用用户提供的输入执行源程序中的指定操作。

在用户输入映射成为输入的过程中,由一个编译器产生的机器语言目标程序比一个解释器快很多。然而,解释器的错误诊断通常比编译器更好,因为它是逐条语句执行源程序。

那么为什么我们为啥一般不用c 语言和 c++ 去编写web 响应程序呢?这里一个讲究的是开发效率,还有一个最重要的是影响用户体验的是网速,因为网络之间的时间远大于代码执行时间。

java 语言处理器结合了编译和解释的过程。一个java程序首先被编译成一个称为字节码的中间表达形式。然后由一个虚拟机对得到的字节码加以解释执行。

采用这种方式的好处是可以跨平台,因为是字节码是中间语言,说到底对计算机来说就是文本,可以理解为一篇文章。那么只要有程序能够阅读这篇文章,那么这篇文章就是被运行了。

这样使得java 跨平台了,只要在不同平台安装指定jvm,那么这个jvm相当于阅读器,那么字节码就可以被运行了,实际上是字节码跨平台了。相同的,为啥c# 可以很容易跨平台呢?

其实是因为c# 翻译成IL语言,然后IL语言是在net平台上运行的,加入net 平台能够在linux 上运行,那么IL就可以在linux上运行,同样的间接的c# 就跨平台了。

为了更快的完成输入到输入的处理,有些被称为即时编译器的java 编译器在运行中间程序处理输入的前一刻首先把字节码翻译成机器语言,然后再执行程序。

所以这里又出现了一个即时编译的概念,这个可以参考jit,也就是java 中的即时编译。因为这个非两句话可以说清,所以暂时略过。

那么对于c++ 或者 c 语言,以及后面要介绍的go,他们是如何创建一个可执行程序的呢?是不是直接通过编译器编译成一个可执行程序呢?

当然不是。

首先其要进行预处理,那么预处理干些什么呢? 看下c 语言的预处理:

预处理是C语言的一个重要功能,它由预处理程序负责完成。

当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

C语言提供多种预处理功能,主要处理#开始的预编译指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

可能有些人还是有点蒙,那么直接看其到底做什么吧。

在集成开发环境中,编译,链接是同时完成的。其实,C语言编译器在对源代码编译之前,还需要进一步的处理:预编译。预编译的主要作用如下:

1、将源文件中以”include”格式包含的文件复制到编译的源文件中。

2、用实际值替换用“#define”定义的字符串。

3、根据“#if”后面的条件决定需要编译的代码。

经过预处理后,然后就可以进行编译了,编译后一般目标代码是汇编,因为汇编语言比较容易调试和输出。

其实是这样的,机器语言编写是非常困难的,后面才有了汇编,如果高级语言直接编译成机器语言,可想而知还得把原来汇编的工作量做一遍,还不如转成汇编,然后汇编有自己的汇编器进行处理。

我们在转成汇编后,那么汇编器会把汇编代码转成可重定位的机器代码。注意,这里是可重位的机器代码。

什么是可重位的机器代码?这东西还和操作系统有关。

我们知道在单道程序的时候,用户还在写二进制的时候呢,用户编写的物理地址是写死的。

哎,这就有人问了,我们的程序里面不都是变量吗?哪来的固定的物理地址呢。举一个简单的例子啊,比如说我们程序要调用一个方法。

那么问题来了,它是如何调用的?是啊,凭什么程序能够调用一个方法呢?它怎么这么聪明呢。其实吧,程序调用一个方法,相当于执行一条指令。

这个指令包括了跳转的命令和具体的地址,以及参数。是啊,如果一个方法没有具体的地址,那么程序怎么知道怎么运行呢,所以可重位的机器代码有的方法的物理位置就是确定的了。

这些地址是固定的,但是我们知道程序在运行前是不会知道自己的内存位置在什么地方,因为这是操作系统分配的。

这么这个可重位的机器代码 的意思是这样的,比如可重位的机器代码里面的地址是180,操作系统是从4000开始给这个程序分配物理地址的,那么可重位的机器代码在运行的时候就会给180加上4000,在4180的物理位置进行操作。

当然这是举一个例子了,也不一定是180+4000这样让程序去算,可能是cpu去搞定。

1、静态重定位:即在程序装入内存的过程中完成,是指在程序开始运行前,程序中的各个地址有关的项均已完成重定位,地址变换通常是在装入时一次完成的,以后不再改变,故成为静态重定位。

2、动态重定位:它不是在程序装入内存时完成的,而是CPU每次访问内存时 由动态地址变换机构(硬件)自动进行把相对地址转换为绝对地址。动态重定位需要软件和硬件相互配合完成。

上面这两个就是重定位方式,第二种里面是非常复杂的,只需理解什么是可重位就行。

那么下面就介绍链接器了。

什么是链接器呢?

链接器的功能是,将一个或多个编译器生成的目标文件及库链接为一个可执行文件。

我们知道我们编译器是编译的是我们当前开发的模块,并不包含我们引用的库的编译,因为其早就编译好了。

而且我们有没有发现,其实我们调用库方法的时候,我们看到的是声明而不是源码。这就是让我们编译器可以通过,因为编译器只要检查声明符合即可。

但是我们知道只有申明是无法运行的,还需要链接器将其链接起来。虽然这样说链接器,不太准确,姑且这么理解,后面独立系列补充。

那么编译器又包括了解析器,解析器其包含了词法分析器、语法分析器:

其主要作用是进行语法分析,提取出句子的结构。广义来说输入一般是程序的源码,输出一般是语法树(syntax tree,也叫parse tree等)或抽象语法树(abstract syntax tree,AST)。

进一步剥开来,广义的解析器里一般会有扫描器(scanner,也叫tokenizer或者lexical analyzer,词法分析器),以及狭义的解析器(parser,也叫syntax analyzer,语法分析器)。

扫描器的输入一般是文本,经过词法分析,输出是将文本切割为单词的流。狭义的解析器输入是单词的流,经过语法分析,输出是语法树或者精简过的AST。

编译器还包括了语义分析器、中间代码生成器、代码优化器、代码生成器。

总之,编译器会将我们的code 转换成目标语言,像c++和c这种目标语言就是汇编语言了。

go 语言相关

说到go语言,那么其是和c++ 一样编译 + 汇编 + 链接器可以生成可执行文件的语言。

也没有去看go语言是不是有解释器哈,但是其主流还是编程成二进制的语言哈,那么其效率是要比在解释器上运行是要快一些的。

那么其如果用go语言去开发http交互应用意义应该不大,因为其主要是网络因素,你说运行效率高也就多台机器的问题了。

那么go语言适合做什么呢? 比较出名的就是docker了,docker 这种肯定不会用java去写的,因为jvm 就让人背负不起。

其应该是替代了原先c++ 和c开发的一些功能,因为其具备垃圾回收,开发起来更加迅速,出错率也低上一些,且执行效率又比其他基于虚拟机要高很多。

go 语言开发环境的安装:

https://www.runoob.com/go/go-environment.html

然后安装后,看下其安装的目录,一般叫做goroot哈。

然后看一下go root下面的bin目录下,bin目录下面一般是可执行文件,一般其实是工具类。

那么看下这两个是用来干什么的哈。
运行:

go help

上面说这个go.exe 是一个工具用来管理go源码的。

可以看到有这样一些功能哈。

如果想知道具体,那么可以通过go help 去查询看哈,看具体的,说多了都是误导。

让我奇怪的是有一个gopath的目录。而且还要一个环境配置,后来我就去查了一下。

我也是一名go初学者,不知道理解的对不对。

它上面说用于解决声明问题,会不会是可以引用其目录下的库文件的呢?这里只是猜测,接着往下看。

下面的文件是go build 生成的。

GOPATH环境变量列出了查找Go代码的位置。

然后gopath 可以是多个,在windows 下面用逗号隔开。

如果环境没有设置,然后就是在windows 在默认就是当前用户下的go目录下。

然后go env GOPATH 可以查看当前的GOPATH 。

然后给了我们这个 https://golang.org/wiki/SettingGOPATH 告诉我们去看一下怎么设置哈。

在GOPATH中列出的每个目录必须有一个规定的结构:

src 保存源码,在src目录下确定了路径和可执行文件名。

pkg目录存放已安装的包对象。 在Go树中,每个目标操作系统和 体系结构对有自己的PKG子目录 (包裹/ GOOS_GOARCH)。

bin目录保存已编译的命令。然后就叽叽歪歪举了一些例子。

Each command is named for its source directory, but only
the final element, not the entire path. That is, the
command with source in DIR/src/foo/quux is installed into
DIR/bin/quux, not DIR/bin/foo/quux. The "foo/" prefix is stripped
so that you can add DIR/bin to your PATH to get at the
installed commands. If the GOBIN environment variable is
set, commands are installed to the directory it names instead
of DIR/bin. GOBIN must be an absolute path.

估计就是可以项目直接互相引用的。因为还没用到,那么暂且知道这样一个概念,等用到了估计就清晰了。

持续更新,一周一到两节,学习记录。下一节命名源码文件。

相关文章: