Golang 基础整理 这一篇就够了

第一个golang程序

package main

import "fmt"

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

基本数据类型

  1. 布尔型( true 或者 false)
  2. 数字类型( 整型 int 和 浮点型 float32、float64 )
  3. 字符串类型( 字符串就是一串固定长度的字符连接起来的字符序列 )
  4. 派生类型:
  • 指针类型(Pointer)
  • 数组类型
  • 结构化类型(struct)
  • Channel 类型
  • 函数类型
  • 切片类型
  • 接口类型(interface)
  • Map 类型

数字类型

  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)
  1. 浮点型
  • float32
    32位浮点型数
  • float64
    64位浮点型数
  • complex64
    32 位实数和虚数
  • complex128
    64 位实数和虚数
  1. 其他数字类型
  • byte
    类似 uint8
  • rune
    类似 int32
  • uint
    32 或 64 位
  • int
    与 uint 一样大小
  • uintptr
    无符号整型,用于存放一个指针

定义变量

// 声明一个变量
var identifier type
// 可以一次声明多个变量
var identifier1, identifier2 type
// 根据值自行判定变量类型
var v_name = value
// 简短形式 省略 var, 注意 := 左侧如果没有声明新的变量
v_name := value

定义常量

// 声明一个常量
const identifier [type] = value
// 显式类型定义
const b string = "abc"
// 隐式类型定义
const b = "abc"
// 多个相同类型的声明(隐式类型定义)
const c_name1, c_name2 = value1, value2

iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值

package main

import "fmt"

func main() {
    
    
	const (
		a = iota   //0
		b          //1
		c          //2
		d = "ha"   //独立值,iota += 1
		e          //"ha"   iota += 1
		f = 100    //iota +=1
		g          //100  iota +=1
		h = iota   //7,恢复计数
		i          //8
	)
	fmt.Println(a,b,c,d,e,f,g,h,i)
}

运行结果

0 1 2 ha ha 100 100 7 8

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1

条件控制语句

if & if else

package main

import "fmt"

func main() {
    
    
	var a = 12
	if a > 10 {
    
    
		fmt.Println("a>10")
	} else {
    
    
		fmt.Println("a<=10")
	}
}

运行结果

a>10

switch

package main

import "fmt"

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

运行结果

12

使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true

package main

import "fmt"

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

运行结果

1
2

select

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。

select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

package main

import "fmt"

func main() {
    
    
	var c1, c2, c3 chan int
	var i1, i2 int
	select {
    
    
	case i1 = <-c1:
		fmt.Printf("received ", i1, " from c1\n")
	case c2 <- i2:
		fmt.Printf("sent ", i2, " to c2\n")
	case i3, ok := <-c3:
		if ok {
    
    
			fmt.Printf("received ", i3, " from c3\n")
		} else {
    
    
			fmt.Printf("c3 is closed\n")
		}
	default:
		fmt.Printf("no communication\n")
	}
}

运行结果

no communication

循环控制语句

for

package main

import "fmt"

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

package main

import "fmt"

func main() {
    
    
	var i = 1
	for i < 10 {
    
    
		fmt.Println(i)
		i++
	}
}

运行结果

1
2
3
4
5
6
7
8
9

死循环

for {
    
    

}

函数

package main

import "fmt"

func main() {
    
    
	test(1)
}
func test(i int) int {
    
    
	for i < 10 {
    
    
		fmt.Println(i)
		i++
	}
	return i
}

运行结果

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
    
    
	i := test(1, 9)
	fmt.Println("最大值为:", i)
}
func test(i, j int) int {
    
    
	if i > j {
    
    
		return i
	} else {
    
    
		return j
	}
}

运行结果

最大值为: 9

函数返回多个值

package main

import "fmt"

func main() {
    
    
	s, s2 := test("hello", "go")
	fmt.Println(s, s2)
}
func test(i, j string) (string, string) {
    
    
	return i, j
}

运行结果

hello go

值传递 和 引用传递

package main

import "fmt"

func main() {
    
    
	var a = 3
	var b = 4
	fmt.Println("值传递运行前a=", a, "b=", b)
	test1(a, b)
	fmt.Println("值传递运行后a=", a, "b=", b)
	fmt.Println("===============================================")
	var i = 1
	var j = 2
	fmt.Println("引用传递运行前i=", i, "j=", j)
	test2(&i, &j)
	fmt.Println("引用传递运行后i=", i, "j=", j)
}

// 值传递
func test1(i, j int) (int, int) {
    
    
	var temp int
	temp = i
	i = j
	j = temp
	return i, j
}

// 引用传递
func test2(i, j *int) (int, int) {
    
    
	var temp int
	temp = *i
	*i = *j
	*j = temp
	return *i, *j
}

运行结果

值传递运行前a= 3 b= 4
值传递运行后a= 3 b= 4
===============================================
引用传递运行前i= 1 j= 2
引用传递运行后i= 2 j= 1

函数作为实参

package main

import "fmt"

func main() {
    
    
	funcA := func(a int) int {
    
    
		return a
	}
	fmt.Println(funcA(12))
}

运行结果

12

闭包

Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

package main

import "fmt"

func main() {
    
    
	next := getSequence()
	fmt.Println(next())
	fmt.Println(next())
	fmt.Println(next())
}

func getSequence() func() int {
    
    
	a := 1
	return func() int {
    
    
		a++
		return a
	}
}

运行结果

2
3
4

方法

Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集

package main

import "fmt"

type Circle struct {
    
    
	radius float64
}

func (circle Circle) getPerimeter() float64 {
    
    
	return 3.14 * circle.radius * 2
}
func main() {
    
    
	var circle Circle
	circle.radius = 10
	fmt.Println(circle.getPerimeter())
}

运行结果

62.800000000000004

变量作用域

Go 语言中变量可以在三个地方声明:

  • 函数内定义的变量称为局部变量
  • 函数外定义的变量称为全局变量
  • 函数定义中的变量称为形式参数
package main

import "fmt"

// 全局变量
var a = 1

func main() {
    
    
	// 局部变量
	var b = 2
	test(a)
	test(b)
}

// 形式参数
func test(a int) {
    
    
	fmt.Println(a)
}

数组

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型

声明数组

// 形式
var variable_name [SIZE] variable_type
// 举例
var balance [10] float32

初始化数组

// 初始化一个长度为5的float32数组
var balance = [5]float32{
    
    1000.0, 2.0, 3.4, 7.0, 50.0}
// 如果忽略 [] 中的数字不设置数组大小
var balance = [...]float32{
    
    1000.0, 2.0, 3.4, 7.0, 50.0}
balance[6] = 60.0

访问数组元素

var a float32 = balance[5]

指针

一个指针变量指向了一个值的内存地址

声明指针

// 形式
var var_name *var-type
// 举例
var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮点型 */
package main

import "fmt"

func main() {
    
    
	var a int = 20 /* 声明实际变量 */
	var ip *int    /* 声明指针变量 */

	ip = &a /* 指针变量的存储地址 */

	fmt.Printf("a 变量的地址是: %x\n", &a)

	/* 指针变量的存储地址 */
	fmt.Printf("ip 变量储存的指针地址: %x\n", ip)

	/* 使用指针访问值 */
	fmt.Printf("*ip 变量的值: %d\n", *ip)
}

运行结果

a 变量的地址是: c00000a0b0
ip 变量储存的指针地址: c00000a0b0
*ip 变量的值: 20

空指针

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

nil 指针也称为空指针。一个指针变量通常缩写为 ptr。

package main

import "fmt"

func main() {
    
    
	var ip *int /* 声明指针变量 */

	/* 指针变量的存储地址 */
	fmt.Printf("ip 的值为: %x\n", ip)
}

运行结果

ip 的值为: 0

空指针判断

if(ptr != nil)     /* ptr 不是空指针 */
if(ptr == nil)    /* ptr 是空指针 */

指针数组

package main

import "fmt"

func main() {
    
    
	a := []int{
    
    10, 100, 200}
	// 遍历数组
	for i := 0; i < len(a); i++ {
    
    
		fmt.Printf("a[%d] = %d\n", i, a[i])
	}
	fmt.Println("==================================")
	// 有一种情况,我们可能需要保存数组,这样我们就需要使用到指针。
	// 以下声明了整型指针数组:
	var ptr [3]*int
	for i := 0; i < len(a); i++ {
    
    
		/* 整数地址赋值给指针数组 */
		ptr[i] = &a[i]
	}
	for i := 0; i < len(ptr); i++ {
    
    
		fmt.Printf("a[%d] = %d\n", i, *ptr[i])
	}
}

运行结果

a[0] = 10
a[1] = 100
a[2] = 200
==================================
a[0] = 10
a[1] = 100
a[2] = 200

指向指针的指针

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

var ptr **int;
package main

import "fmt"

func main() {
    
    
	var a int
	var ptr *int
	var pptr **int

	a = 3000

	/* 指针 ptr 地址 */
	ptr = &a

	/* 指向指针 ptr 地址 */
	pptr = &ptr

	/* 获取 pptr 的值 */
	fmt.Printf("变量 a = %d\n", a)
	fmt.Printf("指针变量 *ptr = %d\n", *ptr)
	fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}

运行结果

变量 a = 3000
指针变量 *ptr = 3000
指向指针的指针变量 **pptr = 3000

指针作为函数参数

package main

import "fmt"

func main() {
    
    
	var a = 3
	var b = 4
	fmt.Println("值传递运行前a=", a, "b=", b)
	test1(a, b)
	fmt.Println("值传递运行后a=", a, "b=", b)
	fmt.Println("===============================================")
	var i = 1
	var j = 2
	fmt.Println("引用传递运行前i=", i, "j=", j)
	test2(&i, &j)
	fmt.Println("引用传递运行后i=", i, "j=", j)
}

// 值传递
func test1(i, j int) (int, int) {
    
    
	var temp int
	temp = i
	i = j
	j = temp
	return i, j
}

// 引用传递
func test2(i, j *int) (int, int) {
    
    
	var temp int
	temp = *i
	*i = *j
	*j = temp
	return *i, *j
}

运行结果

值传递运行前a= 3 b= 4
值传递运行后a= 3 b= 4
===============================================
引用传递运行前i= 1 j= 2
引用传递运行后i= 2 j= 1

结构体

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:

Title :标题
Author : 作者
Subject:学科
ID:书籍ID

定义

type struct_variable_type struct {
    
    
   member definition
   member definition
   ...
   member definition
}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

variable_name := structure_variable_type {
    
    value1, value2...valuen}
或
variable_name := structure_variable_type {
    
     key1: value1, key2: value2..., keyn: valuen}
package main

import "fmt"

// 一、定义结构体
type Books struct {
    
    
	title   string
	author  string
	subject string
	book_id int
}

func main() {
    
    

	// 创建一个新的结构体
	fmt.Println(Books{
    
    "Go 语言", "Google", "Go 语言教程", 6495407})

	// 也可以使用 key => value 格式
	fmt.Println(Books{
    
    title: "Go 语言", author: "Google", subject: "Go 语言教程", book_id: 6495407})

	// 忽略的字段为 0 或 空
	fmt.Println(Books{
    
    title: "Go 语言", author: "Google"})

	fmt.Println("=========================")

	// 二、访问结构体成员
	/* book 2 描述 */
	var Book2 Books
	Book2.title = "Python 教程"
	Book2.author = "Python"
	Book2.subject = "Python 语言教程"
	Book2.book_id = 6495700
	/* 打印 Book2 信息 */
	fmt.Printf("Book 2 title : %s\n", Book2.title)
	fmt.Printf("Book 2 author : %s\n", Book2.author)
	fmt.Printf("Book 2 subject : %s\n", Book2.subject)
	fmt.Printf("Book 2 book_id : %d\n", Book2.book_id)
	fmt.Println("=========================")
	// 三、结构体作为函数参数
	printBook(Book2)
	fmt.Println("=========================")
	// 四、结构体指针
	printBook2(&Book2)
}

func printBook(book Books) {
    
    
	fmt.Printf("Book title : %s\n", book.title)
	fmt.Printf("Book author : %s\n", book.author)
	fmt.Printf("Book subject : %s\n", book.subject)
	fmt.Printf("Book book_id : %d\n", book.book_id)
}

func printBook2(book *Books) {
    
    
	fmt.Printf("Book title : %s\n", book.title)
	fmt.Printf("Book author : %s\n", book.author)
	fmt.Printf("Book subject : %s\n", book.subject)
	fmt.Printf("Book book_id : %d\n", book.book_id)
}

运行结果

{
    
    Go 语言 Google Go 语言教程 6495407}
{
    
    Go 语言 Google Go 语言教程 6495407}
{
    
    Go 语言 Google  0}
=========================
Book 2 title : Python 教程
Book 2 author : Python
Book 2 subject : Python 语言教程
Book 2 book_id : 6495700
=========================
Book title : Python 教程
Book author : Python
Book subject : Python 语言教程
Book book_id : 6495700
=========================
Book title : Python 教程
Book author : Python
Book subject : Python 语言教程
Book book_id : 6495700

切片(Slice)

Go 数组的长度不可改变,与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

定义切片

  1. var identifier []type
  2. var slice1 []type = make([]type, len) 或者简写 slice1 := make([]type, len) 指定长度
  3. make([]T, length, capacity) 指定容量
package main

import "fmt"

func main() {
    
    
	// 切片初始化
	var slice = []int{
    
    1, 2, 3}
	// 从下标startIndex到endIndex-1 下的元素 切片截取
	fmt.Println(slice[0:2])
	fmt.Println(slice[:2])
	fmt.Println(slice[0:])
	fmt.Println("=======================")
	// len() 和 cap() 函数
	fmt.Printf("len=%d cap=%d slice=%v\n", len(slice), cap(slice), slice)
	fmt.Println("=======================")
	// 空切片
	var numbers []int

	fmt.Printf("len=%d cap=%d slice=%v\n", len(numbers), cap(numbers), numbers)

	if numbers == nil {
    
    
		fmt.Printf("切片是空的\n")
	}
	fmt.Println("=======================")
	// append() 和 copy() 函数
	var numbers1 []int
	// append() 追加
	numbers1 = append(numbers1, 1)
	numbers1 = append(numbers1, 2, 3, 4)
	fmt.Printf("len=%d cap=%d slice=%v\n", len(numbers1), cap(numbers1), numbers1)
	fmt.Println("=======================")
	// copy() 复制
	/* 创建切片 numbers2 是之前切片的两倍容量*/
	numbers2 := make([]int, len(numbers1), (cap(numbers1))*2)
	/* 拷贝 numbers1 的内容到 numbers2 */
	copy(numbers2, numbers1)
	fmt.Printf("len=%d cap=%d slice=%v\n", len(numbers2), cap(numbers2), numbers2)
}

运行结果

[1 2]
[1 2]
[1 2 3]
=======================
len=3 cap=3 slice=[1 2 3]
=======================
len=0 cap=0 slice=[]
切片是空的
=======================
len=4 cap=4 slice=[1 2 3 4]
=======================
len=4 cap=8 slice=[1 2 3 4]

范围(Range)

package main

import "fmt"

func main() {
    
    
	//这是我们使用range去求一个slice的和。使用数组跟这个很类似
	nums := []int{
    
    2, 3, 4}
	sum := 0
	for _, num := range nums {
    
    
		sum += num
	}
	fmt.Println("sum:", sum)
	//在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
	for i, num := range nums {
    
    
		if num == 3 {
    
    
			fmt.Println("index:", i)
		}
	}
	//range也可以用在map的键值对上。
	kvs := map[string]string{
    
    "a": "apple", "b": "banana"}
	for k, v := range kvs {
    
    
		fmt.Printf("%s -> %s\n", k, v)
	}
	//range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
	for i, c := range "go" {
    
    
		fmt.Println(i, c)
	}
}

运行结果

sum: 9
index: 1
a -> apple
b -> banana
0 103
1 111

Map(集合)

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

定义集合

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)

如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对

package main

import "fmt"

func main() {
    
    
	var countryCapitalMap = make(map[string]string)

	/* map插入key - value对,各个国家对应的首都 */
	countryCapitalMap["France"] = "巴黎"
	countryCapitalMap["Italy"] = "罗马"
	countryCapitalMap["Japan"] = "东京"
	countryCapitalMap["India "] = "新德里"

	/*使用键输出地图值 */
	for country := range countryCapitalMap {
    
    
		fmt.Println(country, "首都是", countryCapitalMap[country])
	}

	/*查看元素在集合中是否存在 */
	capital, ok := countryCapitalMap["American"] /*如果确定是真实的,则存在,否则不存在 */
	/*fmt.Println(capital) */
	/*fmt.Println(ok) */
	if ok {
    
    
		fmt.Println("American 的首都是", capital)
	} else {
    
    
		fmt.Println("American 的首都不存在")
	}

	fmt.Println("========================")
	// delete() 函数
	for country := range countryCapitalMap {
    
    
		fmt.Println(country, "首都是", countryCapitalMap[country])
	}
	// 删除元素
	delete(countryCapitalMap, "France")
	fmt.Println("法国条目被删除")
	for country := range countryCapitalMap {
    
    
		fmt.Println(country, "首都是", countryCapitalMap[country])
	}
}

运行结果

France 首都是 巴黎
Italy 首都是 罗马
Japan 首都是 东京
India  首都是 新德里
American 的首都不存在
========================
India  首都是 新德里
France 首都是 巴黎
Italy 首都是 罗马
Japan 首都是 东京
法国条目被删除
Japan 首都是 东京
India  首都是 新德里
Italy 首都是 罗马

递归函数

递归,就是在运行的过程中调用自己

阶乘

package main

import "fmt"

func main() {
    
    
	var i int = 15
	fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}

func Factorial(n uint64) (result uint64) {
    
    
	if n > 0 {
    
    
		result = n * Factorial(n-1)
		return result
	}
	return 1
}

运行结果

15 的阶乘是 1307674368000

斐波那契数列

package main

import "fmt"

func main() {
    
    
	var i int
	for i = 0; i < 10; i++ {
    
    
		fmt.Printf("%d\t", fibonacci(i))
	}
}

func fibonacci(n int) int {
    
    
	if n < 2 {
    
    
		return n
	}
	return fibonacci(n-2) + fibonacci(n-1)
}

运行结果

0	1	1	2	3	5	8	13	21	34

类型转换

类型转换用于将一种数据类型的变量转换为另外一种类型的变量。

type_name(expression)
package main

import "fmt"

func main() {
    
    
	var sum int = 17
	var count int = 5
	var mean float32

	mean = float32(sum) / float32(count)
	fmt.Printf("mean 的值为: %f\n", mean)
}

运行结果

mean 的值为: 3.400000

接口

package main

import "fmt"

type Phone interface {
    
    
	call()
}

type NokiaPhone struct {
    
    
}
type IPhone struct {
    
    
}

func main() {
    
    
	n := new(NokiaPhone)
	n.call()
	i := new(IPhone)
	i.call()
}

func (NokiaPhone) call() {
    
    
	fmt.Println("nokiaPhone")
}

func (IPhone) call() {
    
    
	fmt.Println("IPhone")
}

运行结果

nokiaPhone
IPhone

错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

error类型是一个接口类型,这是它的定义

type error interface {
    
    
    Error() string
}

我们可以在编码中通过实现 error 接口类型来生成错误信息。

函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息

package main

import (
	"errors"
	"fmt"
)

func main() {
    
    
	_, err := Sqrt(-1)

	if err != nil {
    
    
		fmt.Println(err)
	}
}

func Sqrt(f float64) (float64, error) {
    
    
	if f < 0 {
    
    
		return 0, errors.New("math: square root of negative number")
	}
	// 实现
	return f, nil
}

运行结果

math: square root of negative number

并发

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式:

go 函数名( 参数列表 )

Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

package main

import (
	"fmt"
	"time"
)

func main() {
    
    
	go say("world")
	say("hello")
}

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

运行结果

hello
world
world
hello
hello
world
world
hello
hello

通道(channel)

通道(channel)是用来传递数据的一个数据结构。

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
声明一个通道,通道在使用前必须先创建:

ch := make(chan int)

注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。

package main

import (
	"fmt"
)

func main() {
    
    
	s := []int{
    
    7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // 从通道 c 中接收

	fmt.Println(x, y, x+y)
}

func sum(s []int, c chan int) {
    
    
	sum := 0
	for _, v := range s {
    
    
		sum += v
	}
	c <- sum // 把 sum 发送到通道 c
}

运行结果

-5 17 12

通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

package main

import (
	"fmt"
)

func main() {
    
    
	// 这里我们定义了一个可以存储整数类型的带缓冲通道
	// 缓冲区大小为2
	ch := make(chan int, 2)

	// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
	// 而不用立刻需要去同步读取数据
	ch <- 1
	ch <- 2

	// 获取这两个数据
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

运行结果

1
2

遍历通道与关闭通道

Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:

v, ok := <-ch
package main

import (
	"fmt"
)

func main() {
    
    
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
	// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
	// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
	// 会结束,从而在接收第 11 个数据的时候就阻塞了。
	for i := range c {
    
    
		fmt.Println(i)
	}
}

func fibonacci(n int, c chan int) {
    
    
	x, y := 0, 1
	for i := 0; i < n; i++ {
    
    
		c <- x
		x, y = y, x+y
	}
	// 关闭通道
	close(c)
}

运行结果

0
1
1
2
3
5
8
13
21
34

猜你喜欢

转载自blog.csdn.net/y1534414425/article/details/109269323