浅谈Go语言(1) - 基础知识

1. 前言

(1) Go的现状

  Go 语言是由 Google 在2009年出品的一门通用型计算机编程语言。作为在近年来快速崛起的编程语言,深受大家的喜爱,许多大厂都已经拥抱 Go 语言,包括以 Java 打天下的阿里巴巴,更别提深爱着 Go 语言的滴滴、今日头条、小米、奇虎 360、京东等明星公司。同时,创业公司也很喜欢 Go 语言,主要因为其入门快、程序库多、运行迅速,很适合快速构建互联网软件产品,比如轻松筹、快手、知乎、探探、美图、猎豹移动等等。

(2) Go的优点

  开发人员在为项目选择语言时,不得不在快速开发和性能之间做出选择。C和C++这类语言提供了很快的执行速度,而 Ruby 和 Python 这类语言则擅长快速开发。Go语言在这两者间架起了桥梁,不仅提供了高性能的语言,同时也让开发更快速。

  可直接编译成机器码,不依赖其他库,glibc的版本有一定要求,部署就是扔一个文件上去就完成了。

  语言层面支持并发,这个就是Go最大的特色,天生的支持并发。Go就是基因里面支持的并发,可以充分的利用多核,很容易的使用并发。

  跨平台编译,如果你写的Go代码不包含cgo,那么就可以做到window系统编译linux的应用,如何做到的呢?Go引用了plan9的代码,这就是不依赖系统的信息。。

(3) 与Go的不解之缘

  我是在2016年才开始接触这门语言的,当时学习搜索引擎,于是用Golang写了个简单的搜索引擎,准备近期梳理好代码后,放到GitHub上,也可以供大家学习参考,作为一个入门还是很合适的。

  之前也通过《Go 并发编程实战》的作者郝林老师18年生病,社区发起了捐款,虽然最后10个小时筹集的10万元他都退了回去,能够支持的就是在极客时间上参与了他的《Go语言核心36讲》学习,感谢他在Go语言上的布道。

(4) 知识体系结构

  整个知识体系结构如下,我会通过学习与实践,带领大家进行系统的了解。

  基础概念:讲述 Go 语言基础中的基础,包括一些基本概念和运作机制。

  数据类型和语句:Go 语言中的数据类型大都是很有特色的,只有理解了它们才能真正玩转 Go 语言。

  程序的测试:单元测试甚至接口测试其实都应该是程序员去做的,且应该受到重视。

  Go 语言开源项目学习:这部分将会讲述如何使用 Go 来实现一个简单的搜索引擎。

2. 工作区和GOPATH

(1) 环境安装

  安装包下载:https://golang.google.cn/

  完成安装后配置以下3个环境变量:

  - GOROOT:Go 语言安装根目录的路径,也就是 GO 语言的安装路径。
  - GOPATH:若干工作区目录的路径。是我们自己定义的工作空间。
  - GOBIN:GO 程序生成的可执行文件(executable file)的路径。

(2) GOPATH 的意义

  可以把 GOPATH 简单理解成 Go 语言的工作目录,它的值是一个目录的路径,也可以是多个目录路径,每个目录都代表 Go 语言的一个工作区(workspace)。

  我们需要利于这些工作区,去放置 Go 语言的源码文件(source file),以及安装(install)后的归档文件(archive file,也就是以“.a”为扩展名的文件)和可执行文件(executable file)。

  Go 语言项目在其生命周期内的所有操作(编码、依赖管理、构建、测试、安装等)基本上都是围绕着 GOPATH 和工作区进行的。

Go 语言源码的组织方式

  Go 语言的源码也是以代码包为基本组织单位的。在文件系统中,这些代码包其实是与目录一一对应的。由于目录可以有子目录,所以代码包也可以有子包。

  代码包的名称一般会与源码文件所在的目录同名。如果不同名,那么在构建、安装的过程中会以代码包名称为准。

  每个代码包都会有导入路径。代码包的导入路径是其他代码在使用该包中的程序实体时,需要引入的路径。在实际使用程序实体之前,我们必须先导入其所在的代码包。具体的方式就是import该代码包的导入路径。就像这样:

import "github.com/labstack/echo"

  Go 语言源码的组织方式就是以环境变量 GOPATH、工作区、src 目录和代码包为主线的。一般情况下,Go 语言的源码文件都需要被存放在环境变量 GOPATH 包含的某个工作区(目录)中的 src 目录下的某个代码包(目录)中。

源码安装后的结果

  源码文件会以代码包的形式组织起来,一个代码包其实就对应一个目录。安装某个代码包而产生的归档文件是与这个代码包同名的。放置它的相对目录就是该代码包的导入路径的直接父级。比如,一个已存在的代码包的导入路径是

github.com/labstack/echo

  如何安装:

go install github.com/labstack/echo

构建和安装 Go 程序的过程

  构建使用命令go build,安装使用命令go install。

  构建和安装代码包的时候都会执行编译、打包等操作,并且,这些操作生成的任何文件都会先被保存到某个临时的目录中。

  如果安装的是库源码文件,那么结果文件会被搬运到它所在工作区的 pkg 目录下的某个子目录中。

  如果安装的是命令源码文件,那么结果文件会被搬运到它所在工作区的 bin 目录中,或者环境变量GOBIN指向的目录中。

  构建和安装的不同之处,以及执行相应命令后得到的结果文件都会出现在哪里。

go get

  与 Go 源码的安装联系紧密的命令。

go get golang.org/x/sync/semaphore

  命令go get会自动从一些主流公用代码仓库(比如 GitHub)下载目标代码包,并把它们安装到环境变量GOPATH包含的第 1 工作区的相应目录中。如果存在环境变量GOBIN,那么仅包含命令源码文件的代码包会被安装到GOBIN指向的那个目录。

3. 命令源码文件

  如果一个源码文件声明属于main包,并且包含一个无参数声明且无结果声明的main函数,那么它就是命令源码文件。 就像下面这段代码:

package main

import "fmt"

func main() {
  fmt.Println("Hello, world!")
}

  例如代码文件名为hello.go,运行改代码:

go run hello.go

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

  基本代码

package main

import (
	// 需在此处添加代码。[1]
	"flag"
	"fmt"
)

var name string

func init() {
	// 需在此处添加代码。[2]
	flag.StringVar(&name, "name", "everyone", "The greeting object.")
}

func main() {
	// 需在此处添加代码。[3]
	flag.Parse()
	fmt.Printf("Hello, %s!\n", name)
}

  执行命令后,对应的输出

go run .\demo.go        
Hello, everyone!

(2) 传入参数,查看参数说明

# 传参
go run .\demo.go -name="world"
Hello, world!

# 查看参数说明
go run .\demo.go --help
Usage of C:\Users\JasonLi\AppData\Local\Temp\go-build409250058\b001\exe\demo.exe:
  -name string
        The greeting object. (default "everyone")
exit status 2

(3) 自定义命令源码文件的参数使用说明

  对变量flag.Usage重新赋值,在main函数体的开始处加入如下代码

flag.Usage = func() {
 fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
 flag.PrintDefaults()
}

  重新执行命令

go run .\demo.go --help
Usage of question:
  -name string
        The greeting object. (default "everyone")
exit status 2

  下面咱们再深入一层,我们在调用flag包中的一些函数(比如StringVar、Parse等等)的时候,实际上是在调用flag.CommandLine变量的对应方法。

// flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)
flag.CommandLine = flag.NewFlagSet("", flag.PanicOnError)
flag.CommandLine.Usage = func() {
    fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
    flag.PrintDefaults()
}

  执行–help后如下:

go run .\demo.go --help
Usage of question:
  -name string
        The greeting object. (default "everyone")
panic: flag: help requested

goroutine 1 [running]:
flag.(*FlagSet).Parse(0xc0000a2060, 0xc000066450, 0x1, 0x1, 0xc000097f50, 0x4058c6)
        C:/Go/src/flag/flag.go:981 +0xff
flag.Parse(...)
        C:/Go/src/flag/flag.go:996
main.main()
        D:/code/go/demo/demo.go:35 +0x7f
exit status 2

  flag.PanicOnErrorflag.ExitOnError都是预定义在flag包中的常量。

  flag.ExitOnError的含义是,告诉命令参数容器,当命令后跟--help或者参数设置的不正确的时候,在打印命令参数使用说明后以状态码2结束当前程序。

  状态码2代表用户错误地使用了命令,flag.PanicOnError与之的区别是在最后抛出 panic

  上述两种情况都会在我们调用flag.Parse函数时被触发。

  调整以上代码,灵活定制命令参数容器,更不会影响到全局变量flag.CommandLine

package main

import (
	"flag"
	"fmt"
	"os"
)

var name string

var cmdLine = flag.NewFlagSet("", flag.PanicOnError)
//var cmdLine = flag.NewFlagSet("question", flag.ExitOnError)

func init() {
	cmdLine.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
		flag.PrintDefaults()
	}
	flag.StringVar(&name, "name", "everyone", "The greeting object.")
}

func main() {
	// flag.Parse()
	cmdLine.Parse(os.Args[1:])

	fmt.Printf("Hello, %s!\n", name)
}

4. 库源码文件

  库源码文件是不能被直接运行的源码文件,它仅用于存放程序实体,这些程序实体可以被其他代码使用

  创建一个文件 demo1.go 代码如下

package main

import (
	"flag"
)

var name string

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

func main() {
	flag.Parse()
	hello(name)
}

hello() 函数写到 demo2.go 中

package main

import "fmt"

func hello(name string) {
	fmt.Printf("Hello, %s!\n", name)
}

  运行代码

go run .\demo1.go .\demo2.go
Hello, everyone!

go run .\demo1.go .\demo2.go -name="world"
Hello, world!

  也可先编译,再运行

go build ./

.\learning36-3.exe
Hello, everyone!

.\learning36-3.exe -name="world"
Hello, world!

代码包声明的基本规则:

  • 第一条规则,同目录下的源码文件的代码包声明语句要一致。要同属于一个代码包,对所有源码文件都适用。如果目录中有命令源码文件,那么其他种类的源码文件也应该声明属于main包。这也是我们能够成功构建和运行它们的前提。

  • 第二条规则,源码文件声明的代码包的名称可以与其所在的目录的名称不同。在针对代码包进行构建时,生成的结果文件的主名称与其父目录的名称一致。对于命令源码文件而言,构建生成的可执行文件的主名称会与其父目录的名称相同。

命令源码文件中的代码拆分到其他代码包

  将demo2.go移动到,mylib/demo2.go中,如何直接编译demo1.go,将mylib/demo2.go当成外部包

package mylib

import "fmt"

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

上面代码做了2个改动:

  • package与父目录保持一致:为了不让该代码包的使用者产生困惑,我们总是应该让声明的包名与其父目录的名称一致。

  • 函数定义为首字母大写:因为名称的首字母为大写的程序实体才可以被当前包外的代码引用,否则它就只能被当前包内的其他代码引用。

go install learning36-3/mylib/

# 能看到,pkg\windows_amd64\learning36-3

  调整demo1.go代码

package main

import (
	"flag"
	"learning36-3/mylib"
)

var name string

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

func main() {
	flag.Parse()
	mylib.Hello(name)
}

  运行代码

go run .\demo1.go
Hello, everyone!

  Reference:

https://golang.google.cn/ref/spec
极客时间 - Go语言核心36讲
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小爱玄策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值