目录
2.7.2 处理 error recover() panic()
一、 数组 切片 map
1.1 数组
1.1.1 数组定义
① var 数组名 [数组大小]数据类型
② 数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 -> 8 int32->4
③ 访问数组元素 数组名[下标]
④ 数组下标必须在指定范围内使用,否则报 panic:数组越界
⑤ 长度是数组类型的一部分,在传递函数参数时 需要考虑数组的长度
1.1.2 for range
① 遍历访问数组的元素
② for index, value := range array01 { }
package main
import "fmt"
func main(){
//数组的定义 var XXX [count]string
//数组的初始化
//①
var a = [3]string{"a", "b", "c"}
fmt.Println(a)
//②
var b = [3]string{2: "asfs"}
fmt.Println(b)
//③
var c = [...]string{"acv", "adaw", "edaf"} //常用
fmt.Println(c)
//数组的遍历
for _, value := range a { //index不要了
fmt.Println(value)
}
//多维数组
var nums1 [3][4]string //初始化
//赋值
nums1[0] = [4]string{"1", "2", "3", "4"} // 第一行四个元素
//……
nums1[2] = [4]string{"3", "4", "5", "6"}
//遍历(3种)
for i := 0; i < len(nums1); i++ {
fmt.Println(nums1[i])
}
fmt.Println("--------------")
for _, value := range nums1 {
fmt.Println(value)
}
fmt.Println("--------------")
for _, value1 := range nums1 {
for _, value2 :=range value1{
fmt.Print(value2 + " ")
}
fmt.Println()
}
}
1.2 切片slice
① 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制
② 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样
③ 切片的长度是可以变化的,因此切片是一个可以动态变化数组
1.2.1 切片定义
var 切片名 [ ]类型
① slice 的确是一个引用类型
② slice 从底层来说,其实就是一个数据结构(struct 结构体)
type slice struct {
ptr *[2]int
len int
cap int
}
1.2.2 切片使用
1.2.2.1 初始化
法一:定义一个切片,然后让切片去引用一个已经创建好的数组
法二:通过 make 来创建切片 (最常用)
基本语法:var 切片名 [ ]type = make([ ]type, len, [cap])
type: 就是数据类型
len : 大小
cap :指定切片容量,可选, 如果你分配了 cap,则要求 cap>=len
make也会创建一个数组。是由切片在底层进行维护
法三:定义一个切片,直接就指定具体数组,使用原理类似 make 的方式
1.2.2.2 遍历
切片的遍历和数组一样,也有两种方式
for 循环常规方式遍历
for-range 结构遍历切片
1.2.2.3 说明
① 切片初始化时,仍然不能越界。范围在 [0-len(arr)] 之间,但是可以动态增长
② cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素
③ 切片必须初始化
package main
import "fmt"
func main(){
//定义切片 var XXX []string
//初始化:1.直接从数组里面取 2.建立一个新的切片 3.make方法
//①
var a = [5]string{"1", "2", "3", "4", "5"}
var b = a[0:2] //左开右闭( ]
fmt.Println(b)
//②
var c = []string {"a", "c", "d"} // var c = []string {2: "afaef"}
fmt.Println(c)
//③
d := make([]string, 3)
d[0] = "1"
d[2] = "5"
fmt.Println(d)
//扩充
var e = []string {"a", "c", "d"}
e = append(e, "f", "d") //解释中的……表示后面可以选择性的加n项
fmt.Println(e)
var f = []string {"g", "h", "j"}
e = append(e, f...)
fmt.Println(e)
//删除d
var g = []string {"a", "b", "c", "d", "e", "f", "g"}
g =append(g[:3], g[4:]...)
fmt.Println(g)
//切片的复制
h := g[:]
fmt.Println(h)
//copy
var new = make([]string, len(h)) // 空间为0,不会动态分配空间
copy(new, h)//前面是新的
fmt.Println(new)
}
1.3 map
1.3.1 基本语法
map[keytype]valuetype
① map需要初始化(make)
② key-value是无序的
③ map是引用数据类型
④ map 的容量达到后,再向 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动态的增长 键值对(key-value)
⑤ map 的 value 也经常使用 struct 类型,更适合管理复杂的数据
1.3.2 map的定义
//定义 map是无序的,方便查询
var mymap = map[string]string{ //【序号的格式】内容的格式
"go": "csgo", //有,号
}
fmt.Println(mymap)
1.3.3 map的初始化
①:
var test1 = map[string]string {}
②:
var test2 = make (map[string]string, 3)
test2["var"] = "var"
fmt.Println(test2["var"])
var map1 = map[string]string {
"a": "b",
"c": "d",
"e": "f",
"g": " ",
}
1.3.4 map的遍历
//map遍历:是无序的
for key, value := range studentMap {
fmt.Println(key, value)
}
fmt.Println("--------------------")
for _, value := range studentMap {
for _, value1 := range value {
fmt.Println(value1)
}
}
1.3.5 增删查改
1.3.5.1 增/改
如果 key 还没有,就是增加,如果 key 存在就是修改。
1.3.5.2 删
使用delete()
内建函数从map中删除一组键值对,delete()
函数的格式如下:
delete(map, key)
map:表示要删除键值对的map
key:表示要删除的键值对的键
如果要删除 map 的所有 key ,没有一个专门的方法一次删除,可以map = make(...),make 一个新的,让原来的成为垃圾,被 gc 回收(把map重新赋值)
1.3.5.3 查
value, ok := map1["g"] // value表示map["g"]的元素 ok表示是否存在,返回bool类型
fmt.Println(value, ok)
if !ok {
fmt.Println("false")
}else {
fmt.Println("ok" + value)
}
1.3.5.4 map长度
len (Type)
1.3.6 map切片
切片的数据类型如果是 map,则我们称为 slice of map,map 切片,这样使用则 map 个数就可以动态变化了。
使用一个 map 来记录 monster 的信息 name 和 age, 也就是说一个 monster 对应一个 map,并且monster的个数可以动态的增加=>map 切片
package main
import "fmt"
func main() {
var 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"] = "5000"
}
//继续扩展 需要用append
newMonster := map[string]string {
"name" : "新时代妖怪",
"age" : "100",
}
monsters = append(monsters, newMonster)
fmt.Println(monsters)
}
1.3.7 map的排序
① golang 中的 map 默认是无序的
② golang 中 map 的排序,是先将 key 进行排序,然后根据 key 值遍历输出即可
map1 := make(map[int]int, 10)
map1[10] = 100
map1[1] = 23
map1[7] = 1
map1[3] = 22
fmt.Println(map1)
var keys []int
for k, _ := range map1 {
keys = append(keys, k)
}
//排序
sort.Ints(keys)
fmt.Println(keys)
for _, j := range keys {
fmt.Printf("map1[%v]=%v\n",j,map1[j])
}
}
1.3.8 map的应用举例
① 统计单词的出现次数:
//统计单词出现的次数
var str = "how do you do"
var strSlice = strings.Split(str, " ")
fmt.Println(strSlice)
var strMap = make(map[string]int)
for _, v := range strSlice {
strMap[v]++
}
fmt.Println(strMap) //map[do:2 how:1 you:1]
}
②:
1) 使用 map[string]map[string]sting 的 map 类型
2) key: 表示用户名,是唯一的,不可以重复
3) 如果某个用户名存在,就将其密码修改"888888",如果不存在就增加这个用户信息,(包括昵称nickname 和 密码 pwd)。
4) 编写一个函数 modifyUser(users map[string]map[string]string, name string) 完成上述功能
func modifyUser(users map[string]map[string]string, name string) {
if users[name] != nil {
users[name]["pwd"] = "888888"
} else {
users[name] = make(map[string]string, 2)
users[name]["pwd"] = "888888"
users[name]["nickname"] = "昵称" + name
}
}
func main(){
users := make(map[string]map[string]string)
users["smith"] = make(map[string]string, 2)
users["smith"]["pwd"] = "feasfeafwe"
users["smith"]["nickname"] = "cat"
modifyUser(users, "tom")
modifyUser(users, "mary")
modifyUser(users, "smith")
fmt.Println(users)
}
二、 函数
2.1 函数概念
① 函数是一等公民
② 基本语法:
func 函数名 (形式参数) (返回值列表) { }
③ 定义一个函数:
1.函数可以作为变量使用
2.匿名函数,闭包函数
3.接口
④ 返回值列表也可以是多个 支持对函数返回值命名
⑤ 在 Go 中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量
了。通过该变量可以对函数调用
res := a(10, 40)
⑥ Go 函数不支持函数重载
⑦ Go 支持可变参数
func add2(desc string, items ...int)(sum int) { //前面括号是函数变量,后面是返回值 ...表示可以传进来任意个
for _, value :=range items {
sum += value;
}
return sum
}
2.2 匿名函数
① 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。
res1 := func (n1 int, n2 int) int { 内容 }
func callback(y int, f func(int, int)) {
f(y, 5)
}
② 全局匿名函数
Fun1 = func (n1 int, n2 int) int { } //Fun1就是一个全局匿名变量
res := Fun1(1, 4) //全局匿名函数调用
2.3 闭包
闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
(待学习)
package main
import "fmt"
//闭包:???、
func diejia() func() int {
local :=0
return func() int {
local += 1
return local
}
}
func main(){
nextfunc :=diejia() //函数传递
for i := 0; i<5; i++ {
fmt.Println(nextfunc())
}
println("------------------")
nextfunc2 :=diejia()
for i := 0; i<3; i++ {
fmt.Println(nextfunc2())
}
}
2.4 init函数
① 每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也就是说 init 会在 main 函数前被调用。
② 如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程全局变量定义->init
函数->main 函数
③ init 函数最主要的作用,就是完成一些初始化的工作
func init() {
Age = 100
Name = "Tom"
}
2.5 defer锁(mutux)
var mu sync.Mutex
mu.lock()
defer mu.Unlock
//内容……
① 连接数据库、打开文件、开始锁, 无论如何 最后都要记得去关闭数据库、关闭文件、解锁
为了在函数执行完毕后,及时的释放资源,Go 的设计者提供 defer (延时机制)。
② defer顺序: 栈(先进后出)
执行到defer后,暂不执行,把defer后面的语句压入“defer栈”中
③ defer可以影响到返回值
package main
import "fmt"
func res () (re int) {
defer func() {
re++
}()
return 20
}
func main() {
//defer顺序: =栈 先进后出
defer fmt.Println("1")
defer fmt.Println("2")
fmt.Println("main")
//defer可以影响到返回值
fmt.Println(res())
}
2.6 内置函数
2.6.1 len new make
1) len:用来求长度,比如 string、array、slice、map、channel
2) new:用来分配内存,主要用来分配值类型,比如 int、float32,struct...返回的是指针
3) make:用来分配内存,主要用来分配引用类型,比如 channel、map、slice。
2.6.2 make
1、func make(Type, size IntegerType) Type
2、make 的返回类型与其参数相同,而非指向它的指针。其具体结果取决于具体的类型:
① 切片: 因此make([]int, 0, 10) 会分配一个长度为0,容量为10的切片。
② 映射:初始分配的创建取决于size,但产生的映射长度为0。size可以省略,这种情况下就会分配一一个小的起始大小。
③ 通道:通道的缓存根据指定的缓存容里初始化。若size为零或被省略,该信道即为无缓存。
2.6.3 时间和日期相关函数
① time.Time 类型,用于表示时间
rec := time.Now()
② 执行时间
package main
import (
"fmt"
"strconv"
"time"
)
func test() {
str := ""
for i := 0; i< 100000; i++ {
str += "hello" + strconv.Itoa(i)
}
}
func main() {
//执行前,先获取当前的unix时间戳
start := time.Now().Unix()
test()
end := time.Now().Unix()
fmt.Printf("执行text()耗时间为%v秒\n", end-start)
}
2.7 错误处理
① 在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃.)
② 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可以在捕获到错误后,给管理员一个提示(邮件,短信。。。)
③ 这里引出我们要将的错误处理机制
2.7.1 基本说明
① Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。
② Go 中引入的处理方式为:defer, panic, recover
③ 这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理
2.7.2 处理 error recover() panic()
① 使用 defer+recover 来处理错误
② errors.New("错误说明") , 会返回一个 error 类型的值,表示一个错误
③ panic 内置函数 ,接收一个 interface{}类型的值(也就是任何值)作为参数。可以接收 error 类
型的变量,输出错误信息,并退出程序
package main
import (
"errors"
"fmt"
)
func A() (int, error) {
//多个defer会形成栈的顺序执行
defer fmt.Printf("main1")
//recover 需要在defer 之间
defer func() {
r := recover()
if r != nil {
fmt.Println("执行到了recover函数:", r)
}
}()
//panic 会直接终止程序,不再往下执行,很少用
defer panic("this is a panic")
return 0, errors.New("this is a error")
}
func main() {
if _,err := A(); err != nil { fmt.Println(err) }
//简写,是 _,err := A() if err != nil
//defer和普通程序的顺序
fmt.Println("main外程序")
defer fmt.Println("main外程序的defer")
}
三、 结构体
3.1 结构体声明
type 结构体名称 struct {
field1 type
field2 type
}
① 结构体是值类型 默认是值拷贝
② 结构体不需要初始化
③ 结构体的所有字段在内存中是连续的
3.2 结构体使用
3.2.1 结构体定义
type Person struct {
name string
age int
addr string
heigth int
}
3.2.2 结构体初始化
①:
person := Person{"zz", 18, "aa", 18}
②:
person2 := Person{
name: "cc",
heigth: 12,
}
③:go 编译器底层 对 person.Name 做了转化 (*person).Name。
person3 := new(Person) //var person *Person = new (Person)
person3.name = "dd" //等于(*person3).nam
④:
person4 := &Person{} //var person *Person = &Person{}
person4.name = "ww"
3.2.3 结构体和切片组合
var persons []Person
//第一种添加方式
persons = append(persons, person)
//第二种添加方式
persons = append(persons, Person{
name: "123",
})
//第三种添加第一种添加方式方式
persons2 := []Person{
{name: "123", age: 12},
{age: 11},
}
fmt.Println(persons2)
3.2.4 结构体和函数
package main
import "fmt"
type Person struct {
name string
age int
}
//第一种方法,值传递
func (p Person) print() {
p.age = 20
fmt.Printf("name:%s,age:%d\r\n", p.name, p.age)
}
//第二种方法,引用传递
func (p *Person) print2() {
p.age = 20
fmt.Printf("name:%s,age:%d\r\n", p.name, p.age)
}
func main() {
p :=Person{
name: "文件箱",
age: 100,
}
p1 :=&Person{
name: "文件箱",
age: 100,
}
p.print()
fmt.Println(p.name, p.age)
fmt.Println("-----------")
p1.print2()
fmt.Println(p1.name, p1.age)
}
3.2.5 嵌套匿名结构体
package main
import "fmt"
type person struct {
name string
age int
}
//嵌套
//第一种定义
type student struct {
p person
fen int
}
//第二种定义 匿名结构体
type student2 struct {
person
fen int
name string
}
func main() {
s :=student{
person{
"name",
18,
}, 90,
}
fmt.Println(s)
fmt.Println(s.p.name)
s1 :=student2{
person{
"name",12,
},10,"name2",
}
s1.name = "bobby"
fmt.Println(s1)
fmt.Println(s1.age)
fmt.Println(s1.name)
}
3.3 方法
3.3.1 介绍
在某些情况下,我们要需要声明(定义)方法。比如 Person 结构体:除了有一些字段外( 年龄,姓名..),Person 结构体还有一些行为比如:可以说话、跑步..,通过学习,还可以做算术题。这时就要用方法才能完成。
方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法
type A struct {
Num int
}
func (a A) test() {
fmt.Println(a.Num)
}
① func (a A) test() {} 表示 A 结构体有一方法,方法名为 test
② (a A) 体现 test 方法是和 A 类型绑定的
3.3.2 方法的定义
func (recevier type) methodName(参数列表) (返回值列表) {
方法体
return 返回值
}
例1:给 Person 结构体添加 speak 方法,输出xxx 是一个好人
func (p Person) speak() {
fmt.Println(p.name, "是个好人")
}
例2:给 Person 结构体添加 getSum 方法,可以计算两个数的和,并返回结果
func (p Person) getSum(n1,n2 int) int {
return n1 + n2
}
3.3.3 方法的调用
var p Person
p.speak()
res := p.getSum(10, 20)
3.4 type
type 的用法
1、给其他类型起别名
2、定义一个新类型
3、定义一个结构体
4、定义接口
package main
import (
"fmt"
"strconv"
)
type myint int
func (mi myint) string() string { return strconv.Itoa(int(mi))}
func main() {
//给类型起别名
type myint2 = int
var a int = 10
var b myint2 = 10
fmt.Println(a + b)
//定义一个新类型
//type myint int
var c myint = 12
var d int = 10
//fmt.Println(c + d) //myint 为一个新的类型不能和int类型进行相加
fmt.Println(int(c) + d)
fmt.Printf("%T\r\n", c)
fmt.Println(c.string())
}
四、 指针
4.1 指针
① 指针存的是一个地址 比如:var ptr *int, 使用*ptr 获取 ptr 指向的值
②值类型和引用类型:
值类型内存通常在栈中分配 引用类型内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由 GC 来回收
1) 值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
2) 引用类型:指针、slice 切片、map、管道 chan、interface 等都是引用类型
③ 指针的两个不同 :
1.可以做常量来修改,进行了优化
2.指针是阉割版 在unsafe包中可以使用
④ 指针的初始化(必须):
初始化两个关键字,map、channel、slice初始化推荐使用make方法
指针初始化推荐使用new函数,指针要初始化否则会出现nil pointer
map必须初始化
⑤swap、函数和指针区别:
swap改的是篮子里的值
函数传递的本质是 复制一份副本
指针传递的本质是 地址的传递
package main
import "fmt"
type person struct {
name string
age int
} //struct可以不初始化
//指针值的交换
func swap(a, b *int) {
p := *a
*a = *b
*b = p
}
func main() {
p := &person{
"wjx", 12,
}
//指针的定义
p.name = "wyh"
(*p).age = 18 //指针的取值 不能用C语言的p->age
fmt.Println(p)
//var pi *person 不行 必须要初始化
ps := &person{} //第一种初始化
var emptyPerson person
pi := &emptyPerson //第二种初始化
var pp = new(person) //第三种初始化
fmt.Println(ps.name)
fmt.Println(pi.name)
fmt.Println(pp.name)
a, b := 1, 2
swap(&a, &b) //不行
fmt.Println(a, b)
}
4.2 nil
package main
import "fmt"
type Person struct {}
func main() {
var m1 = map[string]string{}
m2 := make(map[string]string) //初值empty 不会报错
if m1 == nil {
fmt.Println("m1 is nil")
}
if m2 == nil {
fmt.Println("m2 is nil")
}
m1["name"] = "boby"
m2["name"] = "boby1"
for key, value := range m1 {
fmt.Println(key, value)
}
for key, value := range m2 {
fmt.Println(key, value)
}
}
五、 接口
接口是一种特殊的类型
① 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。
② Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有 implement 这样的关键字
③ Golang 接口中不能有任何变量
④ interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
5.1 基本语法
interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个自定义类型(比如结构体 Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。
5.1.1 接口的定义
type 接口名 interface {
method1(参数列表)(返回值列表)
method1(参数列表)(返回值列表)
}
5.1.2 接口的实现
func(t自定义类型)method1(参数列表)返回值列表{
//方法实现
}
func(t自定义类型)method2(参数列表)返回值列表{
//方法实现
}
实现接口方法:(结构体/自定义类型)
type 自定义类型 struct { }
5.1.3 接口案例
package main
import "fmt"
type animal interface {
move()
eat(string)
}
type cat struct {
name string
feet int8
}
func (c cat) move() {
fmt.Println("走猫步")
}
func (c cat) eat(food string) {
fmt.Printf("吃%v牌猫粮\n", food)
}
func main(){
var a1 animal //interface就是一种特殊类型
bc := cat{
name: "波斯猫",
feet: 4,
}
a1 = bc //bc要等于a1 就需要满足a1的类型
a1.eat("超人") //调用eat
fmt.Println(a1)
}
5.2 指针为接受者
一般都使用指针接受者,而不是值接收者。下例为伪代码,
func (c *cat) move() {
fmt.Println("走猫步")
}
func (c *cat) eat(food string) {
fmt.Printf("吃%v牌猫粮\n", food)
}
func main(){
var a1 animal //interface就是一种特殊类型
//bc := cat{
// name: "波斯猫",
// feet: 4,
//}
bc := cat {"波斯猫", 4}
a1 = &bc //bc要等于a1 就需要满足a1的类型
a1.eat("超人") //调用eat
fmt.Println(a1)
}
5.3 接口嵌套和多个接口实现
package main
import (
"fmt"
)
//接口还可以嵌套
//一个结构体可以实现多个接口
type animal interface {
mover
eater
}
type mover interface {
move()
}
type eater interface {
eat(string)
}
type cat struct {
name string
feet int8
}
func (c *cat) move() {
fmt.Println("走猫步")
}
func (c *cat) eat(food string) {
fmt.Printf("吃%v牌猫粮\n", food)
}
func main() {
var a1 animal
c1 := cat{"波斯猫", 4}
a1 = &c1
a1.eat("超人")
fmt.Println(a1)
}
5.4 空接口
5.4.1 作为函数的参数
func show (a interface{}) (){
fmt.Printf("type:%T, value:%v\n", a, a)
}
5.4.2 作为value的值
func main() {
var m1 map[string]interface{}
m1 = make(map[string]interface{}, 16)
m1["name"] = "cxk"
m1["age"] = 38
5.4.3 空接口类型断言
想知道空接口中接受的值是什么
func assign(a interface{}) {
fmt.Printf("%T\n", a)
switch t := a.(type) {
case string:
fmt.Println("是一个字符串:", t)
case int:
fmt.Println("是一个int:", t)
case int64:
fmt.Println("是一个int64:", t)
case bool:
fmt.Println("是一个布尔类型:", t)
default:
panic("not supported type")
}
}
func add (a, b interface{}) interface{} {
switch a.(type) {
case string:
as, _ := a.(string)
bs, _ := b.(string)
return as + bs
case int:
ai, _ :=a.(int)
bi, _ :=b.(int)
return ai + bi
case float64:
af, _ :=a.(float64)
bf, _ :=b.(float64)
return af + bf
default:
panic("not supported type")
}
}