第一部分 基本数据类型
了解基本数据类型,string类型不属于基本类型
数值型 | |
---|---|
整数类型 | int , int8 , int16 , int32 , int64 , uint , uint8 , uint16 , uint32 , uint64 , byte |
浮点类型 | float32(单精度) , float64(双精度)(默认8位,64字节) |
字符型 | 没有专门的字符型,使用byte来保存单个字符 |
布尔型 | bool(一个字节) |
字符串 | string |
第二部分 关键字、保留字
break | default | func | interface | select |
---|---|---|---|---|
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
第三部分 运算符优先级
分类 | 描述 | 关联性 |
---|---|---|
后缀 | () [] -> . ++ -- | 从左到右 |
单目 | + - ! ~ (type) * & sizeof | 从右到左 |
乘法 | * / % | 从左到右 |
加法 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等(关系) | == != | 从左到右 |
按位and | & | 从左到右 |
按位xor | & | 从左到右 |
按位or | | | 从左到右 |
逻辑and | && | 从左到右 |
逻辑or | || | 从左到右 |
赋值运算符 | = += -= *= /= %= >>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
第四部分 分支、循环、判断
-
支持在if中定义变量,
} else {
不允许换行goif age := 20; age > 18 { //条件不需要加括号 fmt.Println("成年了") }
-
if条件内不允许出现
if b = false
这种赋值语句 -
switch语句case后不需要
break
语句,默认case最后为break;
-
case后的表达式可以为多个用
,
隔开,可以是常量、变量、一个有返回值的函数; -
case后的数据类型与switch后表达式的数据类型
相同
govar num1 int32 = 10 var num2 int32 = 20 switch num1 { case num2, 5, 10: //数据类型相同即可 fmt.Println("Yes") default: //允许没有default fmt.Println("NO") }
-
case后的值如果是常量则不允许重复
dart
var num int = 10 //当作if...else分支使用
switch {
case num == 10:
fmt.Println("Yes")
default:
fmt.Println("NO")
}
var score int = 90
switch {
case score > 90:
fmt.Println("优秀")
case score > 75 && score <= 90:
fmt.Println("良好")
case score >= 60 && score < 75:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
-
switch穿透;默认穿透一层
dartvar num int = 10 switch { case num == 10: fmt.Println("Yes") fallthrough default: fmt.Println("or NO") }
-
Type Switch:switch语句还可以被用于type-switch来判断某个interface变量中实际指向的变量类型
govar x interface{} var y = 10.0 x = y switch i := x.(type) { case nil: fmt.Printf("x的类型:%T\n", i) case int: fmt.Println("x是 int 型") case float64: fmt.Println("x是 float64 型") case func(int) float64: fmt.Println("x是 func(int) 型") case bool, string: fmt.Println("x是 bool或者string 型") default: fmt.Println("未知型") }
-
循环结构标准写法与特有写法
go// 1 str1 := "hello~world!" for i := 0; i < len(str1); i++ { //无法识别中文 fmt.Printf("val = %c\n", str1[i]) } // 1改 str1 := "hello~world!" t := []rune(str1) //[]rune方法接收,可以识别中文 for i := 0; i < len(t); i++ { fmt.Printf("val = %c\n", t[i]) } // 2 str2 := "hello~world" for index, val := range str2 { //识别全部类型 fmt.Printf("index = %d val = %c\n", index, val) }
-
for-range是按照字符的方式遍历,可以接收输出中文
-
go没有while和do...while语法
dartvar num int = 10 for { fmt.Println("hello", num) //循环体在前,判断条件在后,循环体最少执行一次 num++ if num > 10 { break } }
-
生成随机数
goimport ( "fmt" "math/rand" "time" ) func main() { rand.Seed(time.Now().Unix()) num := rand.Intn(100) + 1 //rand.Intn(n)的范围[0,n) fmt.Println(num) //rand.Intn(100) + 1的范围[1,100) }
-
break跳出最近for循环;break lablen跳出标记n之后的循环
-
continue跳出最近for循环的本次循环;continue lablen跳过标记n之后的这本次for循环
-
goto lablen跳转到指定lablen: 的位置,尽量不使用
第五部分 函数
-
函数也是数据类型,可以被赋予;也可以作为形参被调用
-
go支持自定义数据类型;支持对返回值命名
markdowntype mySum func(num1 int,num2 int)(sum01 int, sum02 int) //mySum等价于一个函数类型,func(int, int)(int, int)
gopackage main import "fmt" func main() { a, b := geySumAndSub(1, 2) fmt.Println(a) fmt.Println(b) } func geySumAndSub(n1 int, n2 int) (sum int, sub int) { sum = n1 + n2 sub = n1 - n2 return }
-
_
标识符,忽略返回值goc, _ := getSumAndSub(1, 2) // ↑ 8~9 fmt.Println(c)
-
支持可变参数 (可变参数必须在末尾),args时slice切片,通过args[i]可以访问各个值
go//0~若干 func sum(args... int) sum int{ } //1~若干 func sum(arg int, args... int) sum int{ }
-
形参数据类型相同时,可用逗号隔开,末位加数据类型(num1,num2...numn float32)
-
intit()函数,在main()函数执行前执行
gofunc init() { }
-
执行流程:全局变量=>init()=>main()
-
匿名函数01(范围:当前语句)
gores := func(n1 int, n2 int) int { //只有func (int,int) int格式 return n1 + n2 }(10, 20) //直接输入形参
匿名函数02 res可以复用(范围:每次调用)
gores := func(n1 int, n2 int) int { //将函数赋值res return n1 + n2 } res02 := res(10, 20) //res不是函数名,而是函数类型 fmt.Println(res02)
匿名函数03 赋值给全局变量(范围:当前程序),首字母大写
govar MyFun = func(n1 int,n2 int) int { //赋值给全局变量,myFunc就是全局匿名函数 return n1+n2 } func main() { res := MyFun(10, 20) fmt.Println(res) }
-
闭包
gofunc AddUpper() func(int) int { var num = 0 //3~7,形成闭包,num相当于静态变量,只初始化一次 return func(x int) int { num = num + x return num } } func main() { res := AddUpper() fmt.Println(res(1)) fmt.Println(res(2)) fmt.Println(res(3)) }
-
闭包演示
gofunc makeSuffix(suffix string) func(string) string { return func(name string) string { //3~8为闭包 if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func main() { f := makeSuffix(".jpg") fmt.Println(f("text01")) fmt.Println(f("text02.jpg")) }
-
defer
函数结束后释放资源,defer修饰的语句在当前函数结束后执行 -
defer修饰的语句,执行时,进入独立的
defer栈
,当前函数执行完毕后按照先入后出的方式出栈gofunc sum(n1 int, n2 int) int { defer fmt.Println("第一个入栈") // 4 defer fmt.Println("第二个入栈") // 3 defer fmt.Println("第三个入栈") // 2 res := n1 + n2 fmt.Println("sum函数调用结束") // 1 return res } func main() { res := sum(10, 20) fmt.Println(res) // 5 }
-
defer将语句入栈时,相关的值也会被拷贝入栈
dartvar num int = 10 defer fmt.Println("num = ",num) //函数结束后输出,输出 num = 10 num++
-
defer主要用法;解决关闭代码时机问题
gofunc text() { //1.打开 and 延迟关闭文件 file = openfile(文件名) defer file.close() //text()函数执行完毕后,释放函数创建的资源 //其他代码 //2.引用 and 延迟释放数据库资源 connect= openDatabase() defer connect.close() //其他代码 }
第六部分 简单错误处理
异常处理方式:defer, panic, recover
go
//defer
func test() {
defer func() { //捕获异常
err := recover()
if err != nil { // if err := recover(); err != nil {
fmt.Println("err = ", err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println(res)
}
第七部分 数组
-
初始化数组方法
govar numArr [3]int = [3]int{1, 2, 3} var numArr = [3]int{1, 2, 3} var numArr = [...]int{1, 2, 3} var numArr = [...]int{0:1, 1:2, 2:3} //0、1、2为数组下标 var strArr := [...]string{"1", "2", "3"}
-
for-range
,go独有遍历结构gofor index, value := range array { //array 数组名 // 不需要下标index,可用`_`代替 //输出时,index代表下标 //value,代表下标对应的值 }
-
数组默认值传递
-
arr
[3]int
与arr[4]int
不是一种数据类型,arr []int
是切片
go
var slice []int = []int {1, 2, 3}
var slice = arr[stsrtIndex : endIndex]
var slice = make([]byte, 5, 10) //创建空间,make(数据类型,大小,容量)
-
创建切片为值传递,修改
slice[index]
,arr[startIndex + index]
也会改变 -
切片slice是可以增长的
slice = append(slice,elems...) //将slice以及elems,并赋给slice
slice本身没有变化,
=
将slices与elems赋给了slice
-
切片拷贝,
拷贝不会扩容
govar a []int = []int {1,2,3,4,5} var slice = make([]int,1) //创建slice切片,初始化 fmt.PrintIn(slice) // [ 0 ] copy(slice,a) //将a拷贝到slice内 fmt.Println(slice) // [ 1 ]
第八部分 Map(键值对部分)
-
声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用
map在使用前一定要make map的key是不能重复,如果重复了,则以最后这个key-value为准 map的value是可以相同的 map的key-value是无序 make内置函数数目,make(Type, size) size可以省略,默认值为
1
go//声明,这时map=nil var cities map[string]string //make(map[string]string,l0)分配一个p空间 cities = make(map[string]string, 10) //方式2 //声明,就直接make var cities = make(map[string]string) //方式3 //声明,直接赋值 cities := map[string]string{ "no04": "成都", } cities["no01"] = "北京"
-
map删除:
说明: delete(map,"key"),delete是一个内置函数,如果key存在,就删除该key-value 如果key不存在,不操作,但是也不会报错 如果我们要删除map的所有y,没有一个专门的方法一次删除,可以遍历一下key,逐个删除 或者map=make(...),make一个新的,让原来的成为垃圾,被gc回收
-
Map查找
key,value := cities["no02"] if value{ fmt.Printf("有no01 key值为%v\n",key) }else{ fmt.Printf("没有no01 key\n") }
-
Map切片
动态添加元素govar monsters []map[string]string monsters = make([]map[string]string, 2) //准备放入两个妖怪 if monsters[0] == nil { monsters[0] = make(map[string]string, 2) monsters[0]["name"] = "牛魔王" monsters[0]["age"] = "500" } if monsters[1] == nil { monsters[1] = make(map[string]string, 2) monsters[1]["name"] = "玉兔精" monsters[1]["age"] = "400" } fmt.Println(monsters) newMonster := map[string]string{ "name": "新的妖怪~火云邪神", "age": "200", } monsters = append(monsters, newMonster) fmt.Println(monsters)
-
Map按照key值排序
govar keys []int for k := range map01 { keys = append(keys, k) } fmt.Println(keys) sort.Ints(keys) fmt.Println(keys) for _, k := range keys { fmt.Printf("map01 [%d] : %d\n", k, map01[k]) }
-
变量名首字母大写表示
公开
,首字母小写表示私有
第九部分 结构体
创建结构体方法:
go
type Person struct {
Name string
Age int
}
func main() {
//方式1
//在创建结构体变量时,就直接指定字段的值
var p1 = Person{"name01", 19}
p2 := Person{"name02", 20}
//在创建结构体变量时,把字段名和字段值写在一起,就不依赖字段的定义顺
var p3 = Person{
Name: "name03",
Age: 20,
}
p4 := Person{
Age: 30,
Name: "name04",
}
fmt.Println(p1, p2, p3, p4)
// 方式2
var p5 = &Person{"name05", 29}
p6 := &Person{"name06", 39}
//在创建结构体指针变量时,把字段名和字段值写在一起,不依赖字段的定义顺序
var p7 = &Person{
Name: "name07",
Age: 49,
}
p8 := &Person{
Age: 59,
Name: "name08",
}
fmt.Println(*p5, *p6, *p7, *p8)
}
第十部分 继承、接口
继承
-
结构体可以使用嵌套匿名结构体所有的字段和方去,即:首字母大写或者小写的字段、方法,都可以使用。
-
当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
-
结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有
相同
的字段和方法 (同时结构体本身没有同名的字段和方法) ,在访问时,就必须明确指定匿名结构体名字,否则编译报错 -
如果结构体中是一个
有名结构体
,则访问有名结构体的字段
时,就必须带上有名结构体的名字若嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分
-
嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
-
结构体的匿名字段是基本数据类型
如果一个结构体有int类型的匿名字段,就不能第二个 如果需要有多个int的字段,则必须给int字段指定名字
-
若一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承
接口
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
- 接口中所有的方法都没有方法体,即都是没有实现的方法
- 在Golang中,一个自定义类型需要将某个接口的
所有方法都实现
,我们说这个自定义类型实现了该接口- 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
- 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
- 一个自定义类型可以实现多个接口
- Golang接口中不能有任何变量
- 一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现
- interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用那么会输出nil
- 空接口interface{}没有任何方法,所以
所有类型都实现了空接口
,即我们可以把任何一个变量赋给空接口
学习小结
在学习go语言的过程中,能感受到代码的美感与细节,如数据类型中,go将string不再作为基本数据类型,而是使用更加简短的byte,针对常用数据类型,根据使用时的数据来选择占用空间更小的数据类型;分支循环中,穿透的使用,使得分支冗余量减少,for-range将遍历方式拆解切片,字符串,数组等更加方便
到目前为止的学习中,看到的go兼容简洁与高性能,学习到go的代码简化与其他语言的区别时,更加了解设计者对于每一个关键字,每一个函数的创造思路,观看源码,提升了自己对于编写代程时的各种思维方式,代码并非越复杂越好,优秀的代码应该是简洁高效,尽量减少不必要的实体与不需要的流程,时间复杂度与空间复杂度的取舍不仅要应对当前内容,更要思考面对及大数据量时,应该如何处理,如何避免出现不可知的错误