这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。
-
Hello World
// 指定main包,程序入口main函数必须在main包中 package main // 导入fmt标准库,用于输出输出 import "fmt" // 程序入口main函数 func main() { // 调用Println方法打印hello world! fmt.Println("hello world!") }注意:不用于其他很多编程语言,Golang的语句并不需要以分号(;)结尾,这点与python一致
-
变量
在Golang中有多种变量声明以及初始化方法
// 声明str为string类型 var str string // 声明str并初始化(自动判断类型) var str = "hello" // 声明str为string类型并初始化(指定类型) var str string = "hello" // 声明多个变量 var str1,str2 string = "hello","world" // 声明str并初始化(自动判断类型) // 推荐使用 str := "hello" // 声明常量 const str string = "hello"所有变量在声明且未被负值时会有一个默认的初始值
数值类型均为0,string类型为空字符串“”,bool类型为false
-
条件判断(if else)
if 条件a { //操作1 }else if 条件b { //操作2 }else { //操作3 }注意:条件不能使用圆括号括起来,且每个条件后必须使用{}来包括操作语句块,哪怕具体操作只有一行
-
循环(for)
// 死循环 for { //操作语句 } // 经典循环 for i := 0; i < 10; i++ { //操作语句 } // for后的三段结构可以省略任意一段 // 也表示死循环 for ; ; { //操作语句 } // 可以使用continue语句进入下一轮循环 // 可以使用break语句跳出循环注意:Golang中仅有for循环一种循环
-
选择语句(switch)
a := 2 switch a { case 1: //操作语句1 case 2: //操作语句2 case 3,4: //操作语句3 default: //操作语句4 }case中不需要使用break语句
switch也可以用于条件判断
a := 2 switch { case a < 1: //操作语句1 case a == 2: //操作语句2 default: //操作语句4 } -
数组
// array := [数据长度]数据类型 {初始值} arr := [5]string {}数组类型的长度是在声明时就固定的,且不可改变
若没有赋值,所有元素均为默认值
-
切片(slice)
// 使用make创建切片 // make([]元素类型, 初始值, 初始容量) s := make([]int, 0, 3) // 向切片追加元素 // 新slice = append(旧slice, 新增元素) s = append(s, 1)slice可以像数组意义通过下标索引取值,也可以使用append追加元素。
slice实际上存储了一个长度和容量,以及一个指向数组的指针,append在需要的情况下会扩容,返回一个新的slice
slice具有与python类似的切片操作,但不支持负数索引
// 删除开头n个元素 a = a[n:] // 删除中间n个元素 a = append(a[:i], a[i+n:]...) // 删除尾部n个元素 a = a[:len(a)-n] -
map(哈希/字典)
// 通过make创建,map[key的类型]value的类型 m := make(map[string]int) // 直接添加/修改键值对 m["a"] = 1 m["a"] = 2 // 通过key获取value返回值可以有一个,也可以有两个 // 其中v为对应key的value,ok表示获取是否成功 // ok为bool类型,为false时代表获取失败,即该key不存在 v := m["a"] v,ok := m["a"]注意:Golang中map是完全无序的
-
range(快速遍历)
range可用于快速遍历slice、数组和map
// 快速遍历slice/数组 // i为当前slice/数组的下标,num为以该下标索引的值 n := []int{i,2,3} for i,num := range n { // 具体操作 } // 快速遍历map // k为key值,v为key对应的value m := map[string]string{"a":"A", "b":"B"} for k,v := range m { // 具体操作 } // 若for中只有一个变量,即为遍历key for k := range m { // 具体操作 } -
函数
// 函数通过func定义,变量类型为后置 // func 函数名(变量a 变量a类型, 变量b 变量b类型) (返回值类型) {} func add(a int, b int) int { return a + b }Golang中函数原生支持多个返回值,一般来说,会返回一个错误类型来判断函数的运行情况
func add(a int, b int) (result int, err error) { if 正常运行 { return result, nil }else { trturn result, err } }关于返回值,当仅有一个返回值时可省略括号,多个返回值需要用括号括起来。同时,定义函数时,返回值可以只声明类型,返回对应类型即可,也可以直接指定返回值的变量名,返回时需要返回相应的变量。
-
指针
与其他很多编程语言一样,Golang中的函数很多时候只是传入形参,并不能改变传入变量的值,这时候就需要用到指针
func add1 (n int) { n += 2 } func add2 (n *int) { *n += 2 } func main() { n := 5 // 无效 add1(n) fmt.Println(n) // 有效 add2(&n) fmt.Println(n) }取地址符&与C语言中一致
-
结构体
// 定义结构体 type user struct { userId string name string }声明与初始化结构体有三种方式
-
var u user u.userid = 1 u.name = "a" -
// name为string默认值 u1 := user { userId: 1, } // 顺序可与定义时不一致 u2 := user { name: "a", usrerId: 1, } u3 := user{userId; 1, name: "a"}可以注意到,u2与u3都是初始化两个属性,但u2初始化usrerId属性时后面多一个逗号(,)
这其实与Golang的编译原理有关,我们写Golang代码时是不需要在代码后使用分号(;)的,这是因为编译器会在编译之前自动帮我们加上分号,在u2中如果usrerId的初始化后面不加逗号,编译器会认为语句已经到了最后,帮我们加上分号,导致编译出错,因此下面这种写法是对的
u2 := user { usrerId: 1, name: "a"}具体什么时候编译器加分号,什么时候不加,感兴趣的可以自己去了解
-
u := user{"1","a"}这种声明与初始化方法参数必须与结构体一一对应且必须包含所有属性
-
-
结构体方法
Golang中是没有类(class)的概念的,但可以通过结构体以及结构体方法实现类的作用
// 定义结构体 type user struct { userId string name string } // 定义结构体方法函数 // 不改变结构体 func (u user) checkName(name string) bool { return u.name == name } // 改变结构体 func (u *user) changeName(name string) { u.name = name } //调用 func main() { u := user { userId: 1, name: "a", } fmt.Println(u.checkName("a")) u.changeName("b") }可以把结构体方法看作其他语言中的类成员函数
-
错误处理
错误处理是Golang中很常用的做法,不同于Java中的需要使用异常类,Golang的错误处理使用起来更为简单,也更为直观
func (u user) checkName(name string) (err error) { if u.name == name { return nil }else { return errors.New("name mistake") } }通过errors.New()可以快速创建错误,同时对于函数返回错误也便于定位bug。对于多层调用的结构,也可以通过层层返回错误退出,十分便捷
具体信息见:pkg.go.dev/errors
-
字符串操作
操作字符串的函数基本来自于标准库strings
a := "hello" // 判断是否包含某字符串 fmt.Println(strings.Contains(a,"ll")) // true // 字符串计数 fmt.Println(strings.Count(a,"l")) // 2 // 查找字符串下标 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.ToLower(a)) // hello fmt.Println(strings.ToUpper(a)) // HELLO // 分词 fmt.Println(strings.Split("a-b-c", "-")) // [a b c]具体信息见:pkg.go.dev/strings
格式化字符串的方法与C/C++类似,但使用%v可自动判断类型
-
JSON处理
json是一种十分常用的格式,尤其是在web服务的开发应用中。Golang的标准库encoding/json让json操作变得十分简单
对于已有的结构体,我们可以将其序列化为json字符串;对于一个json字符串,我们也可以将其反序列化赋值到一个结构体上
定义结构体的时候可以在对应字段后打上json的tag,定义对应json字段的名字(否则序列化后默认为属性名),同时该字段首字母需大写(即是公开的)
type user struct { Name string Id int `json:"id"` } u := user{ Name: "a", Id: 1, } // 序列化 buf,err := json.Marshal(u) // buf是[]byte类型,需要转为字符串才能格式化输出 fmt.Println(string(buf)) // {"Name":"a","id":1} // 反序列化 var s user err = json.Unmarshaler(buf, &s)具体信息见:pkg.go.dev/encoding/js…
-
时间处理
// 获取当前时间 now := time.Now() // 构建时间 t := time.Date(2022, 3, 27, 1 ,25, 36, 0, time.UTC) // 可以使用t.Year()、t.Month()等获得需要的字段 // 按照需求格式化时间 t1,err := time.Parse(格式,需要转换的string)具体信息见:pkg.go.dev/time
-
数字解析
Golang标准库strconv包含了许多字符串与数字类型之间的转化
f,_ := strconv.ParseFloat("1.234", 64) // 1.234 i,_ := strconv.ParseInt("111", 10, 64) // 111 i,_ = strconv.ParseInt("0x1000", 0, 64) // 4096由于10进制数与字符串的转换更常用,也有以下方法
n,err := strconv.Atoi("123") // 123 s,err := strconv.itoA(123) // "123"具体信息见:pkg.go.dev/strconv