Go基础语法 | 青训营笔记

402 阅读9分钟

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

本人为零基础小白,本文主要根据青训营课程内容作一定整理,并作一些补充,以便后续回顾。

Go基础语法

1. Go介绍

1.1 什么是Go语言

  • 高性能、高并发
  • 语法简单、学习曲线平缓
  • 丰富的标准库
  • 完善的工具链
  • 静态链接
  • 快速编译
  • 跨平台
  • 垃圾回收

1.2 Go的市场环境

Go在云计算、微服务、大数据、区块链、物联网等领域都有发展,尤其是在云计算、微服务方向。 Docker、Kubernetes、Istio、etcd、prometheus几乎所有的云原生组件都由Go实现。

1.3 字节跳动为什么全面拥抱Go语言

  • 最初使用的Python,由于性能问题换成了Go
  • C++不适合在线Web业务
  • 早期团队非Java背景
  • 性能较好
  • 部署简单、学习成本低
  • 内部RPC和HTTP框架的推广

2. Go快速入门

2.1 开发环境

  • 输入go.dev打开Golang官网,按照提示下载
  • 镜像
  • 如果无法科学上网,按照教程配置go mod proxy用于第三方包下载
  • 使用GoLand或者vscode(需安装插件)
  • 基于云的开发环境(可用github账号登录) 短链接

2.2 基础语法

Hello World

package main

import (
    "fmt"
)

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

包声明

  • 引入包语法 import "包名",可用括号引入多个包
  • 如果一个包引入了但是没有使用,会报错
  • 匿名引入:前面多一个下划线。一般匿名引入最常用的是sql里引入一个driver驱动
  • Goland会帮你自动引入你代码里面用的包

变量

  • Go是强类型语言,每一个变量都有它自己的变量类型
  • 常见的变量类型:字符串,整数,浮点型,布尔型
  • 运算符优先级和 C 类似
  • 首字符大小写控制访问性,大写包外可访问
  • 支持类型推断
  • 默认初始值为0,bool型为false
  • 变量声明语法一 var name type = value
  • 变量声明语法二 name := value 该方法适用于局部变量
  • 作用域:同作用域下一个变量只能声明一次,内部作用域声明可覆盖外部作用域声明,但最好别用。

常量

  • 常量声明语法 const name = value
  • 常量没有确定的类型,根据上下文自动判断

if-else

  • 与 C 相比没有括号,且必须在if后面接大括号,不能把if的语句写在同一行。
  • 如果定义变量在if-else中,则它的作用域仅作用在该if-else中。

循环

  • go没有while循环、do while循环,只有for循环。

常用的几种for循环用法

  • for当while用

for 条件(可以不加) {

}

  • 与C一致,不要括号

for i:=0;i<len(err);i++ {

}

  • for range 针对切片,map等,index为下标,value为下标对应的值

for index, value := range arr {

}

  • 如果只需要value

for _, value := range arr {

}

switch

  • 和 C 的差别在于不用加break。
  • 可以使用任意的变量类型,甚至可以用来取代任意的if else语句。你可以在switch后面不加任何的变量,然后在case里面写分支。这样代码相比用多个if else逻辑更清晰

数组

数组和别的语言的数组差不多,语法是[cap]type

  • 初始化要指定长度(容量)
  • 直接初始化
  • arr[i]的形式访问元素
  • len和cap操作用于获取数组长度,len为当前容量,cap为最大容量,然而数组的容量是确定的,所以二者是一样的

切片

语法[]type,可以看成不声明容量的数组,即括号中有数字为数组,无数字为切片。

  • 推荐写法s:=make([]type,0,capacity)来初始化一个零元素的切片,capacity指最大容量,0表示切片中存放的元素个数,可以用其他正整数替代
  • arr[i]的形式访问元素
  • append追加元素,注意扩容时为倍数扩容,而不是加一个元素扩一次容
  • len获取元素数量
  • cap获取最大容量
s:=make([]int,3,4) // 创建了一个包含3个元素,容量为4的切片。此时s为[0,0,0] len(s)=3 cap(s)=4
s1:=make([]int,4) // 省略了元素个数,创建了一个包含4个元素,容量为4的切片
s=append(s,1) // 此时s为[0,0,0,1] len(s)=3 cap(s)=4
s=append(s,2) // 扩容一般为倍数扩容,此时s为[0,0,0,1,2] len(s)=5 cap(s)=8

map

map又称哈希或者字典,是实际使用过程中最频繁用到的数据结构。我们可以用make创建一个空map,语法为make[value]key其中value和key均指类型

func main() {
	m := make(map[string]int)
	m["one"] = 1
	m["two"] = 2
	fmt.Println(m)           // map[one:1 two:2]
	fmt.Println(len(m))      // 2
	fmt.Println(m["one"])    // 1
	fmt.Println(m["unknow"]) // 0

	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}
	fmt.Println(m2, m3) //map[one:1 one:2] map[one:1 two:2]
}

函数

Go支持多返回值,且返回值可以命名

package main

import "fmt"

func add(a int, b int) int {
	return a + b
}

func add2(a, b int) int {
	return a + b
}

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

func main() {
	res := add(1, 2)
	fmt.Println(res) // 3

	v, ok := exists(map[string]string{"a": "A"}, "a")
	fmt.Println(v, ok) // A True
}

指针

相比于 C 和 C++ ,Go的指针支持的操作有限,指针的主要用途就是修改传入的参数。

结构体

package main

import "fmt"

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)
	fmt.Println(checkPassword(a, "haha"))   // false
	fmt.Println(checkPassword2(&a, "haha")) // false
}

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

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

上面代码中的user结构体包括两个字段,name和password。初始化一个结构体变量可以用结构体名称加对应字段的初始值。结构体也支持指针,能避免大结构体的拷贝开销。

结构体方法

在Golang里可以为结构体定义一些方法,类似 C++ 的类成员函数。

package main

import "fmt"

type user struct {
	name     string
	password string
}

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
}

上述代码将checkPassword的实现从普通函数改为结构体方法。我们可以用a.checkPassword("xx")的方式调用。将普通函数修改为结构体方法具体为将结构体参数加上括号放到函数名前。实现结构体的方法也有两种写法,一个是不带指针,一个是带指针,区别在于带指针的可以对传入的结构体进行修改。

错误处理

常用一个if-else来返回错误信息。

if err:= 调用函数; err != nil {
    错误处理
    return
}

字符串操作

标准库strings中包含了常用的字符串工具函数。

string声明
  • 双引号引起来,则内部双引号需要使用\转义

    // 一般用于短的,不用换行的
    print("He said:\"Hello Go!\"")
    
  • `引号引起来,则内部`需要\转义

    // 一般用于长的,复杂的,需要换行的,比如json串
    print(` He said:"hello,Go"
    换行
    `)
    
string长度
  • 字节长度:和编码无关,用len(str)获取。
  • 字符数量:和编码有关,用编码库来计算。
println(len("你好")) // 输出6
println(utf8.RuneCountInString("你好")) // 用utf8的库,输出2
常用操作
package main

import (
	"fmt"
	"strings"
)

func main() {
	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
	b := "你好"
	fmt.Println(len(b)) // 6
}

contains 判断一个字符串里面是否含有另一个字符串,count字符串计数,index查找某个字符串的位置,join连接多个字符串,replace替换字符串,repeat重复多个字符串,tolower将字符串全部转换为小写,toupper将字符串全部转换为大写。特别要注意的是如果要取中文串的字符数,需要用对应编码库函数来计算,因为len本身算的是字节长度。

字符串格式化

在标准库的fmt包中有很多字符串格式化相关的方法,比如printf类似 C 的printf函数。在Golang中,可以用%v来打印任意类型的变量,不需要区分字符串。%+v打印详细结果,%#v则更详细。

package main

import "fmt"

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}

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

整理一些print函数

  • Print:输出到控制台,相当于对每个操作数用%v
  • Println:输出到控制台并换行
  • Printf:格式化输出到控制台
  • Sprintf:格式化并返回一个字符串而不带任何输出,go不能将数字和字符串拼接,所以常用Sprintf来拼接
  • Fprintf:格式化字符串输出到stream流指定的文件设备中,参数比printf多一个指针FILE*
func main() {
    name:="QIANREN"
    age:=20
    str:=fmt.Sprintf("hello,I am %s,I am %d years old",name,age)
    println(str)
}

JSON处理

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

package main

import (
	"encoding/json"
	"fmt"
)

type userInfo struct {
	Name  string
	Age   int `json:"age"`
	Hobby []string
}

func main() {
	a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
	buf, err := json.Marshal(a)
	if err != nil {
		panic(err)
	}
	fmt.Println(buf)         
	fmt.Println(string(buf)) 

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

	var b userInfo
	err = json.Unmarshal(buf, &b)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%#v\n", b) 
}

时间处理

Go常用time.now()来获取当前时间。其他常用的有.sub()对两个时间进行减法,.UNIX获取时间戳。具体要用到直接查文档就是了。

数字解析

关于字符串和数字类型的转换在strconv包中,常用的有strconv.Atoi(字符转数字)和strconv.Atoi(数字转字符)。

总结

学的有点慢,但收获挺多的。