前言
该笔记灵感来源于B站《【狂神说】Go语言零基础学习视频通俗易懂》
源视频地址:【狂神说】Go语言零基础学习视频通俗易懂
个人声明:本文记录个人在进行该视频学习中的知识总结,帮助大家能更快地进行对该视频内容的学习;
一.环境安装
- 直接一键安装,安装完后在cmd窗口输入“go version"就可以查看Go版本,检测是否安装成功。也可以输入”go env"查看环境
配置环境变量
-
需要工作目录GOROOT、GOPATH ; GOPATH是放代码,各种存储的位置
-
打开电脑的高级系统设置
- 在Goworks下创建三个目录:
二.Go语言基础学习
- Go语言后缀: .go
- 在cmd中运行Go程序:go run hello.go
1.基础go程序-Helloword
- 所有用Go语言编译的可执行程序必须有一个名叫main的包,一个可执行程序有且仅有一个main包
package main
import "fmt"
func main() {
fmt.Printf("Hello!")
}
2.变量
(1)变量的定义
- Go语言是静态类型的语言,所有类型都需要明确的定义。
- var是声明变量的关键字 使用格式:var 变量名 变量类型
- 变量命名规范:遵循驼峰格式,首个单词小写,每个新单词的首字母大写
var name String = "WY" //var创建String类型的变量name
name = "JB" //修改变量值
- 使用var + (),可以将一组变量定义在一起
var( //定义一组变量
age int
name string
)
- 想要定义多个同类型变量
var a,b,c,int //定义多个同类型变量
-
变量被声明之后,会赋值默认值
-
- 数值类型的变量默认值是0,0.0
- 字符串变量默认为空
- 布尔型变量默认为flase
- 切片,函数,指针变量的默认为nil
(2)变量的初始化
a 初始化标准格式
var 变量名 类型 = 值(表达式)
var name string = "sillyfox" //变量初始化赋值
var age = 21
fmt.printf("name:%s,age:%d",name,age) //类似C语言的打印写法
b 简短式初始化
- 这是go语言特有的语法,会根据:= 右边的值自动识别左边变量的类型
- 注意:该使用只能用在函数的内部,不可随处使用
name := "sillyfox"
age := 18
fmt.Println(name,age) //打印值
fmt.Printf("%T,%T",name,age) //打印变量类型
(3)内存地址的打印
var num int
num = 1000
fmt.Printf("num的值:%d,内存地址:%p\n",num,&num)
(4)变量交换
在其他语言中,通常交换变量的方式是定义一个中间变量,例如:
int a = 100;
int b = 200;
int c = 0; //中间变量
c = a;
a = b;
b = a; //通过中间变量c交换a,b的值
在go语言中,可以直接实现换值而不需要中间变量
var a int = 100
var b int = 200
b,a = a,b //交换值
(5)匿名变量
在go语言中提供了一种匿名变量,使用下划线_表示。匿名变量可以被赋予任何值,但所赋的值均将被抛弃。 我们可以在需要接收值,但认为某些值没有作用的时候使用匿名变量。在go语言中,如果定义了变量而不使用,则编译器会报错,但如果使用匿名变量则可以解决这个问题。
匿名变量不占用空间,不会分配内存,匿名变量与匿名变量之间也不会因为多次声明而无法使用。
(6)变量的作用域
- go语言中也有全局变量和局部变量之分,并且全局变量与局部变量的名称相同。
- 如果同时使用局部变量和全局变量,则根据就近原则go语言会优先使用局部变量
如以下代码,打印出来的结果是局部变量age,显示结果为18
var age int = 21 //定义了一个全局变量
func main(){
var age int = 18 //在main函数中的局部变量
fmt.Println(age) //打印age变量,这里的结果是18
}
- 局部变量在函数体内定义,作用域为当前所在的函数体内
- 全局变量在函数体外定义,只需要在一个源文件中定义,就可以在所有源文件中使用,不包含这个全局变量的源文件要使用时需要import该全局变量所在源文件。
(7)常量
- 常量是不会被修改的量
- 常量的定义使用const关键字
常量的定义
- 在go语言中,常量的定义分位显示定义和隐式定义
- 可以同时定义多个常量
- 在隐式定义方式中可以同时定义多个不同类型的常量
- const可以定义一个组,在组中若有一个常量未赋值,则该常量默认为上一个常量的值
const name1 string = "WY" //显示定义
const name2 = "CH" //隐式定义
const age1,age2,age3 int = 17,18,19 //同时显示定义多个常量
const name3,age4,result = "yue",21,true //隐式定义同时定义多个不同类型的常量
const(
a = 100
b = 101
c //默认为101
d //默认为101
)
特殊常量iota
- iota是特殊的常量,是go语言的常量计数器
- iota在const关键字出现时将被重置为0,const中每新增一行常量声明将使iota计数一次
const(
a = iota
b = iota
c = iota
fmt.Println(a,b,c) //输出结果为0,1,2
)
//新建一个const
const(
d = iota //iota重置为0,d = 0
e //e = d = 0,iota = 1
f = "wy" //f = "wy",iota = 2
g //g = f = "wy",iota = 3
h = 100 //h = 100,iota = 4
I //I = h = 100,iota = 5
J //J = I = 100,iota = 6
//共定义了7个变量,最后iota = 6
)
(8)布尔值
- 布尔值bool的值只有两种:true和flase
- 布尔值不赋值时默认值为flase
- 格式打印符号为%t
var Flag bool
fmt.Printf("%T,%t\n",Flag,Flag) //打印结果为bool,flase; %T表示打印类型
(9)数字类型
整型
浮点型
其他数字类型
(9)字符型
- 字符型string, 其值用双引号包起来
- 若将string的值用单引号包括,则打印出来的类型为整形且值打印出来为Unicode编码
- format打印格式:%s
var string name = "WY"
var string new = 'A' //该值打印输出为65,对应Unicode编码表
字符串连接
- go语言的字符串拼接使用方式与java类似,使用”+“拼接
fmt.Println("hello" + "WY")
转义字符
- 双引号: \ "
- 退格: \b
- tab: \t
- 换行: \n
(10)数据类型转换
- go语言中不存在隐式转换,必须显示转换,转换使用()进行强制转换
- 注意:一般是将更小范围的数据类型转换为取值范围更大的数据类型。如果从大类型转换为小类型,则会发生精度丢失
- 某些数据类型无法转换,编译会报错。例如,数值类型转换为布尔类型。
a := 5.0
b := (int)a //强制转换
3.运算符
(1)算术运算符
(2)关系运算符
(3)逻辑运算符
(4)位运算符
(5)赋值运算符
4.go语言的键盘输入详解
- go语言中有三种输入函数,分别是:
- fmt.Scanf() 可以按照指定的格式进行输入
- fmt.Scanln() 通过指针将值赋值给变量
- fmt.Scan()
(1)fmt.Scan
-
Scan从标准输入扫描文本,读取空白符分隔的值保存到传递给本函数的参数中,将换行符视为空白符
-
使用Scan函数需要用取址获取变量的地址,使用&号
-
使用该函数时,会在该函数收到用户输入前停止,直到获取用户输入
代码示例
func main(){
var a int
var b float64 //定义两个变量测试Scan()输入
fmt.Println("请输入a:整数 b:小数")
fmt.Scan(&a, &b) //输入数据,这里要使用&取变量地址
fmt.Printf("a=%d,b=%f", a, b) //打印输入的结果
}
- 换行符也被视作空白符:
(2)fmt.Scanf
-
Scanf根据format参数指定的格式去读取值
-
Scanf必须按照指定的具体的输入内容的格式去进行输入,否则无法获取输入的值
func main() {
var a int
var b float64 //定义两个变量测试Scan()输入
fmt.Println("请输入a:整数 b:小数")
fmt.Scanf("a的值:%d,b的值:%f", &a, &b) //输入数据,这里要使用&取变量地址
fmt.Printf("a=%d,b=%f", a, b) //打印输入的结果
}
-
未按照设定的输入格式输入:
-
严格按照设定的输入格式进行输入
- 可以看到,使用Scanf函数对用户的输入格式非常严格,即使少一个“,”号也会导致无法读取值
(3)fmt.Scanln
- Scanln类似于Scan,它要遇到换行时才停止扫描,最后一个数据后面必须有换行或结束符
5.Go语言流程控制
程序流程的控制结构一般有三种,顺序结构,选择结构,循环结构
(1)选择结构
if语句
- if流程控制与其他语言的if流程控制基本相同
package main
import "fmt"
/*
1、定义一个整数
2、通过if语句进行比较
*/
func main() {
//1、定义一个整数
a := 15
//2、通过if语句进行比较
if a > 20 {
fmt.Println("a>20")
}
if a > 10 {
fmt.Println("a>10")
}
}
if-else
package main
import "fmt"
/*
1、定义一个分数score
2、通过if else语句判断 >=90合格 90<=不合格
*/
func main() {
//1、定义一个分数score
var score int = 90
//2、通过if else语句判断 >=90合格 90<=不合格
if score >= 90 {
fmt.Println("你的成绩合格")
} else {
fmt.Println("你的成绩不合格")
}
}
if嵌套
- go语言中没有java的if-else if -else 语句,但是if语句可以嵌套
package main
import "fmt"
/*
通过键盘输入两次密码进行校验
1、定义三个变量 x,y,password
2、x,y为键盘输入的值
3、通过if嵌套语句进行验证
*/
func main() {
//1、定义三个变量 x,y,password
var x, y, pwd int
pwd = 20230107
fmt.Println("请输入您的密码:")
//2、x,y为键盘输入的值
fmt.Scanln(&x)
if pwd == x {
fmt.Println("请再次输入您的密码确认")
fmt.Scanln(&y)
//3、通过if嵌套语句进行验证
if pwd == y {
fmt.Println("登录成功")
} else {
fmt.Println("密码验证失败")
}
} else {
fmt.Println("密码验证失败")
}
}
Switch语句
- Switch语句的每一个case分支都是唯一的,从上往下执行直到找到匹配项
- 在go语言中,匹配项之后不需要+break,go语言默认在case的最后自带break语句
package main
import "fmt"
func main() {
//1、定义一个变量score 成绩为66
var score int = 60
//2、通过 Switch语句进行判断 90==A 80==B 70-50==C
switch score {
case 90:
fmt.Println("A")
case 80:
fmt.Println("B")
case 70, 50, 60:
fmt.Println("C")
//3、当所有条件不满足执行default语句
default:
fmt.Println("D")
}
}
- 在go语言中,case的匹配项可以是表达式
package main
import "fmt"
func main() {
//1、定义一个成绩score
var score int
//2、通过键盘输入成绩
fmt.Println("请输入一个数:")
fmt.Scanln(&score)
//3、通过Switch条件判断
switch {
case score >= 90:
fmt.Println("您的成绩为A")
case score >= 80 && score < 90:
fmt.Println("您的成绩为B")
case score >= 60 && score > 0:
fmt.Println("您的成绩为C")
case score < 60 && score > 0:
fmt.Println("您的成绩不合格")
default:
fmt.Println("查询不到您的成绩") //因为成绩不能为负数
}
}
fallthrough贯穿switch语句
- switch默认情况下匹配成功后就不会执行其他case,如果我们需要执行后面的case,可以使用fallthrough穿透case。使用fallthrough 会强制执行后面的case语句,fallthrough 不会判断下一条case的表达式结果是否为true。
package main
import "fmt"
func main() {
var a bool = false
switch a {
case false:
fmt.Println("1,case条件为false")
fallthrough //进行穿透,依然会执行下一个case
case true:
fmt.Println("2,case条件为true")
}
}
- 有时候暴力穿透会影响程序,因此可以用break进行终止
package main
import "fmt"
func main() {
var a bool = false
switch a {
case false:
fmt.Println("1,case条件为false")
fallthrough //进行穿透
case true:
if a == false {
break //终止穿透
}
fmt.Println("2,case条件为true")
case false:
fmt.Println("false") //不会执行这里
}
}
(2)循环结构
for循环
- 同其他语言的for语句相同用法
- 可以使用break结束当前整个循环
- 可以使用continue结束当次循环
package main
import "fmt"
/*
for 条件的起始值;循环条件;控制变量自增或者自减
*/
func main() {
sum := 0 //计数
for i := 0; i <= 10; i++ {
sum += i
}
fmt.Println(sum)
}
6.String详解
- Go中的字符串是一个字节的切片,可以通过将其内容封装在双引号中来创建字符串,Go中的字符串Unicode兼容的,并且是UTF-8编码,字符串是一些字节的集合
(1)创建字符串
str := "hello,WY"
fmt.Println(str)
(2)获取字符串长度len()
fmt.Println("字符串长度为:", len(str))
(3)获取字符串指定切片
fmt.Printf("%c\n", str[0]) //%c格式为单个字符的输出
(4)字符串的遍历
for i := 0; i < len(str); i++ {
fmt.Printf("%c", str[i])
}
(5)for range遍历(类似python)
for i,i2 := range str {
fmt.Println(i) //i代表下标
fmt.Printf("%c", i2) //i2代表字符
}
7.函数
(1)函数的定义
- 函数使用func进行定义
- 函数是基本的代码块,用于执行一个任务
- Go语言至少有一个main函数
- 函数声明告诉了编译器函数的名称,返回类型和参数
//1.无参数无返回值函数的定义
func test1(){
fmt.Println("无参数无返回值函数的定义\n")
}
//2.有参数无返回值函数的定义
func test2(str string){
fmt.Println("有参无返回值:%s\n",str)
}
//3.多参无返回值
func test3(a,b int) int{
return a+b
}
//4.多个返回值
func test4(a,b int) (int,int){
return a,b
}
//函数的调用
fun main()
{
test1()
test2("WY")
test3(1,2)
test4(5,6)
}
(2)函数的可变参数
- 一个函数的参数类型确定,但个数不确定时可以使用可变参数
- 定义可变参数时,使用**... **符号
- 可变参数本质上是保存到了一个数组当中
- 注意:可变参数要放在参数定义的最后;一个函数的参数列表最多只有一个可变参数
//可变参数函数定义
func add_sum(num...int){
sum := 0
for _,i := range num{
sum += num[i-1]
}
}
(3)参数传递的类型
-
参数分为两种类型:
- 值类型-操作的是数据本身,例如我们常用的:int,string,bool,float64,array
- 引用类型的数据-操作的是数据的地址:例如slice,map,chan
-
值传递在调用完函数后不会修改原本值传递参数的值
//定义一个函数,进行值传递实验
func update(arr2 [4]int){
fmt.Println("arr2接收到的数据:"arr2)
arr2[0] = 100
fmt.Println("arr2修改后的数据:"arr2)
}
//定义主函数,调用update进行值传递观察
func main(){
arr := [4]int{1,2,3,4} //定义一个数组
fmt.Println(arr)
//传递值
update(arr)
fmt.Println("调用后的数据") //这里的结果为[1,2,3,4]
}
- 引用类型数据的参数传递在调用完函数后会修改原本值传递参数的值
package main
import "fmt"
/*
=====引用传递=======
1、定义一个切片
2、通过方法修改切片里面的数据
3、打印输出修改后值
*/
func main() {
arr := []int{1, 2, 3, 4} //定义一个切片
fmt.Println("调用修改前的数据", arr)
updata2(arr)
fmt.Println("调用修改后的数据", arr)
}
func updata2(arr []int) {
fmt.Println("arr接受的数据:", arr)
arr[0] = 100
fmt.Println("arr修改后的数据:", arr)
}
/*
调用修改前的数据 [1 2 3 4]
arr接受的数据: [1 2 3 4]
arr修改后的数据: [100 2 3 4]
调用修改后的数据 [100 2 3 4]
*/
(4)函数变量的作用域
- 作用域:变量可以使用的范围
- 局部变量:函数内部定义的变量,叫做局部变量,只能在定义该变量的函数内使用
- 全局变量:函数外部定义的变量,叫做全局变量,可以在任何函数里面使用
package main
import "fmt"
// 全局变量 所有函数都可以使用
var num int = 30
func main() {
// 局部变量 temp 只能在定义temp的方法中使用
temp := 100
if b := 1; b < 10 {
temp := 30
fmt.Println("局部变量b:", b)
fmt.Println("局部变量temp:", temp)
}
fmt.Println(temp)
// 调用f1,f2方法
f1()
f2()
}
func f1() {
fmt.Println(num)
}
func f2() {
fmt.Println(num)
}
(5)递归函数
- 递归函数的定义:可以自己调用自己的函数
- 递归函数必须要有一个出口,否则会陷入死循环
package main
import "fmt"
/*
1、创建一个求和函数 getsum
2、给递归函数一个出口
3、创建对象来接收函数
*/
func main() {
//3、创建对象来接收函数
sum := getSum(5)
fmt.Println(sum)
}
//1、创建一个求和函数 getsum
func getSum(n int) int {
//2、给递归函数一个出口
if n == 1 {
return 1
}
return getSum(n-1) + n
}
(6) defer延迟函数
- defer函数可以延迟一个函数的执行
在以下代码块中,控制台打印的结果是1 2 4 3,因为defer将f(3)延迟到最后执行了
package main
import "fmt"
func main() {
f("1")
f("2")
defer f("3") //将该函数延迟到最后执行
f("4")
}
func f(s string) {
fmt.Println(s)
}
- 如果使用了多个defer语句,则函数执行到最后时,这些defer语句会按照逆序执行
package main
import "fmt"
func main() {
f("1")
f("2")
defer f("3")
f("4")
defer f("5")
f("6")
defer f("7")
f("8")
}
func f(s string) {
fmt.Println(s)
}
/*
输出结果:
1
2
4
6
8
7
5
3
*/
(7)func数据类型详解
-
在go语言中,函数是复合类型,本质上可以看作是一个特殊的变量
-
func本身就是一个数据类型,func定义的函数之间可以相互赋值
package main
import "fmt"
/*
fun本身就是一个数据类型
1、创建一个函数
2、在mian方法定义相同类型的函数
3、将定义后的函数赋值个另一个函数
*/
func main() {
//2、在mian方法定义相同类型的函数
var fg func(int, int)
//3、将定义后的函数赋值个另一个函数
fg = ff //将fg函数定义为ff函数相同的功能
fg(1, 2) //最后输出的结果为 1 2
}
//1、创建一个函数
func ff(a, b int) {
fmt.Println(a, b)
}
(8)匿名函数
- 什么是匿名函数:匿名函数就是没有名字的函数
匿名函数的创建
//这就是一个匿名函数,但如果直接放在代码块中不调用则编译器会报错
func() {
fmt.Println("匿名函数")
}
//带参匿名函数
func(a,b int){
fmt.Println("带参匿名函数")
}
//带参且具有返回值函数
func(a,b int) int{
fmt.Println("带参和返回值匿名函数")
return a+b
}
匿名函数多种调用方式
func main(){
//方式1:用一个变量调用匿名函数
f3 := func(){
fmt.Println("匿名函数的调用方式1")
}
f3() //调用匿名函数
//方式2:创建匿名函数后直接调用func(),在{}后直接添加()进行调用
func(){
fmt.Println("匿名函数调用方式2")
}()
//带参匿名函数的调用
func(a,b int){
fmt.Println("带参匿名函数调用")
}(1,2)
//带参和返回值匿名函数调用
f5 := func(a,b int) int{
fmt.Println("带参和返回值匿名函数")
return a+b
}(1,2)
fmt.Println(f5) //打印调用结果
}
(9)高阶函数和回调函数
-
回调函数:将一个函数作为另一个函数的参数
-
若将fun1函数作为fun2函数的参数,则fun2叫做高阶函数,fun1称为回调函数
package main
import "fmt"
//1、创建一个高阶函数oper,传如一个函数类型的参数且有返回值
func oper(a, b int, fun func(int, int) int) int {
r2 := fun(a, b)
return r2
}
//2.创建其他方法的函数
func add(a, b int) int {
return a + b
}
//创建其他方法的函数
func sub(a, b int) int {
return a - b
}
func main() {
r1 := add(1, 2)
fmt.Println(r1)
//3、调用高阶函数时,传入其他方法的函数
r3 := oper(3, 4, add)
fmt.Println(r3)
r4 := oper(8, 4, sub)
fmt.Println(r4)
}
(10)闭包
- 一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量并且该外层函数的返回值就是这个内层函数。
- 这个内层函数和外层函数的局部变量,统称为闭包结构。
- 局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用
package main
import "fmt"
// 定义一个闭包函数。func() int是局部变量和返回值fun的类型
func increment() func() int {
//局部变量
i := 0
//定义一个匿名函数 给变量自增并返回
fun := func() int {
//内层函数,没有执行
i++
return i
}
return fun
}
func main() {
//定义一个变量 接受返回值(函数)
r1 := increment() //创建函数
fmt.Println(r1) //这里打印的是地址,因为r1还没有执行
v1 := r1()
fmt.Println(v1)
//不断调用自增+1
v2 := r1()
fmt.Println(v2)
//不断调用自增+1
fmt.Println(r1())
fmt.Println(r1())
fmt.Println(r1())
fmt.Println(r1())
fmt.Println("====================")
// ==========================================================
r2 := increment() //创建函数,i也会清零
v3 := r2()
fmt.Println(v3)
fmt.Println(r2())
fmt.Println(r2())
fmt.Println(r2())
fmt.Println(r1()) //这里调用的是r1,因此i值还是原来r1的i值,这里自增后为7
fmt.Println(r2())
}
/*
输出结果:
0xd5ea00
1
2
3
4
5
6
====================
1
2
3
4
7
5
*/
8.数组
- 数组的定义:相同类型的数据集合
- go语言中数组的索引从0开始
- 没有赋值的数值型数组,默认值为0
- 数组一旦被创建,它的大小就是不可改变的
(1)声明数组与打印
var 变量名 [大小]变量类型
//数组的声明
var nums [4]int
//数组的赋值
nums[0] = 1
nums[1] = 2
nums[2] = 3
nums[3] = 4
/***************数组的打印*************/
fmt.Printf("%T\n", nums) //打印数组的类型:[4]int
fmt.Println(nums[3]) //打印单个数组的值:4
fmt.Println("长度:", len(nums)) //打印数组的长度:4
fmt.Println("容量:", cap(nums)) //打印数组的容量:4
(2)数组的初始化
在go语言中,数组有多种初始化方式。
- 常量初始化定义数组
- 快速的定义数组
- 定义不确定长度的数组
- 给指定下标进行赋值操作
//1、常量的初始化定义数组
var arrs1 = [5]int{1, 2, 3, 4, 5}
//2、快速定义数组
arrs2 := [5]int{6, 7, 8, 9, 10}
//3、定义不确定长度的数组
arrs3 := [...]string{"hello", "xuexiangban", "kuangshenshuo"}
//4、给指定下标进行赋值操作
arrs4 := [5]int{1: 100, 3: 200}//给下标为1赋值为100,下标为3赋值为200
(3)数组的遍历
- for循环遍历
//1、for循环遍历数组
arr1 := [5]int{1, 2, 3, 4, 5} //创建数组
for i := 0; i < len(arr1); i++ {
fmt.Println(arr1[i])
}
/*输出结果:
1
2
3
4
5
*/
- for-range遍历方式
//2、forage循环遍历数组
for index, value := range arr1 { //index下标,value对应的值
fmt.Println(index, value)
}
/*输出结果:
0 1
1 2
2 3
3 4
4 5
*/
(4)数组是值传递类型
- 在go语言中,数组被看作是值传递类型,因此数组之间可以直接相互赋值
arr1 := [3]int{1, 2, 3} //创建数组1:整型
arr2 := [2]string{"hello", "xuexiangban"} //创建数组2:字符型
arr3 := arr1 //创建数组3直接被赋值数组1
fmt.Println(arr1, arr3) //打印结果:[1 2 3] [1 2 3]
arr3 = [3]int{4, 5, 6} //重新赋值
fmt.Println(arr1, arr3) //[1 2 3] [4 5 6]
(5)多维数组
- go语言支持多维数组,以下为常用的多维数组声明方式
var 变量名[SIZE1][SIZE2]...[SIZEN]变量类型
- 多维数组定义示例:
arr := [3][4]int{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
}
- 多维数组使用示例
//forage循环输出
for _, i := range arr {
fmt.Println(i) //打印外层
fmt.Println("=========") //打印内层
for _, i2 := range i {
fmt.Println(i2)
}
}
/*打印结果:
[1 2 3 4]
=========
1
2
3
4
[5 6 7 8]
=========
5
6
7
8
[9 10 11 12]
=========
9
10
11
12
*/
9.切片
Go语言切片是对数组的抽象
go语言数组的长度是不可改变的,切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大
切片是一种方便、灵活且强大的包装器,切片本身没有任何数据,他们只是对现有数组的引用
切片与数组相比,不需要设定长度,在[]中不用设定值,相对来说比较自由
(1)定义创建切片
切片与数组在定义时不同,可以不用指定长度容量
//1、定义一个切片,里面不添加数据,打印输出
s1 := []int{}
fmt.Println(s1) //输出结果:[]
//2、定义切片向里面添加数据,打印输出类型
s2 := []int{1, 2, 3}
fmt.Println(s2) //输出结果:[1 2 3]
fmt.Printf("%T", s2) //输出结果[]int
补充:长度(length)和容量(cap)区别
-
长度(length)指切片中当前包含的元素个数。使用内置函数
len()
可以获取切片的长度。 -
容量(capacity)指分配给切片的底层数组中可用于存储元素的空间大小。容量至少等于切片长度,但可能会更大以容纳未来的增长。使用内置函数
cap()
可以获取切片的容量。 -
在创建切片的时候,可以只指定长度也可以同时指定长度和容量。当只指定长度时,切片的容量与长度相等。当同时指定长度和容量时,切片的容量可以大于长度。
(2)make函数创建切片
make函数创建切片,可以指定切片的长度和容量
make([]T,length,capactity) //类型,长度,容量
- 使用示例
//1、通过make函数来创建切片
s1 := make([]int, 5, 10) //长度:5,容量为10
//2、给切片里面的元素进行赋值
s1[0] = 100
//3、打印输出长度,容量
fmt.Println(s1)
fmt.Println("长度:", len(s1))
fmt.Println("容量:", cap(s1))
(3)切片扩容append
- 注意:追加数据超过容量后,容量只会2倍扩容,地址会发生改变
//1、创建一个切片s1
s1 := make([]int, 2, 4)
fmt.Println("追加数据前的长度和容量:", len(s1), cap(s1))
//2、s1通过append追加数据
s1 = append(s1, 1, 2, 3, 4) //追加数据,不是添加数据
fmt.Println(s1)
fmt.Println("追加数据后的长度和容量:", len(s1), cap(s1))
// 3、创建一个s2,将s2中数据追加到s1
s2 := make([]int, 3, 3)
s2 = []int{1, 2, 3}
s1 = append(s1, s2...) //将s2里面的数据追加到s1
fmt.Println(s1)
(4)在已有数组上创建切片
从已有的数组上,直接创建切片,该切片的底层数组就是当前的数组,长度是从start到end切到的数据量,但是容量从start到数组的末尾。
从数组上创建的切片,地址与数组是一样的,因此修改数组或切片的某个值,对应的数组/切片的值也会改变
slice := arr[start:end]
//定义数组和其切片
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s1 := arr[:5] //切片从1-5 [1 2 3 4 5]
s2 := arr[4:8] //[5 6 7 8]
s3 := arr[5:] //[6 7 8 9 10]
s4 := arr[:] //[1 2 3 4 5 6 7 8 9 10]
fmt.Println(s1) //[1 2 3 4 5]
fmt.Println(s2) //[5 6 7 8]
fmt.Println(s3) //[6 7 8 9 10]
fmt.Println(s4) //[1 2 3 4 5 6 7 8 9 10]
//修改数组的某个值
arr[0] = 100 //将数组元素值进行更换
fmt.Println(arr) //[100 2 3 4 5 6 7 8 9 10]
fmt.Println(s1) //[100 2 3 4 5 6 7 8 9 10]
//修改切片的某个值
s1[1] = 100
fmt.Println(arr) //[100 100 3 4 5 6 7 8 9 10]
fmt.Println(s1) //[100 100 3 4 5]
//查看数组和对应切片的地址
fmt.Printf("%p\n", s1) //0xc0000ac0f0
fmt.Printf("%p\n", &arr) //0xc0000ac0f0
(5)切片是引用类型
- 与数组不同,数组是值传递类型,而切片是引用类型
- 数组之间可以直接赋值,改变其中一个数组的值,另一个数组的值不会发生改变
- 切片之间若直接赋值,则改变其中一个切片的值,另一个切片的值同样发生改变
s1 := []int{1, 2, 3, 4, 5}
s2 := s1
s2[0] = 100
fmt.Println(s1, s2) //切片2里元素发生变化,切片1也会跟随着变化
fmt.Printf("%p\t%p", s1, s2) //说明切片的copy指向的是地址
(6)copy深拷贝与浅拷贝
- 深拷贝:拷贝的是数据本身
- 浅拷贝:拷贝的是数据的地址,会导致多个变量指向同一块内存
- 引用类型的数据,默认都是浅拷贝,如切片,集合
- 可以用copy强制实现深拷贝
实现切片的深拷贝
//1、通过for循环实现切片的深拷贝
s1 := []int{1, 2, 3, 4}
s2 := make([]int, 0, 0)
for i := 0; i < len(s1); i++ {
s2 = append(s2, s1[i])
}
fmt.Println(s1, s2)
//2、通过copy操作实现深拷贝
s3 := []int{5, 6, 7, 8}
fmt.Println(s1, s3)
copy(s3, s1) //(接收者,传递者) 将s1 copy 给s3
fmt.Println(s1, s3)
10.集合Map
- Map的定义:Map是一种无序的键值对集合,通过key来快速检索数据,Key类似于索引,指向数据的值。
- Map是引用类型
(1)Map的创建
- Map中的nil可以理解为空
- Map创建了没有赋值,编译器会报错
//1.var常量定义
var map1 map[int]string //创建但未初始化,集合为nil
//2.make函数创建
var map2 = make(map[string]string) //make创建会直接初始化,值为nil
//3.创建同时初始化
var map3 = map[string]int{"Go":100,"java":90}
fmt.Println(map1 == nil) //true
fmt.Println(map2 == nil) //false
(2)Map的赋值
- map是无序的,添加重复的key值,会覆盖前面的key
// 添加数据
map2["haha"] = "hehe"
map2["haha2"] = "hehe2"
map2["haha2"] = "hehe3"
map2["haha3"] = "hehe3"
fmt.Println(map2) //map[haha:hehe haha2:hehe3 haha3:hehe3]
(3)Map的使用:键值对赋值,key获取数据,修改数据,删除数据等
//1.创建map
var map1 map[int]string
//2.make初始化,如果没有初始化编译器会报错
map1 = make(map[int]string)
//3.存储键值对,给键值对赋值
map1[1] = "Hello"
map1[2] = "World"
map1[3] = "WY"
fmt.Println(map1) //map[1:hello 2:kuangshenshuo 3:xuexiangban]
//4.通过ok-idiom来判断key value是否存在,并获取map
value, ok := map1[1]
if ok {
fmt.Println("map key存在,其值为:", value)
} else {
fmt.Println("map key不存在")
}
// 5、修改数据
map1[1] = "haha"
fmt.Println(map1)
//6删除数据
delete(map1, 1)
fmt.Println(map1)
(4)Map的遍历
var map1 = map[string]int{"Go": 100, "JAVA": 90, "Python": 80} //创建集合
for s, i := range map1 {
fmt.Println(s, i) //每次遍历,顺序都将会不一样,因为map集合是无序的
}
map2 := map1//map也是引用类型
map2["Go"] = 0
fmt.Println(map1, map2)
(5)Map的特性总结
- Map是无序的,遍历打印出来的结果总是不同
- map的长度不固定,同切片slice一样,是引用类型数据。
- 内置的len函数同样适用于map,返回map拥有的key数量
- map的key可以是任意类型的数据。
- map的值不能通过index获取,必须通过key获取