【基础知识】Go源码文件解析

Go 语言源码的组织方式就是以环境变量 GOPATH、工作区、src 目录和代码包为主线的。环境变量 GOPATH 指向的是一个或多个工作区,每个工作区中都会有以代码包为基本组织形式的源码文件。源码文件又分为三种,即:命令源码文件、库源码文件和测试源码文件,它们都有着不同的用途和编写规则。

命令源码文件

命令源码文件是每个可独立运行的程序的运行入口,属于main包,包含一个无参数声明且无结果声明的main函数,可通过go run命令运行,可接收命令行参数。我们可以通过构建或安装,生成与其对应的可执行文件,这些可执行文件一般会与该命令源码文件的直接父目录同名。

当需要模块化编程时,我们往往会将代码拆分到多个文件,甚至拆分到不同的代码包中。但无论怎样,对于一个独立的程序来说,命令源码文件永远只会也只能有一个。如果有与命令源码文件同包的源码文件,那么它们也应该声明属于main包。同一个代码包中不要存放多个命令源码文件,命令源码文件与库源码文件也不要放在同一代码包中。

  • 命令源码文件构建后生成可执行文件,会被放到源码文件所在的目录即命令执行所在目录中;
  • 命令源码文件安装(安装=构建+链接+把结果文件搬运到指定目录)后生成的可执行文件则会放在它所在工作区的 bin 目录中,或者环境变量GOBIN指向的目录中。

1. 命令源码文件怎样接收参数

Go 语言标准库中有一个代码包专门用于接收和解析命令参数。这个代码包的名字叫 flag。flag.StringVar和flag.String都可以用于接收参数,区别在于后者会直接返回一个已经分配好的用于存储命令参数值的地址。

// flag.StringVar用法
var name string
flag.StringVar(&name, "name", "everyone", "The greeting object.")
/*
第 1 个参数是用于存储该命令参数值的地址,具体到这里就是在前面声明的变量name的地址了,由表达式&name表示。
第 2 个参数是为了指定该命令参数的名称,这里是name。
第 3 个参数是为了指定在未追加该命令参数时的默认值,这里是everyone。
第 4 个函数参数,即是该命令参数的简短说明了,这在打印命令说明时会用到.
*/

// flag.String用法
var name = flag.String("name", "everyone", "The greeting object.")

函数flag.Parse用于真正解析命令参数,并把它们的值赋给相应的变量。对该函数的调用必须在所有命令参数存储载体的声明(这里是对变量name的声明)和设置(这里是在func init中对flag.StringVar函数的调用)之后,并且在读取任何命令参数值之前进行。一般就是放main函数中的第一行。

package main

import (
	"flag"
	"fmt"
)

var name string

func init() {
	flag.StringVar(&name, "name", "everyone", "The greeting object.")
}

func main() {
	flag.Parse()
	fmt.Printf("Hello, %s!\n", name)
}

2. 怎样在运行命令源码文件的时候传入参数,又怎样查看参数的使用说明

如果我们把上述代码存成名为 demo2.go 的文件,那么运行如下命令就可以为参数name传值:

go run demo2.go -name="Robert"

如果想查看该命令源码文件的参数说明,可以这样做:

go run demo2.go --help

库源码文件

库源码文件是不能被直接运行的源码文件,它仅用于存放程序实体,只要遵从 Go 语言规范,这些程序实体可以被其他代码使用。这里的“其他代码”可以与被使用的程序实体在同一个源码文件内,也可以在其他源码文件,甚至其他代码包中。(go库源码文件类似C#里面的dll,不能够直接去运行,只能被调用)。

那么程序实体是什么呢?在 Go 语言中,程序实体是变量、常量、函数、结构体和接口的统称。我们总是会先声明(或者说定义)程序实体,然后再去使用。程序实体的名字被统称为标识符。标识符可以是任何 Unicode 编码可以表示的字母字符、数字以及下划线“_”,但是其首字母不能是数字。

  • 对库源码文件构建的作用在于检查和验证,构建后只生成临时文件,存放在操作系统的临时目录下,开发者一般可以不关心。
  • 对库源码文件安装后生成归档文件即为静态链接库文件(扩展名为“.a”),存放在当前工作区的pkg 目录下的某个子目录中。

1.怎样把命令源码文件中的代码拆分到其他库源码文件?

答案很简单,只需在拆分出来的库源码文件中填入代码包声明语句package main。因为在同一个目录下的源码文件都需要被声明为属于同一个代码包。如果该目录下有一个命令源码文件,那么为了让同在一个目录下的文件都通过编译,其他源码文件应该也声明属于main包。

注意,源码文件声明的代码包的名称可以与其所在的目录的名称不同。在针对代码包进行构建时,生成的结果文件的主名称与其父目录的名称一致。对于命令源码文件而言,构建生成的可执行文件的主名称会与其父目录的名称相同。
例如,把 demo4.go 和 demo4_lib.go 都放在了一个相对路径为puzzlers/article3/q1的目录中。构建当前的代码包再运行,可执行文件的名称与直接父目录名称一致。

$ go build puzzlers/article3/q1
$ ./q1            

2.代码包的导入路径总会与其所在目录的相对路径一致吗?

如果库源码文件 demo5_lib.go 所在目录的相对路径是puzzlers/article3/q2/lib,而它却声明自己属于lib5包。在这种情况下,该包的导入路径是puzzlers/article3/q2/lib,还是puzzlers/article3/q2/lib5?

首先,我们在构建或者安装这个代码包的时候,提供给go命令的路径应该是目录的相对路径:go install puzzlers/article3/q2/lib。该命令会成功完成。之后,当前工作区的 pkg 子目录下会产生相应的归档文件,具体的相对路径是:pkg/darwin_amd64/puzzlers/article3/q2/lib.a,可以看到,这里与源码文件所在目录的相对路径是对应的。其中的 darwin_amd64就是之前在讲工作区时提到的平台相关目录。

请记住,源码文件所在的目录相对于 src 目录的相对路径就是它的代码包导入路径,而实际使用其程序实体时给定的限定符(lib.叫做限定符,旨在指明右边的程序实体所在的代码包)要与它声明所属的代码包名称对应。为了避免困惑,强烈建议package的名字跟父级目录保持一致。

3. Go 语言中对于程序实体访问权限的规则

通过名称,Go 语言自然地把程序实体的访问权限划分为了包级私有的和公开的。名称的首字母为大写的程序实体才可以被当前包外的代码引用(公开的),否则它就只能被当前包内的其他代码引用(包级私有)。对于包级私有的程序实体,即使你导入了它所在的代码包也无法引用到它。

在 Go 1.5 及后续版本中,我们可以通过创建internal代码包让一些程序实体仅仅能被当前模块中的其他代码引用。这被称为 Go 程序实体的第三种访问权限:模块级私有。具体规则是,internal代码包中声明的公开程序实体(名称首字母大写)仅能被该代码包的直接父包及其子包中的代码引用

引用前需要先导入这个internal包。对于其他代码包,导入该internal包都是非法的,无法通过编译。

例子:
***/lib/internal/internal.go

package internal

import (
	"fmt"
	"io"
)

func Hello(w io.Writer, name string) {
	fmt.Fprintf(w, "Hello, %s!\n", name)
}

***/lib/demo6_lib.go

package lib // internal代码包的直接父包可以调用其中的公开程序实体

import (
	"os"
	in "puzzlers/article3/q4/lib/internal" 
)

func Hello(name string) {
	in.Hello(os.Stdout, name)
}

***/demo6.go

package main // 对于其他代码包,导入该internal包都是非法的,无法通过编译。

import (
	"flag"
	"puzzlers/article3/q4/lib"
	//in "puzzlers/article3/q4/lib/internal" // 此行无法通过编译。
	//"os"
)

var name string

func init() {
	flag.StringVar(&name, "name", "everyone", "The greeting object.")
}

func main() {
	flag.Parse()
	lib.Hello(name)
	//in.Hello(os.Stdout, name)
}

测试源码文件

对于程序或软件的测试分很多种,比如:单元测试、API 测试、集成测试、灰度测试等等。

其中,单元测试,又称程序员测试。顾名思义,这就是程序员们本该做的自我检查工作之一。Go 语言的缔造者们从一开始就非常重视程序测试,并且为 Go 程序的开发者们提供了丰富的 API 和工具。利用这些 API 和工具,我们可以创建测试源码文件,并为命令源码文件和库源码文件中的程序实体,编写测试用例。

在 Go 语言中,一个测试用例往往会由一个或多个测试函数来代表,不过在大多数情况下,每个测试用例仅用一个测试函数就足够了。测试函数往往用于描述和保障某个程序实体的某方面功能,比如,该功能在正常情况下会因什么样的输入,产生什么样的输出,又比如,该功能会在什么情况下报错或表现异常,等等。

Go 程序测试分为三类,即:功能测试(test)、基准测试(benchmark,也称性能测试),以及示例测试(example)。其中,示例测试严格来讲也是一种功能测试,只不过它更关注程序打印出来的内容。

一般情况下,一个测试源码文件只会针对于某个命令源码文件,或库源码文件(以下简称被测源码文件)做测试,所以我们应该把它们(即测试源码文件和被测源码文件)放在同一个代码包内

测试源码文件的主名称应该以被测源码文件的主名称为前导,并且必须以“_test”为后缀。例如,如果被测源码文件的名称为 demo.go,那么针对它的测试源码文件的名称就应该是 demo_test.go。

每个测试源码文件都必须至少包含一个测试函数。并且,从语法上讲,每个测试源码文件中,都可以包含用来做任何一类测试的测试函数,即使把这三类测试函数都塞进去也没有问题,只要把控好测试函数的分组和数量就可以了。我们可以依据这些测试函数针对的不同程序实体,把它们分成不同的逻辑组,并且,利用注释以及帮助类的变量或函数来做分割。
我们还可以依据被测源码文件中程序实体的先后顺序,来安排测试源码文件中测试函数的顺序。

参考

《Go语言核心36讲》

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
http.c是goahead web服务器的核心文件之一,主要负责处理HTTP请求和响应。以下是http.c文件源码解析: 1. 首先,http.c文件包含了一些必要的头文件,例如<sys/stat.h>和<fcntl.h>等。这些头文件提供了一些系统级别的函数和结构体。 2. 接着,http.c中定义了一些常量、宏和全局变量。例如,HTTP请求方法的枚举类型、HTTP响应状态码的宏定义、HTTP请求头部的结构体等。 3. 然后,http.c中定义了一些函数,这些函数负责处理HTTP请求和响应。例如,processHttpRequest()函数用于处理HTTP请求,sendHttpResponse()函数用于发送HTTP响应,parseFirstLine()函数用于解析HTTP请求的第一行等。 4. 在processHttpRequest()函数中,首先解析HTTP请求的第一行,然后解析HTTP请求头部,最后根据HTTP请求方法调用不同的处理函数。例如,如果是GET方法,就调用processGetRequest()函数处理请求。 5. 在processGetRequest()函数中,首先解析HTTP请求的路径,然后判断该路径是否合法,最后打开文件并发送HTTP响应。如果文件无法打开或者发送失败,就发送相应的错误响应。 6. 在sendHttpResponse()函数中,首先根据HTTP响应状态码设置响应头部,然后将响应头部和主体发送给客户端。 7. 最后,在main()函数中,启动goahead web服务器,并监听指定的端口号,等待客户端连接。 以上就是http.c文件源码解析

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值