Go 数据类型

一、基础数据类型

1.1 基础数据类型

类型长度(字节)默认值说明
bool1false
byte10uint8,取值范围[0,255]
rune40Unicode Code Point,int32
int,uint4或8032或64位,取决于操作系统
int8,uint810-128~127,0~255
int16,uint1620-32768~32767,0-65535
int32,uint3240-21亿~21亿,0~42亿,rune是int32的别名
int64,uint6480
float3240.0
float6480.0
complex648
complex12816
uintptr4或8以存储指针的uint32或uint64整数

1.2 复合数据类型

类型默认值说明
array值类型
struct值类型
string“”UTF-8 字符串
slicenil引用类型
mapnil引用类型
channelnil引用类型
interfacenil接口
functionnil函数

二、自定义类型

2.1 自定义类型

在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型,Go语言中可以使用 type 关键字来定义自定义类型。

自定义类型是 定义了一个全新的类型 。我们可以基于内置的基本类型定义,也可以通过 struct 定义。例如:

1
2
// 将MyInt定义为int类型
type MyInt int

通过Type关键字的定义,MyInt就是一种新的类型,它具有 int 的特性。

思考:
实际上,Go语言中的结构体类型就是一种自定义类型。
看看结构体的定义格式就知道了:

1
2
3
4
5
type 结构体类型名 struct {
字段名1 字段类型1
字段名2 字段类型2
…
}

是不是和上述自定义类型的格式一模一样呢?

2.2 类型别名

类型别名是Go1.9版本添加的新功能。

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。

1
type TypeAlias = Type

我们之前见过的rune和byte就是类型别名,他们的定义如下:

1
2
type byte = uint8
type rune = int32

2.3 类型定义和类型别名的区别

类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
)

// 类型定义
type NewInt int

// 类型别名
type MyInt = int

func main() {
var a NewInt
var b MyInt

fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
fmt.Printf("type of b:%T\n", b) //type of b:int
}

输出结果:

1
2
type of a:main.NewInt
type of b:int

结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。这是一个新的数据类型。

b的类型是int。MyInt类型别名只会在代码中存在,编译完成时并不会有MyInt类型。

三、字符串

Go语言将字符串作为一种原生的基本数据类型。字符串是一个不可修改的数据类型。它的底层结构如下:

1
2
3
4
type stringStruct struct {
str unsafe.Pointer // 指向底层字节数组的指针
len int // 字节数组长度
}

3.1 字符串赋值

1
2
3
s1 :="MY name is zhangsan \u4f17" //字符串里可以包含任意Unicode字符
s2 := "He say:\"I'm file.\"\nThank\tyou." //包含转义字符
s3 := `here is first \nline.` //反引号里的转文字符无效,原样输出包括空白符和换行符等

3.2 字符串常用操作

方法描述
len(str)求长度
strings.Split分割
strings.Contains判断是否包含
Strings.HasPrefix,strings.HasSuffix前缀/后缀判断
Strings.Index(),Strings.LastIndex()子串出现的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 求长度
s := "123 张三"
fmt.Printf("s len %d", len(s))

// 分割
s := "hello, how are you"
fmt.Printf("%t\n", strings.HasSuffix(s, "are"))
a := strings.Split(s, "l")
fmt.Printf("%s", a)

// 判断是否包含
s := "hello, how are you"
fmt.Println(strings.Contains(s, "how"))

// 前缀/后缀判断
s := "hello, how are you"
fmt.Println(strings.HasPrefix(s, "hello"))
fmt.Println(strings.HasSuffix(s, "hello"))

// 子串出现的位置
s := "hello, how are you"
fmt.Println(strings.Index(s, "o"))
fmt.Println(strings.LastIndex(s, "o"))

3.3 字符串拼接

3.3.1 加号拼接

最常用的方法肯定是 + 连接两个字符串。这与python类似,不过由于golang中的字符串是不可变的类型,因此用 + 连接会产生一个新的字符串对效率有影响。

1
2
3
4
5
6
s1 := "字符串"
s2 := "拼接"
s3 := s1 + s2

// 打印s3字符串
fmt.Print(s3)

3.3.2 sprintf 函数

第二种方法使用sprintf函数,虽然不会像直接使用 + 那样产生临时字符串。但是效率也不高

1
2
3
4
5
s1 := "字符串"
s2 := "拼接"

// 打印s3字符串
s3 := fmt.Sprintf("%s%s", s1, s2)

3.3.3 Join 函数

第三种方法是用Join函数,这里我们需要先引入strings包才能调用Join函数。Join函数会先根据字符串数组的内容,计算出一个拼接之后的长度,然后申请对应大小的内存,一个一个字符串填入,在已有一个数组的情况下,这种效率会很高,如果没有的话效率也不高。

1
2
3
4
5
6
7
8
9
10
// 需要先导入strings包
s1 := "字符串"
s2 := "拼接"

// 定义一个字符串数组包含上述的字符串
var str []string = []string{s1, s2}

// 调用Join函数
s3 := strings.Join(str, "")
fmt.Print(s3)

3.3.4 buffer 函数

第四个方法是调用buffer.WriteString函数,这种方法的性能就要大大优于上面的了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 需要先导入bytes包
s1 := "字符串"
s2 := "拼接"

// 定义Buffer类型

var bt bytes.Buffer

// 向bt中写入字符串
bt.WriteString(s1)
bt.WriteString(s2)

// 获得拼接后的字符串
s3 := bt.String()
fmt.Print(s3)

3.3.5 Builder 函数

第5个方法是用buffer.Builder,这个方法和上面的差不多,不过官方建议用这个,使用方法和上面基本一样

1
2
3
4
5
6
7
8
9
10
// 需要先导入Strings包 
s1 := "字符串"
s2 := "拼接"

var build strings.Builder
build.WriteString(s1)
build.WriteString(s2)

s3 := build.String()
fmt.Print(s3)

3.4 byte 和 rune

Go内置两种字符类型:一种是byte的字节类型(byte也是uint8的别名),另一种是表示Unicode编码的字符rune。

string中每个元素叫”字符”,字符有两种:

  • byte:1个字节,代表ASCII码的一个字符;
  • rune:4个字节,代码UTF-8字符,一个汉字可以用一个rune表示;

string底层是byte数组,string的长度就是该byte数组的长度,UFT-8编码下一个汉字占3个byte,即一个汉字占3个长度;
string可以转换为[]byte或[]rune类型;
string是常量,不能修改其中的字符;

1
2
3
4
5
6
7
8
s := "123 张三"
// arr := []byte(s) //byte类型
arr := []rune(s) //rune类型
for _, ele := range arr {
fmt.Printf("%c ", ele)
}
fmt.Println()
fmt.Printf("arr len %d,s len %d", len(arr), len(s)) //byte和rune中的中文字符长度不一样

四、强制类型转换

golang语言不支持类型自动转换(隐式转换),只支持强制类型转换(显示转换)

即不同类型变量之间赋值需要用到强制类型转换语法

基本语法如下:

表达式T(v),表示将变量v的值转换为T类型,把转换后的值赋值给接收变量

PS:存储范围更大的类型转换成存储范围更小的类型时,如果值超过了后者的取值范围,不会报错但是会直接截断字节,得到很奇怪的值(二进制数被从高位截断导致的)

  • byte和int可以互相转换
  • float和int可以互相转换,小数位会丢失
  • bool和int不能相互转换
  • 不同长度的int或float之间可以相互转换
  • string可以转换为[]byte或[]rune类型,byte或rune可以转换为string
  • 低精度向高精度转换没问题,高精度向低精度转换会丢失位数
  • 无符号向有符号转换,最高位是符号位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 例子:
var i1 int64
var i2 int32
var i3 int8
i1 = 123
i2 = int32(i1)
i3 = int8(i1)
fmt.Printf("i1 类型为 %T ,值= %v\n", i1, i1) // i1 类型为 int64 ,值= 123
fmt.Printf("i2 类型为 %T ,值= %v\n", i2, i2) // i2 类型为 int32 ,值= 123
fmt.Printf("i3 类型为 %T ,值= %v\n", i3, i3) // i3 类型为 int8 ,值= 123

// 存储范围更大的类型转换成存储范围更小的类型时,如果值超过了后者的取值范围,不会报错但是会直接截断字节,得到很奇怪的值(二进制数被从高位截断导致的)
// 例子:
var i4 int = 1000
var i5 int8
fmt.Println(i4) // 1000
i5 = int8(i4)
fmt.Println(i5) // -24

五、数组

数组是同一种数据类型元素的集合,是块连续的内存空间,在声明的时候必须指定长度,使用时可以修改数组成员,且长度不能改变。所以数组在声明的时候就可以把内存空间分配好,并赋上默认值,即完成了初始化。因为数组的长度是固定的,所以在Go语言中很少直接使用

图片1

5.1 数组初始化

1
2
3
4
5
6
7
8
9
var arr1 [5]int = [5]int{}         //数组必须指定长度和类型,且长度和类型指定后不可改变
var arr2 = [5]int{} //推断长度是5的数组
var arr3 = [5]int{3, 2} //给前2个元素赋值
var arr4 = [5]int{2: 15, 4: 22} //给第2,4个元素赋值
var arr5 = [...]int{2, 4, 3, 5, 1} //根据值的数量去推断数组长度
var arr6 = [...]struct { //结构体赋值数组
name string
age int
}{{"Tom", 19}, {"Jin", 22}}

5.2 二维数组初始化

1
2
3
4
//5行3列,只给前2行赋值,且前2行的所有列还没有赋满
var arr1 = [5][3]int{{1}, {2, 3}}
//第1维可以用...推测,第2维不能用...
var arr2 = [...][3]int{{1}, {2, 3}}

5.3 访问数组元素

通过index访问

1
2
访问首元素arr[0]
访问末元素arr[len(arr)-1]

访问二维数组元素

1
访问位于第三行第四列的元素arr[2][3]

5.4 遍历数组

1
2
3
4
5
6
7
8
9
//for range 遍历数组
for i, ele := range arr4 {
fmt.Printf("index=%d,element=%d\n", i, ele)
}

//或 for 循环遍历数组
for i := 0; i <= len(arr4); i++ {
fmt.Printf("index=%d,element=%d\n", i, arr4[i])
}

冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
// 冒泡排序
func main() {
arr01 := [...]int{54,123,11,22,875,124}
for i :=1;i<len(arr01);i++{
//fmt.Println(arr01[i])
for j:=0;j<len(arr01)-i;j++{
if arr01[j] > arr01[j+1]{
arr01[j],arr01[j+1] = arr01[j+1],arr01[j]
}
}
fmt.Println(arr01)
}
}

遍历二维数组

1
2
3
4
5
6
//遍历二维数组
for row, array := range arr1 { //再遍历行
for col, ele := range array { //再遍历列
fmt.Printf("arr[%d] [%d]=%d\n", row, col, ele)
}
}

5.5 数组中的 cap 和 len

  • cap代表capacity容量;
  • len代表length长度;
  • len代表目前数组里的几个元素,cap代表给数组分配的内存空间可以容纳多少个元素;
  • 由于数组初始化之后长度不会改变,不需要给它预留内存空间所以len(arr)==cap(arr);
1
2
3
4
5
6
7
var arr [5]int = [5]int{5, 2, 8, 9, 6}
fmt.Println(len(arr))
fmt.Println(cap(arr))

//输出结果:
5
5

5.6 数组传参

  • 数组的长度和类型都是数组类型的一部分,函数传递数组类型时这两部分都必须吻合;
  • go语言没有按引用传参,全部是按值传参,即传递数组实际上传的是数组的拷贝,当数组的长度很大时,仅传参开销都很大;
  • 如果想修改函数外部的数组,就把它的指针(数组在内存里的地址)传进来;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 不使用指针传递数组
func arrPoint(arr [5]int) {
fmt.Printf("%p\n", &arr)
fmt.Printf("%d\n", arr[0])
arr[0] += 10
fmt.Printf("%d\n", arr[0])
}
func main() {
var crr [5]int = [5]int{5, 2, 8, 9, 6}
fmt.Printf("%p\n", &crr)
arrPoint(crr)
fmt.Printf("%d\n", crr[0])
}

//输出结果:
0xc00000e330
0xc00000e360
5
15
5
//实际上是将外部的数组拷贝了一份,arrPoint函数修改的是拷贝后的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 现在使用指针传递数组
func arrPoint(arr *[5]int) {
fmt.Printf("%p\n", arr)
fmt.Printf("%p\n", &arr[0])
fmt.Printf("%d\n", arr[0])
arr[0] += 10
fmt.Printf("%d\n", arr[0])
}
func main() {
var crr [5]int = [5]int{5, 2, 8, 9, 6}
fmt.Printf("%p\n", &crr)
arrPoint(&crr)
fmt.Printf("%d\n", crr[0])
}
//输出结果:
0xc00000e330
0xc00000e330
0xc00000e330
5
15
15

六、切片

Go 语言切片是对数组的一种抽象。

Go 数组的长度不可改变,在特定场景中就不太适用,Go 中提供了一种灵活,功能强悍的内置类型:切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

需要说明,slice 并不是数组或数组指针。它通过内部指针和相关属性 引用数组片段 ,以实现变长方案。

  • 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
  • 切片的长度可以改变,因此,切片相当于一个可变的数组。
  • 切片遍历方式和数组一样,可以用 len() 求长度。表示可用元素数量,读写操作不能超过该限制。
  • cap 可以求出 slice 最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。
  • 切片的定义:var 切片变量名 []类型,比如 var str []string 、 var arr []int。
  • 如果 slice == nil,那么 len、cap 结果都等于 0。

Go 语言中的切片类型是从数组类型基础上发展出来的新类型,当声明一个数组时,不指定该数组长度,则该类型为切片(“动态数组”),切片有自己独立的内部结构字段(len, cap, array pointer),并于其引用的底层数组共用存储空间。

6.1 切片初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 常规声明
var s, e, f []int //切片声明,len=cap=0
s = []int{} //初始化,len=cap=0
s = []int{1, 2, 3, 4, 5, 6} //初始化,len=cap=6
e = s[0:2:cap(s)] //有2个元素的切片, len为2, cap为6,2个元素是s中的前两位也就是1,2
f = s[:0] //有0个元素的切片, len为0, cap为6

// make() 函数创建
var s []int
s = make([]int, 3) //初始化,len=cap=3
s = make([]int, 3, 5) //初始化,len=3,cap=5
fmt.Printf("%d\n", s)

// 赋值符 := 创建
s2d := [][]int{
{1}, {2, 3}, //二维数组各行的列数相等,但二维切片各行的列数可以不相等
}
fmt.Printf("%d\n", s2d)

6.2 append

  • 切片相对于数组最大的特点就是可以追加元素,可以自动扩容;
  • 追加的元素放到预留的内存空间里,同时len加1;
  • 如果预留空间已用完,则会重新申请一块更大的内存空间,capacity变成之前的2倍(cap<1024)或1.25倍(cat>1024)。把原内存空间的数据拷贝过来,在新内存空间上执行append操作;

末尾添加 预留空间不足时,append会重新分配内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包

// 预留空间不足时,append会重新分配内存
s1 := []int{1, 2} //初始化,len=2,cap=2
fmt.Println(len(s1), cap(s1))
s1 = append(s1, 10)
fmt.Println(s1)
fmt.Println(len(s1), cap(s1)) //预留空间已用完,申请了一个长度cap为4的内存空间
//输出结果:
2 2
[1 2 10]
3 4

开头添加 一般都会重新分配内存

1
2
3
4
// 开头添加(一般都会重新分配内存)
var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3, -2, -1}, a...) // 在开头添加1个切片

中间添加 (append和copy组合实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// append实现,第二个append调用会创建一个临时的切片
var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片

// append和copy组合实现,避免创建中间的临时切片
a = append(a, 0) // 先扩容
copy(a[i+1:], a[i:]) // a[i:]向后移动1个位置
a[i] = x // 设置新添加的元素

// append和copy组合,在指定位置插入切片(多个元素)
a = append(a, x...) // 没有专门的函数来扩容,只有使用append
copy(a[i+len(x):], a[i:])
copy(a[i:], x)

6.3 截取子切片

1
2
s := make([]int,4,5)  //len=4,cap=5
sub_slice = s[1:3] //len=2,cap=4

刚开始,子切片和母切片共享底层的内存空间,修改子切片会反映到母切片上,在子切片上执行append会把新元素放到母切片预留的内存空间上;

当子切片不断执行append,耗完了母切片预留的内存空间,子切片跟母切片就会发生内存分享,此后两个切片相互独立没有任何关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
arr := make([]int, 4, 5)
crr := arr[0:3]
crr[0] = 8
fmt.Println(crr, arr)
crr = append(crr, 9)
crr = append(crr, 10)
fmt.Println(crr, arr)
fmt.Printf("%p %p\n", &crr[0], &arr[0])

//内存分离
crr = append(crr, 12)
fmt.Println(crr, arr)
fmt.Printf("%p %p\n", &crr[0], &arr[0])
//输出结果:
[8 0 0] [8 0 0 0]
0xc00000e330 0xc00000e330
[8 0 0 9 10] [8 0 0 9]
[8 0 0 9 10 12] [8 0 0 9]
0xc000012320 0xc00000e330

七、map

Map 是高级语言中一种重要的数据结构,能够很方便的进行数据组织,主要都是<k,v>结构。除了slice,map,function的内建类型都可以作为key进行使用。因此,struct类型在不包含上述字段的时候,也可以作为key进行使用。

7.1 Map的定义

Go语言中,可以通过自己手工使用基础语法进行map定义,也可以使用内置的make方法进行定义。

基础定义方式

Go语言中,定义map的基本语法格式是:map[k类型] v类型 {k值,v值}一个典型的Map定义实例如下:

1
2
3
4
5
6
7
m := map[string]string {
"name":"ccmouse",
"course":"golang",
"site":"imooc",
"quality":"notbad",
}
fmt.Printf("Map value is %v", m)

使用内置make方法

1
2
3
4
5
6
7
8
9
10
11
12
// k为string类型,v为int类型
m := make(map[string]int)

// 基础声明方法
var m2 map[string]int
m2 = make(map[string]int) //初始化,容量为0
m2 = make(map[string]int, 5) //初始化 容量为5,如果能确定使用容量强烈建议在初始化时给一个合适的容量,减少扩容的概率
m2 = map[string]int{ //初始化时直接赋值
"语文": 10,
"数学": 15,
"化学": 20,
}

map 在使用前一定要 make;
map 的 key 不能重复,如果重复了,则以最后这个 key-value 为准;
map 的 value 是可以相同的;
map 的 key-value 是无序;

7.2 添加和删除key

1
2
3
4
//添加和删除key
m2["化学"] = 67
delete(m2, "化学")
fmt.Println(len(m2), m2)

7.3 根据key查找value

读取key对应的value,如果key不存在,则返回value类型的默认值

取key对应的value建议使用 if

1
2
3
4
5
6
//根据key查找value
if value, exists := m2["化学"]; exists {
fmt.Println(value)
} else {
fmt.Println("map里不存在查找的value", exists)
}

7.4 遍历map

1
2
3
4
//遍历map
for key, value := range m2 {
fmt.Printf("%s=%d\n", key, value)
}

八、channel

Go语言中的通道(channel)是一种特殊的类型。
在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。goroutine 间通过通道就可以通信。

通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。

(1)channel本身是一个队列,先进先出
(2)线程安全,不需要加锁
(3)本身是有类型的,string, int 等,如果要存多种类型,则定义成 interface类型
(4)channel是引用类型,必须make之后才能使用,一旦 make,它的容量就确定了,不会动态增加!!它和map,slice不一样

channel 的特点:
(1)一旦初始化容量,就不会改变了。
(2)当写满时,不可以写,取空时,不可以取。
(3)发送将持续阻塞直到数据被接收
把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。Go 程序运行时能智能地发现一些永远无法发送成功的语句并做出提示
(4)接收将持续阻塞直到发送方发送数据。
如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。
(5)每次接收一个元素。
通道一次只能接收一个数据元素。

8.1 channel 的声明和使用

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
//1. 创建一个可以存放3个int类型的管道
var intChan chan int
intChan = make(chan int, 3)

//2. 看看intChan是什么
//channel其实和指针一样,本身存放在一个内存单元中,有它的地址
fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)

//3. 向管道写入数据
intChan<- 10
num := 211
intChan<- num
intChan<- 50
// //如果从channel取出数据后,可以继续放入
<-intChan
intChan<- 98//注意点, 当我们给管写入数据时,不能超过其容量

//4. 看看管道的长度和cap(容量)
fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 3, 3

//5. 从管道中读取数据
var num2 int
num2 = <-intChan
fmt.Println("num2=", num2)
fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 2, 3

//6. 在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock
num3 := <-intChan
num4 := <-intChan

//num5 := <-intChan
fmt.Println("num3=", num3, "num4=", num4/*, "num5=", num5*/)

8.2 空接口类型的 channel

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"
)

type Cat struct {
Name string
Age int
}

func main() {

//定义一个存放任意数据类型的管道 3个数据
//var allChan chan interface{}
allChan := make(chan interface{}, 3)

allChan<- 10
allChan<- "tom jack"
cat := Cat{"小花猫", 4}
allChan<- cat

//我们希望获得到管道中的第三个元素,则先将前2个推出
<-allChan
<-allChan

newCat := <-allChan //从管道中取出的Cat是什么?

fmt.Printf("newCat=%T , newCat=%v\n", newCat, newCat)
//下面的写法是错误的!编译不通过
//fmt.Printf("newCat.Name=%v", newCat.Name)
//使用类型断言
a := newCat.(Cat)
fmt.Printf("newCat.Name=%v", a.Name)
}

定义 interface类型的空接口,可以接收任意类型的数据,但是在取出来的时候,必须断言!
a := newCat.(Cat)

8.3 channel 的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//声明chan类型,初始化可存放10个int
var ch chan int
ch = make(chan int, 10)

//循环写入
for i := 0; i < 10; i++ {
ch <- i
}

//for ++遍历不需要close
// for j := 0; j < 10; j++ {
// v := <-ch
// fmt.Println(v)
// }

//for range 遍历需要close
close(ch)
for ele := range ch {
fmt.Println(ele)
}

九、引用类型

  • slice、map和channel是go语言里的3种引用类型,都可以通过make函数来进行初始化(申请内存分配);
  • 因为他们都包含一个指向底层数据结构的指针,所以称之后“引用”类型;
  • 引用类型未初始化时都是nil,可以对它们执行len()函数,返回0;
-------------本文结束感谢您的阅读-------------