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 []stringvar 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;
-------------本文结束感谢您的阅读-------------