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调试按钮介绍
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)
// 调用有返回值的函数时,可以不接收其返回值
}