《Go語言編程》 筆記

2016.09.08

初識Go語言

Go語言的主要特性

  • 自動垃圾回收
  • 更豐富的內置類型 數組、字符串、map等
  • 函數多返回值
  • 錯誤處理
  • 匿名函數和閉包
  • 類型和接口
  • 併發編程
  • 反射
  • 語言交互性

Hello, World!

package main

import "fmt"

func main() {
    fmt.Println("Hello, world!")
}

導入沒有用到的包會編譯錯誤

函數:

func 函數名(參數列表)(返回值列表) {
    // 函數體
}
e.g.
func Compute(value1 int, value2 float64)(result float64, err error) {
    // 函數體
}

註釋同C++

左花括號的不能另起一行放置,否則會編譯錯誤

編譯程序

例如:
hello.go爲源文件

編譯並運行程序

$ go run hello.go

只生成編譯結果

$ go build hello.go

運行

$ ./hello

工程管理

計算器例子,目錄結構:

<calcproj>
 |-<src>
    |-<calc>
       |-calc.go
    |-<simplemath>
       |-add.go
       |-add_test.go   # add.go的單元測試
       |-sqrt.go
       |-sqrt_test.go
 |-<bin>
 |-<pkg>  # 包被安裝在此處

把工程根目錄<calcproj>添加到GOPATH環境變量中

把生成文件放到<bin>目錄中

$ mkdir bin
$ cd bin
$ go build calc

不需要編寫makefile工具,Go命令行工具會自動分析包的依賴關係,在編譯calc.go之前根據import語句先把依賴的simplemath編譯打包好。

執行單元測試

$ test simplemath

問題追蹤和調試

打印日誌

fmt.Println()

GDB調試
詳情參考GDB的用法

$ gdb calc

順序編程

變量聲明

var v1 int
var v2 string
var v3 [10]int  // 數組
var v4 []int    // 數組切片
var v5 struct {
    f int
}
var v6 *int     // 指針
var v7 map[string]int // map key - string value - int
var v8 func(a int) int

語句結尾可以加分好也可以不加

聲明多個變量

var (
    v1 int
    v2 string 
)

變量初始化

var關鍵字可有可無

var v1 int = 10
var v2 = 10  // 編譯器可以自動推導v2的類型
v3 := 10

多重賦值

i, j = j, i // 交換i和j變量的值

多返回值和匿名變量

func GetName() (firstName, lastName, nickName string) {
    return "May", "Chan", "Chibi Maruko"
}

如果只想獲得nickName,則

_, _, nickName := GetName()

常量

編譯期間就已知且不可改變的值

例:

const Pi float64 = 3.14
const zero = 0.0
const (
    size int64 = 1024
    eof = -1
)
const u, v float32 = 0, 3
const a, b, c = 3, 4, "foo"
const mask = 1 << 3

預定義常量

true
false
iota 在每一個const關鍵字出現時被重置爲0,然後在下一次const出現之前,每出現一次iota,其代表的數字就會自動加1,用法如下

const (
    c0 = iota  // c0 == 0
    c1 = iota  // c1 == 1
    c2 = iota  // c2 == 2
)

const (
    a = 1 << iota  // a == 1
    b = 1 << iota  // b == 2
    c = 1 << iota  // c == 4
)

const x = iota  // x == 0
const y = iota  // y == 0

// 如果兩個const的賦值語句的表達式是一樣的,那麼可以省略後一個賦值表達式
const (
    c0 = iota   // c0 == 0
    c1          // c1 == 1
    c2          // c2 == 2
)

枚舉

const (
    Sunday = iota
    Monday
    Tuesday
    numberOfDays   // 大寫字母開頭的常量在包外可見,小寫字母開頭爲包內私有
)

類型

內置的基礎類型:

bool
int8 byte(uint8) int16 uint16 int32 uint32 
int64 uint64 
int uint 字節大小與平臺相關
uintptr 指針 32下4字節,64下8字節
float32 float64
complex64 complex128 複數類型
string
rune  字符類型
error 錯誤類型

pointer 指針
array 數組
slice 切片
map   字典
chan  通道
struct 結構
interface 接口

布爾類型

布爾類型不支持自動或強制類型轉換

var b bool
b = 1  // 編譯錯誤
b = bool(1)  // 編譯錯誤

整型

var value2 int32
value1 := 64    // 自動推導爲int類型
value2 = value1 // 編譯錯誤,int int32 被認爲是不同類型
value2 = int32(value1)  // 編譯通過

兩種不同類型的整數不能直接比較
各種類型的整數變量可以直接和字面常量進行比較

位運算符除了取反運算符外其他都一樣,Go中取反運算符爲:

^x

浮點數的比較

import "math"

// p爲用戶自定義的比較精度,比如0.00001
func IsEqual(f1, f2, p float64) bool {
    return math.Fdim(f1, f2) < p
}

複數類型

var value1 complex64
value1 = 3.2 + 12i
value2 := 3.2 + 12.i
value3 := complex(3.2, 12)

// 獲得實部和虛部
z = complex(x, y)
real(z)
imag(z)

Go源文件必須用UTF-8

字符串類型

常用操作

x + y  "Hello" + "123"
len(s) len("Hello")
s[i]   "Hello"[1]

字符串遍歷

// 一共會輸出13個字節,utf-8中文字符佔3個字節
str := "Hello, 世界"
n := len(str)
for i := 0; i < n; i++ {
    ch := str[i]  // 一個字節
    fmt.Println(i, ch)
}

// 以Unicode字符方式遍歷,輸出9個
str := "Hello, 世界"
for i, ch := range str {
    fmt.Println(i, ch)  // ch的類型爲rune
}

字符類型

byte 一個字節
rune 一個Unicode字符

數組

數組聲明方法:

[32]byte    // 長度爲32的數組,每個元素一個字節
[2*N] struct { x, y int32 } // N估計是一個常量
[1000]*float63  // 指針數組
[3][5]int       // 二維數組,3行5列

數組長度

arrLength := len(arr)

range關鍵字遍歷數組

for i, v := range array {
    fmt.Println("Array element[", i, "]=", v)
}

Go語言中數組是值類型,傳遞參數時會產生一次複製動作。

數組切片

類似std::vector動態數組

數組的的長度在定以後無法修改,數組是值類型。
數組切片坯布數組的不足
數組切片就像一個指向數組的指針,實際上它擁有自己的數據結構

創建切片

var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var mySlice1 []int = myArray[:5] // 1, 2, 3, 4, 5
var mySlice2 []int = myArray[:]  // 所有元素
var mySlice3 []int = myArray[5:] // 6, 7, 8, 9, 10

mySlice4 := make([]int, 5)     // 5個元素,初始值0
mySlice5 := make([]int, 5, 10) // 5個元素,初始值0,並預留10個元素的存儲空間
mySlice6 := []int{1, 2, 3, 4, 5} // 創建並初始化5個元素

元素遍歷

for i := 0; i < len(mySlice); i++ {
    fmt.Println(mySlice[i])
}

for i, v := range mySlice {
    fmt.Println(i, v)
}

動態增減元素

cap()函數返回數組切片分配的空間大小
len()函數返回元素個數

增加元素

// 在尾端增加3個元素
mySlice = append(mySlice, 1, 2, 3)     

// 在尾端追加另一個數組切片
// 三個點是必須的,因爲參數只接受元素類型
// 三個點...表示將數組切片的元素打散後傳入
mySlice = append(mySlice, mySlice2...) 

基於數組切片創建數組切片

// 只要範圍不超過`cap()`返回的大小就是合法的
oldSlice := []int{1, 2, 3, 4, 5}
newSlice := oldSlice[:3]

內容複製

copy()將內容從一個數組切片複製到另一個數組切片

// 按較小的那個數組切片的元素個數進行復制
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1)  // 複製slice1的前三個元素到slice2中
copy(slice1, slice2)  // 複製slice2的前三個元素到slice1中

map

map爲內置類型,不需要引入庫

package main
import "fmt"

// PersonInfo是一個包含個人詳細信息的類型
type PersonInfo struct {
    ID string
    Name string
    Address string
}

func main() {
    var personDB map[string] PersonInfo
    personDB = make(map[string] PersonInfo)

    // 插入數據
    personDB["12345"] = PersonInfo{"12345", "Tom", "Room 203"}
    personDB["1"] = PersonInfo{"1", "Jack", "Room 101"}

    // 查找
    person, ok := personDB["1234"]
    if ok {
        fmt.Println("person.Name")
    } else {
        fmt.Println("Did not find person with ID 1234")
    }

}

元素刪除

delete(myMap, "1234")

流程控制

條件語句

花括號是必須存在的
左花括號必須與if或else處於同一行

if a < 5 {
    // ...
} else if a < 10 {
    // ...
} else {
    // ...
}

選擇語句

不使用break
如果繼續執行緊跟的下一個case,使用fallthrough關鍵字

switch i {
case 0:
    // ...
case 1:
    // ...
default:
    // ...
}

循環語句

for i := 0; i < 10; ++i {
    // ...
}

// 無限循環
for {
    // ...
}

// break
for j := 0; j < 5; j++ {
    for i := 0; i < 10; i++ {
        if i > 5 {
            break end
        }
        fmt.Println(i)
    }
}
end:
// ...

跳轉語句

func myfunc() {
    i := 0
    here:
    fmt.Println(i)
    i++
    if i < 10 {
        goto here
    }
}

函數

函數定義

func MyFunc(a int, a int) (ret1 int, ret2 int) {
    return a + b, a - b
}

// 參數類型一樣時,可以寫爲a, b int
// 只有一個返回值時:
func MyFunc(a, b int) int {
    return a + b
}

函數、類型、變量,小寫字母開頭只在本包內可見

面向對象編程

爲類型添加方法

// 定義一個新類型Integer
// 它和int沒有本質不同,只是它爲內置的int類型增加了個新方法Less()
type Interger int
func (a Interger) Less(b Interger) bool {
    return a < b
}

func main() {
    var a Interger = 1
    if a.Less(2) {
        fmt.Println(a, "Less 2")
    }
}
func (a *Interger) Add(b Interger) {
    *a += b
}

func main() {
    var a Interger = 1
    a.Add(2)
}
var a = [3]int{1, 2, 3}
var b = &a
b[1]++
fmt.Println(a, *b)
// 該程序的運行結果如下:
// [1 3 3] [1 3 3]
type Rect struct {
    x, y float64
    width, height float64
}

// 定義成員方法Area()來計算矩形的面積:
func (r *Rect) Area() float64 {
    return r.width * r.height
}

// 創建並初始化Rect類型的對象實例
rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width: 100, height: 200}

在Go語言中沒有構造函數的概念,對象的創建通常交由一個全局的創建函數來完成,以
NewXXX來命名,表示“構造函數”:

func NewRect(x, y, width, height float64) *Rect {
    return &Rect{x, y, width, height}
}

匿名組合 - 繼承的實現

以下代碼定義了一個Base類(實現了Foo()和Bar()兩個成員方法),然後定義了一個
Foo類,該類從Base類“繼承”並改寫了Bar()方法(該方法實現時先調用了基類的Bar()
方法) 。

在“派生類” Foo沒有改寫“基類” Base的成員方法時,相應的方法就被“繼承”,例如在下面的例子中,調用foo.Foo()和調用foo.Base.Foo()效果一致。

type Base struct {
    Name string
}
func (base *Base) Foo() { ... }
func (base *Base) Bar() { ... }
type Foo struct {
    Base
    // ...
}
func (foo *Foo) Bar() {
    foo.Base.Bar()
    // ...
}

接口

在Go語言中,一個類只需要實現了接口要求的所有函數,我們就說這個類實現了該接口,例如:

type File struct {
    // ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error)
func (f *File) Close() error

這裏我們定義了一個File類,並實現有Read()、 Write()、 Seek()、 Close()等方法。設想我們有如下接口:

type IFile interface {
    Read(buf []byte) (n int, err error)
    Write(buf []byte) (n int, err error)
    Seek(off int64, whence int) (pos int64, err error)
    Close() error
}

type IReader interface {
    Read(buf []byte) (n int, err error)
}

type IWriter interface {
    Write(buf []byte) (n int, err error)
}

type ICloser interface {
    Close() error
}

儘管File類並沒有從這些接口繼承,甚至可以不知道這些接口的存在,但是File類實現了這些接口,可以進行賦值:

var file1 IFile = new(File)
var file2 IReader = new(File)
var file3 IWriter = new(File)
var file4 ICloser = new(File)

接口查詢

這個if語句檢查file1接口指向的對象實例是否實現了two.IStream接口,如果實現了,則執行特定的代碼。

var file1 Writer = ...
if file5, ok := file1.(two.IStream); ok {
    // ...
}

在Go語言中,你可以詢問接口它指向的對象是否是某個類型,比如:
這個if語句判斷file1接口指向的對象實例是否是*File類型,如果是則執行特定代碼。

var file1 Writer = ...
if file6, ok := file1.(*File); ok {
    // ...
}

類型查詢

var v1 interface{} = ...
    switch v := v1.(type) {
        case int: // 現在v的類型是int
        case string: // 現在v的類型是string
        // ...
}

Any類型

由於Go語言中任何對象實例都滿足空接口interface{},所以interface{}看起來像是可
以指向任何對象的Any類型,如下:

var v1 interface{} = 1 // 將int類型賦值給interface{}
var v2 interface{} = "abc" // 將string類型賦值給interface{}
var v3 interface{} = &v2 // 將*interface{}類型賦值給interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}

由於Go語言初始定位爲高併發的服務器端程序,尚未在GUI的支持上花費大量的精力,而當前版本的Go語言標準庫中沒有提供GUI相關的功能,也沒有成熟的第三方界面庫,因此不太適合開發GUI程序。

待續

發佈了34 篇原創文章 · 獲贊 21 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章