大家好,今天将梳理出的 Go语言基础语法内容,分享给大家。 请多多指教,谢谢。

本次《Go语言基础语法内容》共分为三个章节,本文为第二章节

本章节内容

  • channel
  • 结构体
  • 指针
  • 控制语句

channel

介绍

单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义,channel就是它们之间的连接。

channel可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。

Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。

注意:goroutine 是go语言中特有的机制,可以理解为go语言中的线程。 使用goroutine, 可以使用go关键字

go并发方面的知识会放到后续文章中,和大家分享。 此知识点中简单了解即可

使用

channel 声明

channel是一种类型,一种引用类型。语法格式:

var 变量 chan 元素类型

例子

 var ch1 chan int   // 声明一个传递整型的通道
var ch2 chan bool // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道

创建 channel

通道是引用类型,通道类型的空值是nil。

声明的通道后需要使用make函数初始化之后才能使用。

make(chan 元素类型, 缓冲大小) // 格式

例子

ch4 := make(chan int)
ch5 := make(chan bool)
ch6 := make(chan []int)

channel操作

通道有发送(send)、接收(receive)和关闭(close)三种操作。

发送和接收都使用 <- 符号。

发送

将一个值发送到通道中

ch := make(chan int)
ch <- 100 // 把100发送到 ch 中

接收

从一个通道中接收值

x := <- ch // 从ch通道中接收, 并赋值 x
<- ch // 从ch通道中接收, 忽略值

关闭

调用内建 close 函数来关闭通道

close(ch)

关闭后的通道有以下特点:

  1. 对一个关闭的通道再发送值就会导致panic
  2. 对一个关闭的通道进行接收会一直获取值直到通道为空
  3. 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值
  4. 关闭一个已经关闭的通道会导致panic

需要注意:只有在通知接收方 goroutine 所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。

无缓冲通道

无缓冲的通道又称为阻塞的通道

无缓冲的通道只有在有人接收值的时候才能发送值。就像你住的小区没有快递柜和代收点,快递员给你打电话必须要把这个物品送到你的手中,简单来说就是无缓冲的通道必须有接收才能发送。

使用 ch := make(chan int) 创建的是无缓冲的通道

func main() {
ch := make(chan int)
ch <- 10
fmt.Println("发送成功")
}

上面这段代码能够通过编译,但是执行的时候会出现死锁错误

fatal error: all goroutines are asleep - deadlock!

一种方法是启用一个 goroutine 去接收值

func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
ch := make(chan int)
go recv(ch) // 启用goroutine从通道接收值
ch <- 10
fmt.Println("发送成功")
}

无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。

使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。

有缓冲通道

只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量。就像你小区的快递柜只有那么个多格子,格子满了就装不下了,就阻塞了,等到别人取走一个快递员就能往里面放一个。

使用 make函数 初始化通道的时候为其指定通道的容量

func main() {
ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道
ch <- 10
fmt.Println("发送成功")
}

从通道循环取值案例

func main() {
ch1 := make(chan int)
ch2 := make(chan int)
// 开启goroutine将0~100的数发送到ch1中
go func() {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}()
// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
go func() {
for {
i, ok := <-ch1 // 通道关闭后再取值ok=false
if !ok {
break
}
ch2 <- i * i
}
close(ch2)
}()
// 在主goroutine中从ch2中接收值打印
for i := range ch2 { // 通道关闭后会退出for range循环
fmt.Println(i)
}
}

单向通道

有的时候我们会将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对其进行限制,比如限制通道在函数中只能发送或只能接收。

单向通道案例

func counter(out chan<- int) {
for i := 0; i < 100; i++ {
out <- i
}
close(out)
} func squarer(out chan<- int, in <-chan int) {
for i := range in {
out <- i * i
}
close(out)
}
func printer(in <-chan int) {
for i := range in {
fmt.Println(i)
}
} func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go counter(ch1)
go squarer(ch2, ch1)
printer(ch2)
}

其中:

  1. chan<- int 是一个只能发送的通道,可以发送但是不能接收;
  2. <-chan int 是一个只能接收的通道,可以接收但是不能发送。

在函数传参及任何赋值操作中将双向通道转换为单向通道是可以的,但反过来是不可以的。

指针

介绍

区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针。

Go语言中的指针需要先知道3个概念:指针地址、指针类型和指针取值。

Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。

传递数据使用指针,而无须拷贝数据。

Go语言中的指针操作非常简单,只需要记住两个符号:&(取地址)和*(根据地址取值)。

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行“取地址”操作。

Go语言中的值类型 (int、float、bool、string、array、struct) 都有对应的指针类型,如:*int、*int64、*string等。

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

使用

取变量指针的语法如下

var v int = 10
ptr := &v
fmt.Printf("v:%d ptr:%p\n", v, ptr) c := *ptr // 指针取值(根据指针去内存取值)
fmt.Printf("c:%d\n", c)

v: 代表被取地址的变量,ptr: 用于接收地址的变量,ptr的类型称做int的指针类型。*代表指针。

程序定义一个int变量num的地址并打印

将a的地址赋给指针p,并通过p去修改a的值

func main() {
var a int
fmt.Println(&a) // 指针地址
var p *int
p = &a // 等同于 p := &a
*p = 20
fmt.Println(a) // 输出 20
}

空指针的判断

func main() {
var p *string
fmt.Printf("p的值是%v\n", p)
if p != nil {
fmt.Println("非空")
} else {
fmt.Println("空值")
}
}

结构体

介绍

结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体,每个值称为结构体的成员。

通常一行对应一个结构体成员,成员的名字在前类型在后,不过如果相邻的成员类型如果相同的话可以被合并到一行。

如果结构体成员名字是以大写字母开头的,那么该成员就是导出的;这是Go语言导出规则决定的。 一个结构体可能同时包含导出和未导出的成员。

一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。(该限制同样适用于数组。)

结构体类型的零值是每个成员都是零值。通常会将零值作为最合理的默认值。

如果结构体没有任何成员的话就是空结构体,写作struct{}。它的大小为0,也不包含任何信息,但是有时候依然是有价值的。

使用

type Info struct {  // 创建结构体
ID int
Name, Hobby string
}
var info Info // 声明结构体

结构体变量的成员可以通过点操作符访问

info.Name = "帽儿山的枪手"

对成员取地址,然后通过指针访问

name := &info.Name
fmt.Println(*name)

如果要在函数内部修改结构体成员的话,用指针传入是必须的;因为在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量。

func UpdateInfo(i *info) {
i.Hobby = "旅游"
}

通过指针创建并初始化一个结构体变量,并返回结构体的地址

pp := &Point{1, 2}

它和下面的语句是等价的

pp := new(Point)
*pp = Point{1, 2}

注:结构体也是可以比较的,两个结构体将可以使用或!=运算符进行比较。相等比较运算符将比较两个结构体的每个成员。

控制语句

if 判断

单条件判断

if condition {
// do something
}

多条件判断

if condition {

} else if condition {
// do something
} else {
// do something
}

if 单条件先跟个语句然后再做条件判断

if statement;condition{
//do something
}

多条件带语句判断

if num := 78;num <= 50{
fmt.Println("Number is less then 50")
} else if num >= 51 && num <= 100{
fmt.Println("The number is between 51 and1 100")
} else{
fmt.Println("The number is greater than 100")
}

for 循环

go语言中只有一种循环方式,for循环。

第一种语法格式

for 循环变量初始化;循环条件;循环变量迭代 {
// 循环操作(语句)
} for j := 1; j <= 10; j++ {
// 循环执行语句
}

第二种语法格式

for 循环判断条件 {
// 循环执行语句
} j := 1
for j <= 10 {
j++
}

第三种语法格式

for {
// 循环执行语句,是一个无限循环,通常需要配合 break 语句使用
}

遍历式语法格式 for-range

var names []string{"xiaoming", "xiaohong", "xiaojun"}
for _, name := range names {
fmt.Println(name)
}

使用 goto 语句跳出 for 循环

func main() {
for i := 0; i < 10; i++ {
fmt.Println(i)
if i == 5 {
goto end
}
}
end:
fmt.Println("end")
}

输出

0
1
2
3
4
5
end

switch

第一种语法格式

func main() {
num := 1
switch num {
case 1:
fmt.Println("num=1")
case 2:
fmt.Println("num=2")
case 3:
fmt.Println("num=3")
default:
fmt.Println("未匹配")
}
}

第二种语法格式

func main() {
num := 1
switch {
case num == 1:
fmt.Println("num=1")
case num == 2:
fmt.Println("num=2")
case num == 3:
fmt.Println("num=3")
default:
fmt.Println("未匹配")
}
}

第三种语法格式

func main() {
switch num := 1; {
case num == 1:
fmt.Println("num=1")
case num == 2:
fmt.Println("num=2")
case num == 3:
fmt.Println("num=3")
default:
fmt.Println("未匹配")
}
}

第四种语法格式

使用关键字 fallthrough 。默认在switch中,每个case都会有一个隐藏的break,如果想要去掉隐藏的break,我们就可以使用fallthrough来进行取代

package main

import (
"fmt"
) func main() {
a := 2
switch a {
case 1:
fmt.Println("a=1")
case 2:
fmt.Println("a=2")
fallthrough
case 3:
fmt.Println("a=3")
case 4:
fmt.Println("a=4")
default:
fmt.Println("default")
}
}

输出

a=2
a=3

select

select语句用来处理与channel有关的I/O操作:

  1. 每个case都必须是一个通信;
  2. 所有channel表达式和被发送的表达式都会被求值;
  3. 任意某个通道可以运行,它就执行,其他被忽略;
  4. 多个case可以运行,随机选一个执行;
  5. 都不可以运行,有default,执行default,没有就阻塞,直到某个通信可以运行,且不会重新对表达式求值;
  6. 一个select最多执行一次case里的代码,需要一直检测case,外层加for循环;
  7. case里的break只退出当前select,和for循环无关;

随机执行case用法

package main

import (
"fmt"
"time"
) func main() {
ch1 := make(chan int)
ch2 := make(chan int) go func() {
time.Sleep(time.Second)
ch1 <- 1
}() go func() {
time.Sleep(time.Second)
ch2 <- 2
}() time.Sleep(2 * time.Second)
select {
case i := <-ch1:
fmt.Println("ch1 receive", i)
case i := <-ch2:
fmt.Println("ch2 receive", i)
default:
fmt.Println("no i/o opeartion")
}
}

结果随机打印:ch1 receive: 1ch2 receive: 2,因为两个channal等待写入,select里两个case都符合执行条件,随机执行。

设置接收channel超时用法

func main() {
ch1 := make(chan int)
select {
case i := <-ch1:
fmt.Println(i)
case <-time.After(5 * time.Second):
fmt.Println("ch receive timeout")
}
}

检查 channel 是否满了用法

func main() {
ch1 := make(chan int, 5)
ch1 <- 1
ch1 <- 2
ch1 <- 3
ch1 <- 4
ch1 <- 5 select {
case ch1 <- 6:
fmt.Println("send 6 to ch1")
default:
fmt.Println("channel is full")
}
}

技术文章持续更新,请大家多多关注呀~~

搜索微信公众号【 帽儿山的枪手 】,关注我

Golang 基础之基础语法梳理 (二)的更多相关文章

  1. Python基础-python基本语法(二)

    一.注释 分类:单行注释和多行注释 1.单行注释 单行注释以#开头,在当前行内,#后面的内容就是注释内容 2.多行注释 被两个   '''    或     ''''''    包括起来的内容就是注释 ...

  2. Java基础之编程语法(二)

    1.常量: 整型:整数,4个字节. 长整型:整数,8个字节.以L结尾. 单精度浮点数:小数,4个字节.以F结尾. 双精度浮点数:小数,8个字节. 布尔:只有两个值,真(true)或假(false),1 ...

  3. Golang 基础之基础语法梳理 (三)

    大家好,今天将梳理出的 Go语言基础语法内容,分享给大家. 请多多指教,谢谢. 本次<Go语言基础语法内容>共分为三个章节,本文为第三章节 Golang 基础之基础语法梳理 (一) Gol ...

  4. Golang 基础之基础语法梳理 (一)

    大家好,今天将梳理出的 Go语言基础语法内容,分享给大家. 请多多指教,谢谢. 本次<Go语言基础语法内容>共分为三个章节,本文为第一章节 Golang 基础之基础语法梳理 (一) Gol ...

  5. flutter--Dart基础语法(二)流程控制、函数、异常

    一.前言 Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,Flutter 开源.免费,拥有宽松的开源协议,支持移动.Web.桌面和嵌入式平台. ...

  6. java基础语法(二)--单列模式

    java基础语法(二)--单列模式 /** * 功能:单列模式 * @author Administrator * */ public class SingletonTest { public sta ...

  7. JS基础语法(二)

    目录 JavaScript基础语法(二) 八. 函数 1. 函数的概念 2. 函数的使用 声明函数 调用函数 3. 函数的封装 4. 函数的参数 函数的参数匹配问题 5. 函数返回值 6. argum ...

  8. Swift3.0基础语法学习<二>

    对象和类: // // ViewController2.swift // SwiftBasicDemo // // Created by 思 彭 on 16/11/15. // Copyright © ...

  9. mySQL的安装和基础使用及语法教程

    mySQL的安装和基础使用及语法指南 一.MySQL的安装.配置及卸载 1.安装 2.配置 3.mySQL5.1的完全卸载 4.MYSQL环境变量的配置 二.MySQL控制台doc窗口的操作命令 1. ...

随机推荐

  1. Firewalld防火墙——基础认知

    Firewalld防火墙 1.Firewalld概述 2.firewalld与iptables 的区别 3.firewalld区域的概念 4.firewalld数据处理流程 5.firewalld检查 ...

  2. Devops 开发运维高级篇之容器管理

    Devops 开发运维高级篇之容器管理 安装docker Dockerfile镜像脚本入门制作 Harbor镜像仓库安装及使用 不过多解释docker直接秀基操 安装docker:(jenkins服务 ...

  3. OpenHarmony移植:如何适配utils子系统之KV存储部件

    摘要:本文介绍移植开发板时如何适配utils子系统之KV存储部件,并介绍相关的运行机制原理. 本文分享自华为云社区<OpenHarmony移植案例与原理 - utils子系统之KV存储部件> ...

  4. 命令行下Git调用IDEA的diff功能

    命令行下git diff, 有人欢喜有人厌, 本文以IDEA diff为例, 介绍如何更换Git的diff工具. IDEA diff IDEA作为一个图形化工具, 其实也提供了极少一部分命令行接口, ...

  5. k8s搭建监控:安装metrics server和dashboard

      安装metrics server 参考:https://github.com/kubernetes-sigs/metrics-server kubectl  create -f component ...

  6. 录毛线脚本,直接手写接口最简洁的LoadRunner性能测试脚本(含jmeter脚本)

    近日翻看了下招聘信息,很多都要求loadrunner和jmeter这两款工具,毕竟是性能测试的主流客户端并发工具. 录制的问题 做性能脚本是性能测试的基本功,loadrunner和jmeter这两款工 ...

  7. [题解]UVA10986 Sending email

    链接:http://vjudge.net/problem/viewProblem.action?id=24941 描述:n个点,m条边的无向图,寻找从S到T的最短路. 思路:基础的单源点最短路 用Di ...

  8. maven通用镜像设置

    <mirrors> <mirror> <id>nexus-aliyun</id> <mirrorOf>central</mirrorO ...

  9. python 中的迭代器和生成器简单介绍

    可迭代对象和迭代器 迭代(iterate)意味着重复,就像 for 循环迭代序列和字典那样,但实际上也可使用 for 循环迭代其他对象:实现了方法 __iter__ 的对象(迭代器协议的基础). __ ...

  10. 使用Python绘制彩色螺旋矩阵

    from turtle import* #导入turtle库 bgcolor("black") #设置画布颜色为黑色 speed(0) #设置画笔绘制速度 colors=[&quo ...