go基礎簡介
Go語言是一種靜態強型別、編譯型、併發型,並具有垃圾回收功能的程式語言。Go 的正式語法使用分號來結束語句,但大多數情況下是可以省略的(編譯器會自動新增);如果你在一行中寫多個語句,則需要用分號隔開不同的語句:
-
Go程式是通過package來組織的;
-
只有package名稱為main的包可以包含main函式;
-
一個可執行程式有且只有一個main包;
-
如果匯入的包但是沒有用到型別或者函式則會報編譯錯誤;
-
package別名: import io "fmt" 這樣將fmt設定一個別名為io,呼叫時為io.Println("...");
在 main.main 函式執行之前所有程式碼都執行在同一個goroutine
go的註釋與C++類似:
-
單行註釋 // ...
-
多行註釋 /* ... */
資料型別
go中定義了以下基本資料型別:
-
整數型別:
-
與系統架構有關的:int與uint(在32位系統下為32,64位系統下為64);
-
固定長度的:int8/16/32/64、uint8/16/32/64;
-
-
浮點型別:float32、float64
-
布林型別:bool
-
複數型別: complex64、complex128
-
字元型別: byte(uint8的別名)、rune(int32的別名,標識Unicode)
-
字串型別: string
-
一個字串是一個不可改變的位元組序列,可以包含任意的資料,包括byte值0;
-
for range 語法對UTF8字串提供了特殊支援;
-
對字串和 []rune 型別的相互轉換提供了支援:如utf8.RuneCountInString(str);
-
字串其實是一個結構體,因此字串的賦值操作也就是reflect.StringHeader 結構體的複製過程,並不會涉及底層位元組陣列的複製。
-
高階型別:
-
陣列:[N]T,[...]T
-
切片:[]T
-
字典:map[K]T
-
通道型別:chan T
-
向無快取通道寫入時,會阻塞等待讀取;
-
關閉nil或已關閉通道會引發panic;
-
向已關閉的通道發生資料會引發panic;
-
從已關閉的通道讀取,總是返回0值和false(對於快取通道,先讀取快取的資料);
-
自定義資料型別(可使用type定義):
-
別名:type BuffSize int;BuffSize為新的型別,底層型別與int相同,但是為不同的型別,互操作時要進行型別轉換(BuffSize(var));
-
自定義型別: type Name struct { ... }
c2 := make(chan struct{}) // 空結構體
go func() {
fmt.Println("c2")
c2 <- struct{}{} // struct{}部分是型別, {}表示對應的結構體值
}()
<-c2
interface介面:golang不支援完整的面向物件思想,它沒有繼承,多型完全依賴介面實現;通過介面模擬繼承(本質是組合)。
-
Interface定義一組方法,且不能包含任何變數;
-
只要型別包含了一個介面中的所有方法,那麼這個型別就實現這個介面;
-
如果一個型別含有了多個interface的方法,那麼就實現了多個介面;
-
如果一個型別只含有了一個interface的部分方法,那就沒有實現這個介面。
type User struct {
Name string
Age int32
}
var user User
var user1 *User = &User{}
var user2 *User = new(User)
// 實現Stringer介面(用於Sprintf)
func (p *User) Name() String{
return p.name
}
類似下面的介面,要注意防止遞迴迴圈:
type MyString string
func (m MyString) String() string {
// return fmt.Sprintf("MyString=%s", m) // 錯誤:會無限遞迴
return fmt.Sprintf("MyString=%s", string(m)) // 可以:注意轉換
}
陣列與切片
Go語言中陣列是值語義。一個數組變數即表示整個陣列,它並不是隱式的指向第一個元素的指標(比如C語言的陣列),而是一個完整的值。當一個數組變數被賦值或者被傳遞的時候,實際上會複製整個陣列。
陣列的大小是其型別的一部分,不同長度或不同型別的資料組成的陣列都是不同的型別,型別 [10]int 和 [20]int 是不同的。
切片是可以動態增長和收縮的序列,切片通過對陣列進行封裝,為資料序列提供了更通用、強大而方便的介面。若某個函式將一個切片作為引數傳入,則它對該切片元素的修改對呼叫者而言同樣可見, 這可以理解為傳遞了底層陣列的指標。
切片的容量可通過內建函式 cap 獲得,它將給出該切片可取得的最大長度。
儘管 Append 可修改 slice 的元素,但切片自身(其執行時資料結構包含指標、長度和容量) 是通過值傳遞的。所以append時需要使用返回值重新賦值:
我們還可以定義一個空的陣列,長度為0的陣列在記憶體中並不佔用空間。
var d [0]int // 定義一個長度為0的陣列
var f = [...]int{} // 定義一個長度為0的陣列
控制語句
控制語句包括if、for、switch 與 select,其的左大括號一定緊跟在同一行(不能放在下一行)。
if 和 switch 像 for 一樣可接受可選的初始化語句;沒有圓括號,而其主體必須始終使用大括號括住。
if err := file.Chmod(0664); err != nil {
log.Print(err)
} else { // else必須跟在右括號後面,不能在下一行
...
}
迴圈只有一個更通用的 for
// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }
閉包對捕獲的外部變數並不是傳值方式訪問,而是以引用的方式訪問。這種行為可能會導致一些隱含的問題:
for i := 0; i < 3; i++ {
func(){ println(i) } ()
}
每個函式引用的都是同一個i迭代變數,在迴圈結束後這個變數的值為3,因此最終輸出的都是3。修復的思路是在每輪迭代中為每個函式生成獨有的變數。
for i := 0; i < 3; i++ {
i := i // 定義一個迴圈體內區域性變數i
func(){ println(i) } ()
}
// 通過函式傳入i
for i := 0; i < 3; i++ {
func(i int){ println(i) } (i)
}
Go 沒有逗號操作符,而 ++ 和 -- 為語句而非表示式。 因此,若你想要在 for 中使用多個變數,應採用平行賦值的方式 (因為它會拒絕 ++ 和 --)
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
for key, value := range oldMap {
newMap[key] = value
}
Go 的 switch 比 C 的更通用。其表示式無需為常量或整數,case 語句會自上而下逐一進行求值直到匹配為止;switch 並不會自動下溯(相當於預設有一個隱藏的break),只有在case中明確使用fallthrough關鍵字,才會繼續執行緊跟的下一個case;也 可通過逗號分隔來列舉相同的處理條件。
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
switch 也可用於判斷介面變數的動態型別。
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T", t) // 輸出 t 是什麼型別
case bool:
fmt.Printf("boolean %t\n", t)
case int:
fmt.Printf("integer %d\n", t) // t 是 int 型別
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t 是 *int 型別
}
當 select 有多個分支可用時,會隨機選擇一個可用的管道分支(可用於模擬隨機數生成),如果沒有可用的管道分支則選擇 default 分支,否則會一直儲存阻塞狀態。
for i := 0; i < n; i++ {
select { // 通道中隨機寫入0/1
case c <- 0:
case c <- 1:
}
}
函式
Go 與眾不同的特性之一就是函式和方法可返回多個值。返回值或結果 “形參” 可被命名,就像傳入的形參一樣,可賦值修改。 命名後:就會被初始化為零值; 若該函式執行了一條不帶實參的 return 語句,則結果形參的當前值將被返回。
func ReadFull(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return // 若沒有命名,則必須return n,err
}
從函式中返回一個區域性變數的地址完全沒有問題,這點與 C 不同。該區域性變數對應的資料 在函式返回後依然有效。
錯誤處理
Go語言中的錯誤是一種介面型別。介面資訊中包含了原始型別和原始的值。只有當介面的型別和原始的值都為空的時候,介面的值才對應 nil 。其實當介面中型別為空的時候,原始值必然也是空的;反之,當介面對應的原始值為空的時候,介面對應的原始型別並不一定為空的。在處理錯誤返回值的時候,沒有錯誤的返回值最好直接寫為 nil。
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // 總是返回non-nil error;應直接return nil;
}
恐慌使用panic丟擲,recover用於捕獲panic。recover 函式呼叫有著更嚴格的要求:我們必須在 defer 函式中直接呼叫 recover ;如果是在巢狀的 defer 函式中呼叫 recover 也將導致無法捕獲異常。
示例程式
go程式的基本框架示例
package main // 包名
import( // 引入包
"fmt"
"time"
"context"
"os"
"os/signal" // 使用signal引用包內元素
"syscall"
"strconv"
)
var _ = strconv.Itoa // 防止未使用包報錯
func TryClose(put chan<- int){
defer func() {
if err := recover(); err!=nil{ // 捕獲恐慌,只能在defer中直接捕獲
fmt.Println("Close fail: ", err)
}
}()
close(put)
}
func Producer(fact int, put chan<- int, ctx context.Context){
OutFor:
for i:=1 ; ; i++ {
select{
default:
case <-ctx.Done(): // 是否完成
fmt.Println(ctx.Err())
break OutFor // 跳到迴圈外部,否則只是跳出select
}
put <- fact * i
time.Sleep(100*time.Millisecond)
}
TryClose(put)
}
func Consumer(get <-chan int){
for v := range get {
fmt.Println(v)
}
fmt.Println("Consumer over")
}
type ByteSize float64
const (
_ = iota // 通過賦予空白識別符號來忽略第一個值0
KB ByteSize = 1 << (10 * iota)
MB
GB // 1 << 10*3
)
const MAXSIZE ByteSize = 1024*8
const MINSIZE = 16
func (p ByteSize)String() string{
switch{
case p>=GB:
return fmt.Sprintf("%.2fGB", p/GB)
case p>=MB:
return fmt.Sprintf("%.2fMB", p/MB)
case p>=KB:
return fmt.Sprintf("%.2f", p/KB)
}
return fmt.Sprintf("%.2fB", p)
}
func VarType(info interface{})(result int){
defer func() {result = result+1}()
// if v, ok := info.(int); ok{
// fmt.Println("int: ", v)
// result = v
// }else if v, ok := info.(string); ok{
// fmt.Println("string: ", v)
// }else {
// fmt.Printf("%v\n", info)
// }
// 直接通過switch-type判斷
switch v := info.(type){
case int: fmt.Println("int: ", v)
case string: fmt.Println("string: ", v)
default: fmt.Printf("Deufalt: %v\n", info)
}
return // 0,實際返回1,因defer在return之後執行
}
func ForTest(que []int){
for _, v := range que{
v := v // 若不重新定義,會全部輸出最後一個值
go func() {
fmt.Println(v)
}()
}
}
func main(){
ch := make(chan int, 10)
// 1秒鐘後超時,若在此之前cancel,也會退出
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
go Producer(3, ch, ctx)
go Producer(5, ch, ctx)
go Consumer(ch)
// 不同變數定義方式
var zero = 0
var one, two int
three, four := 3, 4
ksize := ByteSize(1024*1024*8)
fmt.Println(zero, one, two, three, four, ksize)
VarType(1)
VarType("test")
ForTest([]int{1, 2, 3})
// Ctrl+C to quit
fmt.Println("Press Ctrl+C to quit:")
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
fmt.Printf("quit (%v)\n", <-sig) // 等待訊號
cancel()
}