1 包的使用
// 为了便于组织代码,同一种类型的代码,写在同一个包下,便于管理
// 定义包
-新建一个文件夹
-内部有很多go文件
-在每个go文件的第一行,都要声明包名,并且包名必须一致
-在一个文件夹(同级)下只能有一个包
-在同一个包下,变量和函数都是共享的(一个包相当于一个go文件)
-在一个包下,不能重复定义变量和函数
-除了main包,其他都是用来被导入使用的
-无论是函数,还是变量,大写字母开头表示导出,可以在其他包使用
-尽量包名就是文件夹名
// 老版本的gopath和现在的go moduls的区别
-1.11后才支持go moduls模式,现在都用
-如果使用go path开发,所有项目的代码,必须放在 go path路径下的 src文件夹下,否则找不到,包括包,包括下载的第三方包---》弃用了
-使用go mod以后,现在推荐的
-代码不需要放在go path路径下的 src文件夹下了,放在任何路径下都可以
-项目路径下必须要有个go.mod
-go mod init
// 把go path 修改成go mod项目
//go env -w 修改go的环境变量
-可以移动目录
-go mod init 项目名
-在项目路径下生成 go.mod,自动生成,不要去动
-内容:
module 项目名
go 1.17
-如果有第三方包,需要再执行一下
-go mod tidy // 会去下载第三方包
2 if-else语句
// 逻辑判断
package main
import "fmt"
// if-else的使用
// if 后跟条件,符合条件会执行,代码用{}包裹
// else if 后跟条件,符合条件会执行,代码用{}包裹
// else 不需要加条件,用{}包裹
// {}的{需要和关键字在一行
func main() { // 大括号的 { 不能换到下一行
number := 55
if number >= 90 && number <= 100 {
fmt.Println("优秀")
} else if number >= 60 && number <= 90 {
fmt.Println("良好")
} else {
fmt.Println("不及格")
}
}
3 循环
// go 语言中循环使用for关键字
-基于迭代的循环
-基于索引的循环
package main
import "fmt"
// for 循环的使用
// for 后面三段,分号隔开
// 第一段:索引的开始
// 第二段:条件
// 第三段:自增或自减
func main() {
// 1 基于索引的循环
// 1.1 完整版
// 循环打印出0-9
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// 1.2 省略第一段: i的作用域变大
var i2 = 0
for ; i2 < 10; i2++ {
fmt.Println(i2)
}
// 1.3 省略第三段,自增写在循环体内部
for i3 := 0; i3 < 10; {
fmt.Println(i3)
i3++
}
// 1.4 省略第一段和第三段,
i4 := 0
for ; i4 < 10; {
fmt.Println(i4)
i4++
}
// 简写为:
for i4 < 10 {
fmt.Println(i4)
}
/*
// 死循环
for true {
fmt.Println("死循环1")
}
*/
/*
// 1.5 三部分全省略
for ;;{
fmt.Println("死循环2")
}
// 简写成:
for {
fmt.Println("死循环3")
}
*/
// 2 基于迭代的循环
var a = [3]int{1, 25, 34}
for i5, value := range a {
fmt.Println(i5)
fmt.Println(value)
}
// 用基于索引实现
var a6 = [3]int{1, 25, 34}
for i6 := 0; i6 < len(a6); i6++ {
fmt.Println(i6)
fmt.Println(a6[i6])
}
}
4 switch语句
// 可以优雅的替换掉if-else
package main
import "fmt"
// switch使用
func main() {
// 1 使用方式一
name := "cx"
switch name {
case "cx":
fmt.Println("11")
case "lqz":
fmt.Println("12")
}
// 2 使用方式二:switch后不跟值、case后跟
name2 := "cx"
switch {
case name2 == "cx":
fmt.Println("21")
case name2 == "aa":
fmt.Println("22")
}
// 3 使用方式三:多条件()
// 3.1:,表示或者
name3 := "cx"
switch name3 {
case "cx", "cx2":
fmt.Println("311")
case "lqz", "lqz2":
fmt.Println("312")
}
// 3.2:||表示或者 &&表示并且
name32 := 10
switch {
case name32 == 11 || name32 == 1:
fmt.Println("321")
case name32 > 1 && name32 < 3:
fmt.Println("322")
case name32 == 1, name32 == 2, name32 == 10:
fmt.Println("333")
}
// 4 使用方式4:default的使用,当所有case都不满足后执行
name4 := "cx"
switch name4 {
case "cx2":
fmt.Println("cx")
case "lqz2":
fmt.Println("lqz")
default:
fmt.Println("没匹配上")
}
// 5 fallthrough的使用:无条件执行下一个case 只能放在语句结尾
name5 := "cx"
switch name5 {
case "cx":
fmt.Println("cx")
fallthrough
case "lqz":
fmt.Println("lqz")
default:
fmt.Println("没匹配上")
// 输出:cx lqz
}
}
5 数组
// 类型,变量,连续存储数据,数据类型是一致的,类似于python中的列表(但列表可放不同类型数据)
package main
import "fmt"
// 数组(array)
func main() {
// 1 数组的定义,定义时,大小就固定了,后期不能修改大小
var a [3]int = [3]int{4, 5, 6}
fmt.Println(a)
// 2 使用:修改 取值
// 根据下标使用
a[0] = 99
fmt.Println(a)
fmt.Println(a[0])
//fmt.Println(a[3]) // 报错
// 3 其他定义方式
var a3 = [3]int{4} // 可以少(如果不足的元素值为默认值0),但不能多,否则报错
fmt.Println(a3)
a4 := [3]int{1, 2, 3}
fmt.Println(a4)
a5 := [30]int{28: 1} // 指定位置赋初值 指定索引28对应值为1、其他为默认值0
fmt.Println(a5)
var a6 [3]int
fmt.Println(a6) // [0, 0, 0]
var a7 = [...]int{3, 4, 5} // 虽然使用...初始化,但是长度也是固定的,根据值多少个确定长度
fmt.Println(a7) // [3 4 5]
var a8 = [...]int{50: 99}
fmt.Println(a8)
fmt.Printf("%T", a8) // 长度为51
// 4 数组长度
b := [3]int{1, 2, 3}
fmt.Println(len(b)) // len()不需要导入包、直接使用即可
for i := len(b) - 1; i >= 0; i-- {
fmt.Println(a[i])
}
// 5 数组的循环
// 2.1 基于索引的循环
for i := 0; i < len(b); i++ {
fmt.Println(b[i])
}
// 2.2 反向取
for i := len(b) - 1; i>=0; i-- {
fmt.Println(b[i])
}
// 5.2 基于迭代的循环
// range是一个关键字
// 返回一个值是索引、返回两个值是索引和值
for i := range b {
fmt.Println(b[i])
}
for _, value := range b { // 变量定义后必须使用,但是用_接收可以不使用
fmt.Println(value)
}
// 6 多维数组
var b2 [3][4]int = [3][4]int{{3, 3, 3, 3}, {4, 4, 4}, {5}}
fmt.Println(b2)
// 6.1 循环(两层循环)
for _, value := range b2 {
for _, value2 := range value {
fmt.Println(value2)
}
}
}
6 切片
// 切片是由数组建立的一种方便、灵活且功能强大的包装,切片本身不拥有任何数据。它们只是对现有数组的引用(指针指向数组)
package main
import "fmt"
// 切片(slice)
func main() {
// 1 基于数组,定义切片
a1 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var s []int // 中括号不带任何东西,就是切片类型
s = a1[:] // 把数组从头到尾的引用给s
fmt.Println(a1, s)
fmt.Printf("a的类型是%T,值是%v", a1, a1)
fmt.Println() // 输入空行(换行)
fmt.Printf("s的类型是%T,值是%v", s, s)
fmt.Println()
// 2 使用,取值,改值
fmt.Println(s[0]) // 1
//fmt.Println(s[100]) // 编译不报错,执行会报错
s[1] = 999
fmt.Println(s) // [1 999 3 4 5 6 7 8 9 10]
// 3 切片的变化,会影响底层数组
a3 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var s3 []int = a3[:]
s3[0] = 999
fmt.Println("s3:", s3) // s3: [999 2 3 4 5 6 7 8 9 10]
fmt.Println("a3:", a3) // a3: [999 2 3 4 5 6 7 8 9 10]
// 4 底层数组的变化也会影响数组
a4 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 简写:var s4 []int = a3[:]
var s4 = a4[:]
s4[0] = 9999
fmt.Println("s4:", s4) // s3: [9999 2 3 4 5 6 7 8 9 10]
fmt.Println("a4:", a4) // a3: [9999 2 3 4 5 6 7 8 9 10]
// 5 切片的长度(len)和容量(cap)
// 数组有长度、并且长度不能变
// 切片也有长度、但是长度可以变,切片有容量
a5 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var s5 = a5[0:3] // 顾头不顾尾:索引0、1、2被引用,没有3
fmt.Println(s5)
fmt.Println(len(s5)) // 长度为3
fmt.Println(cap(s5)) // 容量为10(这个切片最多能存多少值,基于底层数组来的)
// 6 长度和容量再次研究
a6 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s6 := a6[2:9]
fmt.Println(s6)
fmt.Println(len(s6)) // 7
fmt.Println(cap(s6)) // 8 容量是底层数组决定的,但是不一定是底层数组的大小,得看切片从哪个位置开始引用数组,从切片引用数组的开始位置到结束,是容量的大小
// 7 通过内置函数make创建切片
// make(类型,长度,容量)
var s7 []int = make([]int, 3, 4) // 简写:var s7 = make([]int,3,4)
fmt.Println(len(s7)) // 3
fmt.Println(cap(s7)) // 4
// 定义切片并初始化
var ss = []int{2, 3, 4}
fmt.Println(len(ss)) // 3
fmt.Println(cap(ss)) // 3
// 8 追加切片
var s8 = make([]int, 3, 4)
fmt.Println(s8)
fmt.Println(len(s8)) // 3
fmt.Println(cap(s8)) // 4
s8 = append(s8, 99)
fmt.Println(s8)
fmt.Println(len(s8)) // 4
fmt.Println(cap(s8)) // 4
// 注意:此时长度已经达到容量值,如继续追加,切片容量会翻倍、自动扩容
s8 = append(s8, 88)
fmt.Println(s8)
fmt.Println(len(s8)) // 5
fmt.Println(cap(s8)) // 8
// 9 追加切片后,底层数组如何变化?
a9 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s9 := a9[2:9]
fmt.Println(s9)
// 9.1 切片变,底层数组变
s9[0] = 666
fmt.Println(s9) // [666 4 5 6 7 8 9]
fmt.Println(a9) // [1 2 666 4 5 6 7 8 9 10]
// 9.2 数组变,切片也会变
a9[8] = 999
fmt.Println(a9) // [1 2 666 4 5 6 7 8 999 10]
fmt.Println(s9) // [666 4 5 6 7 8 999]
// 9.3 追加元素(追加到临界状态,再追加,数组就不够了),底层数组会跟着变
// 底层数组不够了,go语言会自动申请一个新的数组,大小为原来切片容量的2倍,把原来切片的值,复制到新数组上。
// 此时,切片与原来数组脱离,原来数组的变化不再会影响切片了
s9 = append(s9, 93)
fmt.Println(s9) // [666 4 5 6 7 8 999 93]
fmt.Println(a9) // [1 2 666 4 5 6 7 8 999 93]
fmt.Println(len(s9)) // 8
fmt.Println(cap(s9)) // 8
s9 = append(s9, 94) // 此时,切片容量不够,切片与原来数组脱离
fmt.Println(s9) // [666 4 5 6 7 8 999 93 94]
fmt.Println(len(s9)) // 9
fmt.Println(cap(s9)) // 16
s9[0] = 10000
fmt.Println(s9) // [10000 4 5 6 7 8 999 93 94]
fmt.Println(a9) // [1 2 666 4 5 6 7 8 999 93] 不会再跟着变化了
// 切片就是个寄生虫,有了新宿主,就和原来宿主没关系了
// 10 切片的函数传递
/*
数组是值类型,切片是引用类型(指针)
go语言中的参数传递是:copy传递,也就是把变量复制一份到函数中
如果是值类型,复制一个新的值传入,修改新值不会影响原来的值
如果是引用类型,复制这个引用传入(指向没变),修改新值会影响原来的值
*/
var a10 = [3]int{4, 5, 6}
test1(a10) // test函数定义在最下方
fmt.Println(a10) // a10不会变 [4 5 6]
var s10 = a10[:]
test2(s10)
fmt.Println(s10) // s值会变,因为s是个引用 [999 5 6]
/* 补充:
Python与其他语言区别:
其他语言(如Go):分为值类型、引用类型
Python:一切皆对象、一切皆引用
解决方式:分为 可变类型、不可变类型
可变类型:列表、字典
函数中修改会影响到原来的值
不可变类型:字符串、元组、数字
函数中修改不会影响到原来的、如要改加global声明
*/
// 11 多维切片
var s11 = [][]int{{1}, {2, 2}, {3, 3, 3}}
fmt.Println(s11)
fmt.Println(len(s11)) // 3
fmt.Println(cap(s11)) // 3
fmt.Println(len(s11[0])) // 1
fmt.Println(cap(s11[0])) // 1
fmt.Println(len(s11[1])) // 2
fmt.Println(cap(s11[1])) // 2
// 11.2 通过make初始化多维切片
var s112 = make([][]int, 3, 4) // 内层的切片没有初始化,只要使用内层切片,内层所有的切片都要初始化才行
fmt.Println("s112[0]:", s112[0]) // 是个没初始化的切片 s112[0]: []
s112[0] = make([]int, 5, 6) // 初始化内层切片
s112[0][0] = 99
fmt.Println(s112[0]) // [99 0 0 0 0]
// fmt.Println(s112[0][5]) // 报错:切片越界,虽然容量为6,但还没使用到(长度为5),就不能取
// 解决:用append在切片末尾再加一个元素,让长度变为6即可
// 11.3 循环多维切片
// 基于索引
var s113 = [][]int{{1}, {2, 2}, {3, 3, 3}}
for i := 0; i < len(s113); i++ {
for i2 := 0; i2 < len(s113[i]); i2++ {
fmt.Println(s113[i][i2])
}
}
// 基于迭代
for _, v := range s113 {
for _, v2 := range v {
fmt.Println(v2)
}
}
// 12 copy:把一个切片,复制到另一个切片上
var a12 = [10000]int{3, 4, 5}
fmt.Println(a12) //[3 4 5 0 0 0 0 ... ]
s12 := a12[:3]
fmt.Println(s12) // [3 4 5]
// 使用s12会基于一个很大的数组,内存占用高
// 解决:将s12 copy到另一个基于小数组的切片上
s121 := make([]int, 3, 3)
fmt.Println(s121) // [0 0 0]
copy(s121, s12)
fmt.Println(s121) // [3 4 5] ,以后都用s121操作,节约了内存
// 12.2 俩切片一样长,可以copy,如果不一样长呢?
s1221 := make([]int, 5, 5)
copy(s1221, s12)
fmt.Println(s1221) // [3 4 5 0 0] 多了的用默认值0填充
s1222 := make([]int, 2, 2)
copy(s1222, s12)
fmt.Println(s1222) // [3 4] 少了的则截断
}
func test1(a [3]int) {
a[0] = 999
fmt.Println("test1:", a)
}
func test2(a []int) {
a[0] = 999
fmt.Println("test2:", a)
}
7 可变函数参数
package main
import "fmt"
// 可变长函数参数:...类型
func main() {
test3(2, 3, 4, 5) // [2 3 4 5] 类型:[]int
test4("aa", "bb", "cc") // [aa bb cc] 类型:[]string
a := [3]int{3, 4, 5}
fmt.Println(1, 2, "aa", a) // Println函数中形参类型为:...interface{} 空接口类型,可以接收任意类型
s := []int{1, 2, 3}
// test3(s) // 报错
test3(s...) // 相当于将s打散后传入函数 结果:[1 2 3] 类型:[]int
// 如果既想无限接收number类型、又想无限接收string类型。可以自定义类型。
}
func test3(a ...int) { // 可以接收任意长度参数、但类型必须一致
fmt.Println(a)
fmt.Println()
fmt.Printf("类型:%T", a) // 切片类型 []int
}
func test4(a ...string) {
fmt.Println(a)
fmt.Println()
fmt.Printf("类型:%T", a) // 切片类型 []string
// 取到第几个参数
fmt.Println("第一个参数:", a[0])
}
8 map
map 是在 Go 中将值(value)与键(key)关联的内置类型。通过相应的键可以获取到值(Python的字典类型)
value值可以任意,但是go中value值必须都一致
key值只能用数字,字符串,布尔,key值也固定
package main
import "fmt"
// map的使用
func main() {
//1 map的定义
var m map[string]int // map[key类型]value类型
fmt.Println(m) // 只定义,没有初始化,零值也是nil
if m == nil {
fmt.Println("map没有初始化")
}
// 第一个参数传类型,map有长度没有容量
var m2 = make(map[string]int)
fmt.Println(m) //map[] 已经不是nil了,它可以直接用了,因为初始化了
if m2 == nil {
fmt.Println("m2没有初始化")
} else {
fmt.Println("m2已经初始化了")
}
/*
补充 数字,字符串,布尔,数组,切片,map类型的0值
值类型:有自己的0值,
数字:0,
字符串:""
布尔:false
数组:数组里元素类型的零值
引用类型:零值是 nil (None:python中所有类型的空值都是None)
切片
map
*/
// 值类型验证
var a1 float32
var a2 string
var a3 bool
var a4 [4]string
fmt.Println(a1, a2, a3, a4)
// 引用类型验证
var ss []int
fmt.Println(ss) // []
if ss == nil {
fmt.Println("空的") // 执行
}
var s = make([][]int, 3, 3)
if s[0] == nil {
fmt.Println("没有初始化") // 执行
}
// s[0][0] = 99 // 报错 nil[0] 相当于Python的 None[0]
var s1 []int
fmt.Println(s1)
if s1 == nil {
fmt.Println("没有初始化") // 执行
}
s2 := make([]int, 0, 4)
fmt.Println(s2)
if s2 == nil {
fmt.Println("没有初始化") // 执行
}
// 2 map的定义并初始化
var m21 map[string]int = map[string]int{"name": 12, "age": 19}
var m22 = map[string]int{"name": 12, "age": 19} // 简写
m23 := map[string]int{"name": 12, "age": 19}
m24 := map[string]int{"name": 12, "age": 19}
fmt.Println(m21, m22, m23, m24) // map[age:19 name:12] map[age:19 name:12] map[age:19 name:12] map[age:19 name:12]
// 3 map的使用,取值,赋值
m3 := map[string]int{"name": 12, "age": 19}
fmt.Println(m3["age"]) // 取值
m3["age"] = 99 // 赋值存在的则修改值
m3["sex"] = 1 // 赋值不存在的则添加值
fmt.Println(m3) // map[age:99 name:12 sex:1]
// 取不存在的值,不报错,取出value值的0值
fmt.Println(m3["hobby"]) // 0
//根据key取value,判断value是否存在
_, ok := m3["hobby"] // 可以使用两个值来接收,第二个值是个布尔值,如果ok为true,说明存在,否则不存在 range
fmt.Println(ok) //false
m3["hobby"] = 66
_, ok1 := m3["hobby"]
fmt.Println(ok1) // true
// 3.2 删除元素 内置函数 只能根据key值删除
m4 := map[string]int{"name": 12, "age": 19}
delete(m4, "name")
delete(m4, "hobby") // 删除不存在的,不会报错
fmt.Println(m4)
// 4 map长度 总共有多少个元素
m5 := map[string]int{"name": 12, "age": 19}
m5["xx"] = 12
m5["xx1"] = 12
m5["xx2"] = 12
m5["xx3"] = 12
fmt.Println(len(m5)) // 6
// fmt.Println(cap(m5)) // 报错,没有容量这一说,map类型可以无限扩容
// 5 map是引用类型,零值是 nil,引用类型一定要初始化,值类型不需要初始化就有零值
// 当参数传递
m6 := map[string]int{"name": 12, "age": 19}
fmt.Println(m6)
test5(m6) // 函数在最下面定义
fmt.Println(m6) // 会被改掉,应用类型
// map 之间不能用 == 判断,== 只能用来检查 map 是否为 nil
/*
m7 := map[string]int{"name":12,"age":19}
m8 := map[string]int{"name":12,"age":19}
fmt.Println(m7==m8) 报错:map can only be compared to nil
s7:=[]int{4,5,6}
s8:=[]int{4,5,6}
fmt.Println(s7==s8) 报错:map can only be compared to nil
*/
// 引用类型不能比较 ,值类型能比较
// 数组的长度也是类型的一部分,长度不一样,不是同一个类型
a9 := [3]int{4, 5, 6}
a10:=[3]int{4,6,6}
//a10 := [4]int{4, 6, 6} // 加了这句则报错、不同类型不能比较
fmt.Println(a9 == a10) // false
// map 可以无限制的添加值,只要内存够用,自动扩容 因为map是引用类型,扩容直接添加新map引用到上一个map即可
// 数组长度固定,一旦定义不能改变---》为什么? 因为数组是值类型,要定义好放值的大小,后面添加值才好有位置放
}
func test5(m map[string]int) {
m["age"] = 99
fmt.Println(m)
}