Golang入门教程(九)复合数据类型使用案例二
参考:http://www.runoob.com/go/go-slice.html
目录
- 切片
- 字典(map)
- 函数(func)
- 接口(interface)
- 通道(chan)
四、切片(Slice)
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
声明一个未指定大小的数组来定义切片:
1 | var identifier []type |
带有 T 类型元素的切片由 []T 表示,其中T代表slice中元素的类型。切片在内部可由一个结构体类型表示,形式如下:
1 2 3 4 5 | type slice struct { Length int Capacity int ZerothElement *byte } |
可见一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。通过len和cap函数分别返回slice的长度和容量。
切片和数组的区别
- 数组声明需要指定元素类型及元素个数 (数组长度必须是整数且大于 0),语法格式:var variable_name [SIZE] variable_type
- 声明一个未指定大小的数组来定义切片(切片不需要说明长度),语法格式:var identifier []type
- 切片不是数组,但是切片底层指向数组
- 切片本身长度是不一定的因此不可以比较,数组是可以的
- 切片是变长数组的替代方案,可以关联到指向的底层数组的局部或者全部
- 切片是引用传递(传递指针地址),而数组是值传递(拷贝值)
- 切片可以直接创建,引用其他切片或数组创建
- 如果多个切片指向相同的底层数组,其中一个值的修改会影响所有的切片
Demo 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package main import "fmt" func main() { //def & init 1 s1 := []float32{1.2, 1.3, 4.0, 7, 10} fmt.Println( "s1 = " , s1) //def & init 2 s2 := s1[2:4] fmt.Println( "s2 = " , s2) //def & init 3 s3 := s1[:4] fmt.Println( "s3 = " , s3) } |
执行结果
demo2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package main import "fmt" func main() { /* [1] 初始化数组,初始化数组中 {} 中的元素个数不能大于 [] 中的数字*/ var arr1 = [5]float32{1.2,3.4,5.6,4.4,6.6} //必须指定数组大小 // [2] 如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小 var arr2 = [...]int{1,3,5,7,9,11} /* [3] 数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值*/ fmt.Println( "-------打印数组1:" ,arr1[1]) fmt.Println( "-------打印数组2:" ,arr2[1]) fmt.Printf( "-------开始切片----------\n" ) /* [4] 切片的字面值和数组字面值很像,不过切片没有指定元素个数*/ slice1 := []float32{1.2,3.4,5.6,4.4,6.6} /* 使用make函数创建切片 */ // func make([]T, len, cap) []T : T 代表被创建的切片元素的类型。函数 make 接受一个类型、一个长度和一个可选的容量参数。调用 make 时,内部会分配一个数组,然后返回数组对应的切片。 slice2 := make([]int,5,10) // 使用内置函数 len 和 cap 获取切片的长度和容量信息 fmt.Printf( "------获取切片 slice1 的长度和容量信息 len=%d cap=%d slice=%v\n" ,len(slice1),cap(slice1),slice1) fmt.Printf( "------获取切片 slice2 的长度和容量信息 len=%d cap=%d slice=%v\n" ,len(slice2),cap(slice2),slice2) // 零值的切片类型变量为 nil。对于零值切片变量,len 和 cap 都将返回 0。 b := []byte{ 'g' , 'o' , 'l' , 'a' , 'n' , 'g' } // len=6 cap=6 slice=[103 111 108 97 110 103] slice3 := []int{1,2,3,4,5,6,7} fmt.Printf( "------获取切片 b 的长度和容量信息 len=%d cap=%d slice=%v\n" ,len(b),cap(b),b) fmt.Printf( "------获取切片 slice3 的长度和容量信息 len=%d cap=%d slice=%v\n" ,len(slice3),cap(slice3),slice3) } |
执行结果
切片截取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | package main import "fmt" func main() { /* 创建切片 */ numbers := []int{0,1,2,3,4,5,6,7,8} printSlice(numbers) /* 打印原始切片 */ fmt.Println( "numbers ==" , numbers) /* 打印子切片从索引1(包含) 到索引4(不包含)*/ fmt.Println( "numbers[1:4] ==" , numbers[1:4]) /* 默认下限为 0*/ fmt.Println( "numbers[:3] ==" , numbers[:3]) /* 默认上限为 len(s)*/ fmt.Println( "numbers[4:] ==" , numbers[4:]) numbers1 := make([]int,0,5) printSlice(numbers1) /* 打印子切片从索引 0(包含) 到索引 2(不包含) */ number2 := numbers[:2] printSlice(number2) /* 打印子切片从索引 2(包含) 到索引 5(不包含) */ number3 := numbers[2:5] printSlice(number3) } // 参数为一个切片 func printSlice(x []int){ fmt.Printf( "len=%d cap=%d slice=%v\n" ,len(x),cap(x),x) } |
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package main import "fmt" func main() { var numbers []int printSlice(numbers) //允许追加空切片 numbers = append(numbers, 0) printSlice(numbers) /* 向切片添加一个元素 */ numbers = append(numbers, 12) printSlice(numbers) /* 同时添加多个元素 */ numbers = append(numbers, 100, 200, 4000) printSlice(numbers) /* 创建切片 numbers1 是之前切片的两倍容量*/ numbers1 := make([]int, len(numbers), (cap(numbers))*2) /* 拷贝 numbers 的内容到 numbers1 */ copy(numbers1, numbers) printSlice(numbers1) } // 参数为一个切片 func printSlice(x []int) { fmt.Printf( "len=%d cap=%d slice=%v\n" , len(x), cap(x), x) } |
五、字典(map)
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
定义 Map
可以使用内建函数 make 也可以使用 map 关键字来定义 Map
1 | var map_variable map[key_data_type]value_data_type |
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package main import "fmt" func main() { var map_hostip map[string]string map_hostip = make(map[string]string) map_hostip[ "host31" ] = "192.168.32.131" map_hostip[ "host32" ] = "192.168.32.132" map_hostip[ "host33" ] = "192.168.32.133" map_hostip[ "host34" ] = "192.168.32.134" fmt.Println( "map_hostip = " , map_hostip) fmt.Println( "map_hostip[host31] = " , map_hostip[ "host31" ]) } |
执行结果
案例2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | package main import "fmt" func main() { // [1] 声明map 语法:var map_variable map[key_data_type]value_data_type var map1 map[string]string /* 使用make函数创建一个非nil的map(创建集合),nil map不能赋值 */ map1 = make(map[string]string) /* 给已声明的map赋值 */ map1[ "name" ] = "Tinywan" map1[ "age" ] = "24" /* [2] 直接创建map */ map2 := make(map[string]string) /* 赋值 */ map2[ "name" ] = "Tinyaiai" map2[ "age" ] = "25" /* [3] 初始化 + 赋值一体化 */ map3 := map[string]string{ "name" : "ShaoBo Wan" , "age" : "26" , } /* 使用 key 输出 map 值 */ fmt.Printf( "-------遍历 map1---------\n" ) for name := range map1 { fmt.Println( "map1 of" , name, "is" , map1[name]) } // 遍历 map2 fmt.Printf( "-------遍历 map2---------\n" ) for k, v := range map2 { fmt.Println(k, v) } // 遍历 map3 fmt.Printf( "-------遍历 map3---------\n" ) for k, v := range map3 { fmt.Println(k, v) } // 查找键值是否存在 fmt.Printf( "-------查找键值是否存在---------\n" ) if v, ok := map1[ "name" ]; ok { fmt.Println( "存在" , v) } else { fmt.Println( "Key Not Found" ) } /* 删除元素 */ delete (map3, "age" ) fmt.Printf( "--------删除后的 map3--------\n" ) // 遍历 map3 for k, v := range map3 { fmt.Println(k, v) } } |
执行结果
六、函数(func)和接口(interface)
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package main import "fmt" // 定义结构体 type person struct { id int name string country string } // 定义接口 type interface_person interface { introduction() } //实现接口方法 使用指针访问 func ( this *person) introduction() { fmt.Println( "My name is : " , this .name) } func main() { var Tinywan person Tinywan.id = 13669361192 Tinywan.name = "ShaoBo Wan" Tinywan.country = "China" fmt.Println( "Tinywan = " , Tinywan) Tinywan.introduction() } |
说明:在上面的例子中,我们定义了一个接口 interface_person,接口里面有一个方法introduction()。然后我们在main函数里面定义了一个Tinywan类型变量,并分别为之赋值。然后调用introduction()方法
执行结果:
接口
Interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。
interface类型默认是一个指针。
Interface定义:
1 2 3 4 5 | type Car interface { NameGet() string Run(n int) Stop() } |
Interface实现:
Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,golang中没有implement类似的关键字;
如果一个变量含有了多个interface类型的方法,那么这个变量就实现了多个接口;如果一个变量只含有了1个interface的方部分方法,那么这个变量没有实现这个接口。
多态
一种事物的多种形态,都可以按照统一的接口进行操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | package main import "fmt" // 定义Car 接口 type Car interface { NameGet() string Run(n int) Stop() } // 宝马结构体 type BMW struct { Name string } // 实现方法 func (this *BMW) NameGet() string { return this.Name } func (this *BMW) Run(n int) { fmt .Printf( "BMW is running of num is %d \n" , n) } func (this *BMW) Stop() { fmt .Printf( "BMW is stop \n" ) } // 大奔 结构体 type Benz struct { Name string } func (this *Benz) NameGet() string { return this.Name } func (this *Benz) Run(n int) { fmt .Printf( "Benz is running of num is %d \n" , n) } func (this *Benz) Stop() { fmt .Printf( "Benz is stop \n" ) } func (this *Benz) ChatUp() { fmt .Printf( "ChatUp \n" ) } func main() { var car Car /* 声明 car 为 Car 类型 */ fmt .Println( " car is = " , car) fmt .Println( "-----------------------\r\n" ) var bmw BMW = BMW{Name: "宝马" } car = &bmw fmt .Println(car.NameGet()) // 宝马 car.Run(1) // BMW is running of num is 1 car.Stop() // BMW is stop fmt .Println( "-----------------------\r\n" ) benz := &Benz{Name: "大奔" } car = benz fmt .Println(car.NameGet()) // 大奔 car.Run(2) //Benz is running of num is 2 car.Stop() //Benz is stop } |
参考:浅谈Go语言中的结构体struct & 接口Interface & 反射
六、通道(chan)
参考:http://blog.csdn.net/u013870094/article/details/74276726
通道是go所特有的数据类型之一,详细地使用方法需要结合通道的长度/容量/单向或双向等才能更好地理解的这样一种通信手段。
声明一个通道很简单,我们使用 chan
关键字即可,除此之外,还要指定通道中发送和接收数据的类型,这样我们才能知道,要发送什么类型的数据给通道,也知道从这个通道里可以接收到什么类型的数据。
1 | ch:=make(chan int) |
通道类型和Map这些类型一样,可以使用内置的 make
函数声明初始化,这里我们初始化了一个 chan int
类型的通道,所以我们只能往这个通道里发送 int
类型的数据,当然接收也只能是 int
类型的数据。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package main import "fmt" func main() { fmt.Println( "function begins ... " ) c := make(chan bool) go func() { fmt.Println( "func has been called." ) close(c) }() <-c fmt.Println( "Completed." ) } |
执行结果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构