Go语言基础入门 | 青训营笔记

80 阅读15分钟

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


1. Go 语言结构

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释
package main
//定义包名
import "fmt"
//导入包文件
func main() {
//func main() 是主函数,是一个可执行文件必须包含的,另外 { 不能单独放一行
   fmt.Println("Hello, World!")
}

2. Go 语言基础语法

2.1 Go标记

Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。

2.2 行分隔符

在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。

2.3 字符串连接

Go 语言的字符串连接可以通过 + 实现:

2.4 关键字

下面列举了 Go 代码中会使用到的 25 个关键字或保留字:

关键字
breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

2.5 格式化字符串

Go 语言中使用 fmt.Sprintf 或 fmt.Printf 格式化字符串并赋值给新串:

  • Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
  • Printf 根据格式化参数生成格式化的字符串并写入标准输出。
%v:可以表示字符串、数组、结构体
%+v:更加详细的结构
%#v:进一步详细
%.2f:保留两位小数的浮点数

3. Go 语言变量

Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。

声明变量的一般形式是使用 var 关键字:

var identifier type

可以一次声明多个变量:

var identifier1, identifier2 type

3.1 变量声明

第一种,指定变量类型,如果没有初始化,则变量默认为零值

零值就是变量没有做初始化时系统默认设置的值。

package main
import "fmt"
func main() {
​
    // 声明一个变量并初始化
    var a = "Flyone"
    fmt.Println(a)
​
    // 没有初始化就为零值
    var b int
    fmt.Println(b)
​
    // bool 零值为 false
    var c bool
    fmt.Println(c)
}

输出结果为:

Flyone
0 
false

不同数据类型零值不同

  • 数值类型(包括complex64/128)为 0

  • 布尔类型为 false

  • 字符串为 ""(空字符串)

  • 以下几种类型为 nil:

    • var a *int
    • var a []int
    • var a map[string] int
    • var a chan int
    • var a func(string) int
    • var a error //error是接口

第二种,根据值自行判定变量类型。

var v_name = value

第三种,如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误,格式:  v_name := value

例如:

var intVal int 
intVal :=1 // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明
复制代码

直接使用下面的语句即可:

intVal := 1 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句
复制代码

intVal := 1 相等于:

var intVal int 
intVal =1 

3.2 多变量声明

//类型相同多个变量, 非全局变量 
var vname1, vname2, vname3 type 
vname1, vname2, vname3 = v1, v2, v3 

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断 

vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误 ​ ​ 

// 这种因式分解关键字的写法一般用于声明全局变量 
var (   
    vname1 v_type1    
    vname2 v_type2
    )

3.3 值类型和引用类型

所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值

当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝

你可以通过 &i 来获取变量 i 的内存地址,例如:0xf840000040(每次的地址都可能不一样)。

值类型变量的值存储在堆中。

3.4 匿名变量

空白标识符_也被用于抛弃值,如值 5 在 :_ , b = 5, 7 中被抛弃。

_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func 函数同时得到:val, err = Func(var1)


3.5 iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值:

const (
    a = iota
    b = iota
    c = iota
)

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:

const (
    a = iota
    b = iota
    c = iota
)

iota 用法

package main
​
import "fmt"func main() {
    const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   //独立值,iota += 1
            e          //"ha"   iota += 1
            f = 100    //iota +=1
            g          //100  iota +=1
            h = iota   //7,恢复计数
            i          //8
    )
    fmt.Println(a,b,c,d,e,f,g,h,i)
}

以上实例运行结果为:

0 1 2 ha ha 100 100 7 8

4. Go 语言条件语句

语句描述
if 语句if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
if...else 语句if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
if 嵌套语句你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。
switch 语句switch 语句用于基于不同条件执行不同动作。
select 语句select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

注意:Go 没有三目运算符,所以不支持  ? :  形式的条件判断。


4.1 if 语句

与java等语言不同,if 后面没有 ()

if 布尔表达式 {
    //语句
}

4.2 if...else 语句

if 布尔表达式 {
    //语句
    
} else {
    //语句
    
}

4.3 if 嵌套语句

if 布尔表达式 {
    if 布尔表达式{
        
    }
}

4.4 switch 语句

  1. switch 语句中的变量可以是任何类型,可以是一个常量或者一个变量,在每个case后面可以跟多个值,用逗号隔开
  2. Go中的switch语句也支持在条件判断语句前执行简单的语句,这样就可以在一个switch语句中处理多个条件
//switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,
//如果我们需要执行后面的 case,可以使用 fallthrough 。
switch var1 {
    case var1:
        ...
    case var2:
        ...
    default:
        ...
}
//Type Switch
switch x.(type) {
    case type:
        statement(s);
    case type:
        statement(s);
    default(可选):
        statement(s);
}

4.5 select 语句

select 是 Go 中的一个控制结构,类似于 switch 语句。

select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。

select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。

如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。

select {
  case <- channel1:
    // 执行的代码
  case value := <- channel2:
    // 执行的代码
  case channel3 <- value:
    // 执行的代码
​
    // 你可以定义任意数量的 case
​
  default:
    // 所有通道都没有准备好,执行的代码
}

5. Go 语言数组

Go 语言提供了数组类型的数据结构。

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。


声明数组

Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:

var variable_name [SIZE] variable_type

以上为一维数组的定义方式。例如以下定义了数组 balance 长度为 10 类型为 float32:

var balance [10] float32

6. Go 语言切片(Slice)

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。


6.1 定义切片

你可以声明一个未指定大小的数组来定义切片:

var identifier []type

切片不需要说明长度。

或使用 make()  函数来创建切片:

var slice1 []type = make([]type, len)
​
也可以简写为
​
slice1 := make([]type, len)

也可以指定容量,其中 capacity 为可选参数。

make([]T, length, capacity)

6.2 切片初始化

s :=[] int {1,2,3 } 

直接初始化切片,  []  表示是切片类型,  {1,2,3}  初始化值依次是 1,2,3,其 cap=len=3

s := arr[:] 

初始化切片 s,是数组 arr 的引用。

s := arr[startIndex:endIndex] 

将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。

s := arr[startIndex:] 

默认 endIndex 时将表示一直到arr的最后一个元素。

s := arr[:endIndex] 

默认 startIndex 时将表示从 arr 的第一个元素开始。

s1 := s[startIndex:endIndex] 

通过切片 s 初始化切片 s1。

s :=make([]int,len,cap) 

通过内置函数 make()  初始化切片s,  []int 标识为其元素类型为 int 的切片。


6.3 len() 和 cap() 函数

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。


6.4 append() 和 copy() 函数

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。


7. Go 语言范围(Range)

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

for key, value := range oldMap {
    newMap[key] = value
}

以上代码中的 key 和 value 是可以省略。

如果只想读取 key,格式如下:

for key := range oldMap

或者这样:

for key, _ := range oldMap

如果只想读取 value,格式如下:

for _, value := range oldMap

遍历简单的数组,2%d 的结果为索引对应的次方数

package main
​
import "fmt"var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
​
func main() {
   for i, v := range pow {
      fmt.Printf("2**%d = %d\n", i, v)
   }
}

以上实例运行输出结果为:

2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128

for 循环的 range 格式可以省略 key 和 value,如下实例:

package main
import "fmt"func main() {
    map1 := make(map[int]float32)
    map1[1] = 1.0
    map1[2] = 2.0
    map1[3] = 3.0
    map1[4] = 4.0
   
    // 读取 key 和 value
    for key, value := range map1 {
      fmt.Printf("key is: %d - value is: %f\n", key, value)
    }
​
    // 读取 key
    for key := range map1 {
      fmt.Printf("key is: %d\n", key)
    }
​
    // 读取 value
    for _, value := range map1 {
      fmt.Printf("value is: %f\n", value)
    }
}

8. Go 语言Map(集合)

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。


8.1 定义 Map

可以使用内建函数 make 也可以使用 map 关键字来定义 Map:

/* 声明变量,默认 map 是 nil */
var mapName map[type1]type2
​
/* 使用 make 函数 */
maName := make(map[type1]type2)

如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对


8.2 delete() 函数

delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。


9. Go 语言指针

我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

以下实例演示了变量在内存中地址:

package main
​
import "fmt"func main() {
   var a int = 10  
​
   fmt.Printf("变量的地址: %x\n", &a  )
}

9.1 如何使用指针

指针使用流程:

  • 定义指针变量。
  • 为指针变量赋值。
  • 访问指针变量中指向地址的值。

在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

package main
​
import "fmt"func main() {
   var a int= 20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */
​
   ip = &a  /* 指针变量的存储地址 */
​
   fmt.Printf("a 变量的地址是: %x\n", &a  )
​
   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
​
   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip )
}

以上实例执行输出结果为:

a 变量的地址是: 20818a220
ip 变量储存的指针地址: 20818a220
*ip 变量的值: 20

9.2 Go 空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。


10. Go 语言结构体

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:

  • Title :标题
  • Author : 作者
  • Subject:学科
  • ID:书籍ID

10.1 定义结构体

结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:

type name struct {
   member 1
   member 2
   ...
   member n
}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

name := structType {value1, value2...valuen}
或
name := structType { key1: value1, key2: value2..., keyn: value n}

10.2 访问结构体成员

如果要访问结构体成员,需要使用点号 . 操作符,格式为:

结构体.成员名


11. Go 错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

error类型是一个接口类型,这是它的定义:

type error interface {
    Error() string
}

我们可以在编码中通过实现 error 接口类型来生成错误信息。

函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}

在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:

result, err:= Sqrt(-1)
​
if err != nil {
   fmt.Println(err)
}

完整示例

package main
​
import (
    "fmt"
)
​
// 定义一个 DivideError 结构
type DivideError struct {
    dividee int
    divider int
}
​
// 实现 `error` 接口
func (de *DivideError) Error() string {
    strFormat := `
    Cannot proceed, the divider is zero.
    dividee: %d
    divider: 0
`
    return fmt.Sprintf(strFormat, de.dividee)
}
​
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
            dData := DivideError{
                    dividee: varDividee,
                    divider: varDivider,
            }
            errorMsg = dData.Error()
            return
    } else {
            return varDividee / varDivider, ""
    }
​
}
​
func main() {
​
    // 正常情况
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
            fmt.Println("100/10 = ", result)
    }
    // 当除数为零的时候会返回错误信息
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
            fmt.Println("errorMsg is: ", errorMsg)
    }
​
}

执行以上程序,输出结果为:

100/10 =  10
errorMsg is:  
    Cannot proceed, the divider is zero.
    dividee: 100
    divider: 0

12. Go 字符串操作

使用前需要import

import strings


方法

  1. strings.Contains(a,"ll"):判断字符串是否包含另一个字符串
  2. strings.Count(a,"l"):字符串计数
  3. strings.HasPrefix(a,"he"):是否以字符串开头
  4. strings.HasSuffix(a,"llo"):是否以字符串结尾
  5. strings.Index(a,"ll"):查找某个字符串的位置
  6. strings.Join([]string{"he","llo"},"-"]):连接多个字符串
  7. strings.Repeat(a,2):重复多个字符串
  8. strings.Replace(a,"e","E",-1):替换字符串
  9. strings.Split("a-b-c","-"):分割字符串
  10. strings.Tolower(a):小写字符串a
  11. strings.ToUpper(a):大写字符串a

13. Go JSON处理

使用前需要import

import encoding/json


方法

对于一个结构体,如果要JSON处理,只需要保证结构体的每个字段的首字母大写,就能使用下面方法

json.Marshal(a):序列化

打印时需要强制转换为string,否则打印的是16进制的编码

json.Unmarshal(buf,&b):反序列化

14. Go 时间处理

使用前需要import

import time


方法

  1. time.Now():获取当前时间
  2. time.Date(2022,3,27,1,25,36,0,time.UTC):构造一个时区的时间 ——2022-03-27 01:25:36 +0000 UTC
  3. t.Year():获取年份
  4. t.Mouth():获取月份
  5. t.Day():获取日
  6. t.Hour():获取小时
  7. t.Minute():获取分钟
  8. t.Second():获取秒
  9. t.Format(字符串示例):格式化时间
  10. t2.Sub(t):对两个时间做减法运算,得到的是时间段
  11. time.Parse(字符串示例,t):格式化时间
  12. t.Unix():获取时间戳

package main

import (
	"encoding/json"
	"fmt"
)
type userInfo struct {
	Name  string
	Age   int `json:"age"`
	Hobby []string
}

func main() {
	s := userInfo{Name: "Flyone", Age: 20, Hobby: []string{"Golang", "Java"}}
        //通过json.Marshal序列化
	buf, err := json.Marshal(s)
	if err != nil {
		panic(err)
	}
        //序列化后变成but数组,需要转换成字符串,不然会打印出16进制的编码
	fmt.Println(buf)
        //通过转换成字符串即可输出我们的内容
	fmt.Println(string(buf))//{"Name":"Flyone","age":20,"Hobby":["Golang","Java"]}

	buf, err = json.MarshalIndent(s, "", "\t")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(buf))

	var b userInfo
        //反序列化到空变量b中
	err = json.Unmarshal(buf, &b)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%#v\n", b) 
        //输出结果
        //main.userInfo{Name:"Flyone", Age:20, Hobby:[]string{"Golang","Java"}}
}

15. Go 数字解析

使用前需要import

import strconv


方法

  1. strconv.ParseFloat("1.234",64):字符串转浮点数
  2. strconv.ParseInt("111",10,64):字符串转整型,第二个参数为进制(0-自动推测,10-10进制...),64位精度的整型
  3. strconv.ParseInt("0x1000",0,64):4096
  4. strconv.Atoi("123"):迅速转换,可以字符串和数字互相转换,参数不合法会返回错误

16. Go 进程信息

使用前需要import

import (
    os
    os/exec
   )

方法

  1. os.Args:进度执行的一些命令行参数
  2. os.Getenv("PATH"):获取环境变量
  3. os.Setenv("AA","BB"):写入环境变量
  4. exec.Command("grep","127.0.0.1","/etc/hosts").CombinedOutput:快速启动子进程并且获取其输入输出