走进Go语言和Go语言入门程序 | 青训营笔记

64 阅读10分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

一、走进Go语言

1.1 Go语言的起源

Go语言有时候被描述为“C类似语言”,或者是“21世纪的C语言”。Go从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。

1.2 Go语言项目

Go语言的这些地方都做的还不错:拥有自动垃圾回收、一个包系统、函数作为一等公民、词法作用域、系统调用接口、只读的UTF8字符串等。

但是Go语言本身只有很少的特性,也不太可能添加太多的特性。例如,它没有隐式的数值转换,没有构造函数和析构函数,没有运算符重载,没有默认参数,也没有继承,没有泛型,没有异常,没有宏,没有函数修饰,更没有线程局部存储。

使用 Go 语言的优势:

  • Go语言有足够的类型系统以避免动态语言中那些粗心的类型错误,但是,Go语言的类型系统相比传统的强类型语言又要简洁很多。在实践中,Go语言简洁的类型系统给程序员带来了更多的安全性和更好的运行时性能。
  • Go语言的内置数据类型和大多数的准库数据结构都经过精心设计而避免显式的初始化或隐式的构造函数,因为很少的内存分配和内存初始化代码被隐藏在库代码中了。Go语言的聚合类型(结构体和数组)可以直接操作它们的元素,只需要更少的存储空间、更少的内存写操作,而且指针操作比其他间接操作的语言也更有效率。
  • Go语言提供了基于CSP的并发特性支持。Go语言的动态栈使得轻量级线程goroutine的初始栈可以很小,因此,创建一个goroutine的代价很小,创建百万级的goroutine完全是可行的。
  • Go语言的标准库(通常被称为语言自带的电池),提供了清晰的构建模块和公共接口,包含I/O操作、文本处理、图像、密码学、网络和分布式应用程序等,并支持许多标准化的文件格式和编解码协议。

二、Go语言入门

2.1 环境配置和编辑器安装

Go官网下载地址:go.dev/

环境配置参考文档:(直接搜索引擎搜索 "Go下载" 即可 )

编辑器下载:Goland(www.jetbrains.com/go/

注意:

  1. 下载 Go 环境压缩包注意与自己本身电脑系统相匹配
  2. 配置 GOPATH 和 GOROOT 不能放置在同一个目录下

2.2 Hello Go

Go是一门编译型语言,Go语言的工具链将源代码及其依赖转换成计算机的机器指令(译注:静态编译)。Go语言提供的工具都通过一个单独的命令go调用,go命令有一系列子命令。最简单的一个子命令就是run。这个命令编译一个或多个以.go结尾的源文件,链接库文件,并运行最终生成的可执行文件。

package main

import "fmt"

func main() {
	fmt.Print("Hello go!")
}
启动方式:
  1. 编辑器直接运行(使用编辑器默认运行产生的 .exe 文件默认放置在Temp文件夹中)

  1. go run main.go

  1. go build main.go(生成一个可执行的二进制文件)

程序解析:
  1. Go语言的代码通过(package)组织,包类似于其它语言里的库(libraries)或者模块(modules)。一个包由位于单个目录下的一个或多个.go源代码文件组成,目录定义包的作用。每个源文件都以一条package声明语句开始,这个例子里就是package main,表示该文件属于哪个包,紧跟着一系列导入(import)的包,之后是存储在这个文件里的程序语句.
  2. Go的标准库提供了100多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如fmt包,就含有格式化输出、接收输入的函数。Println是其中一个基础函数,可以打印以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。
  3. main包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在main里的main 函数 也很特殊,它是整个程序执行时的入口(译注:C系语言差不多都这样)。main函数所做的事情就是程序做的。当然了,main函数一般调用其它包里的函数完成很多工作(如:fmt.Println)。
  4. 必须告诉编译器源文件需要哪些包,这就是跟随在package声明后面的import声明扮演的角色,必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。这项严格要求避免了程序开发过程中引入未使用的包
  5. import声明必须跟在文件的package声明之后。随后,则是组成程序的函数、变量、常量、类型的声明语句(分别由关键字func、var、const、type定义)。这些内容的声明顺序并不重要(译注:最好还是定一下规范)。这个例子的程序已经尽可能短了,只声明了一个函数,其中只调用了一个其他函数。为了节省篇幅,有些时候示例程序会省略package和import声明,但是,这些声明在源代码里有,并且必须得有才能编译。
  6. 一个函数的声明由func关键字、函数名、参数列表、返回值列表(这个例子里的main函数参数列表和返回值都是空的)以及包含在大括号里的函数体组成。
  7. Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响Go代码的正确解析(译注:比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字break、continue、fallthrough或return中的一个、运算符和分隔符++、--、)、]或}中的一个)。
  8. Go语言在代码格式上采取了很强硬的态度。gofmt工具把代码格式化为标准格式(译注:这个格式化工具没有任何可以调整代码格式的参数)go工具中的fmt子命令会对指定包,否则默认为当前目录中所有.go源文件应用gofmt命令,很多文本编辑器都可以配置为保存文件时自动执行gofmt,这样你的源代码总会被恰当地格式化。还有个相关的工具,goimports,可以根据代码需要,自动地添加或删除import声明。

2.3 命令行参数

1. os.Args

os包以跨平台的方式,提供了一些与操作系统交互的函数和变量。程序的命令行参数可从os包的Args变量获取;os包外部使用os.Args访问该变量。

os.Args变量是一个字符串(string)的切片(slice)

os.Args的第一个元素:os.Args[0],是命令本身的名字;其它的元素则是程序启动时传给它的参数。

// prints its command-line arguments.
package main

import (
	"fmt"
	"os"
)

func main() {
	var s, sep string
	for i := 1; i < len(os.Args); i++ {
		s += sep + os.Args[i]
		sep = " "
	}
	fmt.Println(s)
}

2. 注释

注释语句以//开头。对于程序员来说,//之后到行末之间所有的内容都是注释,被编译器忽略。按照惯例,我们在每个包的包声明前添加注释;对于main package,注释包含一句或几句话,从整体角度对程序做个描述。

3. 变量声明

var声明定义了两个string类型的变量s和sep。变量会在声明时直接初始化。如果变量没有显式初始化,则被隐式地赋予其类型的零值(zero value),数值类型是0,字符串类型是空字符串""。这个例子里,声明把s和sep隐式地初始化成空字符串。

对数值类型,Go语言提供了常规的数值和逻辑运算符。而对string类型,+运算符连接字符串

s := ""
var s string
var s = ""
var s string = ""

第一种形式,是一条短变量声明,最简洁,但只能用在函数内部,而不能用于包变量。第二种形式依赖于字符串的默认初始化零值机制,被初始化为""。第三种形式用得很少,除非同时声明多个变量。第四种形式显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。实践中一般使用前两种形式中的某个,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。

4. 循环

循环索引变量i在for循环的第一部分中定义。符号:=是短变量声明(short variable declaration)的一部分,这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句

Go语言只有for循环这一种循环语句。for循环有多种形式

for循环三个部分不需括号包围。大括号强制要求,左大括号必须和post语句在同一行

for initialization; condition; post {
    // zero or more statements
}
//initialization语句是可选的,在循环开始前执行。
//initalization如果存在,必须是一条简单语句(simple statement),即,短变量声明、自增语句、赋值语句或函数调用。
//condition是一个布尔表达式(boolean expression),其值在每次循环迭代开始时计算。如果为true则执行循环体语句。
//post语句在循环体执行结束后执行,之后再次对condition求值。condition值为false时,循环结束。
//for循环的这三个部分每个都可以省略,如果省略initialization和post,分号也可以省略
// a traditional "while" loop
for condition {
    // ...
}
//如果连condition也省略了,这就变成一个无限循环
//尽管如此,还可以用其他方式终止循环,如一条break或return语句
// a traditional infinite loop
for {
    // ...
}
// Echo2 prints its command-line arguments.
package main

import (
    "fmt"
    "os"
)

func main() {
    s, sep := "", ""
    //在某种数据类型的区间(range)上遍历,如字符串或切片
    for _, arg := range os.Args[1:] {
        s += sep + arg
        sep = " "
    }
    fmt.Println(s)
}

每次循环迭代,range产生一对值;索引以及在该索引处的元素值。这个例子不需要索引,但range的语法要求,要处理元素,必须处理索引。一种思路是把索引赋值给一个临时变量(如temp)然后忽略它的值,但Go语言不允许使用无用的局部变量(local variables),因为这会导致编译错误。

Go语言中这种情况的解决方法是用空标识符(blank identifier),即_(也就是下划线)。空标识符可用于在任何语法需要变量名但程序逻辑不需要的时候(如:在循环里)丢弃不需要的循环索引,并保留元素值。

5. 赋值运算符(+=)

每次循环迭代字符串s的内容都会更新。+=连接原字符串、空格和下个参数,产生新字符串,并把它赋值给s。s原来的内容已经不再使用,将在适当时机对它进行垃圾回收。

如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使用strings包的Join函数:

func main() {
    fmt.Println(strings.Join(os.Args[1:], " "))
}