继Golang学习系列第二天:变量、常量、数据类型和流程语句之后,今天开始学习数据类型之高级类型: 派生类型。

学过java的人都知道,java其实就8种基本类型:byte、short、int、long、float、double、char、boolean,但它有引用数据类型:字符串、数组、集合、类、接口等。

而golang也有这样的划分,基本类型(Golang学习系列第二天已学过)和派生类型(不叫引用类型),派生类型有以下几种:数组类型、切片类型、Map类型、结构体类型(struct)、指针类型(Pointer)、函数类型、接口类型(interface)、Channel 类型。

 

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

1.  数组类型

数组是具有相同数据类型的元素序列。 数组在声明中定义了固定的长度,因此不能扩展超过该长度。 数组声明为

var variable_name [SIZE] variable_type 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第1张

让我们以代码举例如下

package main import "fmt" func main() { var city [5]string city[0] = "北京" city[1] = "上海" city[2] = "广州" city[3] = "深圳" city[4] = "濮阳" fmt.Println(city[0], city[1], city[2], city[3], city[4]) fmt.Println(city) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第2张

将变量city声明为5个字符串的数组,执行输出

Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第3张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第4张

不过也可以在声明数组时设置数组条目,看简洁版

package main import "fmt" func main() { var city = [5]string{ "北京", "上海","广州","深圳","濮阳"} fmt.Println(city[0], city[1], city[2], city[3], city[4]) fmt.Println(city) //简写版 othercity := [5]string{ "北京", "上海","广州", "深圳","濮阳"} fmt.Printf("%q", othercity) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第5张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第6张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第7张

甚至,当您传递值时,可以使用省略号来使用隐式长度

package main import "fmt" func main() { var city = [5]string{ "北京", "上海","广州","深圳","濮阳"} fmt.Println(city[0], city[1], city[2], city[3], city[4]) fmt.Println(city) //简写版 othercity := [5]string{ "北京", "上海","广州", "深圳","濮阳"} fmt.Printf("%q\n", othercity) //隐士长度 other_city := [...]string{ "北京", "上海","广州", "深圳","濮阳"} fmt.Printf("%q", other_city) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第8张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第9张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第10张

 以不同方式打印数组

注意我们使用带Printf的fmt包以及如何使用%q“动词”来打印每个引用的元素。

如果我们使用Println或%s动词,我们将得到不同的结果

package main import "fmt" func main() { //不同方式打印数组 other_city := [...]string{ "北京", "上海","广州", "深圳","濮阳"} fmt.Println("不同方式打印数组") fmt.Println(other_city) fmt.Printf("%s\n", other_city) fmt.Printf("%q", other_city) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第11张

执行后输出如图Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第12张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第13张

 

多维数组

您还可以创建多维数组,示例代码:

package main import "fmt" func main() { var multi [2][3]int for i := 0; i < 2; i++ { for j := 0; j < 3; j++ { multi[i][j] = i + j } } fmt.Println("二维数组: ", multi) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第14张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第15张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第16张

 

2.  切片Slices类型

切片包装数组可为数据序列提供更通用,更强大和更方便的接口。 除了具有明确维数的项(例如转换矩阵)外,Go中的大多数数组编程都是使用切片而不是简单数组完成的。

切片包含对基础数组的引用,如果您将一个切片分配给另一个,则两个切片都引用同一数组。 如果函数采用slice参数,则对slice的元素所做的更改将对调用者可见,这类似于将指针传递给基础数组。

切片指向值数组,并且还包含长度。 切片可以调整大小,因为它们只是另一个数据结构之上的包装。

例如, []T is a slice with elements of type T 表示[] T是具有T类型元素的切片。

举个小例子

package main import "fmt" func main() { var city = []string{ "北京", "上海","广州","深圳","濮阳"} fmt.Println(city[0], city[1], city[2], city[3], city[4]) fmt.Println(city) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第17张

2.1  切分切片

可以对切片进行切片,以创建一个指向相同数组的新切片值。表达式为

slice[start:end]
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第18张

表示计算从start到end-1(含)的元素的一部分。

注意:start和end是表示索引的整数。

下面举个小demo

package main import "fmt" func main() { var city = []string{ "北京", "上海","广州","深圳","濮阳","郑洲"} fmt.Println(city) fmt.Println(city[1:4]) // missing low index implies 0 fmt.Println(city[:3]) // [2 3 5] // missing high index implies len(s) fmt.Println(city[4:]) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第19张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第20张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第21张

2.2   制造切片

除了通过立即传递值(切片文字)来创建切片之外,还可以使用make关键字创建切片。 您创建一个特定长度的空切片,然后填充每个条目。

package main import "fmt" func main() { fmt.Println("准备用make方式创建切片") cities := make([]string, 3) cities[0] = "郑洲" cities[1] = "濮阳" cities[2] = "安阳" fmt.Printf("%q", cities) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第22张

输出结果:Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第23张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第24张

 它通过分配清零的数组并返回引用该数组的切片来工作。

 

2.3  附加到切片

可以通过append方式附加值到切片中

package main import "fmt" func main() { //附加到切片 other_cities := []string{} other_cities = append(other_cities, "濮阳") fmt.Println(other_cities) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第25张

看输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第26张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第27张

 

也可以同时附加多个值到切片中,示例代码同时包括两个城市郑洲和濮阳

package main import "fmt" func main() { //附加多值到切片 other_cities := []string{} other_cities = append(other_cities, "郑洲","濮阳") fmt.Println(other_cities) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第28张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第29张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第30张

 

甚至您还可以使用省略号将切片附加到另一个切片

package main import "fmt" func main() { fmt.Println("准备用make方式创建切片") cities := make([]string, 3) cities[0] = "郑洲" cities[1] = "濮阳" cities[2] = "安阳" fmt.Printf("%q\n", cities) //附加切片到切片 other_cities := []string{"南京"} other_cities = append(other_cities, cities...) fmt.Println(other_cities) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第31张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第32张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第33张

注意: 省略号是该语言的内置功能,这意味着该元素是一个集合。 我们无法将字符串类型([] string)类型的元素附加到字符串切片中,只能附加字符串。 但是,在切片后使用省略号(...),表示要附加切片的每个元素。 因为我们要从另一个片段追加字符串,所以编译器将接受操作,因为类型是匹配的。

您显然无法将[] int类型的切片附加到[] string类型的另一个切片中。

 

2.4  复制切片

切片也可以复制。 在这里,我们创建一个与other_cities长度相同的空切片copycities,并从other_cities复制到copycities中。

package main import "fmt" func main() { fmt.Println("准备用make方式创建切片") cities := make([]string, 3) cities[0] = "郑洲" cities[1] = "濮阳" cities[2] = "安阳" fmt.Printf("%q\n", cities) //附加多值到切片 other_cities := []string{"南京"} other_cities = append(other_cities, cities...) fmt.Println(other_cities) //拷贝切片 fmt.Println("准备用copy方式创建切片") copycities := make([]string, len(other_cities)) copy(copycities, other_cities) fmt.Println("copycities:", copycities) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第34张

输出结果 Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第35张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第36张

2.5  切片长度

您可以随时使用len检查切片的长度。

package main import "fmt" func main() { fmt.Println("准备用make方式创建切片") cities := make([]string, 3) cities[0] = "郑洲" cities[1] = "濮阳" cities[2] = "安阳" fmt.Printf("%q\n", cities) //附加多值到切片 other_cities := []string{"南京"} other_cities = append(other_cities, cities...) fmt.Println(other_cities) //打印切片长度 fmt.Println(len(cities)) countries := make([]string, 42) fmt.Println(len(countries)) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第37张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第38张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第39张

2.6  零片

切片的零值为nil。 无切片的长度和容量为0。

package main import "fmt" func main() { //零片 var temp []int fmt.Println(temp, len(temp), cap(temp)) if temp == nil { fmt.Println("nil!") } }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第40张

 输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第41张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第42张

 

3. map类型

Map 是一种无序的键值对的集合,Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

package main import "fmt" func main() { users := map[string]int{ "admin": 0, "donguangming": 1, "student": 2, } fmt.Printf("%#v", users) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第43张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第44张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第45张

 

当不使用上述map时,使用前必须使用make(不是新的)创建map。 nil映射为空,无法分配给它。

package main import "fmt" type User struct { name string age int city string } var user map[string]User func main() { //通过make创建 user = make(map[string]User) user["dgm"] = User{"董广明", 99, "南京"} fmt.Println(user["dgm"]) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第46张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第47张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第48张

 

3.1  操作map

 3.1.1  在map中插入或更新元素

m[key] = elem
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第49张

  3.1.2    查询一个元素

elem = m[key]
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第50张

  3.1.3  删除一个元素

delete(m, key) 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第51张

   3.1.4  测试键是否存在并且有值

elem, ok = m[key]
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第52张

如果键在m中,则确定为true。 如果不是,则ok为false,elem为map元素类型的零值。 同样,从map中读取时(如果没有按键)则结果是map元素类型的零值。

综上合起来代码如下

package main import "fmt" type User struct { name string age int city string } var user map[string]User func main() { user = make(map[string]User) //赋值 user["dgm"] = User{"董广明", 99, "南京"} fmt.Println("user:", user) //查询 fmt.Println(user["dgm"]) //删除 delete(user, "dgm") fmt.Println("此时user:", user) //测试键 _, u := user["dgm"] fmt.Println("u:", u) var users = map[string]User{ "dmg": {"董广明", 99, "南京"}, "dongguangming": {"董广明", 88, "南京"}, } fmt.Println(users) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第53张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第54张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第55张

 

3.2   map循环

如何在map上进行迭代?您可以使用循环范围来迭代map, 因为映射是无序集合,所以此循环的值可能会有所不同。

package main import "fmt" type User struct { name string age int city string } var user map[string]User func main() { var users = map[string]User{ "dmg": {"董广明", 99, "南京"}, "dongguangming": {"董广明", 88, "南京"}, } fmt.Println(users) /*使用键输出map键值 */ for username := range users { fmt.Println(username, "用户=", users [username]) } }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第56张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第57张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第58张

 

4.  指针类型(Pointer)

学过C(上学时第一门编程语言就是C)的人都知道, 指针是存放值内存地址的地方, 指针由*定义,根据数据类型定义指针,go也是这么玩的,声明格式如下

var var_name *var-type 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第59张

var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。

&运算符可用于获取变量的地址。比如

var ap *int a := 12 ap = &a
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第60张

而指针指向的值可以使用*运算符进行访问,示例如下

package main import "fmt" func main() { var age int= 99 /* 声明实际变量 */ var ip *int /* 声明指针变量 */ ip = &age /* 指针变量的存储地址 */ fmt.Printf("age 变量的地址是: %x\n", &age ) /* 指针变量的存储地址 */ fmt.Printf("ip 变量储存的指针地址: %x\n", ip ) /* 使用指针访问值 */ fmt.Printf("*ip指针变量的值: %d\n", *ip ) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第61张

执行以上代码,输出结果 Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第62张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第63张

 

4.1   空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil,nil 指针也称为空指针。

nil在概念上和其它编程语言的null、None、nil、NULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr。空指针判断方式:

if(ptr != nil) /* ptr 不是空指针 */ if(ptr == nil) /* ptr 是空指针 */
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第64张

综上示例代码如下

package main import "fmt" func main() { var age int =99 /* 声明实际变量 */ var ptr *int /* 声明指针变量 */ var other_ptr *int /* 声明指针变量 */ ptr = &age /* 指针变量的存储地址 */ fmt.Printf("age 变量的地址是: %x\n", &age ) /* 使用指针访问值 */ fmt.Printf("*ptr指针变量的值: %d\n", *ptr ) if(ptr != nil) { /* ptr 不是空指针 */ fmt.Printf("ptr指针变量储存的指针地址: %x\n", ptr ) } if(other_ptr == nil) { /* other_ptr 是空指针 */ fmt.Printf("other_ptr指针变量储存的指针地址: %x\n", other_ptr ) } }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第65张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第66张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第67张

 

4.2  指针数组

 指针数组:简单点说它是一个数组,数组里面的每个元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。格式如下

var ptr [MAX]* type; 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第68张

ptr 为type指针数组,因此每个元素都指向了一个值,只是它的值是指针。

package main import "fmt" const MAX int = 5 func main() { var city = [MAX]string{ "北京", "上海","广州","深圳","濮阳"} var i int var othercity [MAX]*string; for i = 0; i < MAX; i++ { othercity[i] = &city[i] /* 字符串地址赋值给指针数组 */ } for i = 0; i < MAX; i++ { fmt.Printf("指针数组:索引:%d 值:%s 值的内存地址:%d\n", i, *othercity[i] , othercity[i] ) } }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第69张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第70张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第71张

 

4.3  指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。

当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址。

指向指针的指针变量声明格式如下:

var ptr **type; 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第72张

表示指向指针的指针变量为type。访问指向指针的指针变量值需要使用两个 * 号。

package main import "fmt" func main() { var age int var ptr *int var pptr **int age = 99 /* 指针 ptr 地址 */ ptr = &age /* 指向指针 ptr 地址 */ pptr = &ptr /* 获取 pptr 的值 */ fmt.Printf("变量 age = %d\n", age ) fmt.Printf("指针变量 *ptr = %d,内存地址是:%d\n", *ptr, ptr ) fmt.Printf("指向指针的指针变量 **pptr = %d,内存地址是:%d\n", *pptr, pptr) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第73张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第74张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第75张

 

4.4  通过new函数创建指针

go支持通过new函数创建指针, new函数将类型作为参数,并返回指向新分配的作为参数传递的类型的零值的指针。

package main import "fmt" func main() { //通过new函数创建指针 size := new(int) fmt.Printf("size的默认值= %d, 类型是: %T, 地址是: %v\n", *size, size, size) *size = 99 fmt.Println("更改后新的值是:", *size) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第76张

 输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第77张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第78张

 

4.5  指针参数

可以将指针传递给函数

package main import "fmt" func changeValue(val *int) { *val = 66 } func main() { var age int var ptr *int var pptr **int age = 99 /* 指针 ptr 地址 */ ptr = &age /* 指向指针 ptr 地址 */ pptr = &ptr /* 获取 pptr 的值 */ fmt.Printf("变量 age = %d\n", age ) fmt.Printf("指针变量 *ptr = %d,内存地址是:%d\n", *ptr, ptr ) fmt.Printf("指向指针的指针变量 **pptr = %d,内存地址是:%d\n", *pptr, pptr) //函数调用 changeValue(ptr) fmt.Printf("变量age更改后的值 = %d\n", age ) //通过new函数创建指针 size := new(int) fmt.Printf("size的默认值= %d, 类型是: %T, 地址是: %v\n", *size, size, size) *size = 99 fmt.Println("更改后新的值是:", *size) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第79张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第80张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第81张

特别注意这这两种传参的区别

func change(val int) { val = 88 } func changeValue(val *int) { *val = 66 } 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第82张

 Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第83张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第84张

 

5. 函数类型

函数是基本的代码块,用于执行一个任务。

Go 语言最少有个 main() 函数。

你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。

函数声明告诉了编译器函数的名称,参数和返回类型。格式如下

func function_name( [parameter list] ) [return_types] { //函数体 }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第85张

函数定义解析:

  • func:函数由关键字 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

5.1  无参无返回值函数

 package main import ( "fmt" ) func hello(){ fmt.Println("Hello World") } func main() { fmt.Println("函数开始了") hello() }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第86张

 5.2  有参无返回值函数

 package main import ( "fmt" ) func hello(message string){ fmt.Println(message) } func main() { fmt.Println("函数开始了") hello("hello world") } 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第87张

5.3  无参有返回值函数

 package main import ( "fmt" ) func hello() string { return "Hello world" } func main() { fmt.Println("函数开始了") greeting := hello() fmt.Println(greeting) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第88张

5.4  有参有返回值函数

 package main import ( "fmt" ) func hello(message string) string { return message } func main() { fmt.Println("函数开始了") greeting := hello("hello world") fmt.Println(greeting) } 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第89张

5.5  多个参数多返回值函数

package main import ( "fmt" ) func hello(name string, age int) (string, int) { return name, age } func main() { fmt.Println("函数开始了") name, age := hello("董广明", 99) fmt.Printf("name=%s, age = %d\n", name,age ) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第90张

5.6  在函数中预定义返回值的函数

 package main import ( "fmt" ) func hello() (message string) { message = "hello world!" return } func main() { fmt.Println("函数开始了") greeting := hello() fmt.Printf(greeting) } 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第91张

message被定义为返回变量。 因此,定义的变量message将自动返回,而无需在最后的return语句中定义。

5.7  舍弃返回值的函数

 package main import ( "fmt" ) func hello() (string, string) { return "hello world!", "hahaha" } func main() { fmt.Println("函数开始了") greeting, _ := hello() fmt.Printf(greeting) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第92张

 5.8  指针传递函数

 package main import ( "fmt" ) func change(name *string) { *name = "dongguangming" } func main() { fmt.Println("函数开始了") name := "董广明" fmt.Println(name) change(&name) fmt.Printf(name) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第93张

会改变原来的值,这很容易理解

Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第94张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第95张

5.9 可变参数函数

您可以使用Golang中的…运算符来传递数组,也可以使用相同的…运算符来接收参数。

 package main import ( "fmt" ) func print(items ...string) { for _, v := range items { fmt.Println(v) } } func main() { fmt.Println("函数开始了") print("董广明", "dongguangming", "dmg") list := []string{"Hello", "World"} print(list...) // An array argument }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第96张

实际输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第97张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第98张

 

5.10  匿名函数

没有名字的函数被称为匿名函数

package main import ( "fmt" ) func main() { fmt.Println("函数开始了") func () { fmt.Println("我是一个匿名函数") } () } 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第99张

 

6.  结构体Struct

golang的世界里没有像java一样有class类的概念,但它有像C语言一样的结构体Struct。

结构体是不同字段的类型化集合,结构体用于将数据分组在一起。

例如,如果我们要对User类型的数据进行分组,则定义一个user的属性,其中可以包括姓名,年龄,性别,所在城市。 可以使用以下语法定义结构

package main import "fmt" type User struct { name string age int gender string city string } func main() { fmt.Println("结构体开始了") user := User{name: "董广明", age: 31, gender: "man", city: "南京"} fmt.Println(user) //简写 u := User{"董广明", 31, "man", "南京"} fmt.Println(u) //通过指针访问 u1 := &User{"董广明", 31, "man", "南京"} fmt.Println(u1.name) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第100张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第101张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第102张

 

6.1   方法

 方法是带有接收器的一种特殊函数。 接收者可以是值或指针。 

package main import "fmt" type User struct { name string age int gender string city string } // 方法定义 func (u *User) describe() { fmt.Printf("%v 今年 %v岁了\n", u.name, u.age) } //指针参数 func (u *User) setAge(age int) { u.age = age } //值参数 func (u User) setName(name string) { u.name = name } func main() { fmt.Println("结构体开始了") user := User{name: "董广明", age: 31, gender: "man", city: "南京"} fmt.Println(user) user.describe() user.setAge(99) fmt.Println(user.age) user.setName("dongguangming") fmt.Println(user.name) user.describe() }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第103张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第104张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第105张

如图可以看到,现在可以使用点运算符user.describe调用该方法。 注意,接收者是一个指针。 使用指针,我们传递了对该值的引用,因此,如果对方法进行任何更改,它将反映在接收器user中。它也不会创建该对象的新副本,从而节省了内存。

请注意,在上面的示例中,age的值已更改,而name的值未更改,因为方法setName是接收者类型,而setAge是指针类型。

 

6.2   结构体域字段导出

如果字段名以大写字母开头,则定义该结构的包外部的代码将可对其进行读写。 如果该字段以小写字母开头,则只有该结构包中的代码才能读取和写入该字段。

package main import "fmt" type User struct { Name string Type string password string } func main() { u := User{ Name: "董广明", Type: "1", password: "secret", } fmt.Println(u.Name, " 级别:", u.Type) fmt.Println("密码是:", u.password) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第106张

在同一包中,我们可以访问这些字段,如本示例所示, 由于main也在主程序包中,因此它可以引用u.password并检索存储在其中的值。 通常在结构体中具有未导出的字段,并通过导出的方法来访问它们。类比java里的访问权限private域,public访问方法。

以上程序输出结果

董广明  级别: 1
密码是: secret
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第107张

6.3  创建匿名结构体

匿名仅创建新的结构变量,而不定义任何新的结构体类型。

package main import "fmt" func main() { emp := struct { firstName, lastName string age, salary int }{ firstName: "董", lastName: "广明", age: 31, salary: 5000, } fmt.Println("员工", emp) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第108张

以上程序代码中,定义了一个匿名结构变量emp, 正如我们已经提到的,该结构称为匿名结构,因为它仅创建一个新的结构变量emp,而不定义任何新的结构类型。

输出结果

员工 {董 广明 31 5000}
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第109张

6.4 嵌套结构体

结构可能包含一个字段,而该字段又是一个结构。 这些类型的结构称为嵌套结构。

比如我们经常网上购物时,填写订单接收地址(家里,公司,寄存点)

package main import ( "fmt" ) type Address struct { city, state string } type Person struct { name string age int address Address } func main() { var p Person p.name = "董广明" p.age = 31 p.address = Address { city: "南京某街道", state: "第一收货地址", } fmt.Println("名字:", p.name) fmt.Println("年龄:",p.age) fmt.Println("收货地址:",p.address.city) fmt.Println("是否第一:",p.address.state) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第110张

上面程序中的Person结构具有一个字段地址address,该字段地址又是一个结构体。

 

6.5  指向结构体的指针

您可以使用&运算符获取指向结构体的指针

package main import "fmt" type User struct { name string age int gender string city string } func main() { fmt.Println("结构体开始了") user := User{name: "董广明", age: 31, gender: "man", city: "南京"} fmt.Println(user) // Pointer to the user struct user_point := &user fmt.Println(user_point) // Accessing struct fields via pointer fmt.Println((*user_point).name) fmt.Println(user_point.name) // Same as above: No need to explicitly dereference the pointer user_point.age = 99 fmt.Println(user_point) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第111张

以上示例所示,Go使您可以直接通过指针访问结构体的字段。 

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第112张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第113张

Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第114张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第115张

 

6.6  结构体是值类型

结构体是值类型,当您将一个结构体变量分配给另一个时,将创建并分配该结构的新副本。 同样,当您将结构体传递给另一个函数时,该函数将获得其自己的结构副本。

package main import "fmt" type User struct { name string age int gender string city string } func main() { fmt.Println("结构体开始了") user := User{name: "董广明", age: 31, gender: "man", city: "南京"} user2 := user // A copy of the struct `user` is assigned to `user2` fmt.Println("user = ", user) fmt.Println("user2 = ", user2) // user2.name = "dongguangming" user2.age = 99 user2.city ="上海" fmt.Println("\n更改user2:") fmt.Println("user = ", user) fmt.Println("user2 = ", user2) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第116张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第117张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第118张

 

7.  接口Interface类型

Go编程提供了另一种称为接口的数据类型,它表示一组方法签名。

struct数据类型实现接口中定义的方法。

package main import "fmt" import "math" type Shape interface { area() float64 } type Rectangle struct{ height float64 width float64 } type Circle struct{ radius float64 } func (r Rectangle) area() float64 { return r.height * r.width } func (c Circle) area() float64 { return math.Pi * math.Pow(c.radius, 2) } func getArea(shape Shape) float64{ return shape.area() } func main() { fmt.Println("接口开始了") rect := Rectangle{20, 50} circ := Circle{4} fmt.Println("长方形面积 =", getArea(rect)) fmt.Println("圆的面积 =", getArea(circ)) } 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第119张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第120张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第121张

 

8.  Channels类型

通道是一种类型化的管道,可以使用通道运算符<-发送和接收值。

channel<- value    // 发送value值到通道channel value := <-channel // 从通道查询,并把值赋给value 
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第122张

 注意:数据按箭头方向流动

和其他数据类型类似,通道使用前必须先创建,其初始值是 nil。创建通道的语法格式如下:

var c1 chan [value type] c1 = make([channel type] [value type], [capacity])
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第123张
  • [value type] 定义的是 Channel 中所传输数据的类型。
  • [channel type] 定义的是 Channel 的类型,其类型有以下三种:
    • “chan” 可读可写——“chan int” 则表示可读写 int 数据的 channel
    • "chan<-" 仅可写——“chan<- float64” 则表示仅可写64位 float 数据的 channel
    • “<-chan” 仅可读——“<-chan int” 则表示仅可读 int 数据的 channel
  • [capacity] 是一个可选参数,其定义的是 channel 中的缓存区 (buffer)。如果不填则默认该 channel 没有缓冲区 (unbuffered)。对于没有缓冲区的 channel,消息的发送和收取必须能同时完成,否则会造成阻塞并提示死锁错误。

比如我们想创建了一个读写 int 类型,buffer 长度 100 的 channel c1,则如下:

var c1 chan int c1 = make(chan int, 100)
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第124张

通过此通道,我们可以发送int类型的数据。 我们可以在此通道中发送和接收数据

package main import "fmt" func main() { ch := make(chan int) go func() { ch <- 31 }() age := <-ch fmt.Println(age) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第125张

输出结果:Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第126张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第127张

接收方通道等待,直到发送方将数据发送到通道。

 

8.1  单向通道

在有些情况下,我们希望通过通道接收数据但不发送数据, 为此我们还可以创建一个单向通道。 让我们看一个简单的例子:

package main import ( "fmt" ) func main() { ch := make(chan string) go sc(ch) fmt.Println(<-ch) } func sc(ch chan<- string) { ch <- "你好,董广明" }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第128张

在上面的示例代码中,sc是go协程,该例程只能将消息发送到通道,但不能接收消息。

执行代码输出结果

$go run main.go
你好,董广明
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第129张

 

8.2  缓存通道(Buffered channel

在Golang中可以创建一个缓冲通道。 对于缓冲的通道,如果缓冲区已满,则将阻止发送到该通道的消息。 让我们看一个小例子

package main import "fmt" func main() { ch := make(chan string, 2) ch <- "hello" ch <- "world" ch <- "!!!" // 超了缓冲最大值要报错 fmt.Println(<-ch) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第130张

实际上超过缓冲最大值,要报错

Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第131张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第132张

那怎么办呢,还好有以下解决方法

package main import "fmt" func main() { ch := make(chan string, 2) ch <- "hello" ch <- "world" //创建匿名函数 function := func(name string) { ch <- name } // go function("董广明") go function("donguangming") go function("dgm") go function("dgmdgm") go function("3dgm") fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch) }
Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第133张

输出结果Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第134张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第135张

 

 

测验结果

Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第136张Golang学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel通道 go 第137张

 

总结: golang就是综合性编程语言,幸好以前做过java、pyhon、JavaScript开发,上学时又学过C语言(第一编程语言),故学起golang很快,极少部分是golang本身特有的。

一句话概括:golang是几种语言的混合体,外加自己的特性。如果不考虑语言本身,你会觉得像是在写C,有时又像是写python,写函数时又像JavaScript中函数的变体。

 

参考:

  1.  Golang Tutorial — from zero to hero  https://milapneupane.com.np/2019/07/06/learning-golang-from-zero-to-hero/

  2. Understanding Maps in Go   https://www.digitalocean.com/community/tutorials/understanding-maps-in-go

  3. Golang Maps  https://www.geeksforgeeks.org/golang-maps/

  4. Golang Tutorial – Learn Golang by Examples https://www.edureka.co/blog/golang-tutorial/#map

  5. The anatomy of Slices in Go  https://medium.com/rungo/the-anatomy-of-slices-in-go-6450e3bb2b94

  6. GoLang Tutorial - Structs and receiver methods - 2020 https://www.bogotobogo.com/GoLang/GoLang_Structs.php

  7. Golang Cheatsheet: Functions https://ado.xyz/blog/golang-cheatsheet-functions/

  8. Ultimate Guide to Go Variadic Functions  https://medium.com/m/global-identity?redirectUrl=https%3A%2F%2Fblog.learngoprogramming.com%2Fgolang-variadic-funcs-how-to-patterns-369408f19085

  9. Golang Methods Tutorial with Examples https://www.callicoder.com/golang-methods-tutorial/

  10. Go Best Practices: Should you use a method or a function? https://flaviocopes.com/golang-methods-or-functions/

  11. Methods that satisfy interfaces in golang https://suraj.io/post/golang-methods-interfaces/

  12.  Pass by pointer vs pass by value in Go https://goinbigdata.com/golang-pass-by-pointer-vs-pass-by-value/

  13. Go Data Structures: Interfaces https://research.swtch.com/interfaces

  14. How to Define and Implement a Go Interface https://code.tutsplus.com/tutorials/how-to-define-and-implement-a-go-interface--cms-28962

  15. Methods and Interfaces in Go https://dev-pages.info/golang-interfaces/

  16. 理解 Golang 的 Channel 类型 https://studygolang.com/articles/25805

  17. Anatomy of Channels in Go - Concurrency in Go  https://medium.com/rungo/anatomy-of-channels-in-go-concurrency-in-go-1ec336086adb

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄