Golang
安装
根据PC架构下载对应版本的安装包。
Go语言官网
配置环境变量
- GOROOT:Go语言安装根目录的路径,也就是GO语言的安装路径。
- GOPATH:若干工作区目录的路径。是我们自己定义的工作空间。
- GOBIN:GO程序生成的可执行文件(executable file)的路径。
GOPATH的作用
GOPATH简单理解成Go语言的工作目录,它的值是一个目录的路径,也可以是多个目录路径,每个目录都代表Go语言的一个工作区(workspace)。
我们需要利于这些工作区,去放置Go语言的源码文件(source file),以及安装(install)后的归档文件(archive file,也就是以“.a”为扩展名的文件)和可执行文件(executable file)。
- 源码文件通常会被放在某个工作区的src子目录下。
- 安装后如果产生了归档文件(以“.a”为扩展名的文件),就会放进该工作区的pkg子目录
- 如果产生了可执行文件,就可能会放进该工作区的bin子目录。
归档文件存放的具体位置和规则
源码文件会以代码包的形式组织起来,一个代码包其实就对应一个目录。
安装某个代码包而产生的归档文件是与这个代码包同名的。
放置它的相对目录就是该代码包的导入路径的直接父级。比如,一个已存在的代码包的导入路径是
github.com/labstack/echo,
那么执行命令
go install github.com/labstack/echo
生成的归档文件的相对目录就是 github.com/labstack, 文件名为echo.a。
某个工作区的src子目录下的源码文件在安装后一般会被放置到当前工作区的pkg子目录下对应的目录中,或者被直接放置到该工作区的bin子目录中。
构建和安装Go程序的过程
构建
构建命令:go build
构建的主要意义在于检查和验证。
构建->库源码文件->临时目录
构建->命令源码文件->源码文件所在目录
安装
安装命令:go install
安装->构建->链接->搬运到指定目录
(如果安装是库源码源文件,那么就在pkg目录的某个子目录中)
(如果安装是命令源码文件,那么就在bin目录的某个子目录中,或GOBIN)
源码文件分类
- 命令源码文件
- 库源码文件
- 测试源码文件
命令源码文件
命令源码文件是程序的运行入口,是每个可独立运行的程序必须拥有的。我们可以通过构建或安装,生成与其对应的可执行文件,后者一般会与该命令源码文件的直接父目录同名。
如果一个源码文件声明属于main
包,并且包含一个无参数声明且无结果声明的main
函数,那么它就是命令源码文件。
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
命令源码文件怎样接收参数
package main
import (
"flag"
"fmt"
"os"
)
var name string
func init() {
//我们在调用flag包中的一些函数(比如StringVar、Parse等等)的时候,实际上是在调用flag.CommandLine变量的对应方法。
//flag.CommandLine相当于默认情况下的命令参数容器。所以,通过对flag.CommandLine重新赋值,我们可以更深层次地定制当前命令源码文件的参数使用说明。
flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)
flag.CommandLine.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
flag.PrintDefaults()
}
// 第1个参数是用于存储该命令参数值的地址,具体到这里就是在前面声明的变量name的地址了,由表达式&name表示。
// 第2个参数是为了指定该命令参数的名称,这里是name。
// 第3个参数是为了指定在未追加该命令参数时的默认值,这里是everyone。
// 至于第4个函数参数,即是该命令参数的简短说明了,这在打印命令说明时会用到。
flag.StringVar(&name, "name", "everyone", "The greeting object.")
}
func main() {
// 自定义命令源码文件的参数使用说明
//flag.Usage = func() {
// fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
// flag.PrintDefaults()
//}
// 函数flag.Parse用于真正解析命令参数,并把它们的值赋给相应的变量。
// 对该函数的调用必须在所有命令参数存储载体的声明(这里是对变量name的声明)
// 和设置(这里是在[2]处对flag.StringVar函数的调用)之后,并且在读取任何命令参数值之前进行。
// 正因为如此,我们最好把flag.Parse()放在main函数的函数体的第一行。
flag.Parse()
fmt.Printf("Hello, %s!\n", name)
}
在运行命令源码文件的时候传入参数,又怎样查看参数的使用说明
// 运行命令源码文件
go run params.go -name="Robert"
Hello, Robert!
//果想查看该命令源码文件的参数说明,可以这样做
F:\Go_Proejct\Demo1\src\ch1\params>go run params.go --help
Usage of question:
-name string
The greeting object. (default "everyone")
exit status 2
库源码文件
库源码文件是不能被直接运行的源码文件,它仅用于存放程序实体,这些程序实体可以被其他代码使用(只要遵从Go语言规范的话)。
程序实体
在Go语言中,程序实体是变量、常量、函数、结构体和接口的统称。
代码包声明的基本规则
- 同目录下的源码文件的代码包声明语句要一致。也就是说,它们要同属于一个代码包。这对于所有源码文件都是适用的。如果目录中有命令源码文件,那么其他种类的源码文件也应该声明属于
main
包。这也是我们能够成功构建和运行它们的前提。 - 源码文件声明的代码包的名称可以与其所在的目录的名称不同。在针对代码包进行构建时,生成的结果文件的主名称与其父目录的名称一致。对于命令源码文件而言,构建生成的可执行文件的主名称会与其父目录的名称相同,这在我前面的回答中也验证过了。
怎样把命令源码文件中的代码拆分到其他代码包
命令源码文件
//Demo1\src\goProject\two\demo4.go
package main
import (
"flag"
"goProject/two/lib"
)
var name string
func init() {
flag.StringVar(&name, "name", "everyone", "The greeting object.")
}
func main() {
flag.Parse()
lib5.Hello(name)
}
源码文件
//Demo1\src\goProject\two\lib\demo4_lib.go
package lib5
import "fmt"
func Hello(name string) {
fmt.Printf("Hello, %s!\n", name)
}
安装命令
F:\Go_Proejct\Demo1\src>go install goProject/two/lib
代码包的导入路径必须与其所在目录的相对路径保持一致吗?
源码文件所在的目录相对于src目录的相对路径就是它的代码包导入路径,而实际使用其程序实体时给定的限定符要与它声明所属的代码包名称对应。
什么样的程序实体才可以被当前包外的代码引用?
名称的首字母为大写的程序实体才可以被当前包外的代码引用(public),否则它就只能被当前包内的其他代码引用(private)。
通过名称,Go语言自然地把程序实体的访问权限划分为了包级私有的和公开的。对于包级私有的程序实体,即使你导入了它所在的代码包也无法引用到它。
对于程序实体,还有其他的访问权限规则吗?
我们可以通过创建internal
代码包让一些程序实体仅仅能被当前模块中的其他代码引用。这被称为Go程序实体的第三种访问权限:模块级私有。
具体规则是,internal
代码包中声明的公开程序实体仅能被该代码包的直接父包及其子包中的代码引用。当然,引用前需要先导入这个internal
包。对于其他代码包,导入该internal
包都是非法的,无法通过编译。
变量
变量声明赋值有几种方式?
- 用关键字 var
类型推断是一种编程语言在编译期自动解释表达式类型的能力。第一种方式更加通用,它可以被用在任何地方。
var num int
var num int = 8
var num = 8 //类型推断
- 短变量定义 :=
基于第一种方式的代码,赋值符号=右边的代码不动,左边只留下num,再把=变成:= ;至于第二种方式所用的短变量声明,实际上就是Go语言的类型推断再加上一点点语法糖。我们只能在函数体内部使用短变量声明。
num := 8
Tips: 函数外的每个语句都必须以关键字开始(var, func等等),
Go语言的类型推断可以带来哪些好处?
- 代码重构。不改变某个程序与外界的任何交互方式和规则,只改变内部实现。
- Go语言是静态类型,一旦在初始化变量时确定了他的类型,之后就不可能再改变。
- 同时又不会给代码维护带来额外负担,更不会损失程序的运行效率。
变量的重声明是什么意思?
这涉及了短变量声明。通过使用它,我们可以对同一个代码块中的变量进行重声明。
- 由于变量的类型在其初始化时就已经确定了,所以对它再次声明时赋予的类型必须与其原本的类型相同,否则会产生编译错误。
- 变量的重声明只可能发生在某一个代码块中。如果与当前的变量重名的是外层代码块中的变量,那么就是另外一种含义了,我在下一篇文章中会讲到。
- 变量的重声明只有在使用短变量声明时才会发生,否则也无法通过编译。如果要在此处声明全新的变量,那么就应该使用包含关键字
var
的声明语句,但是这时就不能与同一个代码块中的任何变量有重名了。 - 被“声明并赋值”的变量必须是多个,并且其中至少有一个是新的变量。这时我们才可以说对其中的旧变量进行了重声明。
var err error
n, err := io.WriteString(os.Stdout, "Hello, everyone!\n")
Tips:常量不能用 := 语法声明。