大家好,明天将梳理出的 Go 语言根底语法内容,分享给大家。请多多指教,谢谢。
本次《Go 语言根底语法内容》共分为三个章节,本文为第二章节
- Golang 根底之根底语法梳理 (一)
- Golang 根底之根底语法梳理 (二)
- Golang 根底之根底语法梳理 (三)
本章节内容
- 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)
敞开后的通道有以下特点:
- 对一个敞开的通道再发送值就会导致 panic
- 对一个敞开的通道进行接管会始终获取值直到通道为空
- 对一个敞开的并且没有值的通道执行接管操作会失去对应类型的零值
- 敞开一个曾经敞开的通道会导致 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)
}
其中:
chan<- int
是一个只能发送的通道,能够发送然而不能接管;<-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 操作:
- 每个 case 都必须是一个通信;
- 所有 channel 表达式和被发送的表达式都会被求值;
- 任意某个通道能够运行,它就执行,其余被疏忽;
- 多个 case 能够运行,随机选一个执行;
- 都不能够运行,有 default,执行 default,没有就阻塞,直到某个通信能够运行,且不会从新对表达式求值;
- 一个 select 最多执行一次 case 里的代码,须要始终检测 case,外层加 for 循环;
- 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: 1 或 ch2 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")
}
}
技术文章继续更新,请大家多多关注呀~~