Go 语言上手 - 基础语言(1)| 青训营笔记

391 阅读17分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

一、GO语言的优点

1.  高性能、高并发

2.  语言简单、学习曲线平缓(Go语言在C语言的基础上取其精华,弃其糟粕,简化了许多C语言的风格。并且去掉了多余的冗余括号,强制了代码风格)

3.  丰富的标准库 (其中值得称道的是 net/http,仅须简单几条语句就能实现一个高性能 Web Server)

4.  完善的工具链

5.  静态链接

6.  快速编译(编译速度优于c++与java)

7.  跨平台

8.  垃圾回收(相比C++语言不需要手动进行垃圾回收)

二、 Go语言的基础语法

1. 关键字与预留标识符

下面列举了 Go 代码中会使用到的 25 个关键字和36 个预定义标识符:

  1. 关键字:

    第一列第二列第三列第四列第五列
    breakdefaultfuncinterfaceselect
    casedefergomapstruct
    chanelsegotopackageswitch
    constfallthroughifrangetype
    continueforimportreturnvar
  2. 预定义标识符:

    第一列第二列第三列第四列第五列第六列
    appendboolbytecapclosecomplex
    copyFALSETRUEfloat32float64new
    intint8int16int32int64iota
    uintuint8uint16uint32uint64len
    stringprintprintlnmakerecoverreal
    complex64complex128imagpanicuintptrnil

2.变量 与 常量

在Go语言当中变量的声明一共有两种方式:

1.指定变量类型,并且可进行多变量赋值,如果没有初始化,则变量默认为零值。

 package main
 import (
    "fmt"
 )
 func main() {
      var a int
      var b float64
      var c string
      var d bool
      fmt.Println(a,b,c,d)
      var e int = 3
      var f,g string = "hello world","goland"
      fmt.Println(e,f,g)
 }

通过上述代码则会输出

0 0  false
3 hello world goland

当未指定变量类型,编译器自行推到变量类型。

 func main() {
      var a = 1
      var b = 1.5
      var c = "hello world"
      var d = true
      fmt.Println(a,b,c,d)
 }

上述代码则会输出

1 1.5 hello world true

2.短变量声明并初始化

在go语言中var还有一种精简的写法就是 := 进行赋值初始化 例如:

a := 10
b := "hello world"
c,d := 1,2

:=使用的前提是必须在函数块中使用,并且左侧必须存在变量没有被初始化定义过

package main
import (
    "fmt"
)
func main() {
    var a int = 1
    b := 2
    c := 3
    a,d := 2,4
    fmt.Println(a,b,c,d)
}

该代码输出为

2 2 3 4

3. 常量

对于Go语言当中的常量,我们可以通过 const 关键字定义常量时,可以指定常量类型,也可以省略(底层会自动推导),常见的常量定义方式如下:

const Pi float64 = 3.14159265358979323846 
const zero = 0.0                             // 无类型浮点常量 
const (                                      // 通过一个 const 关键字定义多个常量,和 var 类似
    size int64 = 1024
    eof = -1                                 // 无类型整型常量 
) 
const u, v float32 = 0, 3                    // u = 0.0, v = 3.0,常量的多重赋值 
const a, b, c = 3, 4, "hello"                  // a = 3, b = 4, c = "hello", 无类型整型和字符串常量

并且常量的赋值是一个编译期行为,所以右值不能出现任何需要运行后才能得出结果的表达式,比如试图以如下方式定义常量就会导致编译错误

func GetNumber() int {
    return 314
}
const num = GetNumber()

3.if语句与swtich语句

在Go语言当中判定语句中去掉了多余的冗余括号,强制了代码风格

因此在Go语言中,if语句与switch语句后面的判定条件或条件变量是不会携带括号

但是由于Go语言强制了代码风格,if判定条件后面必须紧随大括号,不能将其全部编写在同一行 如下代码所示

if 7%2 == 0 {
    fmt.Println("7 is even")
} else {
    fmt.Println("7 is odd")
}

swtich语句在Go语言中功能比其他语言中的swtich语句功能更加强大,

Go语言中的switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。 并且可以使用任意的字符类型作为条件变量,可以取代任意的if..else语句。

如代码展示可取代相应的if..else语句

package main
import (
    "fmt"
)
func main() {
    t := 12
    switch {               
    case t < 12:                               //选择t<12时执行下面操作
        fmt.Println("It's before noon")
    default:                                   //除了上面case内条件外的其他所有条件
        fmt.Println("It's after noon")
    }
}

4.for循环

在Go语言中的for循环中循环条件也一样去掉了多余的冗余括号

for循环的代码风格格式如下

for i := 0 ;i < 10 ; i++ {
    //代码块..
} 

for循环当中的三个条件都可以进行省略

省略变量定义的for循环

i := 1
for ;i < 10 ; i++{
    //代码块..
}

省略自增与定义变量的for循环

i := 1
for i < 10{
    //代码块..
}

如果将三个全部省略则可以实现一个无限循环的for循环如下

for {
    //代码块..
}

5.数组与切片

Go语言数组

在Go语言中声明数组为以下格式

var 数组名 [数组长度] 数组类型
如:
var a [10]int          //为长度为10的int类型的一维数组
var b [10][10]int      //为长度为10,宽度为10的二维数组

数组的声明也同样适用于:=进行声明

数组的初始化如果未赋值则默认为零值,并且可以直接通过下标进行赋值

在Go语言中,可以通过len(数组名)来进行获取数组的长度来进行for循环遍历

var a [5]int
a[4] = 100
b := [5]int{1, 2, 3, 4, 5}
for i:= 0 ; i < len(a);i++{
    fmt.Println(a[i])
}
fmt.Println(b)

输出如下:

0,0,0,0,100
[1 2 3 4 5]

Go语言切片(slice)

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

切片可以不声明长度大小,并且可以使用make()函数进行创建切片

可以通过copy()函数进行切片的拷贝与使用append()函数进行对切片追加新元素并且当我们使用append()函数进行追加新元素时,必须重新赋值给原切片

s := make([]string, 3)            //定义了长度位的切片
s[0] = "a"                        //对该下标的切片进行赋值
s[1] = "b"
s[2] = "c"
s = append(s, "d")                //通过append进行对切片进行追加单个值
s = append(s, "e", "f")           //通过append同时追加多个值
fmt.Println(s)

c := make([]string, len(s))       //创建一个长度为与切片s一样的切片
copy(c, s)                        // 通过copy()进行对切片进行复制一份

fmt.Println(s[2:5])               //表示输出切片s的下标为2到下标4的数据
fmt.Println(s[:5])                //表示输出切片s的默认下标为0到4的数据
fmt.Println(s[2:])                //表示输出切片s的下标为2到下标len(s)-1的数据

以上代码输出结果为

[a b c d e f]                     //fmt.Println(s)
[c d e]                           //fmt.Println(s[2:5])
[a b c d e]                       //fmt.Println(s[:5])
[c d e f]                         //fmt.Println(s[2:])

6.Map集合

在Go语言中存在一种数据结构叫做Map,在其他编程语言中可以被叫做哈希或者字典,Map 是一种无序的键值对的集合。

Map 的声明方式一共有以下两种

// 通过声明变量,默认 Map 是 nil 
var Map的变量名 map[Key数据类型]Value数据类型

// 使用 make 函数 
Map的变量名 := make(map[Key数据类型]Value数据类型)

Map添加新元素的值可以直接通过中括号来进行赋值

m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m)                // map[one:1 two:2]

Map可以通过delete()函数进行对key-value值进行删除操作

delete(m, "one")
fmt.Println(m)                // map[two:2]

同样len()函数也适用于Map数据结构来输出长度

fmt.Println(len(m))           // 1

7.range

Go 语言中 range 可以快速进行对数组,切片,集合等数据结构进行迭代遍历,使得代码更加简洁,并且在迭代数组或切片的过程中返回元素的索引和索引对应的值,迭代集合会返回key-value 对

以下为通过for循环使用range对数组进行快速迭代

nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
    sum += num
    if num == 2 {
        fmt.Println("index:", i, "num:", num) // index: 0 num: 2
    }
}

以下为通过for循环使用range对集合Map进行快速迭代

m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
    fmt.Println(k, v) // b 8; a A
}
for k,_ := range m {
    fmt.Println("key", k) // key a; key b
}
for _,v := range m {
    fmt.Println("value", v) // value A; value B
}

如果我们不需要索引、key值或者value的值的时候可以通过使用_来代替

8.指针

在Go语言当中指针的操作十分有限,并且相较于其他语言更加的简单。

func add2(n int) {
    n += 2
}
func main() {
    n := 5
    add2(n)
    fmt.Println(n)  // 5
}

如上代码所示当我们想要实现一个自动返回的自增函数时,如果没有带有指针,则函数使用的是传入数据的拷贝,数据输出的结果依旧为5。因此我们需要指针来帮我们将其指向该数据所处的内存空间,并且对其进行修改 修改后的代码如下

func add2(n int*) {
    *n += 2          // 对内存地址取址进行+2
}
func main() {
    n := 5
    add2(&n)
    fmt.Println(n)   // 7
}

如果我们通过打印&n会发现,打印出来的是n在内存的地址,

func main() {
    n := 5
    fmt.Println(&n)   // 0xc000014088
}

因此&符号表示了对变量的取址操作,而*符号就表示获取地址中的数据。

9.结构体

Go语言我们可以为不同项定义不同的数据类型,类似于其他语言中的自定义类

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

type 结构体名称 struct {
    成员变量名   成员数据类型
    成员变量名   成员数据类型
    .....
}

如果成功定义完结构体,就可以用于变量的声明,如以下代码所示

type user struct {
	name     string
	password string
}
func main() {
	a := user{name: "wang", password: "1024"}    //通过变量名进行赋值
	b := user{"wang", "1024"}                    //省略变量名进行依次赋值
	c := user{name: "wang"}                     
	c.password = "1024"                          //通过访问结构体变量成员进行赋值
	var d user
	d.name = "wang"
	d.password = "1024"
	fmt.Println(a, b, c, d)             // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
}

并且结构体也可以像普通的数据类型一样作为指针的对象在函数中被引用,避免一些大结构体在拷贝中的开销

func checkPassword2(u *user, password string) bool {
	return u.password == password
}

10.函数与结构体函数

函数

在Go语言中,我们通过使用func来定义一个函数

func 函数名( [参数列表] ) [返回类型] {
   函数体
}

其中在Go语言中的函数可以返回多个值,例如创建一个函数表示集合中是否存在key,如果存在返回相应的value值与bool值

func exists(m map[string]string, k string) (v string, ok bool) {
	v, ok = m[k]
	return v, ok
}

并且如果在参数列表中,存在相同类型的值时,可以进行简写

func add(a int, b int) int {    //简写前
    return a + b
}
func add2(a, b int) int {       //简写后
    return a + b
}

结构体函数

为了创造只属于结构体才能调用的函数,类似于类结构体中的方法,我们可以通过对函数func 后添加结构体变量来形成结构体函数,如下代码所示

type user struct {
    name     string
    password string
}
func (u user) checkPassword(password string) bool {
    return u.password == password
}

11.  错误处理

错误处理在go语言里面符合语言习惯的做法就是使用一个单独的返回值来传递错误信息。不同于Java 使用的异常。go语言的处理方式,能够很清晰地知道哪个函数返回了错误,并且能用简单的if else 来处理错误。在函数里面,我们可以在那个函数的返回值类型里面,后面加一个error,就代表这个函数可能会返回错误。

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

type error interface {
    Error() string
}

在函数里面,我们可以在那个函数的返回值类型里面,后面加一个error,就代表这个函数可能会返回错误。

那么在函数实现的时候,return 需要同时 return 两个值,要么就是如果出现错误的话,那么可以return nil和一个error。如果没有的话,那么返回原本的结果和nil.

并且我们可以通过errors.New("")来自行描述错误信息

type user struct {                               //定义一个用户的结构体
    name     string
    password string
}
func findUser(users []user, name string) (v *user, err error) {    //实现查找用户是否存在函数
    for _, u := range users {                    //只获取用户数组中的值
        if u.name == name {           
            return &u, nil                       //如果存在则返回该用户所存在的内存地址与nil
        }
    }
    return nil, errors.New("not found")          //如果没找到则返回nil与错误信息
}

12.  字符串处理与格式化操作

在Go语言的标准库当中strings与fmt包中有着很多常用的字符串操作与字符串格式相关的方法,我们只需要通过以下代码就可以在工程中引入这些工具包

import(
    "fmt"
    "strings"
)

strings包

在strings包中有着各种各样的函数,例如contains()判定一个字符串中是否包含指定字符串,count()计算字符串长度,join连接多个字符串等其他常用函数。

a := "hello"
fmt.Println(strings.Contains(a, "ll"))                // 输出true      判定a字符串中是否存在ll字符串
fmt.Println(strings.Count(a, "l"))                    // 输出2         计算a字符串中l字符的个数
fmt.Println(strings.HasPrefix(a, "he"))               // 输出true      判定a字符串是否以he字符串开头
fmt.Println(strings.HasSuffix(a, "llo"))              // 输出true      判定a字符串是否以llo字符串结尾
fmt.Println(strings.Index(a, "ll"))                   // 输出2         判断ll字符串在a中首次出现的位置
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // 输出he-llo    将he与llo通过-进行连接
fmt.Println(strings.Repeat(a, 2))                     // 输出hellohello //重复字符串a两次
fmt.Println(strings.Replace(a, "e", "E", -1))         // 输出hEllo      //将a中的所有的e替换成E
fmt.Println(strings.Split("a-b-c", "-"))              // 输出[a b c]    //将a-b-c字符串以-作为分隔符拆分
fmt.Println(strings.ToLower(a))                       // 输出hello      //将字符串a全部转为小写
fmt.Println(strings.ToUpper(a))                       // 输出HELLO      //将字符串a全部转为大写
fmt.Println(len(a))                                   // 输出5          //计算字符串a的长度
b := "你好"
fmt.Println(len(b))                                   //输出6      中文在Go语言当中字符长度判断

详细参考 : Go语言strings包常用函数

fmt包

在fmt包里有很多字符串格式相关的方法,比如printf这个类似于c语言里面的printf函数,不同的是在Go语言中有更多更简便的使用方法

type point struct {
    x, y int
}
func main() {
    s := "hello"
    n := 123
    p := point{1, 2}
    fmt.Println(s, n)        // hello 123
    fmt.Println(p)           // {1 2}
    fmt.Printf("s=%v\n", s)  // s=hello
    fmt.Printf("n=%v\n", n)  // n=123
    fmt.Printf("p=%v\n", p)  // p={1 2}
    fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
    fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
}

通过以上代码我们可以发现,当我们需要打印多个不同类型数据时,只需要通过 , 就可以进行多个打印,并且可以直接打印结构体数据

在Go语言当中printf不在区别任意类型变量,只需要用%v就可以打印任意类型。并且可以使用%+v与%#v打印更加详细的数据结果

并且fmt还可以打印省略小数位数的数据通过 %.2f 来打印保留两位的小数

 f := 3.141592653
 fmt.Println(f)          // 3.141592653
 fmt.Printf("%.2f\n", f) // 3.14

13.JSON处理

Go语言当中的JSON操作非常简单,对于一个已有的结构体,我们可以什么都不做,只要保证每个字段的第一个字母是大写,也就是是公开字段,那么这个结构体就能用 JSON.marshaler 去序列化,变成一个 JSON 的字符串

序列化之后的字符串也能够用 JSON.unmarshaler 去反序列化到一个空的变量里面。

这样默认序列化出来的字符串的话,它的风格是大写字母开头,而不是下划线。我们可以在后面用json tag等语法来去修改输出JSON结果里面的字段名

type userInfo struct {
    Name  string
    Age   int `json:"age"`             //通过json tag语法来修改输出JSON结果里面的字段名
    Hobby []string
}
func main() {
	a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
	buf, _ := json.Marshal(a)
	fmt.Println(buf)           // [123 34 78 97...]  序列化后的结果
	fmt.Println(string(buf))   
        // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}   
        // Age通过json tag标签修改后字段名为age
        
	buf, _ = json.MarshalIndent(a, "", "\t")
	fmt.Println(string(buf))
	var b userInfo
	err = json.Unmarshal(buf, &b)  //通过对buf反序列化,并将值赋给变量b
	if err != nil {
		panic(err)
	}
	fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}

14.时间处理

在Go语言里面为了实现对时间的处理我们需要引入我们所需要的 time 包,在 time 包中最常用的就是time.now() 来获取当前时间,然后你也可以用 time.date() 去构造一个带时区的时间。

now := time.Now()
fmt.Println(now)                                         // 输出当前时间
time := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
fmt.Println(t)                                           // 输出构造后的时间 

并且我们可以通过特定的函数对获取的时间更加精确的数据

year := now.Year()               // 年
month := now.Month()             // 月
day := now.Day()                 // 日
hour := now.Hour()               // 小时
minute := now.Minute()           // 分钟
second := now.Second()           // 秒

并且我们还可以通过Sub函数对两个时间进行减法操作,获取两个时间之间的时间差

t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
diff := t2.Sub(t)
fmt.Println(diff)                           // 1h5m0s

最后时间包还带有许多对时间格式化输出的函数例如 time.format(),time.Parse() 等其他函数。并且在某些系统交互时,我们经常会使用到时间戳,所以我们又可以用 time.Unix() 来获取时间戳

15.数字解析

下面我们来学习一下在Go语言里面字符串如何与数字互相转换,关于字符串和数字类型之间的转换,我们都需要通过 strconv 这个包下,这个包是 string convert 这两个单词的缩写.

首先我们可以用 strconv.parseInt() 或者 strconv.parseFloat() 来解析一个字符串.

parseInt()

函数: strconv.parseInt(s string,base int,bitSize int)(i int64, err error)

用法: 函数会判断字符串s是否可转换为数字,通过base来判断转化进制,值为2~36,如果为0,则会根据字符串自动判断,bitSize则是转后输出后的进制.

返回值 : 返回 int 和 error

n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n)                           // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n)                           // 4096

ParseFloat()

函数: strconv.ParseFloat(s string, bitSize int) (f float64, err error)

用法: 函数自行判断能将字符串s转换为浮点数,并且以bitSize作为输出浮点数的位数

返回值 : 返回float64 和 error

f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234

Itoa()

函数 : strconv.Itoa(i int)

用法 : 将int类型的数字转换为string类型

返回值 : 返回 string


16.进程信息

在Go语言里,我们能够用 os.argv() 来得到程序执行的时候指定的命令行参数. 比如我们编译的一个二进制文件后面接abcd来启动,输出就是os.argv会是一个长度为5的slice(切片),

//在终端输入 go run example/20-env/main.go a b c d 进行编译启动
fmt.Println(os.Args)

并且我们可以通过 os.Getenv()与os.Setenv() 进行对环境变量的获取与设置

fmt.Println(os.Getenv("PATH")) //        输出环境变量的路径
fmt.Println(os.Setenv("AA", "BB"))

如果我们需要利用go语言来启动其他可执行文件例如后缀为.exe的文件我们就可以利用os/exec包下面的一个叫作exec.Command()的函数进行调用.

附加链接

【Go 语言原理与实践学习资料】

【Go 语言上手 - 基础语言】