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
总结
看过一遍不等于学会,在整理笔记过程中又复习了一遍。希望自己可以保持一直学习。