GO开发
Golang被誉为21世纪的C语言
- 2012.3 - 2020.2 1.0 - 1.14版本
为什么选择GO
- 继承python的简洁 & C语言的性能于一身
环境搭建
执行golang代码。
- go run **.go
或者go文件中 go build 会生成一个文件 在执行可执行文件
- 再或者 go install。会将可执行文件放到bin目录
创建目录
目录结构如下: xxx - bin - pkg - src //用于存放项目代码的目录
- 环境变量
GOROOT, GO编译器安装目录 GOPATH, 用于存放项目代码, 编译后的可执行文件, 编译后的包文件(go 1.11版本后 ==> go.mod). GOBIN, 编译后的可执行的文件存放的目录
开发工具
- goland ide
- vscode 编辑器
配置
- 字体
- 参数提示
项目开发
新项目
打开已经存在的项目
注意: 项目放在$GOPATH/src目录。
GO语法的使用
Go包管理初识 : 知道项目中文件和文件 文件和文件夹之间的关系
输出 , 写代码 在go编译器运行时会在屏幕显示内容
Go的数据类型
- 整型
- 字符串
- 布尔类型
变量 & 常量。当作是昵称 别名
输入
条件语句 if else elif
1. GO包管理
- 一个文件夹可以称为一个包
- 在文件夹(包)中可以创建多个文件
- 在同一个包下的每个文件中必须指定包名称 且必须相同
重点:
- main包。如果是main包 必须写一个main函数 此函数就是项目的入口(main) 编译生成可生成可以执行进制文件 - 非main包
2. 输出
在终端将想要展示的数据显示出来, 例如欢迎登录, 请输入用户名等
- 内置函数
- println
- fmt包(推荐使用)
- fmt.print
- fmt.println
扩展: 进程中有 stdin/stdout/stderr
fmt格式化输出 fmt.printf() 占位符 %s 字符串 %d 整型 %f 小数 %.2f 保留两位小数
注释: 单行注释 // 多行注释 /**/
3. 数据类型
- 整型
- 字符串
- 布尔型 false true
4. 变量var
就是给各种类型的数据起别名 为了方便引用。还可以暂时存储数据
变量名必须只包含:字母、数字、下划线
变量不能以数字开头
不能使用golang中的内置变量 关键字
5. 输入
fmt.scanf()
6. 变量简写
var name string = "ha" var name = "ha" name := "ha" 因式分解 var( name = "zj" age = 18 hobby = "sing" )
go编译器会认为如果声明或者声明且赋值的变量没有进行引用, 就要被删除掉
7. 变量作用域 (namespace名称空间)
如果我们定义了大括号, 那么在大括号中定义的变量
- 不能被上级使用
- 可以在同级中引用使用
- 子级可以引用上级的变量
- 函数内的方法都是局部变量
- 定义全局变量不能使用 := 简写
全局变量和局部变量(都可以进行因式分解):
- 全局变量: 在函数外的定义的非简写变量称为全局变量
- 局部变量: 在{}内定义的变量为局部变量
8. 变量的赋值及内存相关
name := "zj" fmt.println(name, &name) //打印变量内容及内存地址
9. 常量const
不可修改的变量
// 常量 - 不可修改的变量 const age = 12 //age = 13 fmt.Println(age) const ( va = 123 vb = "111" )
10. iota
可有可无的东西 可以理解为计数器
// iota 计数累加器 从0开始 const ( v1 = iota v2 v3 v4 ) fmt.Println(v1, v2, v3, v4)
11. 输入
// 让用户输入数据, 完成数据交互 fmt.scanf fmt.scan fmt.scanln Scan var name string var age int fmt.Println("请输入用户名,姓名:") _, error := fmt.Scan(&name, &age) // _是count输入的总值 , error 是报错nil报错是输入争取 如果不是就是报错的 //fmt.Println(name, age) //fmt.Println(error) if error == nil { fmt.Println(name, age) } else { fmt.Println("输入值错误", error) }
- 常用的为scanln
- 但是scanln的问题在于如果输入的变量存在空格 默认取空格之前的问题 所以我们要使用os.stdin标准输入
// os.stdin标准输入 fmt.Println("请随便输入点东西:") reader := bufio.NewReader(os.Stdin) // line 从stdin中读取一行的数据 数据类型为byte 可以转换为字符串 // reader默认一次只能读取4096个字节 如果读取的字节数小于4096 isprefix=false line, _, _ := reader.ReadLine() // 通过string可以将byte类型的数据换换成字符串 类似于python的decode fmt.Println(string(line))
12. 条件语句
最基本的条件语句
if 条件{ 条件成立执行语句 } else { 条件不成立执行语句 } // 例子 var ( username string passwd string ) fmt.Print("username:") fmt.Scanln(&username) fmt.Print("password:") fmt.Scanln(&passwd) if username == "zj" && passwd == "123456"{ fmt.Printf("用户名%s登录成功", username) }else { fmt.Println("用户名或密码错误") }
多条件判断
if { } else if { } else { }
- 嵌套
if { if { } else { } } else { }
Switch case 语句 类似于shell中的switch case 语句
for循环语句
goto 语法 , 不建议使用
字符串的格式化 ,
运算符的优先级
1.switch case 语句
package main import "fmt" func main() { var number int _, err := fmt.Scanln(&number) if err == nil { switch number { case 1 : fmt.Println("1111") case 2: fmt.Println("2222") case 3: fmt.Println("3333") default: fmt.Println("无法识别你输入的内容") } } } switch 数据类型必须一致
2.for 循环
//1.死循环 for 或者 for true //fmt.Println("开始") //for true { // fmt.Println("123") // time.Sleep(time.Second * 2) // 一秒为单位 time.second // break //} //fmt.Println("end") number := 1 for number < 5 { fmt.Println(number) number += 1 } fmt.Println("end")
// for 循环条件判断 for i:=1; i<10{ } // 循环10次 进行++ for i:=1; i<10; i++{ fmt.Println(i) } count := 0 for i:=1; i<100; i++{ fmt.Println(i) count += i } print(count)
- continue
// 退出本次循环 for i := 1; i <= 10; i++ { if i == 7 { continue } fmt.Println(i) }
- break
//跳出整个循环 for i := 1; i < 5; i++ { if i == 4 { fmt.Println("bye") break } fmt.Println(i) }
*对 for 进行打标签, 然后可以通过break和continue就可以时间多层循环的跳出和中止
test1: for i := 1; i < 5; i++ { for j := 1; j < 3; j++ { if j == 2 { //continue test1 break test1 } fmt.Println(i, j) } }
goto 语句
//跳跃到指定的行向下执行代码 package main import "fmt" func main() { shibai: var name string fmt.Print("请输入用户名:") _, err := fmt.Scanln(&name) fmt.Println(name) if len(name) < 1 { goto shibai } if err == nil { if name == "zj" { fmt.Println("牛皮") } else { fmt.Println("弟弟") goto shibai } } }
- 字符串格式化
// 格式化字符串 package main import "fmt" func main() { var name string var age int var score float64 fmt.Println("please input name") fmt.Scanln(&name) fmt.Println("please input name") fmt.Scanln(&age) fmt.Println("please input name") fmt.Scanln(&score) result := fmt.Sprintf("name is %s, age is %d, score is %.2f", name, age, score) fmt.Println(result) }
必备基础知识
- 进制 - 单位 - 字符编码
golang 数据类型
1.整型 var v1 int8 = 10 var v2 int16 = 20 v3 := int16(v1) + v2 注意: - 低位转高位是没有问题 - 但是高位转成低位 是很有可能出问题的 整型转换成字符串类型:Itoa v1 := 19 result := strconv.Itoa(v1) fmt.Println(result, reflect.TypeOf(result)) 2.字符串转换成整形Atoi v2 := "19" res, err := strconv.Atoi(v2) if err == nil { fmt.Println(res) } else { fmt.Println("转换失败请检查原数据") }
- 进制转换
go:
- 10进制是以整型的方式存在 - 其他进制 都是以字符串形式存在的
- 整型 : 10进制数 转换成其他进制 (FormatInt)
// 使用方法进行进制转换 v1 := 9 // 通过strconv.FormatInt方法进行进制数转换 int类型必须是int64 所以必须进行声明 第二个参数代表的是转换成几进制 // 10进制转换成其他进制 v2 := strconv.FormatInt(int64(v1), 8) fmt.Println(v2, reflect.TypeOf(v2))
- 其他进制转换成10进制parseint
// 其他进制转换成整形=> 10进制 // parseint(其他进制数据, 原本的进制类型, 要转换成的进制类型 ) // 只要转换成功 转化出来的数据类型就是int64类型 data := "10001000" res, err := strconv.ParseInt(data, 2, 10) //fmt.Println(err) if err == nil { fmt.Println(res, reflect.TypeOf(res)) }
- 自己出的小练习 将2进制的10001000转换成8进制数 思路:因为2进制和8进制代码层面不能相互转换 所以我们先将2进制转换成10进制 在将10进制装换成8进行 data := "10001000" res, err := strconv.ParseInt(data, 2, 10) //fmt.Println(err) if err == nil { fmt.Println(res, reflect.TypeOf(res)) test := strconv.FormatInt(int64(res), 8) fmt.Println(test) }
![image-20201228175014934](/Users/mac/Library/Application Support/typora-user-images/image-20201228175014934.png)
常见的数据运算
math 函数 math abs math.Pow(2 ,5) // 2的五次方 math.max(1, 2) // 两个值相比较 取大值 math.min(1, 2) // 两个值相比较 取小值
指针/nil/声明变量/ new
- 声明变量
### 指针/nil/声明变量/new * 声明变量 var v1 int v2 := 999 * 指针 (作用就是节省内存) var v3 *int v4 := new(int) nil v3 -> ^ 0x0 0 v4 -> ^ 0x0 * new 关键字 * new用于创建内存并进行内部数据的初始化。并返回一个指针类型 * nil * nil指go语言中的空值 * var v6 *int * var v7 * int32 * 超大整型 var v1 big.int var v2 *big.int var new(big.int) v1.setint64(1000) var v1 big.Int v1.SetString("123456767898786898092355262738453726315", 2) fmt.Println(v1.String(), reflect.TypeOf(v1.String()))
go Array数组
数组是固定长度的特定类型元素组成的序列 一个数字由零个或多个元素组成 数组的长度是固定的,因此Go更常用slice(切片, 动态的增长或收缩序列) 数组是值类型, 用索引下标访问每一个元素, 范围是0 - len-1, 访问超出数组长度范围 会panic异常 // Go Array 数组中没有复制的数组 会有相应的默认值 // 声明数组 , 并且个数组中的元素赋值 var intArr [5]int fmt.Println(intArr) intArr[0] = 12 intArr[1] = 34 fmt.Println(intArr) // 声明数组 并且直接赋值 var namestr [5]string = [5]string{"1", "2"} fmt.Println(namestr) var namestr2 = [5]string{"1"} fmt.Println(namestr2) // 取数组最后一个元素 顺便展示指定索引赋值 var namestr2 = [5]string{"1", 4: "124124"} fmt.Println(namestr2, namestr2[len(namestr2)-1]) // 自适应数组大小[...] var namestr3 = [...]string{"zj", "zjj", "zjjj"} fmt.Println(namestr3) // 数据结构题类型数组 var namestr5 = [...]struct{ name string age int }{ {"zj", 18}, {"ccn", 18}, } fmt.Println(namestr5) // 数组循环 for i:=0; i < len(namestr3);i++{ fmt.Println("for " + namestr3[i]) } for index, value := range namestr3{ fmt.Println(index, value) } ** 数组注意事项: 数组是多个相同数据的组合, 且长度固定, 无法扩容 [5]int 数组使用步骤: 1.声明数组 2.给数组元素赋值 3.使用数组 4.数组索引从0开始 不能index of range 5.Go数组是值类型, 变量传递默认是值传递, 因此会进行值拷贝 6.修改原本的数组, 可以使用引用传递(指针)
- 字符串的本质
计算机中所有的操作和数据最终都是二进制 : 010101010...
package main import ( "fmt" "strconv" "unicode/utf8" ) func main() { var name string = "张无忌" fmt.Println(name[1], strconv.FormatInt(int64(name[1]), 2)) fmt.Println(len(name)) age := 123 res := strconv.Itoa(age) // len字节长度 fmt.Println(len(res + "")) // 字符串转换为一个 "字节集合" byteset := []byte(name) fmt.Println(byteset) // [229 188 160 230 151 160 229 191 140] // 字节集合转换成字符串string fmt.Println(string(byteset)) // rune集合 testName := "11223ddd" runeset := []rune(testName) fmt.Println(runeset,) fmt.Println(string(runeset[:5])) // 字符串长度获取 // len方法获取的是字节的长度 test2Name := "122333zghang张鉴" runeset2 := []rune(test2Name) fmt.Println(len(runeset2)) // or 使用utf8的方法获取长度 test2Namelength := utf8.RuneCountInString(test2Name) fmt.Println(test2Namelength) }
字符串常见的使用方法 // 获取长度 var name string = "张无极" fmt.Println(name) fmt.Println(utf8.RuneCountInString(name)) // 是否以xx开头 name2 := "张无忌" reslut := strings.HasPrefix(name2, "张") fmt.Println(reslut) // 是否以xx结尾 result := strings.HasSuffix(name2, "无忌") fmt.Println(result) // 是否包含 类似 python in result2 := strings.Contains(name2, "无") fmt.Println(result2) fmt.Println("===============") // 全变大写 类似 python upper stringTest1 := "fff afa fUUUU 涨" fmt.Println(strings.ToUpper(stringTest1)) // 全变小写 fmt.Println(strings.ToLower(stringTest1)) // 替换所有是-1 是左到右第一个 是1 fmt.Println(strings.Replace(stringTest1, "f", "", 1)) fmt.Println(strings.Replace(stringTest1, "f", "", 2)) fmt.Println(strings.Replace(stringTest1, "f", "", -1)) // 分割 split splitTest := strings.Split(stringTest1, " ") fmt.Println(splitTest[len(splitTest)-1]) // 拼接 mes := "你好" + "我好" // 不建议使用 fmt.Println(utf8.RuneCountInString(mes)) // 高效率的字符串拼接方法 非常推荐使用 var builder strings.Builder builder.WriteString("我爱你") builder.WriteString("中国") value := builder.String() fmt.Println(value, utf8.RuneCountInString(value)) // 字符串转换成int var num int = 12 fmt.Println(strconv.Itoa(num), reflect.TypeOf(strconv.Itoa(num))) textStr := "2141414141414" fmt.Println(strconv.Atoi(textStr)) textStr2 := "0101010010" fmt.Println(strconv.ParseInt(textStr2, 2 ,10)) fmt.Println(strconv.FormatInt(int64(num), 16)) // 字符串 转换 v1 := string(100) fmt.Println(v1) v2, size := utf8.DecodeRuneInString("d") fmt.Println(v2, size)
- 索引切片和循环
package main import ( "fmt" ) func main() { var name3 string = "你好你好" // 字节索引 切片 vs1 := name3[0:3] fmt.Println(vs1) // 手动循环所有的字符 range for index, item := range name3 { if index == 6 || index == 3 { fmt.Println(index, string(item)) } //fmt.Println(index, string(item)) } // 转换成rune集合 dataList := []rune(name3) fmt.Println(dataList, string(dataList)) }
- 数组
数组, 定长且元素类型一致的数据集合 (类型必须相同)
// 声明加赋值自适应变量 var numbers = [...]int{1, 2, 3} numbers[0] = 2 fmt.Println(numbers) // 声明定长的数组 不足的用空格代替 len为5 var numbers = [5]string{"test", "alex", "didi"} fmt.Println(numbers) // 指定索引赋值 var namestr2 = [5]string{"1", 4: "124124"} fmt.Println(namestr2, namestr2[len(namestr2)-1]) // 声明指针类型的数组 (指针类型, 不会开辟内存初始化数组中的值) var numbers *[]int
数组的特性(可变和拷贝)
- 元素值是可以被修改的 但是数组一旦声明开辟了内存空间之后 数组的大小就不可以改变
- 可以进行copy
// 修改重新赋值 var numbers = [...]int{1, 2, 3} numbers[0] = 2 fmt.Println(numbers)
- 长度切片[0:2]
- 数组的嵌套
二维数组 // 含义为 数组中有3个元素 每个元素是一个含有2个int元素的数组 var nestData [3][2]int var nestData = [3][2]int{{1, 2}, {3, 4}} nestData[2] = [2]int{11, 22} nestData[2][1] = 6666 fmt.Println(nestData)
最后三种数据结构概览
- 切片
- 字典
- 指针
#切片
切片, 动态数组
切片是Go中重要的数据类型,每个切片对象内部都维护着 : 数组指针,切片长度,切片容量 三个数据
- 创建切片
// 每一个切片内部都存储了三个数据 数组指针*array 、 切片长度len 切片容量[:3] 再向切片中追加数据个数大于容量时, 内部会自动扩容且每次扩容都是当前容量的2倍 [:3] -> [:6] // 创建切片 var nums []int // 创建切片 并赋值 var nums = []int{1,2,3} // 创建切片 // make只用于 切片、字典、channel // 推荐使用 int 类型 默认长度为2 容量为3 cap() var users = make([]int,2,3)
- 自动扩容
v1 := make([]int,1,3) v2 := append(v1, 123) 注意: 扩容前和扩容后的切片内存地址 是不同的
- 切片追加高级用法
// 基本相当于python中的extend 扩展列表 arrayList = append(arrayList, []int{100,200,300,400}...) fmt.Println(arrayList)
- 切片删除
// 切片数据实际上是没有删除操作的 我们可以使用append来拼接生成新的切片 代替删除 newAarrayList := append(arrayList[:6]) newAarrayList = append(newAarrayList, arrayList[7:]...) fmt.Println(newAarrayList) // or newAarrayList := append(arrayList[:6], arrayList[7:]...) fmt.Println(newAarrayList)
- 切片插入
newAarrayList := append(arrayList[:6]) newAarratList = append(arrayList, 123356) newAarrayList = append(newAarrayList, arrayList[7:]...) fmt.Println(newAarrayList)
// 删除插入 效率低下 不使用。应该使用链表
- 切片嵌套
// 嵌套 testList := [][]int{[]int{1, 2}, []int{66, 77, 99}} fmt.Println(testList)
目前为止 上面所学的数据类型中, 在修改切片的内部元素时, 会造成所有的赋值的变量同时修改 (不扩容的前提下)
字典类型(Map)
- 任何编程语言中都会存在字典或者映射 , 同python中的字典 是以键值对的形式存在的数据集合
{ 'name' : 'zj', 'age': 15, }
- 字典的特性map
- 键不能重复
- 键必须可hash(切片和map都不可hash所以不能做键)
- 无序
- map声明
// 第一个string代表键的类型 第二个string代表值的类型 userInfo := map[string]string{ "name": "zj", "age": "18", } fmt.Println(userInfo["name"]) //or // dataInfo := make(map[string]string, 10) dataInfo := make(map[string]string) dataInfo["name"] = "ccn" fmt.Println(dataInfo["name"])
- map 长度和容量
len(userInfo) dataInfo := make(map[string]string, 10) // 根据穿参10 计算出合适的容量 // 一个map 中会包含很多筒,每个筒可以存放8个键值对
- map 增删改查
userInfo := map[string]string{ "name": "zj", "age": "18", } // 增 userInfo["test"] = "123ff" // 改 userInfo["name"] = "ccc" // 删 delete(userInfo, "name") // 查 for key, value := range userInfo { fmt.Println(key, value) } fmt.Println(userInfo["name"])
- map变量赋值
...
注意 : 无论是否存在扩容都指向同一个地址
- map初始化详解
info = make(map[int]int, 10) - 第一步 创建一个hmap的结构体对象 - 第二部 生成一个hash因子 hash0 并赋值到hmap对象中(用于后续为key创建hash值) - 第三部 根据hint=10, 并根据算法来创建 2的B次方筒数,当前应该是1 所以就是创建两筒
- map扩容原理
再向map中添加数据时, 当达到某一个条件, 则会引发字典扩容 扩容的条件: - map中数据总个数/筒个数 > 6.5时, 便会引发翻倍扩容
指针数据
什么是指针 一个指针变量指向了一个内存地址, 类似于变量和常量, 在使用指针前需要声明指针。指针声明格式如下
// 指针类型生来就是用来节省内存的 // 声明指针类型的数据只需要在声明数据类型前加* 即可声明为指针类型 package main import "fmt" func main() { //var a int = 10 //fmt.Printf("%x" , &a) var name string = "yunZhOngKeXin" // 查看内存地址为&符号 fmt.Printf("name的内存地址: %v\n", &name) // 指针变量, 存的是内存地址 // ptr 指针变量指向变量name的内存地址 var ptr *string ptr = &name fmt.Printf("指针变量ptr的内存地址: %v \n", ptr) // 获取指针变量的值, 用*ptr // *ptr表示读取指针指向变量的值 // *ptr 代表真正的数据 fmt.Printf("指针变量ptr指向的值是: %v\n", *ptr) }
- 指针
指针 , 是一种数据类型, 用于表示数据的内存地址。
// 声明一个 字符串类型的变量 (默认初始化为空字符串) var v1 string // 声明一个字符串的指针类型 var v2 *string
// 声明一个字符串类型的变量并赋值 var name string = "zj" // 声明一个字符串的指针类型的变量, 值为name对应的内存地址 var pointer *string = &name
- 指针存在的意义
相当于创建了一个**引用**, 以后可以根据这个引用来获取他里面的值 如果原本的数据发生变化引用也会发生变化,类似于软连接, 快捷方式这种
v1 := "zh" v2 := &v1 fmt.println(v1, v2, *v2)
- 使用指针的场景
// 下边的这种函数传参的方式testData字符串会重新copy一份 func main { Test("zjjjj") } func Test(testData string) { testData = "hahah" fmt.println(testData) } // func main { Test("zzjj") } func Test(ptr *string){ ptr = "fffff" fmt.println(ptr) // 修改指针的内容上边的内容也会被修改 }
- 指针的指针
n1 := "zhjjj" n2 := &n1 fmt.Println(n1, *n2) n3 := &n2 fmt.Println(*n2, **n3) n4 := &n3 fmt.Println(**n3, ***n4)
- 指针的小高级操作
// 指针的相加操作 func Compl() { // 指针的相加操作 // 定义一个int8数组 dataList := [3]int8{11, 22, 33} // 取出第一个元素的内存地址 var firstData *int8 = &dataList[0] // 将第一个铁元素的内存转化成pointer类型 ptr := unsafe.Pointer(firstData) // pointer类型 +1 targetAddress := uintptr(ptr) + 1 // 相加之后重新转换成pointer类型 newPtr := unsafe.Pointer(targetAddress) // 将pointer对象转换成int8 指针类型 value := (*int8)(newPtr) // 使用指针类型获取数据 fmt.Println(*value) }
结构体
什么是结构体?
| 结构体是一个复合类型, 用于表示一组数据
| 结构体由一系列属性组成, 每个数据都有自己的类型和 值
// 定义 type Person struct { name string age int email string } // 初始化 var p1 = Person{"zh", 12, "153"} fmt.Println(p1, reflect.TypeOf(p1)) 结构体基本格式 type 结构体名称 struct{ 字段 类型 ... }
- 结构体的定义
type Person struct { name string age int hobby []string. // 可以是切片哦 }
结构体
package main import ( "fmt" ) func main() { // 定义结构体 type Person1 struct { name, sex string age int } var p1 = Person1{"ccn", "sunhat", 18} fmt.Println(p1) // 结构体嵌套 type Person2 struct { score int p1 Person1 } v2 := Person2{99, Person1{"zj", "nan", 18}} fmt.Println(v2) //结构体匿名 type Person3 struct { city string hobby string Person1 // 匿名结构体 } v3 := Person3{city: "CN", hobby: "students", Person1:Person1{"11","nan",18}} // 由于结构体是匿名的所以可以直接取p1中的name 否则必须v3.变量名.name fmt.Println(v3.city, v3.name) // 注意结构体赋值的时候 数据会全部都的拷贝一份 无论什么类型 // 指针结构体 是不拷贝的 软连接 // 切片和map 数据拷贝的的时候可能会发现感觉并没有拷贝 但其实不是的是因为切片和map没有扩容的时候使用的都是同一块地址导致的 数据的特型 } // 如果我们想要在赋值时不拷贝数据 我们可以将数据变成指针类型数据即可 *[2]string *map[string]int
- 结构体指针
type Person2 struct { lambda string *Person } p3 := Person2{lambda: "这是一个匿名函数", Person: &Person{"Miho", "naming ", 12}} p4 := p3 p3.name = "testzhizhen" fmt.Println(p3, *p3.Person, p4, *p4.Person)
- 结构体标签
// 标签 type Person2 struct { lambda string "声明匿名" *Person "我是Person" } p3 := Person2{lambda: "这是一个匿名函数", Person: &Person{"Miho", "naming ", 12}} p4 := p3 p3.name = "testzhizhen" // 标签1 testStruct := reflect.TypeOf(p3) fmt.Println(testStruct.Field(1).Tag) // 标签2 field, _ := testStruct.FieldByName("lambda") fmt.Println(field.Tag) // 循环获取tag fieldNum := testStruct.NumField() for i := 0; i < fieldNum; i++ { fmt.Println(testStruct.Field(i).Tag) }
函数
可以把函数当作一个公用的代码块,用于实现某一个功能. 并且提高代码的重用性和可读性(函数传参数据会重新拷贝 相当于赋值)
// 函数使用方法 func 函数名(参数 类型) 返回值类型 { 函数执行体 }
package main import "fmt" // 定义了返回值的类型时必须有return , name为接收的参数 string 为返回值的类型 func FirstFun(name string) string { if len(name) > 2 { //fmt.Println(name) return name } return "error" } func main() { res := FirstFun("zjccn") fmt.Println(res) }
- 函数调用指针类型 (节省内存)
package main import "fmt" func FirstFun(name *string) string { *name = "123" if len(*name) > 2 { //fmt.Println(name) return *name } return "error" } func main() { name := "ZjCcn" res := FirstFun(&name) fmt.Println(res, name) }
- 函数也可以作为参数
func main() { TestProxy(10, Add100) } func Add100(data int) (int, bool) { return data + 100, true } func TestProxy(data int, function func(int) (int, bool)) string { resData , flag := function(data) if flag{ fmt.Println(resData, flag) }else { fmt.Println("error") } return "complete" }
- 函数传递可变长参数 可以不确定传多少参数 (变长参数必须放在最后 类似 **kwargs 且只能存在一个)
// 这个num的类型实际上是切片类型。可以任意扩容 func Do(num ... int) int { sum := 0 for _, value := range num{ sum += value } return sum } func main() { numcount := Do(1,2,2,2,2,2,2,2,2,3,3,3,3,3,3) fmt.Println(numcount) }
- 匿名函数
func main() { // 匿名函数 f1 := func(data int) int { return data } fmt.Println(f1(11)) // 第二种匿名函数表达 f2 := func(data int) string { return strconv.Itoa(data) }(110) fmt.Println(f2, reflect.TypeOf(f2)) }
- 函数不仅能做传递的参数 也可以作为返回值
test1 := Test1() fmt.Println(test1(120)) func Test1() func(t1 int) int { funtion := func(t1 int) int { return t1 } return funtion }
- 闭包函数(通过一个函数将我们需要的数据固定包裹在一个包里)
// 闭包函数的作用就是将生成好的函数封装好值也传好, 之后调用的函数的值返回值 都是定义好的无需再次传参 可直接调用 package main import ( "fmt" ) func main() { // 这个切片会存储着五个函数 分别打印 0 1 2 3 4 var funtionList []func() for i := 0; i < 5; i++ { funtion := func(data int) func(){ return func() { fmt.Println(data) } }(i) funtionList = append(funtionList, funtion) } // 运行封装好的包 funtionList[0]() funtionList[2]() funtionList[3]() }
- defer
用于在一个函数执行体完成之后自动触发的语句, 一般用于结束操作之后释放资源
// go中称为延迟执行 可以随意定义位置 // 相当于python中的 __del__ 释放资源 // 类似于堆栈。先进后出 package main import "fmt" func main() { TestDefer() } func TestDefer() { fmt.Println("这是一次测试") for i := 0; i < 10; i++ { defer fmt.Println(i) } }
- 自执行函数 (类似于python init)
// ~= python __init__ // 自执行函数就是匿名函数 func TestDefer() { result := func(age int) int{ return age }(18) fmt.Println(result) }
- 结构体做函数返回值
type funstruct struct { name string age int } func StructTest(name string) (funstruct) { return funstruct{name: name, age: 18} } func main(){ t1 := StructTest("testzh") fmt.PrintLn(t1) }
类型方法
项目开发中可以为type声明的类型编写一些方法, 从而实现对象.方法的操作
// 可以定义执行自己的代码的方法 package main import "fmt" // 声明类型 type myInt int func main() { var testMy myInt = 110 res := testMy.Dosomething(1,2) fmt.Println(res) } // 这个因为前边加上了(i *myInt) 所以他就不是函数了 而是自我定义的方法 myInt可以是指针 也可以是正常的 为了节省内存我们是用指针 func (i *myInt) Dosomething (data1 int , data2 int) int { return data1+data2+int(*i) }
- 方法继承(类似于面向对象的继承)
type Base struct { name string } type Son struct { Base // 匿名结构体 age int test string } func (b1 *Base) Test1() int { return 123 // return b1.name name: "张无忌" } func (s1 *Son) Test2() int { return 222 } func main() { son := Son{Base:Base{name: "张无忌"}, age:13, test:"测试"} fmt.Println(son.Test1(), son.Test2()) } >>>>>>>>>>>>>>>执行结果: 123 222
- 结构体工厂
声明任何数据类型的时候。只要变量名称为小写 就是私有变量 大写就是公共变量。私有就是只有当前这个.go文件可以使用 公有都能使用
- Go接口类型
GO语言中的接口是一种特殊的数据类型, 定义格式如下:
type name interface{
方法名称() 返回值
}
例如:
type Name interface {
f1()
f2() int
f3() (int, bool)
}
定义接口的特点是 不需要写任何逻辑代码 只需要将方法的名和返回值传参数 写好即可
接口的作用
在程序开发中接口一般有两大作用 : 代指类型 和 约束。
空接口, 代指任意类型
package main import ( "fmt" "reflect" ) // 定义个空接口 空接口中可以传任意类型的值 type base interface { } func main() { //dataList := make([]base, 0) // 上边这些接口定义可以简写为: dataList := make([]interface{}, 0) dataList = append(dataList, "test") dataList = append(dataList, 234) dataList = append(dataList, map[string]string{"key": "values"}) fmt.Println(dataList[2], reflect.TypeOf(dataList)) something("test") something("test") something("test") caseSth("haha") caseSth(123) caseSth([]string{"xii", "haha"}) } // 像python一样 弱类型 type person struct { name string } // 接收到数据类型都是接口类型 所以我们要使用.(类型或者类型结构体)转换成我们想要的格式 func something(arg interface{}) { s,ok := arg.(string) if ok{ fmt.Println(s, reflect.TypeOf(s)) } else { fmt.Println("转换失败") } } func caseSth(arg interface{}) { switch data := arg.(type) { case string: fmt.Println(data) case int: fmt.Println(data) case []string: fmt.Println(data) default: fmt.Println("WOZHAOBUDAO") } }
- 非空接口 规范约束
type Ibase interface { test1 () int } type Person struct { name string age int } func (p *Person) test1() int { return 123 } type User struct { username string password string } func (u *User) test1() int { return 567 } func main() { p := Person{"zj", 18} //u := User{"1533333", "520cnn.."} p.test1() List := []Ibase{&Person{"COMPELTE",12}, &User{"完成", "LE"}} fmt.Println(List) for _ , value := range List{ fmt.Println(value, reflect.TypeOf(value)) } }
- flag包
基于os.Args可以获取传入的所有参数
package main import ( "flag" "fmt" ) func main() { //fmt.Println(os.Args) /* >>>结果 mac$ ./main 213 123414155 5125 [./main 213 123414155 5125] */ // h 是命令行要-h 默认是127.0.0.1 后边是备注 host := flag.String("h", "127.0.0.1", "主机名") port := flag.Int("p", 1234, "端口号") //host := flag.String("h", "127.0.0.1", "主机名") flag.Parse() // 必须parse 才能将命令行传入的数据传进来 fmt.Println(*host, *port) /* >>结果 mac$ ./main -h 192.168.1.1 -p 1212 192.168.1.1 1212 */ }
- Go regexp 正则表达式
package main import ( "fmt" "regexp" ) func main() { // 根据字符串匹配返回 是否成功 matchString, err := regexp.MatchString(".*?:(.*?)n.*?", "vaiuhguifg:xixizhangjiannihaonfaoghaiug") if err == nil { fmt.Println(matchString) // true } // 根据匹配值字符串查找 reg1 := regexp.MustCompile(`\d{11}`) res1 := reg1.FindString("1242563837613") // 结果 12425638376 fmt.Println(res1) res2 := reg1.FindAllString("1241245569005837163414115", -1) // 结果就是找所有的11个数字的集合[12412455690 05837163414] fmt.Println(res2) // 获取分组信息 }
文件路径相关的内置包
- 文件增删改查
// 创建单级目录 付给权限 os.Mkdir("testDir", 0755) // 创建多级目录 os.MkdirAll("/test/123/code", 0755) // 删除文件或空文件夹, 文件夹下存在内容数据 就会报错 os.Remove("testDir") // 删除文件或文件夹 (同时删除子目录中的数据) os.RemoveAll("test123")
- 判断目录 或文件是否存在. os.IsNotExist(err)
_, err := os.Stat("test1/test2/123.go") if err != nil { if os.IsNotExist(err) { fmt.Println("文件或文件夹不存在") }else{ fmt.Println("存在") } }
- 判断是否是文件夹IsDir()
file, _ := os.Stat("test/123/") res := file.IsDir()
- 获取绝对路径
abs, err := filepath.Abs("test/123/") // 获得的结果就是绝对路径+test/123/ // 获得绝对路径 abs, err := filepath.Abs(".") if err == nil { // 获得上层路径 fmt.Println(filepath.Dir(abs)) }
- 遍历目录下的文件
package main import ( "fmt" "io/ioutil" "path/filepath" ) func main() { abs, _ := filepath.Abs(".") dir, err := ioutil.ReadDir(abs) fmt.Println(dir) if err == nil { for _, value :=range dir{ if !value.IsDir(){ fmt.Println(value.Name()) } } } }
- walk
// walk会深层递归的查询数据 filepath.Walk(abs, func(path string, info os.FileInfo, err error) error { fmt.Println(info.Name()) fmt.Println(path) return nil })
- 路径拼接 or 文件扩展名
// 拼接 filePath := path.Join("1","2","3","7.dmg") // 文件扩展名 ext := path.Ext("/fres/faf/xxx.txt") 获取到的就是扩展名txt
有疑问加站长微信联系(非本文作者)