Go语言基础语法入门指南 | 青训营

117 阅读11分钟

Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

它被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。

对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。

本篇笔记是学习青训营《Go 语言基础语法》 课程所记录产生,下面是一些基础语法的笔记。

HelloWorld

最基础的helloworld输出需要三部分:

  • package main代表这个文件属于main包的一部分
  • import导入标准库里面的FMT包,这个包主要用来输入输出字符串、格式化字符串
  • main函数,里面调用fmt.printin输出

运行时直接在终端里执行go run main.go命令即可,如果想编译成二进制的话,用go build构建,再运行 ./main即可。

package main
import (
	"fmt"
)
func main() {
	fmt.Println("hello world")
}

补充 go bulid语法

go build [-o 输出名] [-i] [编译标记] [包名]

go build 所有参数都可以忽略,直到只有go build,这个时候意味着使用当前目录进行编译。

从这里我们也可以推测出,go build本质上需要的是一个路径,让编译器可以找到哪些需要编译的go文件。 packages其实是一个相对路径,是相对于我们定义的GOROOT和GOPATH这两个环境变量的,所以有了packages这个参数后, go build就可以知道哪些需要编译的go文件了。

变量

变量声明有以下两种方法:

  • var name string = "",这种会自动去推导变量的类型。
  • 变量:=值

常量的话就是把var改成const。go语言里面的常量没有确定的类型,会根据使用的上下文来自动确定类型

循环for语句

go里面没有while循坏、do while循环,只有唯一的一种for循环。

循环里面可以用break或continue来跳出或者继续循环。

	for n := 0; n < 5; n++ {
		if n%2 == 0 {
			continue
		}
		fmt.Println(n)
	}

if else 语句

if 后面没有括号,类似C,如果写括号的话,那么在保存时编辑器会自动去掉。

if 8%4 == 0 {
		fmt.Println("8 is divisible by 4")
	}

switch语句

与C或者C+=类似,同样在swith后面的变量名,不需要括号。 不同的是,在c++里面,swith case如果不显示加break的话会继续跑完所有的case,在go里面不需要加break。

可以在switch后面不加任何变量,然后在case里面写条件分支。

if 8%4 == 0 {
		fmt.Println("8 is divisible by 4")
	}

array数组

对于一个数组,可以很方便地取特定索引的值或者往特定索引取存储值,也可以直接取打印一个数组。

在日常中,往往很少使用数组,因为它长度是固定的,一般用的更多的是切片。

var a [5]int
b := [5]int{1, 2, 3, 4, 5}  //[1 2 3 4 5]
var twoD [2][3]int

slice切片

使用make 来创建一个切片,可以像数组一样去取值,使用append来追加元素。

slice原理是有一个它存储了一个长度和一个容量,加一个指向一个数组的指针。

容量应该是为定义长度的2倍 如果超过就会报错。在执行append时,容量不够,会扩容并且返回新的slice。

slice初始化的时候可以指定长度。slice拥有像python一样的切片操作,左闭右开。不同于python,不支持负数索引。

    s := make([]string, 3)
    s[0] = "a"
    s[1] = "b"
    s[2] = "c"
    s = append(s, "d")
    s = append(s, "e", "f")
    fmt.Println(s) // [a b c d e f]

    c := make([]string, len(s))
    copy(c, s)
    
    fmt.Println(s[2:5]) // [c d e]

    good := []string{"g", "o", "o", "d"}
    fmt.Println(good) // [g o o d]
         

map字典

可以用make来创建一个空map,这里需要两个类型,一个是key,是string类型。另一个value,是int类型。可以从里面去存储或者取出键值对。也可以用delete从里面删除键值对。

golang的map是完全无序的,便利的时候不会安装字母顺序,也不会按照插入顺序输出,而是随机顺序。

	m := make(map[string]int) //创建空字典
	m["one"] = 1
	m["two"] = 2

	fmt.Println(m["unknow"]) // 0

	//ok用来获取到底有没有r的存在
	r, ok := m["unknow"]
	fmt.Println(r, ok) // 0 false

	delete(m, "one")

	m2 := map[string]int{"one": 1, "two": 2}
	var m3 = map[string]int{"one": 1, "two": 2}
 

range语法

对于一个slice或者一个map时,可以用range来快速遍历。

range遍历时,对于数组会返回两个值,第一个是索引,第二个是对应位置的值。

如果不需要索引的话,我们可以用下划线来忽略。

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

函数

go语言中变量类型是后置的。函数支持返回多个值。在日常学习工作中,几乎所有函数都是返回两个值,第一个是真正返回结果,第二个值是错误信息。

下面是一个判断字符是否存在并返回值的函数例子,我们在main函数内调用它。

func exists(m map[string]string, k string) (v string, ok bool) {
	v, ok = m[k]
	return v, ok
}
func main() {
    v,ok ;= exists(map[string]striing{"a":"A"},"a")
    fmt.Println(v, ok) // A True
 
}

point指针

go里面也支持指针,相对于C和C++里面的指针,支持的操作有限。指针的一个主要用途就是对于传入参数进行修改。

我们来看下面例子中的这个函数。这个函数试图把一个变量加2。但是单纯像上面这种写法其实是无效的。因为传入函数的参数实际上是一个拷贝,那也说这个加2,是对那个拷贝进行了加2,并不起作用。如果我们需要起作用的话,那么我们需要把那个类型写成指针类型,那么为了类型匹配,调用的时候会加一个 & 符号。

func add2(n int) {
	n += 2
} //错误
func add2ptr(n *int) {
	*n += 2
}
func main() {
	n := 5
	add2(n)
	fmt.Println(n) // 5
	add2ptr(&n)
	fmt.Println(n) // 7
}

struct结构体

结构体是带类型的字段的集合。 我们可以用结构体的名称去初始化一个结构体变量,构造的时候需要传入每个字段的初始值。也可以用这种键值对的方式去指定初始值,这样可以只对一部分字段进行初始化。同样的结构体我们也能支持指针,这样能够实现对于结构体的修改,也可以在某些情况下避免一些大结构体的拷贝开销。

// 结构体用  .  调用
type user struct {
	name     string
	password string
}

struct结构体方法

在 Golang 里面可以为结构体去定义一些方法。会有一点类似其他语言里面的类成员函数。

我们把下面一个例子的 checkPassword函数 的实现,从一个普通函数,改成了结构体方法。这样用户可以像 a.checkPassword(“xx")这样去调用。

就是把第一个参数,加上括号写到函数名称前面。在实现结构体的方法的时候也有两种写法,一种是带指针,一种是不带指针。这个它们的区别的话是说如果你带指针的话,那么你就可以对这个结构体去做修改。如果你不带指针的话,那你实际上操作的是一个拷贝,你就无法对结构体进行修改。

type user struct {
	name     string
	password string
}
// func (结构体) 函数名(参数1,参数2) 返回值类型{ }
func (u user) checkPassword(password string) bool {
	return u.password == password
}
// 带指针就能对结构体进行修改
func (u *user) resetPassword(password string) {
	u.password = password
}
func main() {
	a := user{name: "wang", password: "1024"}
	a.resetPassword("2048")
	fmt.Println(a.checkPassword("2048")) // true
}

错误处理

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

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

func findUser(users []user, name string) (v *user, err error) {
	for _, u := range users {
		if u.name == name { //如果存在相符合的name,则返回U,和空nil代表没有错误
			return &u, nil
		}
	}
	return nil, errors.New("not found") //如果有错误 nil非空
}
func main() {
	u, err := findUser([]user{{"wang", "1024"}}, "wang")
	if err != nil { //如果err错误存在
		fmt.Println(err)
		return
	}
	fmt.Println(u.name) // wang
}

字符串常用操作

contains判断字符串里是否包含字符

join 连接多个字符串

repeat 重复多个字符串

下面是常用的一些关于字符串函数

  a := "hello"
	fmt.Println(strings.Contains(a, "ll"))   // true
	fmt.Println(strings.Count(a, "l"))       // 2
	fmt.Println(strings.HasPrefix(a, "he"))  // true
	fmt.Println(strings.HasSuffix(a, "llo")) // true
	fmt.Println(strings.Index(a, "ll"))      // 2
	fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
	fmt.Println(strings.Repeat(a, 2))             // hellohello
	fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
	fmt.Println(strings.Split("a-b-c", "-"))      // [a b c]
	fmt.Println(strings.ToLower(a))               // hello
	fmt.Println(strings.ToUpper(a))               // HELLO
	fmt.Println(len(a))                           // 5

fmt字符串格式化

println 打印变量并换行 printf 跟c类似 %v 打印任意类型的变量 不需要区分%d %c %+v 得到更详细的结构 字段的名字和值 %#v 进一步详细 结构体的类型名称和字段名字 值

	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}

JSON

go语言 里面的JSON 操作非常简单,对于一个已有的结构体,我们可以什么都不做,只要保证每个字段的第一个字母是大写,也就是是公开字段。那么这个结构体就能用JSON.marshaler 去序列化,变成一个JSON 的字符串。序列化之后的字符串也能够用JSON.unmarshaler 去反序列化到一个空的变量里面。这样默认序列化出来的字符串的话,它的风格是大写字母开头,而不是下划线。我们可以在后面用ison tag 等语法来去修改输出JSON 结果里面的字段名。

type userInfo struct {
	Name  string
	Age   int `json:"age"` //把字段Age大写改成小写
	Hobby []string
}
func main() {
	a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
	buf, err := json.Marshal(a) // ***序列化***  后 是一个数组 字符串
	fmt.Println(buf) // [123 34 78 97...]
	//打印必须类型转换 string
	fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
	var b userInfo
	err = json.Unmarshal(buf, &b) //***反序列化到一个空变量里***

	fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}

时间

在go语言里面最常用的就是 time.now()来获取当前时间, 然后你也可以用time.date 去构造一个带时区的时间。 然后也能用点. sub 去对两个时间进行减法,得到一个时间段。 时间段又可以去得到它有多少小时,多少分钟、多少秒。 在和某些系统交互的时候,我们经常会用到时间戳。那您可以用.UNIX 来获取时间戳。

数字解析

关于字符串和数字类型之间的转换都在 STR conv 这个包下,这个包是 string convert 这两个单词的缩写。

我们可以用 parselnt 或者 parseFloat 来解析一个字符串。

parseint 参数我们可以用 Atoi 把一个十进制字符串转成数字。可以用 itoA 把数字转成字符串。如果输入不合法,那么这些函数都会返回error

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

	n, _ = strconv.ParseInt("0x1000", 0, 64)
	fmt.Println(n) // 4096
	//用atoi把一个十进制字符串转为数字
	//用itoa把数字转为字符串
	//如果输入不合法,这些函数都会返回error
	n2, _ := strconv.Atoi("123")
	fmt.Println(n2) // 123

	n2, err := strconv.Atoi("AAA")
	fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax

进程信息

在 go 里面,我们能够用 os.argv 来得到程序执行的时候的指定的命令行参数。比如我们编译的一个二进制文件,command。 后面接 abcd 来启动,输出就是 s.argv 会是一个长度为 5的 slice ,第一个成员代表二进制自身的名字。我们可以用 so.getenv来读取环境变量。

运行时要在main.go后面加上传入的参数go run example/20-env/main.go a b c d

fmt.Println(os.Args) // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
	//os.args  获取进程命令行参数
	fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
	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)) // 127.0.0.1       localhost

总结

看过一遍不等于学会,在整理笔记过程中又复习了一遍。希望自己可以保持一直学习。