go、select、break、case、 continue、default、defer、else、fallthrough、for、goto、if、range、return、switch |
|
注:
Go 言語では、プログラム エンティティの宣言と定義は、そのデータ型システムに基づいています。たとえば、キーワード chan、func、interface、map、および struct は、Go 言語の複合データ型である Channel (チャネル)、Function (関数)、Interface (インターフェイス)、Map (辞書)、および Struct (構造) にそれぞれ対応します。
プログラム制御フローには合計 15 個のキーワードがあります。このうち go と select は主に Go 言語での同時プログラミングに使用されます。
2. パッケージ管理
2.1 import
#import はパッケージをインポートするために使用されます。したがって、パッケージからエクスポートされた識別子を使用できます。インポート形式は次のとおりです。
import _ "package path"
import . "package path"
import alias "package path"
import (
_ "package path"
. "package path"
alias "package path"
)
パッケージ パスの前に 3 つの修飾子のいずれかを付けることができます。アンダースコアは空の識別子です。これは、パッケージ内の識別子が使用されず、パッケージの副作用のみが必要であることを意味します。つまり、パッケージ レベルの変数の初期化式が計算され、インポートされた変数の init 初期化関数が計算されます。パッケージが実行されます。パッケージのエイリアスがドットに置き換えられ、パッケージ内のエクスポートされた識別子へのアクセスにパッケージ名が必要ないことを示します。 alias はパッケージのエイリアスを表します。
インポート例は次のとおりです。
导入声明 Sin的本地名
import "lib/math" math.Sin
import m "lib/math" m.Sin
import . "lib/math" Sin
2.2 package
package は宣言に使用されます。パッケージの名前は、go ファイル内のすべてのコードの先頭に配置する必要があります。パッケージは 1 つ以上の go ソース ファイルで構成されており、これらの go ソース ファイルは同じディレクトリに配置する必要があり、同じディレクトリ内のこれらの go ファイルのパッケージ名は 1 つの名前のみを持つことができます。宣言形式は次のとおりです:
packagename を空白の identifier_ にすることはできません。
3. プログラムの実体の宣言と定義
3.1 chan
chan は宣言に使用されます。チャンネル(チャンネル)。チャネルは、同時に実行される 2 つの関数が同期し、特定の要素タイプの値を渡すことによって通信するためのメカニズムを提供します。初期化されていないチャネル値は nil です。宣言形式は次のとおりです。
chan T // 可以被用来发送和接收类型T的值
chan<- T // 只能被用来发送浮点数
<-chan T // 只能被用来接收整数
ここで、<-演算子はチャネルの方向 (送信または受信) を指定します。方向が指定されていない場合、チャネルは双方向です。チャネルは、型変換または割り当てによって強制的に送信専用または受信専用にすることができます。
チャネルの初期化は make 関数を通じて実行でき、その結果の値は基礎となるデータ構造への参照として機能します。バッファ サイズは初期化中にチャネルに設定できます。デフォルト値は 0 で、バッファなしまたは同期チャネルを示します。
ci := make(chan int) // 整数类型的无缓冲信道
cj := make(chan int, 0) // 整数类型的无缓冲信道
cp := make(chan *os.File, 100) // 指向文件指针的带缓冲信道
3.2 const
const は定数の定義に使用され、作成後に割り当てたり変更したりすることはできません。 const は、キーワード var が出現できる場所であればどこにでも出現できます。定数を宣言する方法は、var を使用して変数を宣言する方法と同じです。形式は次のとおりです:
const name = value
const name T = value
const (
name = value
name T = value
)
注意してください。 Golang の const は C のようなものをサポートしていません/C で関数のパラメータや戻り値を変更することは、以下のステートメントが不正であることを意味します。
func test(const name *string)
func test(name *string) const *string
3.3 func
func は関数の定義に使用されます。 Go 関数は、変数パラメーターと複数の戻り値をサポートしますが、デフォルト パラメーターはサポートしません。関数に複数の戻り値の仮パラメータがある場合は、それらを括弧で囲む必要があります。定義形式は次のとおりです。
func funcName(){} //无参无返回值
func funcName(t T) T {} //有参有返回值
func funcName(t T, list ...T) (T1,T1) {} //有变参有多个返回值
コード形式で注意する必要があるのは、関数本体の最初の中括弧 関数名は一致する必要があります。これは Go のコード形式に関する必須要件であり、if else ステートメント、for ステートメント、switch ステートメント、select ステートメントなどの他のステートメントにも当てはまります。
3.4 インターフェース
インターフェースはインターフェースを定義するために使用されます。インターフェイスはメソッドのセットです。型がインターフェイス内のすべてのメソッド セットを実装する場合、その型はこのインターフェイスを実装します。インターフェイス型変数には、インターフェイスを実装する任意の型の値を格納できます。特に、interface{} は空のインターフェース タイプを表します。デフォルトでは、すべてのタイプが空のインターフェースを実装するため、interface{} は任意のタイプの値を受け取ることができます。例は次のとおりです。
//空接口
interface{}
//一个简单的File接口
type File interface {
Read(b Buffer) bool
Write(b Buffer) bool
Close()
}
3.5 map
map は、マッピング変数を宣言するために使用されます。マッピングは、同じタイプの要素の順序付けされていないグループであるコンテナ タイプであり、対応する値は一意のキーを通じて取得できます。 make を使用してマップ変数を作成できます。初期化されていないマップ値は nil です。マップ変数を作成するには主にいくつかの方法があります:
//创建0容量的map
var myMap = make(map[T1] T2)
//创建指定容量的map
var myMap = make(map[T]T2, cap)
//创建并初始化map
var myMap = map[string]int {
"dable" : 27,
"cat" : 28,
}
使用例:
package main
import "fmt"
func main() {
nameAge := make(map[string]int)
nameAge["bob"] = 18 //增
nameAge["tom"] = 16 //增
delete(nameAge, "bob") //删
nameAge["tom"] = 19 //改
v := nameAge["tom"] //查
fmt.Println("v=",v)
v, ok := nameAge["tom"] //查,推荐用法
if ok {
fmt.Println("v=",v,"ok=",ok)
}
for k, v :=range nameAge { //遍历
fmt.Println(k, v)
}
}
出力結果:
v= 19
v= 19 ok= true
tom 19
3.6 struct
struct は構造体を定義するために使用されます。構造体はコンテナ型であり、同じ型または異なる型の複数の値のコレクションです。
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // 类型为 Vertex
v2 = Vertex{X: 1} // Y:0 被省略
v3 = Vertex{} // X:0 和 Y:0
p = &Vertex{1, 2} // 类型为 *Vertex
)
func main() {
fmt.Printf("%#v %#v %#v %#v\n", v1, v2, v3, p)
}
出力結果:
main.Vertex{X:1, Y:2} main.Vertex{X:1, Y:0} main.Vertex{X:0, Y:0} &main.Vertex{X:1, Y:2}
3.7 type
type 用于定义类型,比如定义struct、interface与等价类型。
//定义struct
type Person struct { name string }
//定义接口
type Person interface {
speak(word string)
}
//定义等价类型,rune等价于int32
type rune int32
3.8 var
var 用于定义变量,语法格式主要有:
var name T //name默认为类型T的零值
var name = value //根据值value推断变量name的类型
var name T = value //赋初始值时指明类型
var name1, name2 T //同时定义多个同类型变量
//同时定义多个不同类型的变量
var (
name string ="dable"
age int = 18
)
定义变量可以使用:=来替代var,但是:=运算符只能用于函数体内。
4.程序流程控制
4.1 for range break continue
(1)for 与 range
for是Go中唯一用于循环结构的关键词。有三个使用方式,分别是单个循环条件,经典的初始化/条件/后续形式,还有和range关键词结合使用来遍历容器类对象(数组、切片、映射)。
//单条件
i := 1
for i <= 3 {
fmt.Println(i)
i = i + 1
}
//初始化/条件/后续形式
//注意Go中没有前置自增与自减运算符,即++i是非法的
for i:=0; i < 3; i++ {
fmt.Println(i)
}
//for range形式遍历数组
array :=[...]int{0,1,2,3,4,5}
for i, v :=range array{
fmt.Println(i,v)
}
(2)break
break用于终止最内层的"for"、“switch"或"select"语句的执行。break可以携带标签,用于跳出多层。如果存在标签,则标签必须放在"for”、"switch"或"select"语句开始处。
//终止for
L:
for i < n {
switch i {
case 5:
break L
}
}
(3)continue
continue通常用于结束当前循环,提前进入下一轮循环。也可以像break一样携带标签,此时程序的执行流跳转到标签的指定位置,可用于跳出多层"for"、“switch"或"select”,提前进入下一轮的执行。示例如下:
//提前进入下一轮循环
for i:=0; i < 3; i++ {
if i == 1 {
continue
}
fmt.Println(i)
}
//输出结果
0
2
//提前进入标签处for的下一轮循环
L:
for i:=0; i < 2; i++ {
for j:=0; j < 3; j++{
if j == 1 {
continue L
}
fmt.Println(i, j)
}
}
//输出结果
0 0
1 0
4.2 goto
goto用于将程序的执行转移到与其标签相应的语句。可以使用goto退出多层"for"、“switch"或"select”,功能类似于break携带标签。
//终止for
L:
for i < n {
switch i {
case 5:
goto L
}
}
注意事项:
(1)执行"goto"不能在跳转过程中跳过变量的定义,不然会报编译错误。例如:
goto L //编译报错
v := 3
L:
fmt.Println(v)
(2)在块外的goto语句不能跳转至该块中的标签。例如:
if n%2 == 1 {
goto L1
}
for n > 0 {
f()
n--
L1:
f()
n--
}
是错误的,因为标签 L1 在"for"语句的块中而 goto 则不在。
(3)程序设计时,应尽量避免使用goto语句,因为程序执行流的随意跳转会破坏结构化设计风格,导致代码可读性下降。
4.3 switch case default fallthrough
这四个关键词是结合使用的。switch语句提供多路执行,表达式或类型说明符与switch中的case相比较从而决定执行哪一分支。default用于给出默认分支,即所有的case分支都不满足时执行default分支。Go中的switch语句在执行完某个case子句后,不会再顺序地执行后面的case子句,而是结束当前switch语句。使用fallthrough可以继续执行后面的case与default子句。
下面分别以表达式选择或类型选择为例演示switch case default fallthrough的用法。
//表达式选择
switch tag {
default: s3() //default子句可以出现在switch语句中的任意位置,不一定是最后一个
case 0, 1, 2, 3: s1() //case表达式可以提供多个待匹配的值,使用逗号分隔
case 4, 5, 6, 7: s2()
}
switch x := f(); {
case x < 0: return -x //case表达式无需为常量
default: return x
}
switch { //缺失的switch表达式意为"true"
case x < y: f1()
fallthrough //强制执行下一个case子句
case x < z: f2()
//此处没有fallthrough,switch执行流在此终止
case x == 4: f3()
}
//类型选择
switch i := x.(type) {
case int:
printInt(i) // i 的类型为 int
case float64:
printFloat64(i) // i 的类型为 float64
case func(int) float64:
printFunction(i) // i 的类型为 func(int) float64
case bool, string:
printString("type is bool or string") // i 的类型为 bool or string
default:
printString("don't know the type") // i 的类型未知
}
4.4 if else
if与else实现条件控制,与C有许多相似之处,但也有其不同之处。变化主要有三点:
(1)可省略条件表达式的括号;
(2)支持初始化语句,可定义代码块局部变量;
(3)if与else块中只有一条语句也需要添加大括号;
(4)起始大括号必须与if与else同行。
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
4.5 return defer
(1)return
return用于函数执行的终止并可选地提供一个或多个返回值。 任何在函数F中被推迟的函数会在F 返回给其调用者前执行。函数可以通过return返回多个值。如果返回值在函数返回形参中指定了名字,那么return时可不带返回值列表。
//无返回值
func noResult() {
return
}
//单返回值
func simpleF() int {
return 2
}
//多返回值
func complexF2() (float64, float64) {
re = 7.0
im = 4.0
return re, im
}
//返回值已具名
unc complexF3() (re float64, im float64) {
re = 7.0
im = 4.0
return
}
(2)defer
defer语句用于预设一个函数调用,即推迟函数的执行。 该函数会在执行 defer 的函数返回之前立即执行。它显得非比寻常, 但却是处理一些事情的有效方式,例如无论以何种路径返回,都必须释放资源的函数。 典型的例子就是解锁互斥和关闭文件。
//将文件的内容作为字符串返回。
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // f.Close 会在函数结束后运行
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...)
if err != nil {
if err == io.EOF {
break
}
return "", err // 我们在这里返回后,f 就会被关闭
}
}
return string(result), nil // 我们在这里返回后,f 就会被关闭
}
推迟诸如 Close 之类的函数调用有两点好处:第一, 它能确保你不会忘记关闭文件。如果你以后又为该函数添加了新的返回路径时, 这种情况往往就会发生。第二,它意味着“关闭”离“打开”很近, 这总比将它放在函数结尾处要清晰明了。
使用defer时,需要注意两点:
(a)被推迟函数的实参(如果该函数为方法则还包括接收者)在推迟执行时就会求值,而不是在调用执行时才求值。这样不仅无需担心变量值在函数执行时被改变, 同时还意味着可以给被推迟的函数传递不同参数。下面是个简单的例子。
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
(b)被推迟的函数按照后进先出(LIFO)的顺序执行,因此以上代码在函数返回时会打印 4 3 2 1 0。
4.6 go
go用于创建Go程(goroutine),实现并发编程。Go程是与其它Go程并发运行在同一地址空间的函数,相比于线程与进程,它是轻量级的。Go程在多线程操作系统上可实现多路复用,因此若一个线程阻塞,比如说等待I/O,那么其它的线程就会运行。Go程的设计隐藏了线程创建和管理的诸多复杂性。
在函数或方法前添加 go 关键字能够在新的Go程中调用它。当调用完成后,该Go程也会安静地退出。效果有点像Unix Shell中的 & 符号,它能让命令在后台运行。
package main
import (
"fmt"
"time"
)
func main() {
go func(){
fmt.Println("in first goroutine")
}()
go func(){
fmt.Println("in second goroutine")
}()
fmt.Println("main thread start sleep, and other goroutine start execute")
time.Sleep(10*time.Second)
}
输出结果:
main thread start sleep, and other goroutine start execute
in second goroutine
in first goroutine
注意,从输出结果可以看出,go程的执行顺序和创建的顺序是没有关系的,也就是说存在多个go程时,其执行的顺序是随机的。
4.7 select
select语句用来选择哪个case中的发送或接收操作可以被立即执行。它类似于switch语句,但是它的case涉及channel有关的I/O操作。也就是说select就是用来监听和channel有关的IO操作,它与select, poll, epoll相似,当IO操作发生时,触发相应的动作,实现IO多路复用。
package main
import "fmt"
func main(){
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch1 <- 3
ch2 <- 5
select {
case <- ch1:
fmt.Println("ch1 selected.")
case <- ch2:
fmt.Println("ch2 selected.")
default:
//如果ch1与ch2没有数据到来,则进入default处理流程。如果没有default子句,则select一直阻塞等待ch1与ch2的数据到来
fmt.Println("default")
}
}
输出结果:
ch1 selected.
//或者
ch2 selected.
从输出结果可以看出,当存在多个case满足条件,即有多个channel存在数据时,会随机的选择一个执行。
【相关推荐:Go视频教程、编程教学】