Golang学习笔记

Golang学习笔记

Scroll Down

0、vs code小技巧

0.1 vs code 调试报错go.mod file not found in current directory or any parent directory; see 'go help modules'

( 前提vscode已经创建编辑好了调试所需的launch.json文件 )在IDE软件选择调试模式的时候,这个报错是因为当前go文件所在目录或上级目录中缺少go.mod文件,终端进入当前目录,使用go mod init 文件/文件夹名即可解决问题

0.2 vs code调试按钮介绍

vscode_debug_button.png

1、变量

package main

import "fmt"

// 单个声明变量
var name string
var age int
var city string = "北京"

// 批量声明
var (
	school string
	height int
)
var company, org string

// 单一常量声明
const name66 = "zhijb"

// 批量常量声明
const (
	webName = "ishells"
	pi      = 3.141592
)

// 同时声明多个常量时,如果省略了值则表示和上面一行的值相同
const (
	n1 = 100
	n2
	n3
)

// iota
const (
	iota_1 = iota // 0
	iota_2        // 1
	iota_3        // 2
)

// iota在const关键字出现时被重置为0 const中每新增一行常量声明将使iota计数一次(即值+1)
// 使用 _ 匿名变量跳过iota的值
const (
	iota_4 = iota // 0
	iota_5        // 1
	_             // 2,匿名变量不保存,丢弃2
	iota_6        // 3
)

const (
	iota_7 = iota // 0
	num    = 100  // 100
	iota_8 = iota // 2
	iota_9        // 3
)

/*
	定义数量级 (这里的<<表示左移操作,
	1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。
	同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。)
*/
const (
	_  = iota             // 0,匿名变量丢弃
	KB = 1 << (10 * iota) // 1左移10位,变成二进制10000000000,十进制1024
	MB = 1 << (10 * iota)
	GB = 1 << (10 * iota)
	TB = 1 << (10 * iota)
	PB = 1 << (10 * iota)
)

// 多个iota定义在一行
const (
	a, b = iota + 1, iota + 2 //1, 2
	c, d = iota + 1, iota + 2 //c=1+1,d=1+2
	e, f                      //3,4
)
const (
	g, h = iota, iota
	i, j // 1,1
)

func main() {

	// var sex string
	// 初始化变量
	// name = "zhijb"
	// age = 88
	// school = "xcu"
	// height = 170
	fmt.Println("hello world")
	// fmt.Print(name)                   // 直接打印输出
	// fmt.Println("school is" + school) // 输出后换行
	// fmt.Printf("age is: %d", age)     // 格式化输出

	fmt.Println("n1: ", n1)
	fmt.Println("n2: ", n2)
	fmt.Println("n3: ", n3)

	fmt.Println("iota_1: ", iota_1)
	fmt.Println("iota_2: ", iota_2)
	fmt.Println("iota_3: ", iota_3)

	fmt.Println("iota_4: ", iota_4)
	fmt.Println("iota_5: ", iota_5)
	fmt.Println("iota_6: ", iota_6)

	fmt.Println("iota_7: ", iota_7)
	fmt.Println("iota_8: ", iota_8)
	fmt.Println("iota_9: ", iota_9)
	fmt.Println("KB: ", KB)
	fmt.Println("MB: ", MB)
	fmt.Println("GB: ", GB)
	fmt.Println("TB: ", TB)
	fmt.Println("PB: ", PB)

	fmt.Println("i: ", i)
	fmt.Println("j: ", j)

}

/*
    0、函数外只能放置标识符(变量、常量、函数、类型)的声明,不能放置任何语句
    // 程序的入口函数
   		func main(){
       		fmt.Println("Hello world")
   		}

	// 变量
		单变量声明:
		第一种,指定变量类型,如果没有初始化,则变量值为该类型默认值
		var v_name v_type
		v_name = value
		第二种,根据值自行判断变量类型
		var v_name = value
		第三种,省略var,使用 :=   (这种方式只能用在函数内部)
		v_name := value


		多变量非全局变量声明:
		var vname1, vname2, vname3 v_type
		vname1, vname2, vname3 = v1, v2, v3

		var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断
		vname1, vname2, vname3 := v1, v2, v3

		多变量全局变量声明:
		var(
			v_name1 v_type1
			v_name2 v_type2
		)

		1、 GO语言中的变量需要先声明才能使用,
			声明变量时需要指定变量类型,
			不像Python之类的解释性语言可以直接使用变量

		2、变量声明时,如果没有进行初始化,会自动对变量对应内存区域执行初始化操作
			例如:
			整型和浮点型变量的默认值为 0
			字符串型变量的默认值为 空字符串
			布尔型变量的默认值为 false
			切片、函数、指针变量的默认为	nil

		3、同一作用域内不支持变量的重复声明

		4、匿名变量:
			在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量,匿名变量用一个下划线_表示
			匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。


	// 常量
		1、常量就是恒定不变的值,多用于定义程序运行期间不会改变的那些值。
		  常量的声明和变量的声明非常相似,关键字是 const
		  常量定义时必须赋值,定义之后不能重复赋值

		2、常量批量定义
		批量定义时,如果未给定值,与上一行保持一致
		const(
			n1 = 100
			n2		// 100
			n3		// 100
		)

		iota:
			iota是go语言的常量计数器,只能在常量的表达式中使用
			iota在const关键字出现时被重置为0,在const中每新增一行常量声明将使iota计数一次(即值+1)
		例:

*/

/*
	变量声明易错点:
		1、变量声明后未使用
		2、变量类型不匹配
		3、同一作用域下,变量只能声明一次
*/

2、数据类型

package main

import (
	"fmt"
	"math"
	"strings"
	"unicode/utf8"
)

func main() {
	// 1、整型
	/*
		整型分为以下两个大类:
			按长度分为:int8、int16、int32、int64
			对应无符号整型:uint8、uint16、uint32、uint64

			uint8	无符号 8位整型 (0 到 255)
			uint16	无符号 16位整型 (0 到 65535)
			uint32	无符号 32位整型 (0 到 4294967295)
			uint64	无符号 64位整型 (0 到 18446744073709551615)
			int8	有符号 8位整型 (-128 到 127)
			int16	有符号 16位整型 (-32768 到 32767)
			int32	有符号 32位整型 (-2147483648 到 2147483647)
			int64	有符号 64位整型 (-9223372036854775808 到 9223372036854775807)

			uint	32位操作系统上就是uint32,64位操作系统上就是uint64
			int		32位操作系统上就是int32,64位操作系统上就是int64
			uintptr	无符号整型,用于存放一个指针
	*/
	//  %d	十进制
	//	%b	二进制
	//	%o	八进制
	//	%ox	十六进制
	//  %T	显示数据类型

	// 2、浮点型
	/*
		Go语言支持两种浮点型数:float32和float64。
		float32 的浮点数的最大范围约为 3.4e38,可以使用常量定义:math.MaxFloat32
		float64 的浮点数的最大范围约为 1.8e308,可以使用一个常量定义:math.MaxFloat64
	*/
	fmt.Printf("保留两位小数的pi值: %.2f\n", math.Pi)

	// 3、布尔值
	// go语言以 bool 类型进行声明布尔型数据,只有	true 和 false 两个值
	/*
		注意:
			① 布尔类型变量的默认值为false。
			② Go 语言中不允许将整型强制转换为布尔型.
			③ 布尔型无法参与数值运算,也无法与其他类型进行转换。
	*/

	// 4、字符串
	/*

			① go语言中的字符串只能使用双引号"",单引号''的是字符
			② 1 字节 = 8 Bit(8个二进制位)
		   	  1个字符 'A' = 1 字节
		   	  1个utf8编码汉字 '学' = 一般占3个字节

	*/
	// 4.1 多行字符串(原样输出,带格式)
	s := `
			1
			2
			3
	`
	fmt.Println("3、多行字符串原样输出: " + s)

	// 字符串求长度 len(str)
	// len()返回的是字符串长度的字节数,与其他编程语言不同的地方

	str_len := "直接"
	fmt.Printf("使用len方法计算\"直接\"字节长度:%v\n", len(str_len))

	// 可以使用utf8库下的RuneCountInString方法获取字符长度
	fmt.Printf("使用utf8.RuneCountInString方法计算\"直接\"字符长度:%v\n", utf8.RuneCountInString(str_len))

	// 正经计算字符长度的时候,就使用utf8下的方法来计算,不要使用len()/3来进行计算(避免错误,有的表情之类的是4字节)

	// 4.2 字符串拼接  +或者fmt.Sprintf
	var name string = "zhijb"
	var age string = "22"

	word := name + age
	fmt.Println("4.1 +字符串拼接: " + word)
	ss := fmt.Sprintf("%s%s", name, age)
	fmt.Println("4.2 Sprintf字符串拼接:" + ss)

	// 4.3 字符串分割 strings.Split
	var dir_location string = "/home/zhijb/data_dir/code_dir/src"
	fmt.Println()
	fmt.Println(strings.Split(dir_location, "/"))

	// 4.4 字符串判断包含 strings.Contains
	fmt.Print("6.1 字符串是否包含字符zhijb: ")
	fmt.Println(strings.Contains(dir_location, "zhijb"))
	fmt.Print("6.2 字符串是否包含字符支付宝: ")
	fmt.Println(strings.Contains(dir_location, "zhifubao"))

	// 4.5 前缀开头判断strings.HasPrefix()	结尾判断strings.HasSuffix()
	fmt.Print("7.1 判断路径是否以/home字符串开头: ")
	fmt.Println(strings.HasPrefix(dir_location, "/home"))
	fmt.Print("7.2 判断路径是否以src结尾: ")
	fmt.Println(strings.HasSuffix(dir_location, "src"))

	// 4.6 字符串位置判断
	fmt.Print("8.1 d字符在字符串中第一次出现位置: ")
	fmt.Println(strings.Index(dir_location, "d"))
	fmt.Print("8.2 d字符在字符串中最后一次出现位置: ")
	fmt.Println(strings.LastIndex(dir_location, "d"))

	// 5、字符类型:byte 和 rune
	/*
		Go 语言的字符有以下两种:
			uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
			rune类型,代表一个 UTF-8字符

		当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32 !!
		Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode 的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾
	*/

	// 遍历字符串
	string1 := "hello世界"
	for i := 0; i < len(string1); i++ { //byte
		fmt.Printf("%v(%c) ", s[i], s[i])
	}
	fmt.Println()
	for _, r := range string1 { //rune
		fmt.Printf("%v(%c) ", r, r)
	}
	fmt.Println()

	var rune12 rune = '你'
	fmt.Println(rune12)

	// 5、修改字符串
	/*
		要修改字符串,需要先将其转换成 []rune 或 []byte
		完成后再转换为string。
		无论哪种转换,都会重新分配内存,并复制字节数组
	*/
	s1 := "big"
	// 强制类型转换
	byteS1 := []byte(s1)
	byteS1[0] = 'p'
	fmt.Println(string(byteS1))

	s2 := "白萝卜"
	runeS2 := []rune(s2)
	runeS2[0] = '红'
	fmt.Println(string(runeS2))

	// 6、类型转换
	/*
		go语言中类型转换与python类似,直接使用 data_type(变量/复杂算式/函数返回值) 进行强制转换
		不会更改原数据的类型

		go语言中没有隐式类型转换,即以下示例代码会报错:
			var num1 int = 13
			var num2 unint = 15
			// 下面这一句无法通过编译,因为golang是强类型语言,并且不会帮你做任何的转换
			println(a == c)
	*/
	var int_num int = 8
	fmt.Printf("%T\n", float32(int_num))

	stringsss := "s"
	stringbyte := '@'
	fmt.Printf("%T --- %T", stringsss, stringbyte)
}

3、条件语句

package main

import "fmt"

func main() {

	fmt.Println("hello world")
	// 1、if条件语句

	// Go语言规定与if匹配的左括号{必须与if和表达式放在同一行,{放在其他位置会触发编译错误。
	// 同理,与else匹配的{也必须与else写在同一行,else也必须与上一个if或else if右边的大括号在同一行。

	/*
		if 表达式1{
			分支1
		}else if 表达式2{
			分支2
		}else{
			分支3
		}
	*/

	/*
		在 if 表达式之前可以添加一个执行语句,再根据变量值进行判断
		if score := 65; score >= 90 {
			fmt.Println("A")
		} else if score > 75 {
			fmt.Println("B")
		} else {
			fmt.Println("C")
		}
	*/

	// 2、go语言中只有for循环,没有其它的循环语句

	/*
		for 初始语句;条件表达;结束语句{
			循环体语句
		}
	*/

	for i := 0; i < 5; i++ {
		fmt.Println(i)
	}

	// 2.1 for循环的初始语句可以被忽略,但是初始语句后的分号必须要写
	/*
		i := 0
		for ; i < 10; i++ {
			fmt.Println(i)
		}
	*/

	// 2.2 for循环的初始语句和结束语句可以都省略
	/*
		i := 0
		for i < 10 {
			fmt.Println(i)
			i++
		}
	*/

	// 2.3 for range循环(键值循环)
	s := "helloworld世界"
	// i 表示索引, v 表示值
	fmt.Println("从字符串中拿出索引和字符:")
	for i, v := range s {
		fmt.Printf("%d %c\n", i, v)
	}

	// 从字符串中拿出具体的字符
	fmt.Println("从字符串中拿出具体的字符: ")
	for _, c := range s {
		fmt.Printf("%c\n", c)
	}

	// 2.4 continue、break跳出循环
	/*
		break 跳出循环
		continue	开始下一次循环

	*/

	// 2.5 使用goto语句简化代码
	/*
		goto语句通过标签进行代码间的无条件跳转。
		goto语句可以在快速跳出循环、避免重复退出上有一定的帮助。
		Go语言中使用goto语句能简化一些代码的实现过程。
		例如双层嵌套的for循环要退出时
	*/
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				// 设置退出标签
				goto breakTag
			}
			fmt.Printf("%v-%v\n", i, j)
		}
	}
	return
	// 标签
breakTag:
	fmt.Println("结束for循环")

	// 3、switch canume
	// 当已给定条件的case都不满足的时候,默认会执行 default,
	// Go语言规定每个switch只能有一个default分支
	for num := 0; num < 5; num++ {
		switch num {
		case 1:
			fmt.Println("num is: 1")
		case 2:
			fmt.Println("num is: 2")
		case 3:
			fmt.Println("num is: 3")
		case 4:
			fmt.Println("num is: 4")
		default:
			fmt.Println("无效的输入!")
		}
	}

	// 一个分支可以有多个值,多个case值中间使用英文逗号进行分割
	switch num1 := 7; num1 {
	case 1, 3, 5, 7, 9:
		fmt.Println("奇数")
	case 2, 4, 6, 8:
		fmt.Println("偶数")
	default:
		fmt.Println(num1)
	}

	// 分支还能使用表达式,这时候switch后面不需要在跟判断变量
	age := 30
	switch {
	case age < 25:
		fmt.Println("年龄小于25")
	case age > 25 && age < 35:
		fmt.Println("年龄大于25小于35")
	case age > 60:
		fmt.Println("年龄大于60")
	default:
		fmt.Println("年轻真好")
	}

	// fallthrough 语法可以主动执行该case的下一个case
	// 下面例子当匹配到a的case时,顺带把b case也执行了!
	stringsss1 := "a"
	switch {
	case stringsss1 == "a":
		fmt.Println("a")
		fallthrough
	case stringsss1 == "b":
		fmt.Println("b")
	case stringsss1 == "c":
		fmt.Println("c")
	default:
		fmt.Println("...")
	}

}

// 打印乘法表
for i := 1; i <= 9; i++ {
		for j := 1; j <= i; j++ {
			fmt.Printf("%d * %d = %d   ", j, i, i*j)
		}
		fmt.Println()
	}

4、运算符

package main

func main() {
	// 算术运算符 + - * / %
	// ++(自增) --(自减) 在go语言中是单独的语句,不能放在等号=的右边

	// 关系运算符
	// == != > >= < <=

	// 逻辑运算符
	// &&(逻辑and运算符)  ||(逻辑or运算符) !(逻辑非运算符)

	// 位运算符
	// &(相与)	|(相或)		^(异或运算)	<<(左移n位就是乘以2的n次方)		>>(右移n位就是除以2的n次方)

	// 赋值运算符
	// =	+=		-=		*=		/=		%=		<<=		>>=		&=		|=		^=
}

5、数组

package main

import "fmt"

// 数组

// 声明数组时 必须指定 存放元素的 类型 和 长度
var a1 = [6]int{0, 1, 2, 3, 4, 5}

func main() {
	// var 数组名 [长度]数据类型
	var array1 [3]bool
	var array2 [3]bool

	fmt.Printf("a1:%T a2:%T", array1, array2)

	// 数组的初始化
	// 如果数组不进行初始化,默认元素都是零值(各种数据类型的初始默认值)
	fmt.Println()
	// 1、初始化方式1,根据数组长度分别进行初始化
	array1 = [3]bool{true, true, true}
	fmt.Println(array1)

	// 2、初始化方式2,自动推断数组长度进行初始化
	array6 := [...]int{0, 1, 2, 3, 4, 5}
	fmt.Println(array6)

	// 3、初始化方式3,根据索引指定值
	array3 := [5]int{0: 1, 3: 6}
	fmt.Println(array3)

	// 数组的遍历
	citys := [...]string{"beijing", "shanghai", "shenzhen"}
	// 1、根据索引遍历
	for i := 0; i < len(citys); i++ {
		fmt.Println(citys[i])
	}
	// 2、for range 进行遍历
	for i, v := range citys {
		fmt.Println(i, v)
	}

	// 多维数组
	// [[北京 上海] [广州 深圳] [成都 重庆]]
	array4 := [3][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}
	fmt.Println(array4)
	// 根据索引取多维数组的值
	fmt.Println(array4[2][1])

	// 二维数组的遍历
	for _, v1 := range array4 {
		for _, v2 := range v1 {
			fmt.Printf("%s\t", v2)
		}
		fmt.Println()
	}

	// 多维数组只有第一层可以使用...来让编译器推导数组长度(多维数组仅最外层可以使用...)
	//支持的写法
	array7 := [...][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}
	fmt.Println(array7)
	//不支持多维数组的内层使用...
	// b := [3][...]string{
	// 	{"北京", "上海"},
	// 	{"广州", "深圳"},
	// 	{"成都", "重庆"},
	// }

	// 数组是值类型 数组是值类型,赋值和传参会复制整个数组。因此,改变副本的值,不会改变本身的值
	b1 := [3]int{1, 2, 3}
	b2 := b1
	b2[0] = 100
	fmt.Println(b1, b2)
	var sum int
	z1 := [...]int{1, 2, 3, 4, 5, 6}
	for _, s := range z1 {
		sum = sum + s
	}
	fmt.Println(sum)
}

6、切片

package main

import (
	"fmt"
)

// 指针、容量、长度

// 切片slice

// 切片的定义
// 切片基于数组类型做了一层封装(依赖数组实现),支持自动扩容
// 切片是一个引用类型,它的内部结构包括 地址 长度 和 容量。切片一般用于快速地操作一块数据集合
func main() {
	// 1、声明切片
	// 声明切片的基本语法如下(切片不用声明长度):
	// var Name []Type

	var a []string              //声明一个字符串切片
	var b = []int{}             //声明一个整型切片并初始化
	var c = []bool{false, true} //声明一个布尔切片并初始化
	// var d = []bool{false, true} //声明一个布尔切片并初始化
	fmt.Println(a)        //[]
	fmt.Println(b)        //[]
	fmt.Println(c)        //[false true]
	fmt.Println(a == nil) //true
	fmt.Println(b == nil) //false
	fmt.Println(c == nil) //false
	// fmt.Println(c == d)   //切片是引用类型,不支持直接比较,只能和nil比较

	// 切片仅声明之后并未分配内存地址,其值为nil,需要初始化之后才会有内存地址

	// 2、切片的本质
	// 切片本身不保存数值,本质就是对底层的数组进行操作
	// 切片的本质就是对底层数组的封装,它包含三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)
	// https://www.liwenzhou.com/posts/Go/06_slice/ 参考理解切片的长度和容量两个概念

	// 切片的长度即为从底层数组中所获取到的元素个数,而切片的容量则是从底层数组获取到第一个元素开始,直到 底层数组的最后一个元素
	// 举例:
	// 数组 var a  = [8]int{0,1,2,3,4,5,6,7}
	// 切片 var s1 = a[:5] 此时,s1的值为{0,1,2,3,4},切片s1的长度len为5,容量cap为8
	// 切片 var s1 = a[3:6] 此时,s1的值为{3,4,5},切片s1的长度len为3,容量cap为5

	// 3、切片的长度和容量
	// 内置的len()函数求长度,内置的cap()函数求容量

	// 4、切片表达式
	// go里边的切片表达式类似python中的切片操作,就不详细记笔记了
	// 简单的切片表达式		a[low:high]
	// 完整的切片表达式(字符串不支持完整切片),按照high-low左包右不包进行切片获取元素,而容量会被设置为max-low		a[low:high:max]
	// 完整切片表达式需要满足的条件是0 <= low <= high <= max <= cap(a),其他条件和简单切片表达式相同
	// max 的值不能超出底层数组的总长度,否则就会越界

	array1 := [5]int{1, 2, 3, 4, 5}
	s1 := array1[1:3:5]
	fmt.Printf("s1:%v len(s1):%v cap(s1):%v \n", s1, len(s1), cap(s1))

	// 5、使用make()函数构造切片
	// 不仅可以通过数组来构造切片,如果需要动态的创建一个切片,可以使用内置的make()函数
	// make()函数创建完切片之后会填入默认值(即各个数据类型的零值)

	// make([]T, size ,cap)
	// T:切片的元素数据类型
	// size:切片中元素的数量,即长度
	// cap:切片的容量
	array2 := make([]int, 2, 10)
	fmt.Println(array2)      //[0 0]
	fmt.Println(len(array2)) //2
	fmt.Println(cap(array2)) //10

	// 6、判断切片是否为空
	// 要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil来判断

	// 切片不能直接比较
	// 切片之间是不能比较的,不能使用==操作符来判断两个切片是否含有全部相等元素。
	// 切片唯一合法的比较操作是和nil比较
	// 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。
	// 但是我们不能说一个长度和容量都是0的切片一定是nil
	// 所以要判断一个切片是否是空的,要使用len(s) == 0来判断,不应该使用s == nil来判断

	// 下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容
	slice1 := make([]int, 3) //[0 0 0]
	slice2 := slice1         //将slice1直接赋值给slice2,slice1和slice2共用一个底层数组
	slice2[0] = 100
	fmt.Println(slice1) //[100 0 0]
	fmt.Println(slice2) //[100 0 0]
	/*
	   由于切片是引用类型,所以slice1和slice2其实都指向了同一块内存地址。修改slice2的同时slice2的值也会发生变化
	*/

	// 下面的代码演示了切片对数组的引用,修改切片的值并不会影响底层数组的值
	array3 := [6]int{0, 1, 2, 3, 4, 5}
	slice4 := array3

	slice5 := slice4
	slice4[0] = 100
	slice5[1] = 200
	

	// 修改之后 array3:[0 1 2 3 4 5] slice4:[100 1 2 3 4 5] slice5:[0 200 2 3 4 5]
	fmt.Printf("array3:%v slice4:%v slice5:%v", array3, slice4, slice5)

	// 7、切片的遍历和数组一样,支持索引遍历和 for range 遍历

	// 8、append()方法为切片增加元素
	// 内建函数append()可以为切片动态添加元素。
	// 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)

	// 调用append()函数必须用原来的切片变量接受返回值
	// 举例,如创业公司xxx初始就2个人,只需要2个工位,
	// 后来公司规模变大,就需要变更办公地址以容纳更多的员工,但是公司名称还是xxx,仅仅只是办公地址变了
	var slice6 []int
	slice6 = append(slice6, 1)       // [1]
	slice6 = append(slice6, 2, 3, 4) // [1 2 3 4]
	slice7 := []int{5, 6, 7}
	slice6 = append(slice6, slice7...) // [1 2 3 4 5 6 7]

	// 注意:通过var声明的零值切片可以在append()函数直接使用,无需初始化
	var slice8 []int
	slice8 = append(slice8, 1, 2, 3)

	// append() 方法可为切片动态添加元素,每个切片都指向一个底层数组
	// 当当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换
	//append()添加元素和切片扩容
	var numSlice []int
	for i := 0; i < 10; i++ {
		numSlice = append(numSlice, i)
		fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
	}
	// 从输出结果可以看出来
	// ① append()函数将元素追加到切片的最后并返回该切片。
	// ② 切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。

	// 9、切片的扩容策略
	// 查看$GOROOT/src/runtime/slice.go源码
	// https://www.liwenzhou.com/posts/Go/06_slice/#autoid-2-5-0
	/*
		首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
		否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
		否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
		如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)
	*/

	// 10、使用copy()函数复制切片
	// copy()复制切片,copy(dst, src)
	// copy()函数不会自动扩容目的切片,如果目的切片容量不足也无法完成copy()
	slice9 := []int{1, 2, 3, 4, 5}
	slice10 := make([]int, 5, 5)
	slice11 := slice9
	copy(slice10, slice9) //使用copy()函数将切片slice9中的元素复制到切片slice10
	fmt.Println(slice9)   //[1 2 3 4 5]
	fmt.Println(slice10)  //[1 2 3 4 5]
	fmt.Println(slice11)  //[1 2 3 4 5]
	slice9[0] = 1000
	fmt.Println(slice9)  //[1000 2 3 4 5]
	fmt.Println(slice10) //[1 2 3 4 5]  copy()复制切片的值不受原值的影响
	fmt.Println(slice11) //[1000 2 3 4 5]

	// 11、Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素
	// 从切片中删除元素
	slice12 := []int{30, 31, 32, 33, 34, 35, 36, 37}
	// 要删除索引为2的元素
	slice10 = append(slice12[:2], slice12[3:]...)
	fmt.Println(slice12) //[30 31 33 34 35 36 37 37]

	x1 := [...]int{1, 3, 5} // 数组
	// [:]就是从头取到尾,左包右也包
	s2 := x1[:]             // 切片,从数组x1的开始切到最后
	fmt.Println(s2, len(s2), cap(s2))
	/*
		1、切片不保存具体的值
		2、切片对应一个底层数组
		3、底层数组都是占用一块连续的内存
	*/
	fmt.Printf("%p\n", &s1[0])
	s2 = append(s2[:1], s2[2:]...) // 修改了切片的底层数组(非原始数组),将s2数组索引为0的元素和索引为2的元素拼接起来,相当于删除了索引为2的元素
	fmt.Printf("数组x1地址:%p 切片s2地址:%p \n", &x1, &s2)
	fmt.Printf("数组x1值:%v 切片s2值:%v \n", x1, s2) // 数组x1:[1 5 5] 切片s2:[1 5]
	x1[0] = 100
	fmt.Printf("数组x1值:%v 切片s2值:%v \n", x1, s2)
	// * 总结起来一句话,修改数组的值会同时修改引用该数组的切片值,而仅仅修改切片的值则不会修改其引用的原数组值(只是修改了切片对应内存地址的底层数组的值) *
	
	// append修改或删除切片的值,会修改(相当于挪动元素位置)该切片所引用的原数组值,具体看下例子:

	example_array := [...]int{1, 2, 3, 4, 5, 6}
	example_slice := example_array[:]
	fmt.Printf("未删除切片索引为2处的元素前 example_slice值为 %v\n", example_slice)
	// 删除掉切片索引为2处的元素
	example_slice = append(example_slice[:2], example_slice[3:]...)
	fmt.Printf("删掉索引2处元素后example_slice切片值为:%v \n", example_slice) // [1 2 4 5 6]
	fmt.Printf("删掉索引2处元素后example_array数组值为:%v \n", example_array) // [1 2 4 5 6 6] 前面的append操作相当于把数组后三个元素往前推了三位,其他元素不变

	// 切片真实面试题(非常nice)
	var a_test = make([]int, 5, 10)
	fmt.Printf("使用make创建完长度为5的切片之后,切片已经有5个int默认值了,即0 \n")
	fmt.Println(a_test)
	for i := 0; i < 10; i++ {
		a_test = append(a_test, i)
	}
	fmt.Println(a_test)

	// 12、指针
	/*
		go 语言中没有指针操作,只有两个符号:
		① & 获得地址
		② * 根据地址取值
	*/
	// 使用 & 取地址
	num := 23
	mem_add := &num
	fmt.Println(mem_add)
	fmt.Printf("%T \n,*int 代表int类型的指针", mem_add)
	// 使用 * 根据地址取值
	num1 := *mem_add
	fmt.Println(num1)
	fmt.Printf("%T \n", num1)

	// 指针类型数据的零值是 nil,很多类型数据的零值都是nil

	// 使用 new(Type) 函数申请一个内存地址,new()函数申请的是内存地址,如果需要赋值还需要结合*符号使用
	mem_add3 := new(int)
	*mem_add3 = 100
	print(*mem_add3)

	// 使用 make() 函数分配内存,区别于 new() 函数,make() 只能用于slice、map、chan的内存创建
	// func make(t Type, size ...IntegerType)

	// make() 函数创建slice、map的格式不同!

	/* 例:
	make创建map:	make(type,cap)  ->  make(map[string]int,200)
	make创建slice:	make([]T, size ,cap)  ->  make([]int, 5, 10)
	make创建元素为map类型的slice:	make([]map[int]string, 5, 10) slice格式是[]type,此例Type是键为int,value为string的map
	make创建元素为slice类型的map:	make(map[string][]int, 10)  map[string]Type , 此例中Type是int类型的slice
	声明元素类型为map的切片时,切片和map都需要分别初始化
	*/

	/* make和new的区别:
	① make和new都是用来申请内存的
	② new很少用,一般用来给基本数据类型申请内存,例如string、int,new()返回的是对应类型的指针(*string)
	③ make用来给slice、map、chan申请内存,make函数返回的是对应的这三个类型本身
	*/

}

7、map

package main

import (
	"fmt"
	"math/rand"
	"sort"
	"time"
)

func main() {
	// map 是一种无序的基于key-value的数据结构,go语言中的map是引用类型,必须初始化才能使用

	// 1、map 定义:
	// map[keyType]ValueType ,KeyType代表键的类型,ValueType代表键对应的值类型

	// map类型的变量默认初始值为nil,需要使用make()函数来分配内存。语法为:
	// make(map[keyType]ValueType, [cap])		cap即为容量大小,该参数非必须,但是最好在初始化map的时候就为其指定一个合适的容量

	var map1 map[string]int
	fmt.Println(map1 == nil) // map1还没有进行初始化(没有在内存空间中开辟空间)
	map1 = make(map[string]int, 10)
	map1["age"] = 23
	map1["height"] = 226
	fmt.Println(map1)
	fmt.Println(map1["age"])
	fmt.Println(map1["weigh"]) // 如果不存在这个key,拿到对应数值类型的零值
	// 约定成俗
	value, ok := map1["age"]
	if !ok {
		fmt.Println("map1中不存在此键值")
	} else {
		fmt.Println(value)
		//fmt.Printf("map1[%v]的值是%v", value, map1["age"])
	}

	// 2、map 的遍历

	for k, v := range map1 {
		fmt.Println(k, v)
	}
	// 只遍历key值
	for k := range map1 {
		fmt.Println(k)
	}
	// 只遍历value值
	for _, v := range map1 {
		fmt.Println(v)
	}

	// 3、使用delete()方法删除
	// delete(mapName,keyName)
	// 内建函数delete按照指定的键将元素从映射中删除,若map为空或无此元素,delete不进行操作
	delete(map1, "height")
	fmt.Println(map1)

	// 4、按照指定顺序遍历map
	/*
		① 首先声明一个map容量200,使用循环对map初始化赋值,key依次增加,value来自随机数
		② 将map的所有key值取出来存到切片中
		③ 对切片进行排序
		④ 根据排序后的key值获取value值
	*/
	rand.Seed(time.Now().UnixNano()) //初始化随机数种子

	var scoreMap = make(map[string]int, 200)

	for i := 0; i < 100; i++ {
		key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
		value := rand.Intn(100)          //生成0~99的随机整数
		scoreMap[key] = value
	}
	//取出map中的所有key存入切片keys
	var keys = make([]string, 0, 200)
	for key := range scoreMap {
		keys = append(keys, key)
	}
	//对切片进行排序
	sort.Strings(keys)
	//按照排序后的key遍历map
	for _, key := range keys {
		fmt.Println(key, scoreMap[key])
	}

	// 5、元素为map类型的切片
	// 声明元素类型为map的切片时,切片和map都需要进行初始化

	// make创建切片格式 make([]Type,length,cap)

	var slice1 = make([]map[int]string, 10, 10) // 使用make创建切片
	slice1[0] = make(map[int]string, 1)         // 初始化slice中的第一个map元素
	slice1[0][0] = "beijing"
	fmt.Println(slice1)

	// 6、值为切片类型的map

	// 使用make创建map格式: make(type,cap)

	var map2 = make(map[string][]int, 10) // map[string]Type , 此例中Type []int ,是int类型的slice
	map2["beijing"] = []int{10, 20, 30}
	fmt.Println(map2)
}

8、函数

package main

import "fmt"

// 函数的定义
/*
	func 函数名(参数)(返回值){
		函数体
	}

	go语言是强引用类型,任何时候声明变量都是指定数据类型
	这里的参数、返回值也都必须指定数据类型

	函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
	参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
	返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
	函数体:实现指定功能的代码块
*/

// Go语言中函数传递的都是值类型,修改之后并不会影响初始变量的值

func sum(x int, y int) (ret int) {
	return x + y
}

// 函数的参数和返回值都是可选的,例如可以实现一个既没有参数也没有返回值的函数
func sayHello() {
	fmt.Println("Hello World")
}

// 参数类型简写
// 函数的参数中如果相邻变量的类型相同,则可以省略数据类型
// intSum函数有两个参数,这两个参数的类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型
func intSum(x, y int) int {
	return x + y
}

// 可变参数
/*
	可变参数是指函数的参数变量不固定。Go语言中的可变参数通过在参数名后加  ...  来标识
	注意:可变参数通常要作为函数的最后一个参数
*/
func intSum2(x ...int) int {
	fmt.Println(x)
	sum := 0
	for _, v := range x {
		sum = sum + v
	}
	return sum
}

// 固定参数搭配可变参数使用时,可变参数要放到固定参数的后面,示例代码如下:
// 本质上,函数的可变参数是通过切片来实现的
func intSum3(x int, y ...int) int {
	fmt.Println(x, y)
	sum := x
	for _, v := range y {
		sum = sum + v
	}
	return sum
}

// 返回值
// Go语言中函数支持多返回值,函数如果有多个返回值时,定义返回参数必须用()将所有返回值包裹起来
func calc(x, y int) (int, int) {
	sum := x + y
	sub := x - y
	return sum, sub
}

// 返回值命名:
// 函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过 return 关键字返回
// 例如:
func calc1(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}

// 返回值补充,当函数返回值类型为slice时,nil可以看作是一个有效的slice,没必要显示返回一个长度为0的切片
func someFunc(x string) []int {
	if x == "" {
		return nil // 没必要返回[]int{}
	} else {
		fmt.Println("helloworld")
	}
	return nil
}

func main() {
	// 调用函数可以通过  函数名()的方式
	r := sum(1, 2)
	fmt.Println(r)

	// 调用有返回值的函数时,可以不接收其返回值

}