一、基础数据类型
1.1 基础数据类型
类型 | 长度(字节) | 默认值 | 说明 |
---|---|---|---|
bool | 1 | false | |
byte | 1 | 0 | uint8,取值范围[0,255] |
rune | 4 | 0 | Unicode Code Point,int32 |
int,uint | 4或8 | 0 | 32或64位,取决于操作系统 |
int8,uint8 | 1 | 0 | -128~127,0~255 |
int16,uint16 | 2 | 0 | -32768~32767,0-65535 |
int32,uint32 | 4 | 0 | -21亿~21亿,0~42亿,rune是int32的别名 |
int64,uint64 | 8 | 0 | |
float32 | 4 | 0.0 | |
float64 | 8 | 0.0 | |
complex64 | 8 | ||
complex128 | 16 | ||
uintptr | 4或8 | 以存储指针的uint32或uint64整数 |
1.2 复合数据类型
类型 | 默认值 | 说明 |
---|---|---|
array | 值类型 | |
struct | 值类型 | |
string | “” | UTF-8 字符串 |
slice | nil | 引用类型 |
map | nil | 引用类型 |
channel | nil | 引用类型 |
interface | nil | 接口 |
function | nil | 函数 |
二、自定义类型
2.1 自定义类型
在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型,Go语言中可以使用 type 关键字来定义自定义类型。
自定义类型是 定义了一个全新的类型 。我们可以基于内置的基本类型定义,也可以通过 struct 定义。例如:
1 | // 将MyInt定义为int类型 |
通过Type关键字的定义,MyInt就是一种新的类型,它具有 int 的特性。
思考:
实际上,Go语言中的结构体类型就是一种自定义类型。
看看结构体的定义格式就知道了:
1 | type 结构体类型名 struct { |
是不是和上述自定义类型的格式一模一样呢?
2.2 类型别名
类型别名是Go1.9版本添加的新功能。
类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
1 | type TypeAlias = Type |
我们之前见过的rune和byte就是类型别名,他们的定义如下:
1 | type byte = uint8 |
2.3 类型定义和类型别名的区别
类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。
1 | package main |
输出结果:
1 | type of a:main.NewInt |
结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。这是一个新的数据类型。
b的类型是int。MyInt类型别名只会在代码中存在,编译完成时并不会有MyInt类型。
三、字符串
Go语言将字符串作为一种原生的基本数据类型。字符串是一个不可修改的数据类型。它的底层结构如下:
1 | type stringStruct struct { |
3.1 字符串赋值
1 | s1 :="MY name is zhangsan \u4f17" //字符串里可以包含任意Unicode字符 |
3.2 字符串常用操作
方法 | 描述 |
---|---|
len(str) | 求长度 |
strings.Split | 分割 |
strings.Contains | 判断是否包含 |
Strings.HasPrefix,strings.HasSuffix | 前缀/后缀判断 |
Strings.Index(),Strings.LastIndex() | 子串出现的位置 |
1 | // 求长度 |
3.3 字符串拼接
3.3.1 加号拼接
最常用的方法肯定是 + 连接两个字符串。这与python类似,不过由于golang中的字符串是不可变的类型,因此用 + 连接会产生一个新的字符串对效率有影响。
1 | s1 := "字符串" |
3.3.2 sprintf 函数
第二种方法使用sprintf函数,虽然不会像直接使用 + 那样产生临时字符串。但是效率也不高
1 | s1 := "字符串" |
3.3.3 Join 函数
第三种方法是用Join函数,这里我们需要先引入strings包才能调用Join函数。Join函数会先根据字符串数组的内容,计算出一个拼接之后的长度,然后申请对应大小的内存,一个一个字符串填入,在已有一个数组的情况下,这种效率会很高,如果没有的话效率也不高。
1 | // 需要先导入strings包 |
3.3.4 buffer 函数
第四个方法是调用buffer.WriteString函数,这种方法的性能就要大大优于上面的了。
1 | // 需要先导入bytes包 |
3.3.5 Builder 函数
第5个方法是用buffer.Builder,这个方法和上面的差不多,不过官方建议用这个,使用方法和上面基本一样
1 | // 需要先导入Strings包 |
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 | s := "123 张三" |
四、强制类型转换
golang语言不支持类型自动转换(隐式转换),只支持强制类型转换(显示转换)
即不同类型变量之间赋值需要用到强制类型转换语法
基本语法如下:
表达式T(v),表示将变量v的值转换为T类型,把转换后的值赋值给接收变量
PS:存储范围更大的类型转换成存储范围更小的类型时,如果值超过了后者的取值范围,不会报错但是会直接截断字节,得到很奇怪的值(二进制数被从高位截断导致的)
- byte和int可以互相转换
- float和int可以互相转换,小数位会丢失
- bool和int不能相互转换
- 不同长度的int或float之间可以相互转换
- string可以转换为[]byte或[]rune类型,byte或rune可以转换为string
- 低精度向高精度转换没问题,高精度向低精度转换会丢失位数
- 无符号向有符号转换,最高位是符号位
1 | // 例子: |
五、数组
数组是同一种数据类型元素的集合,是块连续的内存空间,在声明的时候必须指定长度,使用时可以修改数组成员,且长度不能改变。所以数组在声明的时候就可以把内存空间分配好,并赋上默认值,即完成了初始化。因为数组的长度是固定的,所以在Go语言中很少直接使用
5.1 数组初始化
1 | var arr1 [5]int = [5]int{} //数组必须指定长度和类型,且长度和类型指定后不可改变 |
5.2 二维数组初始化
1 | //5行3列,只给前2行赋值,且前2行的所有列还没有赋满 |
5.3 访问数组元素
通过index访问
1 | 访问首元素arr[0] |
访问二维数组元素
1 | 访问位于第三行第四列的元素arr[2][3] |
5.4 遍历数组
1 | //for range 遍历数组 |
冒泡排序
1 | // 冒泡排序 |
遍历二维数组
1 | //遍历二维数组 |
5.5 数组中的 cap 和 len
- cap代表capacity容量;
- len代表length长度;
- len代表目前数组里的几个元素,cap代表给数组分配的内存空间可以容纳多少个元素;
- 由于数组初始化之后长度不会改变,不需要给它预留内存空间所以
len(arr)==cap(arr)
;
1 | var arr [5]int = [5]int{5, 2, 8, 9, 6} |
5.6 数组传参
- 数组的长度和类型都是数组类型的一部分,函数传递数组类型时这两部分都必须吻合;
- go语言没有按引用传参,全部是按值传参,即传递数组实际上传的是数组的拷贝,当数组的长度很大时,仅传参开销都很大;
- 如果想修改函数外部的数组,就把它的指针(数组在内存里的地址)传进来;
1 | // 不使用指针传递数组 |
1 | // 现在使用指针传递数组 |
六、切片
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 | // 常规声明 |
6.2 append
- 切片相对于数组最大的特点就是可以追加元素,可以自动扩容;
- 追加的元素放到预留的内存空间里,同时len加1;
- 如果预留空间已用完,则会重新申请一块更大的内存空间,capacity变成之前的2倍(cap<1024)或1.25倍(cat>1024)。把原内存空间的数据拷贝过来,在新内存空间上执行append操作;
末尾添加 预留空间不足时,append会重新分配内存
1 | var a []int |
开头添加 一般都会重新分配内存
1 | // 开头添加(一般都会重新分配内存) |
中间添加 (append和copy组合实现)
1 | // append实现,第二个append调用会创建一个临时的切片 |
6.3 截取子切片
1 | s := make([]int,4,5) //len=4,cap=5 |
刚开始,子切片和母切片共享底层的内存空间,修改子切片会反映到母切片上,在子切片上执行append会把新元素放到母切片预留的内存空间上;
当子切片不断执行append,耗完了母切片预留的内存空间,子切片跟母切片就会发生内存分享,此后两个切片相互独立没有任何关系。
1 | arr := make([]int, 4, 5) |
七、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 | m := map[string]string { |
使用内置make方法
1 | // k为string类型,v为int类型 |
map 在使用前一定要 make;
map 的 key 不能重复,如果重复了,则以最后这个 key-value 为准;
map 的 value 是可以相同的;
map 的 key-value 是无序;
7.2 添加和删除key
1 | //添加和删除key |
7.3 根据key查找value
读取key对应的value,如果key不存在,则返回value类型的默认值
取key对应的value建议使用 if
1 | //根据key查找value |
7.4 遍历map
1 | //遍历map |
八、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 | //1. 创建一个可以存放3个int类型的管道 |
8.2 空接口类型的 channel
1 | package main |
定义 interface类型的空接口,可以接收任意类型的数据,但是在取出来的时候,必须断言!
a := newCat.(Cat)
8.3 channel 的遍历
1 | //声明chan类型,初始化可存放10个int |
九、引用类型
- slice、map和channel是go语言里的3种引用类型,都可以通过make函数来进行初始化(申请内存分配);
- 因为他们都包含一个指向底层数据结构的指针,所以称之后“引用”类型;
- 引用类型未初始化时都是nil,可以对它们执行len()函数,返回0;