init函数
每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也 就是说 init 会在 main 函数前被调用。可以做初始化操作。
func init() {
fmt.Println(123)
}
func main() {
fmt.Println(456)
}
init函数的注意事项和细节
1)如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程全局变量定义->init 函数->main 函数
匿名函数
Go 支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考 虑使用匿名函数,匿名函数也可以实现多次调用。
(1)在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。
func main() {
res1 := func(n1 int, n2 int) int {
return n1 + n2
}(10, 20)
fmt.Println(res1)
}
(2)将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
func main() {
a := func(n1 int, n2 int) int {
return n1 - n2
}
res2 := a(10, 30)
fmt.Println(res2)
}
(3)全局匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序 有效
var (
fun1 = func(n1 int, n2 int) int {
return n1 * n2
}
)
闭包
基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
1)AddUpper 是一个函数,返回的数据类型是 fun (int) int
2)返回的是一个匿名函数, 但是这个匿名函数引用到函数外的 n ,因此这个匿名函数就和 n 形成一 个整体,构成闭包。
3)大家可以这样理解: 闭包是类, 函数是操作,n 是字段。函数和它使用到 n 构成闭包。
4)当我们反复的调用 f 函数时,因为 n 是初始化一次,因此每调用一次就进行累计。
5)我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引 用到的变量共同构成闭包。
func AddUpper() func(int) int {
var n int = 10
return func(x int) int {
n = n + x
return n
}
}
func main() {
//累加器
f := AddUpper()
fmt.Println(f(2))
}
** 函数的 defer**
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完 毕后,及时的释放资源,Go 的设计者提供 defer (延时机制)。
defer 的注意事项和细节
1)当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈 中, 然后继续执行函数下一个语句。
2)当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制),
3)在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈
defer的最佳实战
1)在 golang 编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是 锁资源), 可以执行 defer file.Close() defer connect.Close()
2)在 defer 后,可以继续使用创建资源.
3)当函数完毕后,系统会依次从 defer 栈中,取出语句,关闭资源.
4)这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。
两种传递方式
1)值传递
2)引用传递 其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的 拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的 数据大小,数据越大,效率越低。
值类型和引用类型
1)值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
2)引用类型:指针、slice 切片、map、管道 chan、interface 等都是引用类型
变量作用域
1)函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
2)函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用 域在整个程序有效
3)如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块
字符串常用的系统函数
1)统计字符串的长度,按字节 len(str)
func main() {
str := "hello北"
fmt.Println("str len=", len(str))
}
2)字符串遍历,同时处理有中文的问题 r := []rune(str)
str2 := "hello北京"
//字符串遍历,同时处理有中文的问题 r := []rune(str)
r := []rune(str2)
for i := 0; i < len(r); i++ {
fmt.Printf("字符=%c\n", r[i])
}
3)字符串转整数: n, err := strconv.Atoi(“12”)
n, err := strconv.Atoi("123")
if err != nil {
fmt.Println("转换错误", err)
}else {
fmt.Println("转成的结果是", n)
}
4)整数转字符串 str = strconv.Itoa(12345)
str = strconv.Itoa(12345)
fmt.Printf("str=%v, str=%T\n", str, str)
5)字符串 转 []byte: var bytes = []byte(“hello go”)
var bytes = []byte("hello go")
fmt.Printf("bytes=%v\n", bytes)
6)[]byte 转 字符串: str = string([]byte{97, 98, 99})
str = string([]byte{97, 98, 99})
fmt.Printf("str=%v\n", str)
7)10 进制转 2, 8, 16 进制: str = strconv.FormatInt(123, 2) // 2-> 8 , 16
str = strconv.FormatInt(123, 2)
fmt.Printf("123对应的二进制是=%v\n", str)
str = strconv.FormatInt(123, 16)
fmt.Printf("123对应的16进制是=%v\n", str)
8)查找子串是否在指定的字符串中: strings.Contains(“seafood”, “foo”) //true
b := strings.Contains("seafood", "mary")
fmt.Printf("b=%v\n", b)
9)统计一个字符串有几个指定的子串 : strings.Count(“ceheese”, “e”) //4
num := strings.Count("ceheese", "e")
fmt.Printf("num=%v\n", num)
10)不区分大小写的字符串比较(==是区分字母大小写的): fmt.Println(strings.EqualFold(“abc”, “Abc”)) // true
b = strings.EqualFold("abc", "Abc")
fmt.Printf("b=%v\n", b) //true
fmt.Println("结果","abc" == "Abc") // false //区分字母大小写
11)返回子串在字符串第一次出现的 index 值,如果没有返回-1 : strings.Index(“NLT_abc”, “abc”) // 4
index := strings.Index("NLT_abcabcabc", "abc") // 4
fmt.Printf("index=%v\n",index)
12)返回子串在字符串最后一次出现的 index,如没有返回-1 : strings.LastIndex(“go golang”, “go”)
index = strings.LastIndex("go golang", "go") //3
fmt.Printf("index=%v\n",index)
13)将指定的子串替换成 另外一个子串: strings.Replace(“go go hello”, “go”, “go 语言”, n) n 可以指 定你希望替换几个,如果 n=-1 表示全部替换
str2 = "go go hello"
str = strings.Replace(str2, "go", "北京", -1)
fmt.Printf("str=%v str2=%v\n", str, str2)
14)按 照 指 定 的 某 个 字 符 , 为 分 割 标 识 , 将 一 个 字 符 串 拆 分 成 字 符 串 数 组 : strings.Split(“hello,wrold,ok”, “,”)
//strings.Split("hello,wrold,ok", ",")
strArr := strings.Split("hello,wrold,ok", ",")
for i := 0; i < len(strArr); i++ {
fmt.Printf("str[%v]=%v\n", i, strArr[i])
}
fmt.Printf("strArr=%v\n", strArr)
15)将字符串的字母进行大小写的转换: strings.ToLower(“Go”) // go strings.ToUpper(“Go”) // GO
str = "goLang Hello"
str = strings.ToLower(str)
str = strings.ToUpper(str)
fmt.Printf("str=%v\n", str) //golang hello
16)将字符串左右两边的空格去掉: strings.TrimSpace(" tn a lone gopher ntrn ")
str = strings.TrimSpace(" tn a lone gopher ntrn ")
fmt.Printf("str=%q\n", str)
17)将字符串左右两边指定的字符去掉 : strings.Trim("! hello! “, " !”) // [“hello”] //将左右两边 ! 和 " "去掉
str = strings.Trim("! he!llo! ", " !")
fmt.Printf("str=%q\n", str)
18)将字符串左边指定的字符去掉 : strings.TrimLeft("! hello! “, " !”) // [“hello”] //将左边 ! 和 " “去掉
19)将字符串右边指定的字符去掉 :strings.TrimRight(”! hello! “, " !”) // [“hello”] //将右边 ! 和 " "去掉
20)判断字符串是否以指定的字符串开头: strings.HasPrefix(“ftp://192.168.10.1”, “ftp”) // true
b = strings.HasPrefix("ftp://192.168.10.1", "hsp") //true
fmt.Printf("b=%v\n", b)
21)判断字符串是否以指定的字符串结束: strings.HasSuffix(“NLT_abc.jpg”, “abc”) //false
时间和日期相关函数
1)时间和日期相关函数,需要导入 time 包
2)time.Time 类型,用于表示时间
now := time.Now()
fmt.Printf("now=%v now type=%T\n", now, now)
3)如何获取到其它的日期信息
//2.通过now可以获取到年月日,时分秒
fmt.Printf("年=%v\n", now.Year())
fmt.Printf("月=%v\n", now.Month())
fmt.Printf("月=%v\n", int(now.Month()))
fmt.Printf("日=%v\n", now.Day())
fmt.Printf("时=%v\n", now.Hour())
fmt.Printf("分=%v\n", now.Minute())
fmt.Printf("秒=%v\n", now.Second())
4)格式化日期时间
方式 1: 就是使用 Printf 或者 SPrintf
fmt.Printf("当前年月日 %d-%d-%d %d:%d:%d \n", now.Year(),
now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
dateStr := fmt.Sprintf("当前年月日 %d-%d-%d %d:%d:%d \n", now.Year(),
now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
fmt.Printf("dateStr=%v\n", dateStr)
方式二: 使用 time.Format() 方法完成:
//格式化日期时间的第二种方式
fmt.Printf(now.Format("2006-01-02 15:04:05"))
fmt.Println()
fmt.Printf(now.Format("2006-01-02"))
fmt.Println()
fmt.Printf(now.Format("15:04:05"))
fmt.Println()
fmt.Printf(now.Format("2006"))
fmt.Println()
5)时间的常量
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
6)结合 Sleep 来使用一下时间常量
i := 0
for {
i++
fmt.Println(i)
//休眠
//time.Sleep(time.Second)
time.Sleep(time.Millisecond * 100)
if i == 100 {
break
}
}
7)time 的 Unix 和 UnixNano 的方法
fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())
编写一段代码来统计 函数 test03 执行的时间
func test03() {
str := ""
for i := 0; i < 100000; i++ {
str += "hello" + strconv.Itoa(i)
}
}
func main() {
//在执行test03前,先获取到当前的unix时间戳
start := time.Now().Unix()
test03()
end := time.Now().Unix()
fmt.Printf("执行test03()耗费时间为%v秒\n", end-start)
}
内置函数
1)len:用来求长度,比如 string、array、slice、map、channel
2)new:用来分配内存,主要用来分配值类型,比如 int、float32,struct…返回的是指针
3)make:用来分配内存,主要用来分配引用类型,比如 channel、map、slice(后面在写相关笔记)
错误处理
1)在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃.)
2)如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可 以在捕获到错误后,给管理员一个提示(邮件,短信。。。)
3)这里引出错误处理机制
1)Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。
2)Go 中引入的处理方式为:defer, panic, recover
3)这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中 通过 recover 捕获这个异常,然后正常处理
使用 defer+recover 来处理错误
func test() {
//使用defer + recover 来捕获和处理异常
defer func() {
err := recover() // recover()内置函数,可以捕获到异常
if err != nil { // 说明捕获到错误
fmt.Println("err=", err)
//这里就可以将错误信息发送给管理员....
fmt.Println("发送邮件给admin@sohu.com~")
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res=", res)
}
func main() {
//测试
test()
for {
fmt.Println("main()下面的代码...")
time.Sleep(time.Second)
}
}
自定义错误的介绍
//函数去读取以配置文件init.conf的信息
//如果文件名传入不正确,我们就返回一个自定义的错误
func readConf(name string) (err error) {
if name == "config.ini" {
//读取...
return nil
} else {
//返回一个自定义错误
return errors.New("读取文件错误..")
}
}
func test02() {
err := readConf("config2.ini")
if err != nil {
//如果读取文件发送错误,就输出这个错误,并终止程序
panic(err)
}
fmt.Println("test02()继续执行....")
}
func main() {
test02()
fmt.Println("main()下面的代码...")
}
数组的定义和内存布局
var 数组名 [数组大小]数据类型
var a [5]int
赋初值 a[0] = 1 a[1] = 30 …
数组在内存布局(重要)
1)数组的地址可以通过数组名来获取 &intArr
2)数组的第一个元素的地址,就是数组的首地址
3)数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 -> 8 int32->4…
数组的使用
//四种初始化数组的方式
var numArr01 [3]int = [3]int{1, 2, 3}
fmt.Println("numArr01=", numArr01)
var numArr02 = [3]int{5, 6, 7}
fmt.Println("numArr02=", numArr02)
//这里的 [...] 是规定的写法
var numArr03 = [...]int{8, 9, 10}
fmt.Println("numArr03=", numArr03)
var numArr04 = [...]int{1: 800, 0: 900, 2:999}
fmt.Println("numArr04=", numArr04)
//类型推导
strArr05 := [...]string{1: "tom", 0: "jack", 2:"mary"}
fmt.Println("strArr05=", strArr05)
数组的遍历
for-range 结构遍历
//演示for-range遍历数组
heroes := [...]string{"宋江", "吴用", "卢俊义"}
for i, v := range heroes {
fmt.Printf("i=%v v=%v\n", i , v)
fmt.Printf("heroes[%d]=%v\n", i, heroes[i])
}
for _, v := range heroes {
fmt.Printf("元素的值=%v\n", v)
}
数组使用的注意事项和细节
1)数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化
func main() {
//数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化。
var arr01 [3]int
arr01[0] = 1
arr01[1] = 30
//这里会报错
arr01[2] = 1.1
//其长度是固定的, 不能动态变化,否则报越界
arr01[3] = 890
fmt.Println(arr01)
}
2)var arr []int 这时 arr 就是一个 slice 切片,切片后面专门讲解,不急哈.
3)数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
4)数组创建后,如果没有赋值,有默认值(零值)
//数组创建后,如果没有赋值,有默认值(零值)
//1. 数值(整数系列, 浮点数系列) =>0
//2. 字符串 ==> ""
//3. 布尔类型 ==> false
var arr01 [3]float32
var arr02 [3]string
var arr03 [3]bool
fmt.Printf("arr01=%v arr02=%v arr03=%v\n", arr01, arr02, arr03)
5)使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值(默认零值) 3 使用数组
6)数组的下标是从 0 开始的
7)数组下标必须在指定范围内使用,否则报 panic:数组越界,比如 var arr [5]int 则有效下标为 0-4
8)Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响
func test01(arr [3]int) {
arr[0] = 88
}
func main() {
//Go的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响
arr := [3]int{11, 22, 33}
test01(arr)
fmt.Println("main arr=", arr) //
}
9)如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
//函数
func test02(arr *[3]int) {
fmt.Printf("arr指针的地址=%p", &arr)
(*arr)[0] = 88 //!!
}
arr := [3]int{11, 22, 33}
fmt.Printf("arr 的地址=%p", &arr)
test02(&arr)
fmt.Println("main arr=", arr)
10)长度是数组类型的一部分,在传递函数参数时 需要考虑数组的长度
数组的应用案例
1)创建一个 byte 类型的 26 个元素的数组,分别 放置’A’-'Z‘。使用 for 循环访问所有元素并打印 出来。提示:字符数据运算 ‘A’+1 -> 'B
//思路
//1. 声明一个数组 var myChars [26]byte
//2. 使用for循环,利用 字符可以进行运算的特点来赋值 'A'+1 -> 'B'
//3. 使用for打印即可
//代码:
var myChars [26]byte
for i := 0; i < 26; i++ {
myChars[i] = 'A' + byte(i) // 注意需要将 i => byte
}
for i := 0; i < 26; i++ {
fmt.Printf("%c ", myChars[i])
}
2)请求出一个数组的最大值,并得到对应的下标。
//思路
//1. 声明一个数组 var intArr[5] = [...]int {1, -1, 9, 90, 11}
//2. 假定第一个元素就是最大值,下标就0
//3. 然后从第二个元素开始循环比较,如果发现有更大,则交换
var intArr [6]int = [...]int {1, -1, 9, 90, 11, 9000}
maxVal := intArr[0]
maxValIndex := 0
for i := 1; i < len(intArr); i++ {
//然后从第二个元素开始循环比较,如果发现有更大,则交换
if maxVal < intArr[i] {
maxVal = intArr[i]
maxValIndex = i
}
}
fmt.Printf("maxVal=%v maxValIndex=%v\n\n", maxVal, maxValIndex)
3)请求出一个数组的和和平均值。for-range
//思路
//1. 就是声明一个数组 var intArr[5] = [...]int {1, -1, 9, 90, 11}
//2. 求出和sum
//3. 求出平均值
//代码
var intArr2 [5]int = [...]int {1, -1, 9, 90, 12}
sum := 0
for _, val := range intArr2 {
//累计求和
sum += val
}
//如何让平均值保留到小数.
fmt.Printf("sum=%v 平均值=%v \n\n", sum, float64(sum) / float64(len(intArr2)))
4)要求:随机生成五个数,并将其反转打印 , 复杂应用
//思路
//1. 随机生成五个数 , rand.Intn() 函数
//2. 当我们得到随机数后,就放到一个数组 int数组
//3. 反转打印 , 交换的次数是 len / 2, 倒数第一个和第一个元素交换, 倒数第2个和第2个元素交换
var intArr3 [5]int
//为了每次生成的随机数不一样,我们需要给一个seed值
len := len(intArr3)
rand.Seed(time.Now().UnixNano())
for i := 0; i < len; i++ {
intArr3[i] = rand.Intn(100) // 0<=n<100
}
fmt.Println("交换前~=", intArr3)
//反转打印 , 交换的次数是 len / 2,
//倒数第一个和第一个元素交换, 倒数第2个和第2个元素交换
temp := 0 //做一个临时变量
for i := 0; i < len / 2; i++ {
temp = intArr3[len - 1 - i]
intArr3[len - 1 - i] = intArr3[i]
intArr3[i] = temp
}
fmt.Println("交换后~=", intArr3)
切片
切片的基本介绍
1)切片的英文是 slice
2)切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
3)切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样。
4)切片的长度是可以变化的,因此切片是一个可以动态变化数组。
5)切片定义的基本语法: var 切片名 []类型 比如:var a [] int
- slice 的确是一个引用类型
- slice 从底层来说,其实就是一个数据结构(struct 结构体)
type slice struct { ptr *[2]int len int cap int }
切片的使用
第一种方式:定义一个切片,然后让切片去引用一个已经创建好的数组,比如前面的案例就是这 样的。
var intArr [5]int = [...]int{1, 22, 33, 66, 99}
//声明/定义一个切片
slice := intArr[1:3]
fmt.Println("intArr=", intArr)
fmt.Println("slice 的元素是 =", slice) // 22, 33
fmt.Println("slice 的元素个数 =", len(slice)) // 2
fmt.Println("slice 的容量 =", cap(slice)) // 切片的容量是可以动态变化
第二种方式:通过 make 来创建切片. 基本语法:var 切片名 []type = make([]type, len, [cap])
参数说明: type: 就是数据类型 len : 大小 cap :指定切片容量,可选, 如果你分配了 cap,则要 求 cap>=len.
//演示切片的使用 make
var slice []float64 = make([]float64, 5, 10)
slice[1] = 10
slice[3] = 20
//对于切片,必须make使用.
fmt.Println(slice)
fmt.Println("slice的size=", len(slice))
fmt.Println("slice的cap=", cap(slice))
对上面代码的小结:
1)通过 make 方式创建切片可以指定切片的大小和容量
2)如果没有给切片的各个元素赋值,那么就会使用默认值[int , float=> 0 string =>”” bool => false]
3)通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去 访问各个元素.
第 3 种方式:定义一个切片,直接就指定具体数组,使用原理类似 make 的方式
fmt.Println()
//第3种方式:定义一个切片,直接就指定具体数组,使用原理类似make的方式
var strSlice []string = []string{"tom", "jack", "mary"}
fmt.Println("strSlice=", strSlice)
fmt.Println("strSlice size=", len(strSlice)) //3
fmt.Println("strSlice cap=", cap(strSlice)) // ?
切片的遍历
for 循环常规方式遍历
//使用常规的for循环遍历切片
var arr [5]int = [...]int{10, 20, 30, 40, 50}
//slice := arr[1:4] // 20, 30, 40
slice := arr[1:4]
for i := 0; i < len(slice); i++ {
fmt.Printf("slice[%v]=%v ", i, slice[i])
}
fmt.Println()
for-range 结构遍历切片
//使用for--range 方式遍历切片
for i, v := range slice {
fmt.Printf("i=%v v=%v \n", i, v)
}
切片的使用的注意事项和细节讨论
1)切片初始化时 var slice = arr[startIndex:endIndex]
说明:从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。
2)切片初始化时,仍然不能越界。范围在 [0-len(arr)] 之间,但是可以动态增长.
var slice = arr[0:end] 可以简写 var slice = arr[:end]
var slice = arr[start:len(arr)] 可以简写: var slice = arr[start:]
var slice = arr[0:len(arr)] 可以简写: var slice = arr[:]
3)cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
4)切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make 一 个空间供切片来使用
5)切片可以继续切片
slice2 := slice[1:2] // slice [ 20, 30, 40] [30]
slice2[0] = 100 // 因为arr , slice 和slice2 指向的数据空间是同一个,因此slice2[0]=100,其它的都变化
fmt.Println("slice2=", slice2)
fmt.Println("slice=", slice)
fmt.Println("arr=", arr)
6)用 append 内置函数,可以对切片进行动态追加
//用append内置函数,可以对切片进行动态追加
var slice3 []int = []int{100, 200, 300}
//通过append直接给slice3追加具体的元素
slice3 = append(slice3, 400, 500, 600)
fmt.Println("slice3", slice3) //100, 200, 300,400, 500, 600
//通过append将切片slice3追加给slice3
slice3 = append(slice3, slice3...) // 100, 200, 300,400, 500, 600 100, 200, 300,400, 500, 600
fmt.Println("slice3", slice3)
切片 append 操作的底层原理分析: 切片 append 操作的本质就是对数组扩容
go 底层会创建一下新的数组 newArr(安装扩容后大小)
将 slice 原来包含的元素拷贝到新的数组 newArr
slice 重新引用到 newArr
注意 newArr 是在底层来维护的,程序员不可见
7)切片的拷贝操作 切片使用 copy 内置函数完成拷贝
//切片使用copy内置函数完成拷贝,举例说明
fmt.Println()
var slice4 []int = []int{1, 2, 3, 4, 5}
var slice5 = make([]int, 10)
copy(slice5, slice4)
fmt.Println("slice4=", slice4)// 1, 2, 3, 4, 5
fmt.Println("slice5=", slice5) // 1, 2, 3, 4, 5, 0 , 0 ,0,0,0
(1)copy(para1, para2) 参数的数据类型是切片
(2)按照上面的代码来看, slice4 和 slice5 的数据空间是独立,相互不影响,也就是说 slice4[0]= 999, slice5[0] 仍然是 1
string 和 slice
1)string 底层是一个 byte 数组,因此 string 也可以进行切片处理 案例演示:
//string底层是一个byte数组,因此string也可以进行切片处理
str := "hello@atguigu"
//使用切片获取到 atguigu
slice := str[6:]
fmt.Println("slice=", slice)
2)string 和切片在内存的形式,以 “abcd” 画出内存示意图
3)string 是不可变的,也就说不能通过 str[0] = ‘z’ 方式来修改字符串
4)如果需要修改字符串,可以先将 string -> []byte / 或者 []rune -> 修改 -> 重写转成 string
//如果需要修改字符串,可以先将string -> []byte / 或者 []rune -> 修改 -> 重写转成string
//"hello@atguigu" =>改成 "zello@atguigu"
arr1 := []byte(str)
arr1[0] = 'z'
str = string(arr1)
fmt.Println("str=", str)
// 细节,我们转成[]byte后,可以处理英文和数字,但是不能处理中文
// 原因是 []byte 字节来处理 ,而一个汉字,是3个字节,因此就会出现乱码
// 解决方法是 将 string 转成 []rune 即可, 因为 []rune是按字符处理,兼容汉字
冒泡排序实现
//冒泡排序
func BubbleSort(arr *[5]int) {
fmt.Println("排序前arr=", (*arr))
temp := 0 //临时变量(用于做交换)
//冒泡排序..一步一步推导出来的
for i :=0; i < len(*arr) - 1; i++ {
for j := 0; j < len(*arr) - 1 - i; j++ {
if (*arr)[j] > (*arr)[j + 1] {
//交换
temp = (*arr)[j]
(*arr)[j] = (*arr)[j + 1]
(*arr)[j + 1] = temp
}
}
}
fmt.Println("排序后arr=", (*arr))
}
func main() {
//定义数组
arr := [5]int{24,69,80,57,13}
//将数组传递给一个函数,完成排序
BubbleSort(&arr)
fmt.Println("main arr=", arr) //有序? 是有序的
}
查找
//有一个数列:白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王
//猜数游戏:从键盘中任意输入一个名称,判断数列中是否包含此名称【顺序查找】
//思路
//1 定义一个数组, 白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王 字符串数组
//2.从控制台接收一个名字,依次比较,如果发现有,提示
//代码
names := [4]string{"白眉鹰王", "金毛狮王", "紫衫龙王", "青翼蝠王"}
var heroName = ""
fmt.Println("请输入要查找的人名...")
fmt.Scanln(&heroName)
//顺序查找:第2种方式
index := -1
for i := 0; i < len(names); i++ {
if heroName == names[i] {
index = i //将找到的值对应的下标赋给 index
break
}
}
if index != -1 {
fmt.Printf("找到%v , 下标%v \n", heroName, index)
} else {
fmt.Println("没有找到", heroName)
}
二分查找
//二分查找的函数
/*
二分查找的思路: 比如我们要查找的数是 findVal
1. arr是一个有序数组,并且是从小到大排序
2. 先找到 中间的下标 middle = (leftIndex + rightIndex) / 2, 然后让 中间下标的值和findVal进行比较
2.1 如果 arr[middle] > findVal , 就应该向 leftIndex ---- (middle - 1)
2.2 如果 arr[middle] < findVal , 就应该向 middel+1---- rightIndex
2.3 如果 arr[middle] == findVal , 就找到
2.4 上面的2.1 2.2 2.3 的逻辑会递归执行
3. 想一下,怎么样的情况下,就说明找不到[分析出退出递归的条件!!]
if leftIndex > rightIndex {
// 找不到..
return ..
}
*/
func BinaryFind(arr *[6]int, leftIndex int, rightIndex int, findVal int) {
//判断leftIndex 是否大于 rightIndex
if leftIndex > rightIndex {
fmt.Println("找不到")
return
}
//先找到 中间的下标
middle := (leftIndex + rightIndex) / 2
if (*arr)[middle] > findVal {
//说明我们要查找的数,应该在 leftIndex --- middel-1
BinaryFind(arr, leftIndex, middle - 1, findVal)
} else if (*arr)[middle] < findVal {
//说明我们要查找的数,应该在 middel+1 --- rightIndex
BinaryFind(arr, middle + 1, rightIndex, findVal)
} else {
//找到了
fmt.Printf("找到了,下标为%v \n", middle)
}
}
func main() {
arr := [6]int{1,8, 10, 89, 1000, 1234}
//测试一把
BinaryFind(&arr, 0, len(arr) - 1, -6)
}
二维数组
使用方式 1: 先声明/定义,再赋值
语法: var 数组名 [大小][大小]类型
比如: var arr [2][3]int , 再赋值。
var arr2 [2][3]int //以这个为例来分析arr2在内存的布局!!
arr2[1][1] = 10
fmt.Println(arr2)
fmt.Printf("arr2[0]的地址%p\n", &arr2[0])
fmt.Printf("arr2[1]的地址%p\n", &arr2[1])
fmt.Printf("arr2[0][0]的地址%p\n", &arr2[0][0])
fmt.Printf("arr2[1][0]的地址%p\n", &arr2[1][0])
使用方式 2: 直接初始化
声明:var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值…},{初值…}}
赋值(有默认值,比如 int 类型的就是 0)
fmt.Println()
arr3 := [2][3]int{{1,2,3}, {4,5,6}}
fmt.Println("arr3=", arr3)
二维数组的遍历
双层 for 循环完成遍历
//演示二维数组的遍历
var arr3 = [2][3]int{{1,2,3}, {4,5,6}}
//for循环来遍历
for i := 0; i < len(arr3); i++ {
for j := 0; j < len(arr3[i]); j++ {
fmt.Printf("%v\t", arr3[i][j])
}
fmt.Println()
}
for-range 方式完成遍历
//演示二维数组的遍历
var arr3 = [2][3]int{{1,2,3}, {4,5,6}}
//for-range来遍历二维数组
for i, v := range arr3 {
for j, v2 := range v {
fmt.Printf("arr3[%v][%v]=%v \t",i, j, v2)
}
fmt.Println()
}
map的声明
map 是 key-value 数据结构,又称为字段或者关联数组。类似其它编程语言的集合, 在编程中是经常使用到
基本语法
var map 变量名 map[keytype]valuetype
key 可以是什么类型
golang 中的 map,的 key 可以是很多种类型,比如 bool, 数字,string, 指针, channel , 还可以是只 包含前面几个类型的 接口, 结构体, 数组
通常 key 为 int 、string
注意: slice, map 还有 function 不可以,因为这几个没法用 == 来判断
valuetype 可以是什么类型
valuetype 的类型和 key 基本一样,这里我就不再赘述了
通常为: 数字(整数,浮点数),string,map,struct
注意:声明是不会分配内存的,初始化需要 make ,分配内存后才能赋值和使用。 案例演示
map 的使用
//第一种使用方式
var a map[string]string
//在使用map前,需要先make , make的作用就是给map分配数据空间
a = make(map[string]string, 10)
a["no1"] = "宋江" //ok?
a["no2"] = "吴用" //ok?
a["no1"] = "武松" //ok?
a["no3"] = "吴用" //ok?
fmt.Println(a)
//第二种方式
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "天津"
cities["no3"] = "上海"
fmt.Println(cities)
//第三种方式
heroes := map[string]string{
"hero1" : "宋江",
"hero2" : "卢俊义",
"hero3" : "吴用",
}
heroes["hero4"] = "林冲"
fmt.Println("heroes=", heroes)
map 的增删改查操作
//第二种方式
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "天津"
cities["no3"] = "上海"
fmt.Println(cities)
//因为 no3这个key已经存在,因此下面的这句话就是修改
cities["no3"] = "上海~"
fmt.Println(cities)
//演示删除
delete(cities, "no1")
fmt.Println(cities)
//当delete指定的key不存在时,删除不会操作,也不会报错
delete(cities, "no4")
fmt.Println(cities)
map查找
//演示map的查找
val, ok := cities["no2"]
if ok {
fmt.Printf("有no1 key 值为%v\n", val)
} else {
fmt.Printf("没有no1 key\n")
}
//如果希望一次性删除所有的key
//1. 遍历所有的key,如何逐一删除 [遍历]
//2. 直接make一个新的空间
cities = make(map[string]string)
fmt.Println(cities)
map遍历
//使用for-range遍历map
//第二种方式
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "天津"
cities["no3"] = "上海"
for k, v := range cities {
fmt.Printf("k=%v v=%v\n", k, v)
}
//使用for-range遍历一个结构比较复杂的map
studentMap := make(map[string]map[string]string)
studentMap["stu01"] = make(map[string]string, 3)
studentMap["stu01"]["name"] = "tom"
studentMap["stu01"]["sex"] = "男"
studentMap["stu01"]["address"] = "北京长安街~"
studentMap["stu02"] = make(map[string]string, 3) //这句话不能少!!
studentMap["stu02"]["name"] = "mary"
studentMap["stu02"]["sex"] = "女"
studentMap["stu02"]["address"] = "上海黄浦江~"
for k1, v1 := range studentMap {
fmt.Println("k1=", k1)
for k2, v2 := range v1 {
fmt.Printf("\t k2=%v v2=%v\n", k2, v2)
}
fmt.Println()
}
map 切片
切片的数据类型如果是 map,则我们称为 slice of map,map 切片,这样使用则 map 个数就可以动 态变化了。
//演示map切片的使用
/*
要求:使用一个map来记录monster的信息 name 和 age, 也就是说一个
monster对应一个map,并且妖怪的个数可以动态的增加=>map切片
*/
//1. 声明一个map切片
var monsters []map[string]string
monsters = make([]map[string]string, 2) //准备放入两个妖怪
//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"
}
// 下面这个写法越界。
// if monsters[2] == nil {
// monsters[2] = make(map[string]string, 2)
// monsters[2]["name"] = "狐狸精"
// monsters[2]["age"] = "300"
// }
//这里我们需要使用到切片的append函数,可以动态的增加monster
//1. 先定义个monster信息
newMonster := map[string]string{
"name" : "新的妖怪~火云邪神",
"age" : "200",
}
monsters = append(monsters, newMonster)
fmt.Println(monsters)
map排序
1)golang 中没有一个专门的方法针对 map 的 key 进行排序
2)golang 中的 map 默认是无序的,注意也不是按照添加的顺序存放的,你每次遍历,得到的输出 可能不一样.
3)golang 中 map 的排序,是先将 key 进行排序,然后根据 key 值遍历输出即可
//map的排序
map1 := make(map[int]int, 10)
map1[10] = 100
map1[1] = 13
map1[4] = 56
map1[8] = 90
fmt.Println(map1)
//如果按照map的key的顺序进行排序输出
//1. 先将map的key 放入到 切片中
//2. 对切片排序
//3. 遍历切片,然后按照key来输出map的值
var keys []int
for k, _ := range map1 {
keys = append(keys, k)
}
//排序
sort.Ints(keys)
fmt.Println(keys)
for _, k := range keys{
fmt.Printf("map1[%v]=%v \n", k, map1[k])
}
1)map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来 的 map
2)map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动 态的增长 键值对(key-value)
3)map 的 value 也经常使用 struct 类型,更适合管理复杂的数据(比前面 value 是一个 map 更好),比如 value 为 Student 结构体
type Stu struct {
Name string
Age int
Address string
}
//map的value 也经常使用struct 类型,
//更适合管理复杂的数据(比前面value是一个map更好),
//比如value为 Student结构体 【案例演示,因为还没有学结构体,体验一下即可】
//1.map 的 key 为 学生的学号,是唯一的
//2.map 的 value为结构体,包含学生的 名字,年龄, 地址
students := make(map[string]Stu, 10)
//创建2个学生
stu1 := Stu{"tom", 18, "北京"}
stu2 := Stu{"mary", 28, "上海"}
students["no1"] = stu1
students["no2"] = stu2
fmt.Println(students)
//遍历各个学生信息
for k, v := range students {
fmt.Printf("学生的编号是%v \n", k)
fmt.Printf("学生的名字是%v \n", v.Name)
fmt.Printf("学生的年龄是%v \n", v.Age)
fmt.Printf("学生的地址是%v \n", v.Address)
fmt.Println()
}