Skip to content

基本語法

Go 的基本語法十分簡單易懂,讓我們從一個最簡單的例子開始。

go
package main

import "fmt"

func main() {
   fmt.Println("Hello 世界!")
}
  • package關鍵字聲明了是當前 go 文件屬於哪一個包,入口文件都必須聲明為main包,入口函數是main函數,在自定義包和函數時命名應當盡量避免與之重復。

  • import是導入關鍵字,後面跟著的是被導入的包名。

  • func是函數聲明關鍵字,用於聲明一個函數。

  • fmt.Println("Hello 世界!")是一個語句,調用了fmt包下的Println函數進行輸出。

以上就是一個簡單的語法介紹,下面就來略微細致地去了解裡面的概念。

在 Go 中,程序是通過將包鏈接在一起來構建的。Go 中進行導入的最基本單位是一個包,而不是.go文件。包其實就是一個文件夾,英文名 package,包內共享所有變量,常量,以及所有定義的類型。包的命名風格建議都是小寫字母,並且要盡量簡短。

可見性

前面提到過包內共享所有變量,常量,以及所有定義的類型,但對於包外而言並不是這樣,有時候你並不想讓別人訪問某一個類型,所以就需要控制可見性。你可能在其它 OOP 語言中見過PublicPravite等關鍵字,不過在 Go 語言中沒有這些,它控制可見性的方式非常簡單,規則如下

  • 名稱大寫字母開頭,即為公有類型/變量/常量
  • 名字小寫或下劃線開頭,即為私有類型/變量/常量

比如下面的一個例子,常量MyName就是公開的,而常量mySalary就是私有的。

go
package example

// 公有
const MyName = "jack"

// 私有
const mySalary = 20_000

這個可見性的規則適用於整個 Go 語言的任何地方。

導入

導入一個包來使用這個包中的類型/方法/函數/變量,導入的語法就是import加上包名

go
package main

import "example"

當導入多個包時,你可以這麼寫

go
package main

import "example"
import "example1"

也可以用括號括起來,下面這種方法在實踐中更加常用。

go
package main

import (
  "example"
  "example1"
)

如果有包名重復了,或者包名比較復雜,你也可以給它們起別名

go
package main

import (
  e "example"
  e1 "example1"
)

別名為下劃線_時就是匿名導入,匿名導入的包無法被使用,這麼做通常是為了加載包下的init 函數,但又不需要用到包中的類型,一個常見的例子就是注冊數據庫驅動,但是你並不需要去手動使用驅動。

go
package main

import (
  e "example"
  _ "mysql-driver-go"
)

當你導入後,想要訪問包中的類型時,通過包名.標識符去訪問即可,比如下面這個例子,若你嘗試去訪問一個私有的類型,編譯器就會告訴你無法訪問。

go
package main

import (
  "example"
   "fmt"
)

func main() {
    fmt.Println(example.MyName)
}

有一種特殊的導入方式就是將該包中的所有類型都導入到當前包作用域,以這種方法導入的類型不再需要.運算符去訪問,但是如果有重名的類型將會無法通過編譯。

go
package main

import (
  . "example"
)

WARNING

Go 中無法進行循環導入,不管是直接的還是間接的。例如包 A 導入了包 B,包 B 也導入了包 A,這是直接循環導入,包 A 導入了包 C,包 C 導入了包 B,包 B 又導入了包 A,這就是間接的循環導入,存在循環導入的話將會無法通過編譯。

內部包

go 中約定,一個包內名為internal 包為內部包,外部包將無法訪問內部包中的任何內容,否則的話編譯不通過,下面看一個例子。

/home/user/go/
    src/
        crash/
            bang/              (go code in package bang)
                b.go
        foo/                   (go code in package foo)
            f.go
            bar/               (go code in package bar)
                x.go
            internal/
                baz/           (go code in package baz)
                    z.go
            quux/              (go code in package main)
                y.go

由文件結構中可知,crash包無法訪問baz包中的類型。

注釋

Go 支持單行注釋和多行注釋,注釋與內容之間建議隔一個空格,例如

go
// 這是main包
package main

// 導入了fmt包
import "fmt"

/*
*
這是啟動函數main函數
*/
func main() {
  // 這是一個語句
  fmt.Println("Hello 世界!")
}

標識符

標識符就是一個名稱,用於包命名,函數命名,變量命名等等,命名規則如下:

  • 只能由字母,數字,下劃線組成
  • 只能以字母和下劃線開頭
  • 嚴格區分大小寫
  • 不能與任何已存在的標識符重復,即包內唯一的存在
  • 不能與 Go 任何內置的關鍵字沖突

下方列出所有的內置關鍵字,也可以前往參考手冊-標識符查看更多細節

go
break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

運算符

下面是 Go 語言中支持的運算符號的優先級排列,也可以前往參考手冊-運算符查看更多細節。

Precedence    Operator
    5             *  /  %  <<  >>  &  &^
    4             +  -  |  ^
    3             ==  !=  <  <=  >  >=
    2             &&
    1             ||

有一點需要稍微注意下,go 語言中沒有選擇將~作為取反運算符,而是復用了^符號,當兩個數字使用^時,例如a^b,它就是異或運算符,只對一個數字使用時,例如^a,那麼它就是取反運算符。go 也支持增強賦值運算符,如下。

go
a += 1
a /= 2
a &^= 2

TIP

Go 語言中沒有自增與自減運算符,它們被降級為了語句statement,並且規定了只能位於操作數的後方,所以不用再去糾結i++++i這樣的問題。

a++ // 正確
++a // 錯誤
a-- // 正確

還有一點就是,它們不再具有返回值,因此a = b++這類語句的寫法是錯誤的。

字面量

字面量,按照計算機科學的術語來講是用於表達源代碼中一個固定值的符號,也叫字面值。兩個叫法都是一個意思,寫了什麼東西,值就是什麼,值就是「字面意義上「的值。

整型字面量

為了便於閱讀,允許使用下劃線_來進行數字劃分,但是僅允許在前綴符號之後數字之間使用。

go
24 // 24
024 // 24
2_4 // 24
0_2_4 // 24
10_000 // 10k
100_000 // 100k
0O24 // 20
0b00 // 0
0x00 // 0
0x0_0 // 0

浮點數字面量

通過不同的前綴可以表達不同進制的浮點數

go
0.
72.40
072.40       // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
1_5.         // == 15.0
0.15e+0_2    // == 15.0

0x1p-2       // == 0.25
0x2.p10      // == 2048.0
0x1.Fp+0     // == 1.9375
0X.8p-0      // == 0.5
0X_1FFFP-16  // == 0.1249847412109375
0x15e-2      // == 0x15e - 2 (integer subtraction)

復數字面量

go
0i
0123i         // == 123i
0o123i        // == 0o123 * 1i == 83i
0xabci        // == 0xabc * 1i == 2748i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i
0x1p-2i       // == 0x1p-2 * 1i == 0.25i

字符字面量

字符字面量必須使用單引號括起來'',Go 中的字符完全兼容utf8

go
'a'
'ä'
''
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'

轉義字符

Go 中可用的轉義字符

\a   U+0007 響鈴符號
\b   U+0008 回退符號
\f   U+000C 換頁符號
\n   U+000A 換行符號
\r   U+000D 回車符號
\t   U+0009 橫向制表符號
\v   U+000B 縱向制表符號
\\   U+005C 反斜槓轉義
\'   U+0027 單引號轉義 (該轉義僅在字符內有效)
\"   U+0022 雙引號轉義 (該轉義僅在字符串內有效)

字符串字面量

字符串字面量必須使用雙引號""括起來或者反引號``(反引號字符串不允許轉義)

go
`abc`                // "abc"
`\n
\n`                  // "\\n\n\\n"
"\n"
"\""                 // `"`
"Hello, world!\n"
"今天天氣不錯"
"日本語"
"\u65e5\U00008a9e"
"\xff\u00FF"

函數

Go 中的函數聲明方式通過func關鍵字來進行,跟大多數語言類似

go
func main() {
  println(1)
}

不過 Go 中的函數有兩個不同的點,第一個是參數類型後置,像下面這樣

func Hello(name string) {
  fmt.Println(name)
}

第二個不同的點就是多返回值,而且可以帶名字

go
func Pos() () (x, y float64) {
    ...
}

風格

關於編碼風格這一塊 Go 是強制所有人統一同一種風格,Go 官方提供了一個格式化工具gofmt,通過命令行就可以使用,該格式化工具沒有任何的格式化參數可以傳遞,僅有的兩個參數也只是輸出格式化過程,所以完全不支持自定義,也就是說所有通過此工具的格式化後的代碼都是同一種代碼風格,這會極大的降低維護人員的心智負擔,所以在這一塊追求個性顯然是一個不太明智的選擇。

下面會簡單列舉一些規則,平時在編寫代碼的時候也可以稍微注意一下。

函數花括號換行

關於函數後的花括號到底該不該換行,幾乎每個程序員都能說出屬於自己的理由,在 Go 中所有的花括號都不應該換行

go
// 正確示例
func main() {
  fmt.Println("Hello 世界!")
}

如果你真的這麼做了,像下面這樣

go
// 錯誤示例
func main()
{
  fmt.Println("Hello 世界!")
}

這樣的代碼連編譯都過不了,所以 Go 強制所有程序員花函數後的括號不換行。

代碼縮進

Go 默認使用Tab也就是制表符進行縮進,僅在一些特殊情況會使用空格。

代碼間隔

Go 中大部分間隔都是有意義的,從某種程度上來說,這也代表了編譯器是如何看待你的代碼的,例如下方的數學運算

2*9 + 1/3*2

眾所周知,乘法的優先級比加法要高,在格式化後,*符號之間的間隔會顯得更緊湊,意味著優先進行運算,而+符號附近的間隔則較大,代表著較後進行運算。

花括號省略

在其它語言中的 if 和 for 語句通常可以簡寫,像下面這樣

c
for (int i=0; i < 10; i++) printf("%d", i)

但在 Go 中不行,你可以只寫一行,但必須加上花括號

go
for i := 0; i < 10; i++ {fmt.Println(i)}

三元表達式

Go 中沒有三元表達式,所以像下面的代碼是無法通過編譯的

go
var c = a > b ? a : b

通過這篇文章你可以對 Go 的語法有一個初步的認知,後續的內容中會進行更細致的展開。

Golang學習網由www.golangdev.cn整理維護