Go 语言是一种强大高效的编程语言,有着高性能、高并发、语法简单、丰富的标准库、完善的工具链、静态链接、快速编译、跨平台垃圾回收等特性,以简单、可靠和高效吸引人。
本篇笔记探讨了 Go 语言的基础语法和常用特性,涵盖了从最基本的 "Hello World" 程序到复杂的进程信息处理等一系列内容。其中包括了变量声明、控制结构(如 if-else 语句和循环)、数据结构(如数组、切片、Map 集合)、函数、以及 Go 中的指针等基本语法。在常用特性部分,介绍了错误处理,字符串操作与格式化,JSON 处理,时间处理,数字解析以及进程信息处理等主题。
1. 基础语法
1.1 Hello World
这段代码涵盖了 Go 语言的一些基本要素。package main
表示这是程序的 main 包,main 包也就是程序的入口包。import "fmt"
导入了 "fmt" 包,这个包主要是用来往屏幕输入输出文本和格式化。func main()
是程序的入口点,fmt.Println()
打印文本到控制台。
go
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
1.2 变量
Go语言中的变量有两种定义的方式。
第一种是可以使用 var
关键字定义,如果在声明变量时进行了显式赋值,那么变量的类型可以被省略。如果没有对声明变量赋值,那么必须显式声明变量的类型。
第二种是可以使用 :=
号来声明变量,也不需要指明变量类型。:=
语法是声明并初始化变量的简写方式。
go
package main
import "fmt"
func main() {
var a int // Type must be indicated
a = 10
//var b int = 20
var b = 20 //Type can be omitted
var c = "Hello"
//var d string = "World"
var d = "World" //Type can be omitted
fmt.Println(a, b)
fmt.Println(c, d)
e := 1.11
fmt.Printf("%.2f", e)
}
1.3 控制结构
If-Else 条件语句
Go 语言的 if else
语句与 C/C++ 编程语言类似。不同点在于,Go 语言的 if
语句后面没有括号,并且条件后面必须接大括号换行。
go
age := 18
if age >= 18 {
fmt.Println("你是成年人。")
} else {
fmt.Println("你是未成年人。")
}
循环
Go 语言中有且只有一种循环的写法,就是 for
循环。for
循环后也没有括号,直接跟条件和大括号。如果不写条件就是一个死循环,循环中间可以用 continue
和 break
继续和跳出循环。
css
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
// 使用 for 作为 while 循环
sum := 0
for sum < 10 {
sum += 2
}
Switch 语句
Go 语言中的 switch
分支结构也与 C/C++ 编程语言类似,同样的在 switch
后面的变量名不需要加括号。同时,在 C++ 里面,switch case
如果不加 break
的话会然后会继续往下跑完所有的 case
,而在 go 语言里面是不需要加 break
的。
go
day := "星期日"
switch day {
case "星期一", "星期二", "星期三", "星期四", "星期五":
fmt.Println("这是工作日。")
case "星期六", "星期日":
fmt.Println("这是周末。")
default:
fmt.Println("无效的日期。")
}
1.4 数据结构
数组
数组是一种固定大小,存储相同类型元素的集合。对于一个数组,可以很方便的通过索引存取值。但是在实际业务中,因为数组的大小固定,更多使用的是切片。
go
var numbers [5]int
numbers = [5]int{1, 2, 3, 4, 5}
// 简短声明
moreNumbers := [...]int{6, 7, 8, 9, 10}
切片
切片和数组类似,但不同于数组可以任意更改长度。可以通过声明一个未指定大小的数组来定义切片,或者使用 make()
函数来创建切片。当使用 append()
函数时,要将返回值赋值回原数组,因为当切片容量不够进行扩容时,将返回新的切片,指针位置将改变。copy()
函数可以拷贝切片。切片可以通过索引进行切取,但不支持负索引。
go
cars := make([]string, 3)
cars[0] = "小汽车"
cars[1] = "大货车"
cars[2] = "火车"
cars = append(cars, "自行车")
fruits := []string{"苹果", "香蕉", "橙子"}
fruits = append(fruits, "葡萄")
c := make([]string, 4)
copy(c, fruits)
// 切片切取
citrusFruits := fruits[1:3]
fmt.Println(cars, fruits, c, citrusFruits)
Map 集合
Map 是无序的键值对集合,所以我们可以像迭代数组和切片那样迭代它。但因为它是无序的,遍历 Map 时返回的键值对的顺序是不确定的。在获取 Map 的值时,如果键不存在,将返回该类型的零值。可以使用内建函数 make()
或使用 map
关键字来定义 Map。
go
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m["one"])
ages := map[string]int{
"小明": 30,
"小红": 28,
"小花": 22,
}
fmt.Println(ages["小明"])
1.5 使用 range
进行迭代
range
关键字用于遍历数组、切片、映射和字符串,可以使得代码更加简洁。当 range
遍历数组时会返回两个值,第一个是索引,第二个是对应位置的值,如果不需要索引可以通过下划线忽略。
perl
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Printf("索引:%d,数值:%d\n", index, value)
}
for key, value := range ages {
fmt.Printf("姓名:%s,年龄:%d\n", key, value)
}
1.6 函数
Go 语言和别的很多语言不同的是它的变量类型是后置的。Go 语言的函数原生支持返回多个值,实际的业务代码中几乎所有函数都会返回两个值,第一个是真正的返回结果,第二个值是错误信息。
css
func add(a, b int) int {
return a + b
}
func main() {
result := add(3, 5)
fmt.Println(result)
}
1.7 指针
Go 语言中也支持指针,但相比 C/C++,指针的操作有限,主要用途就是对于传入参数进行修改。
go
func add2(num *int) {
*num += 2
}
func main() {
num := 10
add2(&num)
fmt.Println(num) // 输出:12
}
1.8 结构体、结构体方法
结构体是一种带类型的字段的集合,可将不同类型的变量组合在一起。构造时需要传入每个字段的初始值,也可以用键值对的方式后续再指定初始值。
结构体方法类似其它语言里的类成员函数,可以直接通过构造的结构体进行调用。
go
type Person struct {
Name string
Age int
}
func (p *Person) changeAge(newAge int) {
p.Age = newAge
}
func main() {
p := Person{Name: "小红", Age: 30}
fmt.Println(p.Name)
var p2 Person
p2.Name = "小明"
p2.Age = 33
p2.changeAge(35)
fmt.Println(p2)
}
2. 常用特性
2.1 错误处理
Go 语言鼓励显式地处理错误,使用一个单独的返回值来传递错误,并且能用简单的 if else
来处理错误。在函数的返回值类型里的后面加一个 error, 就代表这个函数可能会返回错误。那么在函数实现的时候,就需要同时 return 两个值,如果出现错误的话,那么就可以 return 一个 nil 和一个 error。如果没有出现错误的话,那么就返回原本的结果和一个 nil。
go
// 一个简单的函数,模拟除法操作,并处理除数为零的错误情况
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
func main() {
dividend := 10.0
divisor := 0.0
result, err := divide(dividend, divisor)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Printf("%.2f 除以 %.2f 的结果是 %.2f\n", dividend, divisor, result)
}
2.2 字符串操作、字符串格式化
Go 语言中的 string 标准库中有很多字符串工具函数,比如 contain()
、count()
、index()
、join()
、repeat()
、replace()
。fmt 标准库中也可以通过 fmt.printf()
支持的各种打印格式来输出字符串,例如 %v 可以用来打印任意类型的变量,也可以用 %+v 打印详细结果,%#v 更详细。
go
// Contains() 函数用于检查一个字符串是否包含子串
str := "Hello, Go!"
if strings.Contains(str, "Go") {
fmt.Println("字符串中包含 Go")
} else {
fmt.Println("字符串中不包含 Go")
}
// Count() 函数用于统计子串在字符串中出现的次数
count := strings.Count(str, "o")
fmt.Println("字符串中 'o' 出现的次数:", count)
// Index() 函数用于查找子串在字符串中的位置,若不存在则返回 -1
index := strings.Index(str, "Go")
if index != -1 {
fmt.Printf("子串 'Go' 在字符串中的位置:%d\n", index)
} else {
fmt.Println("子串 'Go' 不存在于字符串中")
}
// Join() 函数用于连接字符串切片,并添加指定分隔符
words := []string{"Hello", "World", "Go"}
joined := strings.Join(words, "-")
fmt.Println("连接后的字符串:", joined)
// Repeat() 函数用于重复字符串 count 次
repeated := strings.Repeat("Go ", 3)
fmt.Println("重复后的字符串:", repeated)
// Replace() 函数用于替换字符串中的子串
replaced := strings.Replace(str, "Go", "Golang", -1)
fmt.Println("替换后的字符串:", replaced)
// fmt.Printf() 函数用于根据指定的格式输出字符串
name := "Alice"
age := 30
fmt.Printf("姓名:%s,年龄:%d\n", name, age)
2.3 JSON
Go语言的 JSON 操作非常简单,对于一个已有的结构体,只要保证每个字段的第一个字母是大写,那么这个结构体就能用 json.marshal()
去序列化,变成一个 JSON 的字符串。序列化之后的字符串也能够用json.unmarshal()
去反序列化到一个空的变量里面。这样默认序列化出来的字符串的风格是大写字母开头,可以在结构体定义变量时,在后面用 json tag 等语法去修改输出时的字段名。
go
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
p := Person{Name: "小红", Age: 30}
// 编码为 JSON
jsonData, err := json.Marshal(p)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println(string(jsonData))
// 从 JSON 解码
var newPerson Person
err = json.Unmarshal(jsonData, &newPerson)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println(newPerson) // 输出:{小红 30}
}
2.4 时间处理
在 Go 语言里面最常用的就是 time.now()
来获取当前时间,用 time.date()
去构造一个时间,还可以用 t1.sub(t2)
让两个时间相减得到差值,t1.UNIX()
得到时间戳。
scss
// 获取当前时间
currentTime := time.Now()
fmt.Println("当前时间:", currentTime)
// 构造一个特定时间:2023年7月29日 12时0分0秒,时区为 UTC
specificTime := time.Date(2023, time.July, 29, 12, 0, 0, 0, time.UTC)
fmt.Println("特定时间:", specificTime)
// 计算两个时间的差值
duration := currentTime.Sub(specificTime)
fmt.Println("当前时间与特定时间的差值:", duration)
// 获取时间戳(秒)
timestamp := currentTime.Unix()
fmt.Println("当前时间的时间戳(秒):", timestamp)
2.5 数字解析
Go 语言中关于字符串和数字类型之间的转换都在 strconv
这个包下。可以使用 ParseInt()
或者 ParseFloat()
来解析一个字符串,Atoi()
把十进制字符串转成数字,itoA()
把数字转成字符串。
css
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f)
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n)
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n)
n, _ = strconv.ParseInt("1000", 16, 64)
fmt.Println(n)
n2, _ := strconv.Atoi("123")
fmt.Println(n2)
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err)
2.6 进程信息
os.Args
是一个字符串切片,包含了当前程序的命令行参数。切片的第一个元素是程序的名称,后续元素是传递给程序的命令行参数。
os.Getenv()
函数用于获取指定环境变量的值。
os.Setenv()
函数用于设置环境变量的值。注意,修改的环境变量仅在当前进程中有效。
exec.Command()
函数用于创建一个执行外部命令的对象。它接受一个可执行文件的名称和参数,并返回一个 *exec.Cmd
类型的对象,用于执行该命令。
CombinedOutput()
方法用于执行命令并返回标准输出和标准错误的组合结果。在本代码中,我们使用 exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
执行 grep
命令来搜索文件 /etc/hosts
中包含 127.0.0.1
的行,并将输出保存在 buf
中。
panic()
函数用于如果执行命令时出现了错误,进行中断程序执行,并将错误信息打印出来。
lua
fmt.Println(os.Args)
fmt.Println(os.Getenv("PATH"))
fmt.Println(os.Setenv("AA", "BB"))
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf))
总结
通过学习 Go 语言的基础语法和常用特性,可以更深入地理解这种语言的强大之处和实用之处。本笔记从基本语法到常用特性,全面介绍了 Go 语言的各个方面。基本语法部分包括变量、控制结构、数据结构、函数和指针等主要构成部分。常用特性部分则涵盖了错误处理、字符串操作和格式化、JSON 处理、时间处理、数字解析和进程信息处理等实用特性。
通过这些主题的深入学习,可以更好地理解和应用 Go 语言,从而有效地编写和维护Go代码。