Golang 学习系列第三天:学习数组、切片、Map、结构体、指针、函数、接口类型、channel 通道
继Golang学习系列第二天之后,今天开始学习数据类型之高级类型: 派生类型。
学过java的人都知道,java其实就8种基本类型:byte、short、int、long、float、double、char、boolean,但它有引用数据类型:字符串、数组、集合、类、接口等。
而golang也有这样的划分,基本类型(Golang学习系列第二天已学过)和派生类型(不叫引用类型),派生类型有以下几种:数组类型、切片类型、Map类型、结构体类型(struct)、指针类型(Pointer)、函数类型、接口类型(interface)、Channel 类型。
1. 数组类型
数组是具有相同数据类型的元素序列。 数组在声明中定义了固定的长度,因此不能扩展超过该长度。 数组声明为
var variable_name [SIZE] variable_type
让我们以代码举例如下
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)
}
将变量city声明为5个字符串的数组,执行输出
不过也可以在声明数组时设置数组条目,看简洁版
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)
}
甚至,当您传递值时,可以使用省略号来使用隐式长度
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)
}
以不同方式打印数组
注意我们使用带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)
}
多维数组
您还可以创建多维数组,示例代码:
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)
}
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)
}
2.1 切分切片
可以对切片进行切片,以创建一个指向相同数组的新切片值。表达式为
slice[start:end]
表示计算从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:])
}
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)
}
它通过分配清零的数组并返回引用该数组的切片来工作。
2.3 附加到切片
可以通过append方式附加值到切片中
package main
import "fmt"
func main() {
//附加到切片
other_cities := []string{}
other_cities = append(other_cities, "濮阳")
fmt.Println(other_cities)
}
也可以同时附加多个值到切片中,示例代码同时包括两个城市郑洲和濮阳
package main
import "fmt"
func main() {
//附加多值到切片
other_cities := []string{}
other_cities = append(other_cities, "郑洲","濮阳")
fmt.Println(other_cities)
}
甚至您还可以使用省略号将切片附加到另一个切片
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)
}
注意: 省略号是该语言的内置功能,这意味着该元素是一个集合。 我们无法将字符串类型([] 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)
}
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))
}
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!")
}
}
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)
}
当不使用上述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"])
}
3.1 操作map
3.1.1 在map中插入或更新元素
m[key] = elem
3.1.2 查询一个元素
elem = m[key]
3.1.3 删除一个元素
delete(m, key)
3.1.4 测试键是否存在并且有值
elem, ok = m[key]
如果键在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)
}
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])
}
}
4. 指针类型(Pointer)
学过C(上学时第一门编程语言就是C)的人都知道, 指针是存放值内存地址的地方, 指针由*定义,根据数据类型定义指针,go也是这么玩的,声明格式如下
var var_name *var-type
var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。
&运算符可用于获取变量的地址。比如
var ap *int
a := 12
ap = &a
而指针指向的值可以使用*运算符进行访问,示例如下
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 )
}
4.1 空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil,nil 指针也称为空指针。
nil在概念上和其它编程语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。空指针判断方式:
if(ptr != nil) /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */
综上示例代码如下
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 )
}
}
4.2 指针数组
指针数组:简单点说它是一个数组,数组里面的每个元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。格式如下
var ptr [MAX]* type;
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] )
}
}
4.3 指针的指针
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址。
指向指针的指针变量声明格式如下:
var ptr **type;
表示指向指针的指针变量为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)
}
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)
}
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)
}
特别注意这这两种传参的区别
func change(val int) {
val = 88
}
func changeValue(val *int) {
*val = 66
}
5. 函数类型
函数是基本的代码块,用于执行一个任务。
Go 语言最少有个 main() 函数。
你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。
函数声明告诉了编译器函数的名称,参数和返回类型。格式如下
func function_name( [parameter list] ) [return_types] {
//函数体
}
函数定义解析:
- 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()
}
5.2 有参无返回值函数
package main
import (
"fmt"
)
func hello(message string){
fmt.Println(message)
}
func main() {
fmt.Println("函数开始了")
hello("hello world")
}
5.3 无参有返回值函数
package main
import (
"fmt"
)
func hello() string {
return "Hello world"
}
func main() {
fmt.Println("函数开始了")
greeting := hello()
fmt.Println(greeting)
}
5.4 有参有返回值函数
package main
import (
"fmt"
)
func hello(message string) string {
return message
}
func main() {
fmt.Println("函数开始了")
greeting := hello("hello world")
fmt.Println(greeting)
}
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 )
}
5.6 在函数中预定义返回值的函数
package main
import (
"fmt"
)
func hello() (message string) {
message = "hello world!"
return
}
func main() {
fmt.Println("函数开始了")
greeting := hello()
fmt.Printf(greeting)
}
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)
}
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)
}
会改变原来的值,这很容易理解
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
}
5.10 匿名函数
没有名字的函数被称为匿名函数
package main
import (
"fmt"
)
func main() {
fmt.Println("函数开始了")
func () {
fmt.Println("我是一个匿名函数")
} ()
}
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)
}
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()
}
如图可以看到,现在可以使用点运算符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)
}
在同一包中,我们可以访问这些字段,如本示例所示, 由于main也在主程序包中,因此它可以引用u.password并检索存储在其中的值。 通常在结构体中具有未导出的字段,并通过导出的方法来访问它们。类比java里的访问权限private域,public访问方法。
以上程序输出结果
董广明 级别: 1
密码是: secret
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)
}
以上程序代码中,定义了一个匿名结构变量emp, 正如我们已经提到的,该结构称为匿名结构,因为它仅创建一个新的结构变量emp,而不定义任何新的结构类型。
输出结果
员工 {董 广明 31 5000}
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)
}
上面程序中的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)
}
以上示例所示,Go使您可以直接通过指针访问结构体的字段。
" class="reference-link">
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)
}
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))
}
8. Channels类型
通道是一种类型化的管道,可以使用通道运算符<-发送和接收值。
channel<- value // 发送value值到通道channel
value := <-channel // 从通道查询,并把值赋给value
注意:数据按箭头方向流动
和其他数据类型类似,通道使用前必须先创建,其初始值是 nil。创建通道的语法格式如下:
var c1 chan [value type]
c1 = make([channel type] [value type], [capacity])
- [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)
通过此通道,我们可以发送int类型的数据。 我们可以在此通道中发送和接收数据
package main
import "fmt"
func main() {
ch := make(chan int)
go func() { ch <- 31 }()
age := <-ch
fmt.Println(age)
}
接收方通道等待,直到发送方将数据发送到通道。
8.1 单向通道
在有些情况下,我们希望通过通道接收数据但不发送数据, 为此我们还可以创建一个单向通道。 让我们看一个简单的例子:
package main
import (
"fmt"
)
func main() {
ch := make(chan string)
go sc(ch)
fmt.Println(<-ch)
}
func sc(ch chan<- string) {
ch <- "你好,董广明"
}
在上面的示例代码中,sc是go协程,该例程只能将消息发送到通道,但不能接收消息。
执行代码输出结果
$go run main.go
你好,董广明
8.2 缓存通道(Buffered channel)
在Golang中可以创建一个缓冲通道。 对于缓冲的通道,如果缓冲区已满,则将阻止发送到该通道的消息。 让我们看一个小例子
package main
import "fmt"
func main() {
ch := make(chan string, 2)
ch <- "hello"
ch <- "world"
ch <- "!!!" // 超了缓冲最大值要报错
fmt.Println(<-ch)
}
实际上超过缓冲最大值,要报错
那怎么办呢,还好有以下解决方法
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就是综合性编程语言,幸好以前做过java、pyhon、JavaScript开发,上学时又学过C语言(第一编程语言),故学起golang很快,极少部分是golang本身特有的。
一句话概括:golang是几种语言的混合体,外加自己的特性。如果不考虑语言本身,你会觉得像是在写C,有时又像是写python,写函数时又像JavaScript中函数的变体。
参考:
Golang Tutorial — from zero to hero milapneupane.com.np/2019/07/06/lea...
Understanding Maps in Go www.digitalocean.com/community/tut...
Golang Maps www.geeksforgeeks.org/golang-maps/
Golang Tutorial – Learn Golang by Examples www.edureka.co/blog/golang-tutoria...
The anatomy of Slices in Go medium.com/rungo/the-anatomy-of-sl...
GoLang Tutorial - Structs and receiver methods - 2020 www.bogotobogo.com/GoLang/GoLang_S...
Golang Cheatsheet: Functions ado.xyz/blog/golang-cheatsheet-fun...
Ultimate Guide to Go Variadic Functions medium.com/m/global-identity?redir...
Golang Methods Tutorial with Examples www.callicoder.com/golang-methods-...
Go Best Practices: Should you use a method or a function? flaviocopes.com/golang-methods-or-...
Methods that satisfy interfaces in golang suraj.io/post/golang-methods-inter...
Pass by pointer vs pass by value in Go goinbigdata.com/golang-pass-by-poi...
Go Data Structures: Interfaces research.swtch.com/interfaces
How to Define and Implement a Go Interface code.tutsplus.com/tutorials/how-to...
Methods and Interfaces in Go dev-pages.info/golang-interfaces/
Go (Golang) - understanding the object oriented features with structs, methods, and interfaces unixsheikh.com/articles/go-underst...
理解 Golang 的 Channel 类型 studygolang.com/articles/25805
Anatomy of Channels in Go - Concurrency in Go medium.com/rungo/anatomy-of-channe...
本作品采用《CC 协议》,转载必须注明作者和本文链接