这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
基础语法
快速入门
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
package定义包名,指明该文件属于哪个包,package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
import是导入本文件中所需要的包。
go运行代码的方式有两种,一种是直接运行,另一种是编译生成可执行文件再运行。
直接运行:go run main.go
编译运行:go build main.go
变量
Go语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
变量类型包含:整型、浮点型、字符串、布尔型以及派生类型(如:指针、数组、结构体、channel、函数、切片、接口、Map等)。
变量声明
变量在使用前必须要提前声明,可以使用类型推断或者显示声明类型,声明变量的一般形式是使用 var 关键字。
- 显示声明,var 变量名 类型,可以声明变量的同时给变量进行赋值,且多个变量可以在同一行声明。
// 声明变量时赋值
var a int =1
// 不在声明变量时赋值
var b int
b=1
// 多个变量同时声明
var c, d int = 1,2
var(
e int
f string
)
- 类型推断,需要为变量指定值,go会根据值来推断该变量是什么具体类型。
// 全局使用
var num1 = 1
// := 仅函数内可以使用,且为该变量首次赋值
num2 := 1
num2 := 2 // 错误,:= 仅在首次赋值的时候可以用
num2 = 2 // 正确,重新赋值
常量声明
Go在声明常量时,使用const关键字。常量必须显示指定类型,并且值不可更改。
// 单一常量声明
const PI float=3.14
// 多个常量声明
const(
a=3
b=4
)
流程控制
条件语句
Go的if语句不需要括号且if后面的开始大括号必须跟if在同一行,不能换行,同时if语句支持if-else-if语句。
num := 1
if num<5 {
fmt.Println("num is smaller than five")
} else if num>5 {
fmt.Println("num is bigger than five")
} else {
fmt.Println("you are succeed")
}
if语句后面也可以定义变量,但只能用一个分号。
if a, b := 1, 2; a < b {
fmt.Println("a < b")
} else {
fmt.Println("a >= b")
}
循环语句
在Go里没有while和do-while循环,只有for循环,for集成for和while的功能。
// 两种方式打印 0~9
//1.for实现while循环功能
num:=0
for num<9 {
fmt.Println(num)
num++
}
//2.普通for循环
for i := 0; i < 9; i++ {
fmt.Println(i)
}
在for循环里也可以使用continue和break来跳出循环。
// 使用continue
for i := 0; i < 9; i++ {
if i == 5 {
continue
}
fmt.Println(i)
}
// 使用break
for i := 0; i < 9; i++ {
if i == 5 {
break
}
fmt.Println(i)
}
switch 语句
switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上到下逐一测试,直到匹配为止,如果在switch后面不加匹配条件,可以在case里进行匹配。
// 1. switch + 匹配值
a := 1
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
default:
fmt.Println("other")
}
// 2. switch 后无匹配值
t:=time.Now()
switch {
case t.Hour()<12 :
fmt.Println("before noon")
case t.Hour()==12 :
fmt.Println("noon")
default:
fmt.Println("after noon")
}
数组Array
数组(array)是是同一种数据类型的固定长度的序列。
一维数组
数组声明以及访问如下:
// 声明一个长度为5的数组
var a [5]int
a[4] = 100
fmt.Println( a[4])
fmt.Println("len:", len(a))
// 使用:=声明数组并初始化
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
多维数组
多维数组的声明方式和一维数组类似,几维数组就加几个中括号,例如二维数据的声明以及赋值代码如下:
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
切片(slice)
切片(slice)并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。可以将切片理解成可变长度的数组。
切片的声明
slice可以通过make创建,也可以通过:=声明并初始化。
// 声明一个空切片
s1 := make([]string, 3)
// 声明并初始化
s2 :=[] int {1,2,3,4,5,6}
对切片的操作
对切片元素的赋值,可以像数组一样操作。
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
通过append方法向切片中添加元素,因为切片会涉及到扩容,所以需要一个变量来接收返回值。
s = append(s, "d")
s = append(s, "e", "f")
可以通过copy方法对切片进行复制,复制后是两个独立的切片,更改元素值相互不影响。
c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]
slice 拥有想 python 一样的切片操作
// 返回下标2到4的值
fmt.Println(s[2:5]) // [c d e]
// 返回下标0到4的值
fmt.Println(s[:5]) // [a b c d e]
// 返回下标2到最后一个元素
fmt.Println(s[2:]) // [c d e f]
集合(Map)
Map 是一种无序的键(key)值(value)对的集合,也被称为映射、字典。Map是无序的,遍历的时候不会按照字母顺序,也不会按照插入顺序输出,而是随机顺序。
Map 的声明
Map可以通过make创建,也可以通过:=声明并初始化
// make 创建一个空的map
m1 := make(map[string]int)
// 通过 := 创建map并初始化
m2 := map[string]int{"one": 1, "two": 2}
对Map的访问
对Map的赋值和数组类似通过”下标“进行操作,只不过这个”下标“对应的是key,如果访问的key不存在的话会返回0。
m["one"] = 1
m["two"] = 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
对于不存在的key返回0,如果一个key的值也为0,就很容易产生不必要的误会,Go在查询key时会返回了两个值,第一个值是对应key的结果,第二个值代表对应的键值对是否存在,如果对应key的值不存在,会返回false。
delete(m, "one") // 可以使用delete函数删除map的键值对
r, ok := m["one"]
fmt.Println(r, ok) // 0 false
range
对于slice、Map可以使用range来遍历,通过range可以得到两个值,第一个值是索引,第二个值是对应位置的值,如果不需要索引或值的话,可以使用下划线来忽略。
nums := []int{2, 3, 4}
sum := 0
// 遍历 slice
for i, num := range nums {
fmt.Println("index:", i, "num:", num)
}
// 遍历 Map
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
// 只获取 key,忽略value
for k := range m {
fmt.Println("key", k) // key a; key b
}
// 只获取 value,忽略
for _, v := range m {
fmt.Println("value", v) // value A; value B
}
函数
函数定义格式如下:
func 变量名( [变量] ) [返回值类型] {
函数体
}
//定义一个两个数相加的函数
func add(a int, b int) int {
return a + b
}
Go函数可以返回多个值,但是在实际的业务逻辑代码中几乎所有的函数都返回两个值,第一个是函数的返回值,第二个是错误信息。
// 返回多个值,与返回值类型对应
func operator(a int,b int) (m int, n int){
return a+b,a*b // m=a+b n=a*b
}
// 实际业务中常用
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
指针
区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针。
Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据。类型指针不能进行偏移和运算。Go语言中的指针操作非常简单,只需要记住两个符号:&(取地址)和*(根据地址取值)。
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
}
结构体
Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
结构体定义
结构体定义需要使用 type 和 struct 这两个关键字 。
type 类型名 struct {
变量 变量类型
...
}
定义一个包含name和password的user结构体:
type user struct {
name string
password string
}
结构体的实例化
结构体的实例化可以全部赋值,也可以指定字段进行赋值。
a := user{name: "wang", password: "1024"}
b := user{name: "wang"}
结构体的访问
对于结构体内部成员的访问可以通过.操作符来进行访问或赋值。
var u user
u.name = "wang"
u.password = "1024"
fmt.Println(u.name)
结构体方法
结构体方法的定义是在func关键字后面加上结构体信息,就代表这个方法是属于该结构体的。
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
为user结构体添加一个检查密码的函数:
func (u user) checkPassword(password string) bool {
return u.password == password
}
为user结构体添加一个重置密码的函数,因为要修改结构体内部的值,所以要使用指针类型:
func (u *user) resetPassword(password string) {
u.password = password
}
结构体方法的调用:
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
错误处理
Go内置的错误接口提供了非常简单的错误处理机制,error类型是一个接口类型,其定义如下:
type error interface {
Error() string
}
在函数里面,我们可以在函数的返回值类型中加一个error,就代表这个函数可以会返回错误,如果函数不出现错误的话,可以直接返回nil,通过err与nil的比较判断是否发生错误。
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
fmt.Println(err)
}