GO学习笔记

Updated on with 0 views and 0 comments

一、简介

Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易,具有以下特性

  • 支持并发
  • 内存管理、编译迅速
  • 函数多返回值
  • 异常处理

二、安装

2.1 下载安装

下载地址All releases - The Go Programming Language (google.cn)

选择稳定版本

image.png

2.2 环境变量配置

配置两个环境变量,两个变量不要指向同一个目录

image.png

  • GOROOT为go的安装目录
  • GOPATH为go的工作空间

然后再path环境变量中新增两行

  • %GOPATH%\bin
  • %GOROOT%\bin

在控制台输出go version,提示版本信息就安装成功了

image.png

2.3 hello world

我使用了goland作为编译器,下载后开箱即用,创建一个go工程,命名为hello,在根目录下新建一个main包,然后建立helloworld.go,内容如下

package main

import "fmt"

func main() {
	fmt.Println("hello world")
}

运行结果为

image.png

三、基础知识

3.1 语法

3.1.1 语句分隔符

在c,java等语言中,我们用分号标识一个可执行语句,但是在go中,我们使用换行符分割语句,如下

package main

import "fmt"

func main() {
	fmt.Println("不需要分号")
	fmt.Println("使用换行符分割可执行语句")
}

3.1.2 注释

  • 行注释 //
  • 段落注释 /**/

示例代码:

package main

import "fmt"

func main() {
	// 行注释
	fmt.Println("不需要分号")
	/*
		段落注释
	*/
	fmt.Println("使用换行符分割可执行语句")
}

3.1.3 标识符

go的标识符只能以字母或下划线开头

3.2 数据类型

  • 布尔
  • 数字(int、float16、float32)
  • 字符串 go的字符串由单个字节链接起来的,并且使用UTF-8编码标识unicode文本
  • 派生类型
    • 指针类型
    • 数组类型
    • 结构化类型
    • Channel类型
    • 函数类型
    • 切片类型
    • 接口类型
    • Map类型

3.2.1 数字类型

  • uint8 无符号 8 位整型 (0 到 255)
  • uint16 无符号 16 位整型 (0 到 65535)
  • uint32 无符号 32 位整型 (0 到 4294967295)
  • uint64 无符号 64 位整型 (0 到 18446744073709551615)
  • int8 有符号 8 位整型 (-128 到 127)
  • int16 有符号 16 位整型 (-32768 到 32767)
  • int32 有符号 32 位整型 (-2147483648 到 2147483647)
  • int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

3.2.2 浮点型

  • float32 IEEE-754 32位浮点型数
  • float64 IEEE-754 64位浮点型数
  • complex64 32 位实数和虚数
  • complex64 位实数和虚数

3.3 变量

3.3.1 变量声明

使用var声明一个变量,变量名称只能是字母或者下划线开头

  • 使用var声明变量
  • 使用:=声明变量

示例代码如下

package main

import "fmt"

func main() {
	// 声明变量并给定值
	var name = "name"
	// 声明变量不设置值
	var name1 string
	// 一次性声明多个变量
	var age, sex = 18, "男"
	// 使用:=声明变量
	name2 := "name2"
	fmt.Println(name)
	fmt.Println(name1)
	fmt.Println(name2)
	fmt.Println(age)
	fmt.Println(sex)
}

运行结果为

image.png

3.3.2 值类型和引用类型

  • 基本类型的变量直接存储了值
  • 应用类型的变量存储的是值在内存中的地址,使用&操作符可以获取变量在内存中的地址

3.3.3 全局变量和局部变量

  • 函数体内声明的变量为局部变量
  • 函数体外声明的变量为全局变量

全局变量和局部变量可以同名,但是函数内的变量会被优先使用

3.3.4 变量作用域

  • 全局变量 函数外定义的变量
  • 局部变量 函数内部定义的参数
  • 形式参数 函数定义的参数

3.4 常量

常量使用const标识

const 常量名 类型 = 值

类型可以省略,编译器会通过值来判断变量类型

3.4.1 iota

iota是一个计数器,只能在常量表达式中使用,把第一个常量设置为iota后,后面的常量就会递增

3.4.1.1 递增
const (
	a = iota
	b = iota
	c = iota
)

上述代码等价于

const (
	a = iota
	b
	c 
)

a=0,b=1,c=2

3.4.1.2 下划线

使用下划线可以跳过这次递增的赋值

const (
	a = iota
	_
	c
	d
)

a=0 c=2 d=3

3.4.1.3 同一行的iota值相同
const (
	a, b = iota, iota
	_, _
	c, d
	e, f
)

a=b=0 c=d=2 e=f=3

3.5 条件判断

3.5.1 if

package main

import "fmt"

func main() {
	if true {
		fmt.Println("if")
	}
}

3.5.2 if-else

package main

import "fmt"

func main() {
	var a int = 2
	if a == 1 {
		fmt.Println(1)
	} else if a == 2 {
		fmt.Println(2)
	} else {
		fmt.Println(3)
	}
}

3.5.3 switch

go的switch不需要break来结束判断,遇到符合条件的语句块执行了就直接结束了,下述代码输出结果为1

package main

import "fmt"

func main() {
	var a int = 1
	switch a {
	case 1:
		fmt.Println(1)
	case 2:
		fmt.Println(2)
	default:
		fmt.Println("default")
	}
}

fallthrough会强制执行后面的case内的代码块,不会判断条件是否为true,下述代码执行结果为1 2

package main

import "fmt"

func main() {
	var a int = 1
	switch a {
	case 1:
		fmt.Println(1)
		fallthrough
	case 2:
		fmt.Println(2)
	default:
		fmt.Println("default")
	}
}

3.6 循环

3.6.1 for循环

for 初始化值;条件;复制表达式

package main

import (
	"fmt"
)

func main() {
	for i := 0; i < 5; i++ {
		fmt.Println(i)
	}
}

  • break跳出循环
  • continue直接进入下一轮循环
  • goto跳转到标记的代码并执行

goto示例:

package main

import "fmt"

func main() {

	var i = 0
LOOP:
	for i < 5 {
		fmt.Println(i)
		if i == 0 {
			i += 1
			goto LOOP
		}
		i++
	}
}

在i=0时,跳转到for循环执行输出结果0 1 2 3 4

3.7 函数

函数定义: func 函数名([参数1 类型,参数2 类型......]) [返回值类型]

函数是可以有多个返回值的,示例如下

package main

import "fmt"

func main() {

	var x, y string = swap("1", "2")
	fmt.Println(x + y)

}

func swap(x, y string) (string, string) {
	return y, x
}

3.7.1 函数参数

  • 值传递 复制一个值给函数,函数中改变这个值不会影响原来的值
  • 引用传递 将实际参数的地址传递给函数,函数中修改后会影响实际参数

值传递示例如下,输出结果为100 200 100 200

package main

import "fmt"

func main() {

	var a int = 100
	var b int = 200
	fmt.Printf("交换前a的值为%d\n", a)
	fmt.Printf("交换前b的值为%d\n", b)
	swap(a, b)
	fmt.Printf("交换后a的值为%d\n", a)
	fmt.Printf("交换后b的值为%d\n", b)

}

func swap(x, y int) {
	var temp int
	temp = x
	x = y
	y = temp
}

引用传递示例如下,输出结果为100 200 200 100

package main

import "fmt"

func main() {

	var a int = 100
	var b int = 200
	fmt.Printf("交换前a的值为%d\n", a)
	fmt.Printf("交换前b的值为%d\n", b)
	swap(&a, &b)
	fmt.Printf("交换后a的值为%d\n", a)
	fmt.Printf("交换后b的值为%d\n", b)

}

func swap(x *int, y *int) {
	var temp int
	temp = *x
	*x = *y
	*y = temp
}

3.7.2 函数用法

3.7.2.1 函数作为实参

定义一个变量,并声明为一个函数,示例如下

package main

import "fmt"

func main() {
	var sumFunc = func(a, b int) int {
		return a + b
	}
	fmt.Println(sumFunc(1, 2))
}

3.7.2.2 匿名函数

匿名函数在go语言也成为闭包

示例代码如下,输出结果5 5

package main

import "fmt"

func main() {
	// 定义并调用一个匿名函数
	var add = func(a, b int) int {
		return a + b
	}
	fmt.Println(add(2, 3))

	//将参数声明为一个匿名函数
	var myFunc = func(operation func(a, b int) int, x, y int) int {
		return operation(x, y)
	}
	fmt.Println(myFunc(add, 2, 3))
}

3.8 数组

var 数组名 [数组大小]数据类型

3.8.1 数组初始化及遍历

package main

import "fmt"

func main() {
	// 初始化一个素组
	var num [5]int
	fmt.Println(num)
	// 用列表初始化数组
	var num1 = [5]int{1, 2, 3, 4, 5}
	fmt.Println(num1)
	// 数组长度不确定
	var num2 = [...]int{1, 2, 3, 4, 5}
	fmt.Println(num2)

	// 数组遍历
	var index int
	for index = 0; index < 5; index++ {
		fmt.Printf("数组下标%d,值为%d\n", index, num2[index])
	}
}

3.8.2 多维数组

var 数组名称 [size][size]...[size]数组类型

3.8.2.1 多维数组创建及初始化

示例如下

package main

import (
	"fmt"
)

func main() {
	// 创建一个二维数组并初始化值
	var arr = [2][2]int{
		{1, 2},
		{3, 4},
	}
	fmt.Println(arr)
	// 创建空的二位数组并添加值
	var arr1 [][]int
	arrRow := []int{1, 2}
	arrRow1 := []int{1, 2, 3, 4}
	arr1 = append(arr1, arrRow)
	arr1 = append(arr1, arrRow1)
	fmt.Println(arr1)
}

3.9 指针

3.9.1 声明

指针指向变量得内存地址,通过var 变量名称 变量类型声明一个变量,通过&操作符可以访问一个变量的地址,通过 *变量名称可以访问指针的值

示例代码如下

package main

import "fmt"

func main() {
	var a int = 20   // 声明变量
	var pointer *int // 声明指针
	pointer = &a     // 让指针指向变量得实际地址
	fmt.Printf("变量a得实际地址为%d\n", &a)
	fmt.Printf("指针指向的变量的地址%d\n", pointer)
	fmt.Printf("指针自己的地址%d\n", &pointer)
	fmt.Printf("指针变量指向的地址存储的值%d\n", *pointer)
}

运行结果如下,内存由程序分配,每次运行结果可能会不同,但第一项和第二项值相同

image.png

3.9.2 空指针

在go语言中,空指针用nil标识

package main

import "fmt"

func main() {
	//declare()
	var pointer *int
	//var pointer *int = nil 等同于上述语句
	if pointer == nil {
		fmt.Println("指针pointer为空")
	}
}

func declare() {
	var a int = 20   // 声明变量
	var pointer *int // 声明指针
	pointer = &a     // 让指针指向变量得实际地址
	fmt.Printf("变量a得实际地址为%d\n", &a)
	fmt.Printf("指针指向的变量的地址%d\n", pointer)
	fmt.Printf("指针自己的地址%d\n", &pointer)
	fmt.Printf("指针变量指向的地址存储的值%d\n", *pointer)
}

3.9.3 指针数组

指针数组的每一个元素都指向了一个值

示例代码如下

package main

import "fmt"

func main() {
	var intArray = [3]int{1, 2, 3}
	var index int
	var arrayPointer [3]*int
	for index = 0; index < 3; index++ {
		// 整数地址复制给指针数组
		arrayPointer[index] = &intArray[index]
	}
	for index = 0; index < 3; index++ {
		fmt.Printf("arrayPoint[%d]=%d\n", index, *arrayPointer[index])
	}
}

运行结果为

arrayPoint[0]=1
arrayPoint[1]=2
arrayPoint[2]=3

3.9.4 指针的指针

指针是可以指向指针的,通过**符号声明

示例如下

package main

import "fmt"

func main() {
	var point *int
	var pointToPoint **int
	var num int = 1
	point = &num
	pointToPoint = &point

	fmt.Printf("变量a=%d\n", num)
	fmt.Printf("指针point值为%d\n", *point)
	fmt.Printf("指针pointToPoint的值为%d\n", **pointToPoint)
}

输出结果为

image.png

3.9.5 将指针作为函数参数

指针可以作为函数参数,在函数中修改了指针的值,函数外的指针值会跟着一起变动

示例代码如下

package main

import "fmt"

func main() {
	var a int = 1
	var b int = 2
	fmt.Printf("交换前a=%d,b=%d\n", a, b)
	swap(&a, &b)
	fmt.Printf("交换后a=%d,b=%d", a, b)
}

func swap(x *int, y *int) {
	*x, *y = *y, *x
	/*
		上述代码等同于
		var temp int
		temp = *x
		*x = *y
		*y = temp
	*/

}

运行结果为

image.png

3.10 结构体

结构体是由一系列数据构成的数据集合

3.10.1 声明

结构体通过type struct定义

type 结构体名称 struct{
  属性名 类型
  ......
}

声明方式如下

变量名称:=结构体名称{value1,...}
变量名称:=结构体名称{属性名:value1....}

示例代码如下

package main

import "fmt"

type Person struct {
	name string
	age  int
}

func main() {
	var person Person = Person{"问", 10}
	var person1 Person = Person{name: "龙", age: 10}
	var person2 Person = Person{name: "坤"}
	fmt.Println(person)
	fmt.Println(person1)
	fmt.Println(person2)
}

输出结果为

image.png

3.10.2 访问结构体属性

结构体属性通过结构体变量.属性名可以直接访问

代码示例如下

package main

import "fmt"

type Person struct {
	name string
	age  int
}

func main() {
	var person Person
	person.name = "龙"
	person.age = 1
	fmt.Printf("person.name=%s\n", person.name)
	fmt.Printf("person.age=%d", person.age)
}

输出结果为

image.png

3.10.3 结构体指针

结构体可以作为函数参数,结构体指针存储的也是结构体的地址

代码示例如下

package main

import "fmt"

type Person struct {
	name string
	age  int
}

func main() {
	var person Person = Person{"问", 10}
	var person1 *Person = &person
	printAttr(person, person1)
}

func printAttr(person Person, person1 *Person) {
	fmt.Printf("person.name=%s\n", person.name)
	fmt.Printf("person.age=%d\n", person.age)

	fmt.Printf("person1.name=%s\n", person1.name)
	fmt.Printf("person1.age=%d", person1.age)
}

输出结果为

image.png

3.11 切片

go语言中我们可以定义数组,但是数组长度是不能修改的,而切片可以,切片是数组的抽象,切片的长度是不固定的

3.11.1 创建

可以通过数组创建,也可以通过make关键字创建

示例如下

package main

import "fmt"

func main() {
	// 使用未指定大小的数组来定义切片
	var slice []int
	// 通过make创建数组切片,make([]类型,数组长度(切片初始大小))
	var slice1 []int = make([]int, 5)

	fmt.Println(slice)
	fmt.Println(slice1)
}

3.11.2 初始化

示例代码如下

package main

import "fmt"

func main() {
	// 创建一个数组
	var arr = []int{1, 2, 3}
	// 初始化切片为数组的引用,等同于var slice = arr[:endIndex]
	var slice = arr[:]
	// 截取数组从startIndex到~ndIndex-1下的元素作为一个新的切片
	var slice1 = arr[0:2]
	// 截取数组从startIndex到最后一个元素作为一个新的切片
	var slice2 = arr[2:]
	// 也可以从一个切片创建另一个切片
	var slice3 = slice[0:2]
	fmt.Println(slice)
	fmt.Println(slice1)
	fmt.Println(slice2)
	fmt.Println(slice3)
}

运行结果为

image.png

3.11.3 len()和cap()

  • len可以计算切片长度
  • cap可以计算切片容量(最大长度)

示例代码如下,输出结构为3 5

func main() {
	// make第一个参数为类型,第二个参数为切片长度,第三个参数为切片容量
	var arr = make([]int, 3, 5)
	fmt.Println(len(arr))
	fmt.Println(cap(arr))
}

3.11.4 append()和copy()

  • copy复制切片
  • append向切片追加数据

如下,实现一个切片扩容方法

package main

import "fmt"

func main() {
	var numbers []int
	// 向切片添加一个数字
	numbers = append(numbers, 0)
	fmt.Printf("len=%d cap=%d numbers=%v\n", len(numbers), cap(numbers), numbers)
	// 创建一个切片,在老切片的基础上扩容
	var numbers1 = make([]int, len(numbers), cap(numbers)*2)
	// 将numbers切片复制到numbers1切片
	copy(numbers1, numbers)
	fmt.Printf("len=%d cap=%d numbers=%v\n", len(numbers1), cap(numbers1), numbers1)
}

运行结果如下

image.png

3.12 map

map是一个无序的键值对集合,map是一个引用类型,将map赋值给其他变量,修改了map的值会影响所有引用他的变量

3.12.1 创建

创建语法为

var 变量名称=nake(map[键类型值类型]值类型,初始容量)

map中的键值对容量达到初始容量时会自动扩容,初始容量可以不指定

3.12.2 基础操作

示例代码如下

package main

import "fmt"

func main() {
	// 初始化一个map
	var myMap = make(map[string]string)
	// 新增一个key
	myMap["龙"] = "帅"
	// 获取元素
	var myValue = myMap["龙"]
	fmt.Printf("key=龙value=%s\n", myValue)
	var myValue1, ok = myMap["龙1"]
	if ok {
		fmt.Printf("key=龙1value=%s", myValue1)
	} else {
		fmt.Printf("数据不存在\n")
	}
	// 删除元素
	delete(myMap, "龙")
	var myValue2, ok1 = myMap["龙"]
	if ok1 {
		fmt.Printf("key=龙value=%s\n", myValue2)
	} else {
		fmt.Printf("数据已删除不存在")
	}

}

3.13 range

range用于迭代数组、map、切片或channel中的元素,在数组和切片中,返回元素的索引和对应的值,在集合中返回key-value

3.13.1 迭代数组

代码示例

package main

import "fmt"

func main() {
	var arr = []int{0, 1, 2}
	for index, value := range arr {
		fmt.Printf("下标=%d,值=%d\n", index, value)
	}
	/*
		上述代码等同于
		var index, value int
		for index, value = range arr {
			fmt.Printf("下标=%d,值=%d", index, value)
		}
	*/
}

3.13.2 迭代map

package main

import "fmt"

func main() {
	var myMap = make(map[string]string)
	myMap["问"] = "问"
	myMap["尤"] = "尤"
	myMap["龙"] = "龙"

	// 遍历key value
	for key, value := range myMap {
		fmt.Printf("key=%s,value=%s\n", key, value)
	}
	// 只遍历key
	for key := range myMap {
		fmt.Printf("key=%s\n", key)
	}
	// 只遍历value
	for _, value := range myMap {
		fmt.Printf("value=%s\n", value)
	}
}

3.14 接口

接口定义规则如下:

// 定义接口
type 接口名称 interface{方法名称() 返回值类型}
// 定义结构体
type 结构体名称 struct{}
// 实现接口的方法
func (结构体变量名 结构体名称) 方法名 返回值类型

示例代码如下

package main

import "fmt"

// Phone 接口
type Phone interface {
	call()
}

// Xiaomi 结构体
type Xiaomi struct {
}

func (xiaomi Xiaomi) call() {
	fmt.Println("我是小米")
}

// Huawei 结构图
type Huawei struct {
}

func (huawei Huawei) call() {
	fmt.Println("我是华为")
}

func main() {
	var xiaomi = new(Xiaomi)
	xiaomi.call()

	var huawei = new(Huawei)
	huawei.call()
}

再来一个带返回值的示例

package main

import "fmt"

type Shape interface {
	area() float64
}

type Circle struct {
	radius float64
}

func (circle Circle) area() float64 {
	return 3.14 * circle.radius * circle.radius
}

type Rectangle struct {
	width  float64
	height float64
}

func (rectangle Rectangle) area() float64 {
	return rectangle.width * rectangle.height
}

func main() {
	var shape Shape
	shape = Circle{1.0}
	var circleArea = shape.area()

	shape = Rectangle{1.0, 2.0}
	var rectangleArea = shape.area()

	fmt.Printf("圆形面积%f\n", circleArea)
	fmt.Printf("矩形面积%f", rectangleArea)

}

3.15 类型转换

类型转换表达式为

类型名称(值或表达式)

3.15.1 值类型转换

代码示例如下,只能从低精度转为高精度

package main

import "fmt"

func main() {
	var a int = 10
	var b int = 5
	var mean float32
	mean = float32(a) / float32(b)
	fmt.Println(mean)
}

3.15.2 字符串类型转换

代码示例

package main

import (
	"fmt"
	"strconv"
)

func main() {
	stringToInt()
	intToString()
	stringToFloat()
	floatToString()
}

func floatToString() {
	var num float64 = 3.14
	var str = strconv.FormatFloat(num, 'f', 2, 64)
	fmt.Println(str)
}
func stringToFloat() {
	var str string = "3.14"
	var num, err = strconv.ParseFloat(str, 64)
	if err != nil {
		fmt.Println("类型转换报错", err)
	} else {
		fmt.Println(num)
	}
}
func intToString() {
	var a int = 10
	var str string = strconv.Itoa(a)
	fmt.Println(str)
}
func stringToInt() {
	// 字符串转数组
	var str = "10 ..."
	var intValue, err = strconv.Atoi(str)
	if err != nil {
		fmt.Println("转换错误", err)
	} else {
		fmt.Printf("转换的值为%d", intValue)
	}
}

输出结果为

image.png

3.15.3 接口类型转换

3.15.3.1 类型断言

类型断言用于将接口类型转为指定类型,通过下述形式操作

value.(type)或value.(T)

value是接口类型的变量,type(T)是要转换的类型

下述代码中,建立一个变量originType类型为接口,值为hello world,然后通过接口类型转换,将origintType转换成一个字符串并返回

package main

import "fmt"

func main() {
	var originType interface{} = "hello world"
	var str, ok = originType.(string)
	if ok {
		fmt.Println(str)
	} else {
		fmt.Printf("转换失败")
	}
}

3.15.3.2 类型转换

类型转换将一个接口类型的值转换成另一个接口类型,语法为T(value)

示例代码如下

package main

import "fmt"

type Write interface {
	write(data string) (int, error)
}
type StringWriter struct {
	str string
}

// 这里需要修改传递的参数内部的值,所以传递指针类型
func (sw *StringWriter) write(data string) (int, error) {
	sw.str = data
	return len(data), nil
}
/*
type FloatWriter struct{}
*/


func main() {
	var w Write = &StringWriter{}
	var strLen, err = w.write("你好")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("输入文本长度为%d", strLen)
	}
	var sw *StringWriter = w.(*StringWriter)
	/*
	转换的值和目标目标接口必须是兼容的,否则会报错
	var sw *StringWriter = w.(*FloatWriter)
	 */
	fmt.Println(sw.str)
}

3.16 错误处理

3.16.1 定义

go有一个内置的error接口

type error interface {
	Error() string
}

在函数中可以在返回值中返回错误信息,示例如下

package main

import (
	"errors"
	"fmt"
)

func main() {
	var a, err = getData(1)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(a)
	}
}

func getData(a int) (int, error) {
	if a == 0 {
		return 0, nil
	}
	return a, errors.New("数字故为0,请输入0")
}

3.16.2 自定义错误信息

package main

import "fmt"

type CustomError struct {
	errorInfo string
}

func (ce *CustomError) Error() string {
	return "异常内容为---" + ce.errorInfo
}

func myFunc(data int) (ret int, errorMsg error) {
	if data == 0 {
		return 0, nil
	}
	return 0, &CustomError{"这是一个自定义异常"}
}
func main() {
	_, error := myFunc(1)
	if error != nil {
		var err = error.(*CustomError)
		fmt.Println(err.errorInfo)
		fmt.Println(err.Error())
	}

}

运行结果如下

image.png

3.17 并发

go语言通过goroutine开启并发支持,goroutine是轻量级线程,调度由golang的运行时进行管理

语法 go 函数名(参数)

代码示例如下,go语句开启了一个新的线程,同一个程序中的所有goroutine共享同一个地址空间

package main

import (
	"fmt"
	"time"
)

func sayHello(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(10 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	/*
		sayHello("hello")  语句前不加go会按顺序执行,先输出五个hello,在输出五个world
	*/
	go sayHello("hello")
	sayHello("world")
}

输出结果为五个hello和五个world交替输出

image.png

3.17.1 通道

通道是用来传递数据的一个数据结构,可用于两个goroutine之间通过传递一个指定类型的值来同步运行和通讯操作符<-用于指定通道的方向,发送或接受。如果未指定,则默认是一个双向通道,通道默认是没有缓冲区的,发送端发送数据后,必须要有接收端接收数据

ch<-v 把v发送到通道ch
v:=<-ch 从ch接受数据并把值赋给v

使用chan关键字声明一个通道,通道必须在使用前创建

ch:=make(chan int)

示例代码如下

如果不带缓冲区,发送方会阻塞直到接收方接受了值,如下代码会阻塞直到数据被接受,所以下述代码中虽然开启了两个线程,但是都会阻塞等待通道值被接收

package main

import "fmt"

func sum(data []int, c chan int) {
	var sum = 0
	for _, value := range data {
		sum += value
	}
	// 把sum发送到通道c
	c <- sum
}
func main() {
	var array []int = []int{1, 2, 3, 4, 5}
	var c chan int = make(chan int)
	go sum(array[:len(array)/2], c)
	go sum(array[len(array)/2:], c)
	x, y := <-c, <-c
	fmt.Printf("x+y=%d", x+y)
}

3.17.2 通道缓冲区

如果通道不带缓冲,发送方会阻塞直到接收方从通道接受了值,如果通道带缓冲,发送方会阻塞,直到自己的值被拷贝到缓冲区;如果缓冲区满了,则需要等待直到接收方获取值,接收方在获取值之前会一直阻塞

ch:=make(chan int,100)

示例代码如下

package main

import "fmt"

func main() {
	s := make(chan int, 2)
	// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
	// 而不用立刻需要去同步读取数据
	s <- 1
	s <- 2
	// 获取数据
	fmt.Println(<-s)
	fmt.Println(<-s)
}

3.17.3 通道遍历与关闭

使用range遍历通道,close关闭通道,示例代码如下

package main

import "fmt"

func main() {
	var c = make(chan int, 10)
	go myChannel(c)
	for i := range c {
		fmt.Printf("从通道读取数据%d\n", i)
	}
}

func myChannel(c chan int) {
	for i := 0; i < 10; i++ {
		fmt.Printf("发送数据%d至通道\n", i)
		c <- i
	}
	// 发送完成
	fmt.Println("关闭通道")
	close(c)
}

输出结果为

image.png


标题:GO学习笔记
作者:wenyl
地址:http://www.wenyoulong.com/articles/2023/08/10/1691658040746.html