前言
前面学习了Go数据类型中的数字、字符串,下面看看Go中是否有Python中的list、dict等复合数据类型?
需要注意的是在Go中数组是可变类型+值(拷贝)类型,而go中切片是引用类型;
数组声明之后它里面的元素是可以被修改,如果把1个数组变量引用到1个新的变量,那么这个新的变量会重新拷贝一份旧数组的数组。
Python中的list是可变+引用数据类型

llist1 = ["A", "B", "C"] llist2 = llist1 llist1[0] = "a" llist2[2] = "b" print(llist1, id(llist1)) print(llist2, id(llist2)) """ ['a', 'B', 'b'] 42775624 ['a', 'B', 'b'] 42775624 """
Golang中的arry是可变+拷贝数据类型

package main import "fmt" func main() { arry1 := [3]string{"A", "B", "C"} //注意声明arry变量时不要忘记[指定长度],否则声明的是slice变量 arry2 := arry1 arry1[0] = "a" arry2[1] = "b" //说明go中的arry不是引用类型,单属于可变数控类型可以原处修改 fmt.Printf("%v %p\n", arry1, &arry1) fmt.Printf("%v %p\n", arry2, &arry2) } /* [a B C] 0xc000062330 [A b C] 0xc000062360 */
Python的list切片之后会开辟1开新的内存生产1个新的list数据类型

nameList=["张","根"] firstName=nameList[0] lastName=nameList[1] print(firstName) print(lastName) #Python的list切片会开辟1块新的内存,创建1个新的列表。 nameSlice=nameList[0:2] nameSlice[0]="Martin" nameSlice[1]="Zhang" print(nameList,nameSlice) print(type(nameList),type(nameSlice)) print(id(nameList),id(nameSlice)) ''' 张 根 ['张', '根'] ['Martin', 'Zhang'] <class 'list'> <class 'list'> 42632648 42632712 '''
Golang的arry切片之后会开辟1个新的内存生成1个slice数据类型

package main import ( "fmt" "reflect" ) func main() { nameArry:=[2]string{"张","根"} //1.获取数组的长度 fmt.Println(len(nameArry)) //2.通过数组索引取值 firstName:=nameArry[0] lastName:=nameArry[1] fmt.Println(firstName) fmt.Println(lastName) //3.数组组切片 nameSlice:=nameArry[0:2] nameSlice[0]="Martin" nameSlice[1]="Zhange" fmt.Println(nameArry,nameSlice) //[Martin Zhange] [Martin Zhange] fmt.Println(reflect.TypeOf(nameArry),reflect.TypeOf(nameSlice)) //[2]string(数组类型)-------- []string(切片类型) fmt.Printf("切片之前的内存地址:%p,切片之后的slice内存地址:%p-- ",&nameArry,nameSlice) }
package main import "fmt" func main() { //声明1个字符串变量 var name string fmt.Println(name) //声明1个数组变量 var a1 [3]int fmt.Println(a1) //声明1个切片类型的变量 var s1 []int fmt.Println(s1 == nil) //声明二维数组 var a3 [3][3]int //对二维数组进行初始化 a3 = [3][3]int{ [3]int{1, 2, 3}, [3]int{4, 5, 6}, [3]int{7, 8, 8}, } fmt.Println(a3) // a4 := [3]int{1, 2, 3} a5 := modifyArry(a4) fmt.Println(a4) fmt.Println(a5) s2 := []string{ "河北" , "保定" , "唐县" } s3 := modifySlice(s2) fmt.Println(s2) fmt.Println(s3) } //数组是值类型:修改数组时,会复制 1个新的数组让我们修改 func modifyArry(a1 [3]int) (ret [3]int) { a1[0] = 100 return a1 } //切片是引用类型:修改切片时,不会复制1个新的切片让我们修改(修改原来的) func modifySlice(s2 []string) (ret2 []string) { s2[0] = "河北省" return s2 } |
数组
数组就是存放同1种数据基本数据类型(数值,字符、字符串、布尔值)元素的容器。
Go中的数组有一下特点:
在声明arry变量时就要声明arry的长度、容器内存放元素的数据类型;
数组的长度是数据类型的1部分,所以2个长度不同arrr,即使他们都属于数组,但2者的数据类型完全不一致,所以2者无法相互比较和赋值。
数组如果没有初始化里面的元素默认为0值(bool=false,int or float=0, string=空字符串)
数组的声明和初始化
package main import "fmt" func main() { //数组:存放同1中基础数据类型的容器 //声明1个长度为3的数组,里面存放bool数据类型 var arry1 [3]bool var arry2 [4]bool var arry3 [3]bool fmt.Printf( "arry1:%T arry2:%T arry3:%T\n" , arry1, arry2, arry3) fmt.Println(arry1 == arry3) //初始化方式1: arry1 = [3]bool{true, true, true} fmt.Println(arry1) //[true true true] //初始化方式2:[...]自动推算数组的长度 arry4 := [...]int{1, 2, 3, 4} fmt.Println(arry4) //初始化方式3:利用默认值 arry5 := [5]int{12, 13} fmt.Println(arry5) //[12 13 0 0 0] //初始化方式4:根据索引指定arry中的元素 arry6 := [5]int{0: 1, 4: 6} fmt.Println(arry6) //[1 0 0 0 6] } |
遍历数组

package main import ( "fmt" "reflect" ) func main() { nameArry := [2]string{"张", "根"} //1.获取数组的长度 fmt.Println(len(nameArry)) //2.通过数组索引取值 firstName := nameArry[0] lastName := nameArry[1] fmt.Println(firstName) fmt.Println(lastName) //3.数组组切片 nameSlice := nameArry[0:2] nameSlice[0] = "Martin" nameSlice[1] = "Zhange" fmt.Println(nameArry, nameSlice) //[Martin Zhange] [Martin Zhange] fmt.Println(reflect.TypeOf(nameArry), reflect.TypeOf(nameSlice)) //[2]string(数组类型)-------- []string(切片类型) fmt.Printf("切片之前的内存地址:%p,切片之后的slice内存地址:%p--\n", &nameArry, nameSlice) //2.循环数组 fmt.Println(nameArry) for i := 0; i < len(nameArry); i++ { fmt.Println(i, nameArry[i]) } for item := range nameArry { fmt.Println(item) } for index := range nameArry { fmt.Println(index) } for index, item := range nameArry { fmt.Println(index, item) } }
多维数组
多维数组就是数组里面嵌套数组
package main import ( "fmt" ) func main() { //定义多维数组 a1 := [3][2]int{{1, 2}, {1, 3}, {1, 4}} fmt.Println(a1) //[[1 2] [1 3] [1 4]] //遍历多维数组 for _, v := range a1 { for _, v1 := range v { fmt.Println(v1) } } } |
数组的内存管理机制
Golang字符串类型存储的机制
我们在Golang中声明的字符串类型变量,编译器不会直接存储该字符串的值。
而是先开辟1块内存把字符串的值存储起来,然后开辟1块内存把这个字符串的指针(8个字节)和长度(8个字节)存储起来赋予变量。
为什么要这样存储1个字符串呢?额外开辟1块存储指针和长度的内存空间。
我感觉这是是为了字符串被放到到Arry或Slice之后的查询效率而做的铺垫。
1.数组的内存地址是连续的
2.数组的内存地址=数组中第1个元素的内存地址
这种设计可以让我们快速的从1个元素获取到数组中最后1个元素,查询效率大大提升。
既然声明1个数组即开辟了1块连续的内存,对Arry中的item进行顺序存储,那么Arry的item含有字符串呢?
字符串可能是英文/汉字它们占用内存的规格可不一致啊!既然如此那我就同一一下规格!
3.声明1个字符串数组时,开启一段连续的内存之后,(并不会在这段连续的内存上直接存储字符串的值,而是存储字符串值的指针(占4个字节)和长度(占4字节)=16个字节。这也是Golang中字符串数据类型的存储机制,连续的内存+指针=加快查询效率...)
这样Arry中每1个元素对这段连续内存的使用都有了固定的规格,节约了Arry对内存空间占用。
随意字符串存储时增加了额外的空间去存储指针和长度,但是加速了查询效率。用空间换时间吧!

package main import "fmt" //1.数组的内存是连续的 //2.因为数组的内存是连续的,所以数组的内存地址就是数组里第1个元素的内存地址。 func main() { numbers := [3]int8{11, 22, 33} fmt.Printf("%p\n", &numbers) //0xc00000a0a8 fmt.Println(numbers) //[11 22 33] fmt.Printf("%p\n", &numbers[0]) //0xc00000a0a8 fmt.Printf("%p\n", &numbers[1]) //0xc00000a0a9 fmt.Printf("%p\n", &numbers[2]) //0xc00000a0aa numbers32 := [3]int32{11, 22, 33} fmt.Printf("%p\n", &numbers32) //0xc00011e0b4 fmt.Println(numbers) //[11 22 33] fmt.Printf("%p\n", &numbers32[0]) //0xc00011e0b4 fmt.Printf("%p\n", &numbers32[1]) //0xc00011e0b8 fmt.Printf("%p\n", &numbers32[2]) //0xc00011e0bc var names = [2]string{"张根", "Martin"} fmt.Printf("%p\n", &names) //0xc0000044c0 fmt.Println(names) //[张根 Martin] fmt.Printf("%p\n", &names[0]) //0xc000114460 fmt.Printf("%p\n", &names[1]) //0xc000114470 }
Go中多维数组是数值类型不是引用类型
值类型:就是重新开辟新的内存,与引用类型相反
package main import ( "fmt" ) func main() { //arry数值类型辩证 a1 := [...]string{ "河北" , "河南" , "山东" } a2 := a1 a1[len(a1)-1] = "山西" fmt.Println(a1) //[河北 河南 山西] fmt.Println(a2) //[河北 河南 山东] } |
练习题
package main import ( "fmt" ) func main() { //1.求数组[1, 3, 5, 7, 8]所有元素的和 arr1 := [...]int{1, 3, 5, 7, 8} var sum int for _, v := range arr1 { sum += v } fmt.Println(sum) /*找出数组中和为指定值的两个元素的下标,比如从数组[1, 3, 5, 7, 8]中找出和为8的两个元素的下标分别为(0,3)和(1,2)*/ for i := 0; i < len(arr1); i++ { for j := i + 1; j < len(arr1); j++ { if arr1[i]+arr1[j] == 8 { fmt.Printf( "%d %d\n" , i, j) } } } } |

package main import "fmt" /* 1.Go语言的int占多少个字节? 答:取决于你的操作系统 32位 4个字节,64位8个字节 2.整型中有符号和无符号是什么意思? 答:所谓有符号是使用1个位表示征服性质,因为使用第1位表示正负性质,所以有符号整型可表示的正数范围比较小,无符号整型表示的整数范围比较大! 3.整型可以表示的最大范围是 答:0-2**64,超出之后使用bigint包 4.什么是nil 答:nil就是空的数据 5.十进制是以整型的方式存在,其他其他进制以字符串的形式存在?如何实现进制之间的转换? 答:在golang中10进制是以整型存在,其他进制(二进制、八进制、十六进制)都是以字符串的形式存在 bynaryNumber:=strconv.FormatInt(int64(29),2)//十进制转二进制 octalNumber:=strconv.FormatInt(int64(29),8)//十进制转八进制 hexaDecimal:=strconv.FormatInt(int64(29),16)//十进制转八进制 fmt.Println(bynaryNumber,octalNumber,hexaDecimal) fmt.Println(strconv.ParseInt(hexaDecimal,16,32)) 6.简述如下代码的意义:var v1 int|var v2 *int var v3=new(int) var v1 int //开辟1块个内存+内存初始化值位0,v1指向该内存地址,打印显示0 fmt.Println(v1) v2:=999 //开辟1块个内存+内存初始化值设置为999,v2指向该内存地址,打印显示0 fmt.Println(v2) var v3 *int fmt.Printf("编译器开辟1块名为:%p的内存 存储了1个%p的空指针,空指针指向了nil(并没有初始化)\n",&v3,v3) v4:=new(int) fmt.Printf("编译器开辟1块名为:%p的内存 存储了1个指针%p,指针指向%p内存这块内存上存了值%d\n",&v4,v4,&*v4,*v4) 7.浮点型为什么无法精准表示小数? 答:这取决于 浮点型的小数部。小数部分 分转换成二进制时得不出1就这个浮点型就无法精确表达。 8.如何使用第3方包decimal? 答: var v1 =decimal.NewFromFloat(0.0000019) var v2 =decimal.NewFromFloat(0.298766) var v3 =v1.Add(v2) var v4 =v3.Sub(v2) var v5 =v4.Mul(v2) var v6 =v4.Div(v1) fmt.Println(v3) fmt.Println(v4) fmt.Println(v5) fmt.Println(v6) var price=decimal.NewFromFloat(3.6615926) fmt.Println(price.Round(1)) //保留小数点后1位自动四舍五入 fmt.Println(price.Truncate(2))//保留小数点后2位不需要四舍五入 9.简述ascii、Unicode、utf-8的关系 答: American Standard Code for Information Interchange 是存储了欧美国家在计算机中可以用到字符和对应二进制的字符集,这些二进制使用8位表示足够 Unicode:描述全球人使用字符和对应码位的字符集 使用32位表示码位范围。 utf-8是对unicode的码位进行画区间然后根据字符的码位区间进行模板选择完成Unicode码位和模板的填充 10.判断Go语言中的字符是utf-8编码的字节 name:="张根" fmt.Println(strconv.FormatInt(int64(name[0]),2)) fmt.Println(strconv.FormatInt(int64(name[1]),2)) fmt.Println(strconv.FormatInt(int64(name[2]),2)) //11100101 10111100 10100000 去对应utf-8的模板一看便知 11.什么是rune? rune表示英文字符或者汉字在Unicode字符中对应的码位 name:="张根" EnglishName:="Martin" fmt.Println([]rune(name)) fmt.Println([]rune(EnglishName)) [24352 26681] [77 97 114 116 105 110] 12.判断:字符串是否可变? 答:不可变 13.列举你记得的字符串常见功能 答:Prefix() stufix() 14.字符串集合和rune集合如何实现相互转换 name:="Alex" newRune:=[]rune(name) newRune[0]='a' fmt.Println(string(newRune)) 15.如何实现字符串高效率拼接 var builder strings.Builder builder.WriteString("我爱") builder.WriteString("北") builder.WriteString("京") result11:=builder.String() fmt.Println(result11) 16.简述数组的存储原理 答:一块连续的内存地址,第一个元素即该数组的内存地址。如果数组内包含字符串 存储字符串(长度+指针)进行统一规格! 17.names:=[3]string{"alex","超级无敌小JJ","傻儿子"} a.根据索引获取“傻儿子” b.根据索引获取“alex” c.根据索引获取“小JJ” d.请将names数组最后1个元素修改为“大烧瓶” 答: names := [3]string{"alex", "超级无敌小JJ", "傻儿子"} fmt.Println(names[0]) fmt.Println(names[1]) fmt.Println(names[2]) names[0]="大烧饼" fmt.Println(names) 18.看代码输出结果 var nestdata [3][2]int fmt.Println(nestdata) [[0 0] [0 0] [0 0]] 19.请声明1个3个元素的数组,元素的类型是数组,并在数组中初始化值 var nestdata=[3][2]int{{1,2},{3,4},{5,6}} fmt.Println(nestdata) 20循环下列数组并使用字符串格式化输出一下内容: dataList:=[ ["alex","qwe123"], ["eric","qw2"], ["tony","qpep23"], ] 最终实现: 我是Alex我的密码是qwe123 答: var userList = [3][2]string{{"alex", "qwe123"}, {"eric", "qw2"}, {"tony", "qpep23"}} fmt.Println(userList) for _, item := range userList { fmt.Printf("我是%s我的秘密是%s\n", item[0], item[1]) } 21.实现用户登录 //userList中有3个用户, dataList:=[ ["alex","qwe123"], ["eric","qw2"], ["tony","qpep23"], ] 从dataList中获取用户名验证用户合法性 */ func main() { var userList = [3][2]string{{"alex", "qwe123"}, {"eric", "qw2"}, {"tony", "qpep23"}} var userName string var passWord string fmt.Print("请输入用户名: ") fmt.Scan(&userName) fmt.Print("请输入秘密: ") fmt.Scan(&passWord) isAuthenticated := 0 if len(userName) > 0 && len(passWord) > 0 { isAuthenticated = 197 for _, item := range userList { if userName == item[0] { //当用户名和密码全部正确是也会走这个if分支! isAuthenticated = 198 } if passWord == item[1] { //当用户名和密码全部正确是也会走 这个if分支! isAuthenticated = 199 } if userName == item[0] && passWord == item[1] { isAuthenticated = 200 break } } } switch isAuthenticated { case 200: fmt.Println("登录成功!") case 197: fmt.Println("用户和密码都不对!") case 198: fmt.Println("用户对了,密码不对!") case 199: fmt.Println("密码对了,用户名不对!") default: fmt.Println("用户和密码都没有输入!") } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南