目录
一、布尔类型(bool)
二、整型(int、uint)
三、浮点型(float64)
四、复数类型(complex64)
五、字符串(string)
六、数组([]数组里元素的类型
代表数组类型,[]
内填写数组的长度(可以是...,如果我们不想指定长度的话),所以数组类型并不是array)
七、数组切片([]数组切片里元素的类型
代表数组切片类型,[]
内写长度就是数组类型,[]
内不写长度就是数组切片类型,所以数组切片类型并不是slice)
八、字典(字典类型为map[字典内key的类型]字典内value的类型
,而不仅仅是个map)
九、不定参数类型(...参数类型
,其实不定参数类型的本质是一个数组切片)
十、结构体(struct)
十一、指针
十二、通道(channel)
十三、接口(interface)
Go语言支持的数据类型有:
- 布尔类型
- 整型
- 浮点型
- 字符串
- 数组
- 数组切片
- 字典
- 任意类型
- 通道
- 接口
请记住:Go语言里大多数数据类型都是值语义的,除了数组切片、字典、通道和接口是引用语义的。
所谓值语义和引用语义类似于我们OC的深拷贝和浅拷贝。值语义就是指赋值或者传参的时候会完全复制一份,修改新的不会影响旧的。引用语义是指赋值或者传参的时候不会赋值,而是新的和旧的同用一个值,修改新的也会同时修改旧的。
OC里面都支持这些类型,或者有类似的功能。
一、布尔类型(bool)
Go里布尔值只能赋值ture、false这两个预定义常量或者是可以直接推导出结果的表达式。如:
(清单1.1)
flag1 := false // 编译通过
flag2 := true // 编译通过
flag3 := (1 == 2) // 编译通过
Go语言的布尔值不接受其它类型赋值,也不接受强转,否则编译会报错。如:
(清单1.2)
var flag3 bool = 2 // 编译报错
var flag4 bool = bool(2) // 编译报错
二、整型
Go提供了很多整型,对于一般开发来说我们使用int和uint就可以了,没必要用其它的,避免移植的困难。
三、浮点型
我们知道C里面的float的精确度为6-7位,绝对有效的位数为6位;double的精确度为15-16位,绝对有效的位数为15位。
Go里的浮点型为float32和float64,float32对应float,float64对应double,浮点型会被自动推导为float64,所以一般用浮点型就用float64就可以了。
需要注意的是:Go语言里面的类型要求是非常严格的,即便都是浮点型,float32和float64也是不一样的,它们俩不能比较,更不能相互赋值,需要强转。因此就更别说int类型和浮点型的比较和赋值了,更不行了,需要我们手动强转。如:
(清单3.1)
var v1 float32 = 11 // float32类型
v2 := 11.0 // 自动推导为float64类型
if v1 == v2 { // 两者比较,编译报错,类型不匹配
fmt.Println(1)
} else {
fmt.Println(0)
}
强转之后就可以了:
(清单3.2)
var v1 float32 = 11 // float32类型
v2 := 11.0 // 自动推导为float64类型
if v1 == float32(v2) { // 强转之后比较,编译通过
fmt.Println(1)
} else {
fmt.Println(0)
}
同理,两个浮点数的赋值操作,也需要强转。如:
(清单3.3)
var v1 float32 // float32类型
v2 := 11.0 // 自动推导为float64类型
v1 = v2 // 把float64类型赋值给float32类型,编译报错
强转之后就可以了:
(清单3.3)
var v1 float32 // float32类型
v2 := 11.0 // 自动推导为float64类型
v1 = float32(v2) // 强转之后赋值,编译通过
四、复数类型
z = a + bi,z就是一个复数,a是z的实部,b是z的虚部。
创建一个复数,也有三种办法:
(清单4.1)
// 1、定义+赋值法
var v1 complex64 // 定义一个复数
v1 = 11 + 12i // 复数赋值
// 2、:=法
v2 := 11 + 12i
// 3、预置函数complex()法
v3 := complex(11, 12)
我们也可以通过预置函数real()
和imag()
来分别获取一个复数的实部和虚部。如:
(清单4.2)
var v1 complex64 // 定义一个复数
v1 = 11 + 12i // 复数赋值
r := real(v1)
i := imag(v1)
五、字符串(string)
当然了,这里再说一遍,Go里面所有变量的定义(2种)、赋值(2种)、初始化(3种)都无外乎前面说的那几种方法,所以字符串也不例外,接下来要说的其它类型也不例外,这里再敲一遍。
- 字符串的定义:
(清单5.1)
var str1 string
var str2 string
或者
var (
str1 string
str2 string
)
- 字符串的赋值
(清单5.2)
var (
str1 string
str2 string
)
str1 = "你好"
str1, str2 = "你好", "杭州"
- 字符串的初始化
(清单5.3)
var str1 string = "你好"
var str2 = "杭州"
str3, str4 := "你好", "杭州"
- 字符串的操作
从Go字符串的操作来看,它的某些方面像我们OC的不可变字符串,有的方面又像我们OC的可变字符串。
①字符串的元素访问:像数组那样取就可以了,但是要记住Gp语言里的字符串并非容器类型
(清单5.4)
var str1 string = "Hello"
var str2 = "杭州"
str1 = str1 + str2
ch := str1[0]
但是字符串的元素访问,Go里的字符串又不能像OC的可变字符串那样改变字符串中某个指定的字符,所以又不能全看做可变字符串。
②字符串的拼接(看起来像可变字符串):用+
来拼接
(清单5.5)
var str1 string = "你好"
var str2 = "杭州"
str1 = str1 + str2
③求字符串的长度:用函数len()
来计算
注意:Go里面一个汉字占3个字节,OC是占两个字节的
(清单5.6)
var str1 string = "你好"
var str2 = "杭州"
str1 = str1 + str2
fmt.Println(len(str1))
④遍历字符串:有两种办法,一种是老老实实的for循环,一种是for ... range循环法(这都不仅应用于字符串,别的容器类型也可以)
- 老老实实for循环法
(清单5.7)
var str1 string = "你好"
var str2 = "杭州"
str1 = str1 + str2
for i := 0; i < len(str1); i++ {
fmt.Println(str1[i])
}
-
for ... range循环法:会返回下标和下标对应的值
(清单5.8)
var str1 string = "你好"
var str2 = "杭州"
str1 = str1 + str2
for idx, v := range str1 { // 意思是在str1的range内循环,并返回str1的每一个index和index对应的值
fmt.Println(idx, v)
}
六、数组([]数组里元素的类型
代表数组类型,[]
内填写数组的长度,所以数组类型并不是array)
- (1)数组的定义
注意:[]数组里元素的类型
代表Go语言里的数组类型,[]
内填写数组的长度,数组类型并不是array。所以我们使用[数组长度]数组里元素的类型
来定义数组的,而不是array。
①常规定义法:
(清单6.1)
var arr1 [1]int
var arr2 [2]int
var (
arr3 [5]int
arr4 [5]int
)
②make()函数
构造法:是不能用于数组的,是用于数组切片的
Go提供了一个make()函数
来定义数组切片、字典、通道等容器类的数据类型。
make()函数
至少能输入两个参数,第一个参数是容器数据类型的类型,第二个是容器数据类型的长度(当然这个参数不传也可以,让系统来自己估计)。
注意:数组不能用make()函数
构造法来创建,因为如下创建的是一个数组切片,因为[]int
的[]
里没有指定长度。
(清单6.2)
arr1 := make([]int, 5) // 代表要定义一个整型数组切片,长度为5
- (2)数组的赋值
注意:其它数据类型在赋值的时候,直接赋值就可以了,但是容器类的数据类型在赋值时不能省略它的类型,而且赋值格式都是数据类型加大括号。
(清单6.3)
var arr1 [5]int
arr1 = [5]int{1, 2}
- (3)数组的初始化
(清单6.4)
var arr1 [5]int = [5]int{1, 2} // 方式1
var arr2 = [5]int{3, 4} // 方式1
arr3 := [2]string{"你好", "杭州"} // 方式3:如果我们不想指定数组的长度,用三个点...来代替数组长度
- (4)数组的操作
①元素访问
Go语言里的数组不仅可以访问元素,还可以修改指定的元素。因此它看起来有点像OC的可变数组,但是数组一旦在指定数组长度之后就不能修改它的长度了,又有点像不可变数组,但是它又可以不指定数组由系统来自己估量,所以又像可变数组,是否指定数组长度看需求而定。
(清单6.5)
arr1 := make([]int, 5) // 代表要定义一个整型数组,长度为5
for idx, _ := range arr1 {
arr1[idx] = idx
}
fmt.Println(arr1[2])
②值类型
我们要时刻记得Go语言里的数组是一个值类型,而Go里所有的值类型的变量在赋值或者作为参数传递时都会发生一次复制操作,即都是值传递,而不是指针传递,我们改的都是副本,而不是原本。如:
(清单6.6)
数组赋值:
arr1 := [2]int{1, 2}
var arr2 = arr1 // Go里数组的赋值操作会发生一次复制操作,所以现在arr2和arr1是两个不同的数组
arr2[0] = 2 // 修改了arr2的第一个元素,那么arr2 = {2, 2},而arr1 = {1, 2}
(清单6.7)
数组作为参数传递:
func hello(arr [5]int) { // Go里数组的赋值操作会发生一次复制操作,所以下面的arr和arr1是两个不同的数组
arr[0] = 2 // 修改了arr的第一个元素,那么arr = {2, 2},而arr1 = {1, 2}
fmt.Println(arr)
}
func main() {
arr1 := [5]int{1, 2}
hello(arr1)
fmt.Println(arr1) // arr1 = {1, 2}
}
那么,如果我们非要想在一个函数内部修改一个数组的值呢?怎么办?需要用到数组切片。
七、数组切片([]数组切片里元素的类型
代表数组切片类型,[]
内写长度就是数组类型,[]
内不写长度就是数组切片类型,所以数组切片类型并不是slice)
通过数组切片的常用操作,我们会发现数组切片操作起来有点类似我们OC里的可变数组。
但是如果数组切片是基于数组创建的,我们又可以把数组切片看做是一个指向原数组某个区间的指针。
(1)数组切片的定义
[]数组切片里元素的类型
代表数组切片类型,[]
内写长度就是数组类型,[]
内不写长度就是数组切片类型,所以数组切片类型并不是slice
①常规定义法:
(清单7.1)
var slice1 []int
var slice2 []int
var (
slice3 []int
slice4 []int
)
②make()函数构造法:
(清单7.2)
var slice2 = make([]int, 5) // 定义一个元素个数为5的数组切片
slice2 = []int{1, 2, 3, 4, 5, 6} // 但是元素个数可以超出5个
我们发现虽然我们定义了一个元素个数为5的数组切片,但是它其实可以超出这个个数,就像我们OC的可变数组一样,也是这样的。
(2)数组切片的赋值
同理,数组切片的赋值也不能少了类型。如:
(清单7.3)
var slice1 []int // 定义一个数组切片
slice1 = []int{1, 2, 3, 4, 5, 6}
(3)数组切片的初始化
(清单7.4)
var slice1 []int = []int{1, 2, 3, 4, 5, 6}
var slice2 = []int{1, 2, 3, 4, 5, 6}
slice3 := []int{1, 2, 3, 4, 5, 6}
(4)此外数组切片还可以基于数组来创建
- 数组切片可以基于数组来创建,创建方式为arr[index1, index2],代表截取下标index1开始(包含index1)到下标index2为止的元素(不包含index2)。
- 如果index1=0可省略index1,如果index2=len(arr)-1可省略index2。
(清单7.5)
arr := [11]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice1 := arr[:] // 获取数组全部的内容
slice2 := arr[2:]
slice3 := arr[:9]
slice4 := arr[3:8] // 获取数组下标3(包含)到下标8(不包含)的内容
(5)数组切片的操作
和我们OC一样,可变数组继承于不可变数组,所以不可变数组的操作都适用于可变数组,同时可变数组有扩展了一些它特有的方法。数组和数组切片也有类似的关系。
①元素访问
(清单7.6)
arr := [11]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice1 := arr[3:8]
slice1[0] = 11
for _, v := range slice1 {
fmt.Println(v)
}
②数组切片增加元素:使用append()
函数(但是并没有直接提供删除元素的函数)
append()
函数有两个参数,第一个参数必须是一个数组切片,第二个可以填一个或者一串单个的元素,也可以填数组切片,但是如果是数组切片的话,后面就必须跟上三个点...
,同时我们需要注意,当数组切片作为参数传递的时候后面就必须跟上三个点...
来打碎它。
- 新增元素
(清单7.7)
arr := [11]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice1 := arr[3:8]
slice1[0] = 11
slice1 = append(slice1, 11, 12, 13)
fmt.Println(slice1)
- 新增数组切片
(清单7.8)
arr := [11]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice1 := arr[3:8]
slice1[0] = 11
slice1 = append(slice1, arr[:5]...)
插入
还记得上一小节数组最后的遗留问题吗?--“那么,如果我们非要想在一个函数内部修改一个数组的值呢?怎么办?需要用到数组切片。”
其实答案就是通过数组切片来实现。我们举个例子:
(清单7.9)
arr := [11]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice1 := arr[:] // 数组切片获取数组全部元素
slice1[0] = 11 // 修改数组切片的第一个元素
fmt.Println(slice1, arr) // 打印,我们会发现数组切片和数组的第一个元素都被修改了
输出:
[11 1 2 3 4 5 6 7 8 9 10] [11 1 2 3 4 5 6 7 8 9 10]
可见,数组切片如果基于数组创建,那它又可以看做是指向数组某一块区间的一个指针,修改数组切片就会修改原数组。
③数组切片的复制操作:使用copy()
函数来操作,注意copy()
函数只能用于数组切片或者字符串的复制
数组切片支持复制操作,可以用copy函数来复制。如果两个数组切片的大小不一样,那么就会按照小的那个切片的大小来复制。
要注意:数组的赋值操作是会复制一份的,但是数组切片的赋值操作是不会复制一份的,除非我们用copy()
函数来主动复制一份数组切片
(清单7.10)
slice1 := make([]int, 3)
slice2 := []int{1, 2, 3, 4, 5}
copy(slice1, slice2) // 会复制slice2的前三个元素到slice1中
八、字典(字典类型为map[字典内key的类型]字典内value的类型
,而不仅仅是个map)
注意:字典的赋值操作不同于数组,它是个指针操作。
- (1)字典的定义
①常规定义法:
(清单8.1)
var dict1 map[int]string
var (
dict2 map[int]string
dict3 map[int]string
)
②make()函数构造法:
(清单8.2)
dict := make(map[string]int)
(2)字典的赋值
同理,字典的赋值也不能少了类型。如:
(清单8.3)
dict := make(map[int]string)
dict = map[int]string{
1: "你好",
2: "杭州",
}
注意:最后一对key-value后也需要在逗号,而且我们也发现Go里的字典的key可以是任何类型。
(3)字典的初始化
(清单8.4)
var dict1 map[int]string = map[int]string{
1: "你好",
2: "杭州",
}
var dict2 = map[int]string{
1: "你好",
2: "杭州",
}
dict3 := map[int]string{
1: "你好",
2: "杭州",
}
(4)字典的操作
看做可变字典好了。
①元素访问:修改元素的值,新增一对key-value,删除一对key-value(delete()
函数只能用于字典删除key-value)
(清单8.5)
dict := map[int]string{
1: "你好",
2: "杭州",
}
dict[1] = "Hello" // 改一个元素的值
dict[3] = "很棒,但就是雨多" // 新增一个元素并赋值
delete(dict, 2) // 删除字典里key-2对应的一对key-value
②元素查找
我们通过dict[key]
的方式来读取字典里某个key对应的value,但其实在Go语言里这样的读取方式是会返回两个值的,一个值是一个bool值,用来查找字典里有没有某个key,另一个值才是key对应的value。如:
(清单8.6)
dict := map[int]string{
1: "你好",
2: "杭州",
}
v, ok := dict[1]
if ok {
fmt.Println(v)
} else {
fmt.Println("字典里没有这个key")
}
打印:你好
dict := map[int]string{
1: "你好",
2: "杭州",
}
v, ok := dict[3]
if ok {
fmt.Println(v)
} else {
fmt.Println("字典里没有这个key")
}
打印:字典里没有这个key
九、不定量参数类型(...参数类型
,其实不定量参数类型的本质是一个数组切片)
(1)不定参数的使用:不定量参数类型是Go语言一种特殊的类型,它的格式为“三个点+参数类型”...参数类型
。如果我们想让一个函数拥有不定数量的参数,就可以通过不定量参数类型来实现。而且这种数据类型它只能作为函数参数的类型存在,不能用于别的用途。
(清单9.1)
func main() {
myFunc(1, 2, 3)
myFunc(1, 2, 3, 4)
}
func myFunc(args ...int) { // 传入不定数量的整型数
sum := 0
for _, v := range args {
sum += v
}
fmt.Println(sum)
}
打印:
6
10
成功: 进程退出代码 0.
上面传入的是不定数量的整型参数,我们也可以把不定参数类型的参数类型设置为任意类型interface{}
,这样我们就可以给一个函数传任意类型的、任意个数的参数了。如:
(清单9.2)
func main() {
slice := []int{1, 2}
myFunc("杭州", 1, slice)
}
func myFunc(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println("这是一个整型")
case string:
fmt.Print("这是一个字符串")
case []int:
fmt.Print("这是一个数组切片")
}
}
}
打印:
这是一个字符串
这是一个整型
这是一个数组切片
成功: 进程退出代码 0.
(2)不定参数的传递:不定量参数类型的本质是一个数组切片,因此在传递它的时候,后面需要跟三个点...
来打碎。
(清单9.3)
func main() {
myFunc(1, 2, 3)
}
func myFunc(args ...interface{}) {
myFunc1(args...) // 传递不定量参数后面加三点来打碎数组切片
}
func myFunc1(args ...interface{}) {
fmt.Print(args)
}
网友评论