Golang編程基礎第一篇——Golang快入門

目錄

 

一,爲什麼我們需要一門新語言

二,順序編程

2.1 變量

2.2 常量

2.3 類型

2.4 流程控制

2.5 函數

2.6 錯誤處理 (golang的錯誤處理適合單出一篇)


以go語言編程爲基底,記錄下心得 需要後期維護和積累

一,爲什麼我們需要一門新語言

從摩爾定律說起,爲什麼我們需要一門新語言。

摩爾定律(英語:Moore's law)是由英特爾(Intel)創始人之一戈登·摩爾提出的。其內容爲:集成電路上可容納的晶體管數目,約每隔兩年便會增加一倍;經常被引用的“18個月”,是由英特爾首席執行官大衛·豪斯(David House)提出:預計18個月會將芯片的性能提高一倍(即更多的晶體管使其更快),是一種以倍數增長的觀測。

如果不能很快速理解的話,可以參考wiki總結的摩爾定律規律:

摩爾定律的定義歸納起來,主要有以下三種版本:
1,集成電路上可容納的晶體管數目,約每隔18個月便增加一倍。
2,微處理器的性能每隔18個月提高一倍,或價格下降一半。
3,相同價格所買的電腦,性能每隔18個月增加一倍。

然而隨着器件尺寸越來越接近物理極限,科學家們開始研究新的工藝結構,具體做法不用細究,總體上隨着新工藝節點的不斷推出,晶體管中原子的數量已經越來越少,因爲在較小規模上一些量子特性開始出現導致漏電等一系列問題,並且精細晶體管成本增加,製造商開始着手從其他方面提高處理器的性能:
1. 向處理器添加越來越多的內核
2. 超線程技術
3. 爲處理器加入緩存

Go做了什麼?

同時需要提高軟件的效率來解除硬件限制的瓶頸,其中就有09年推出的go

我們現在常用的編程語言大多誕生於單線程時代,即使推出適用於多核編程框架,如netty卻會耗費大量精力去學習和運用(編程語言一個最大的比較點就是學習週期的長短)

而Go語言的併發是基於 goroutine 的,goroutine 類似於線程,但並非線程。可以將 goroutine 理解爲一種虛擬線程。Go 語言運行時會參與調度 goroutine,並將 goroutine 合理地分配到每個 CPU 中,最大限度地使用CPU性能。開啓一個goroutine的消耗非常小(大約2KB的內存),你可以輕鬆創建數百萬個goroutine。

goroutine的特點:

goroutine具有可增長的分段堆棧。這意味着它們只在需要時纔會使用更多內存。
goroutine的啓動時間比線程快。
goroutine原生支持利用channel安全地進行通信。
goroutine共享數據結構時無需使用互斥鎖。

從性能上看,go的性能更接近於java(單核運行某些還是沒有java強,但特點是多核處理),而又同C一樣是編譯型語言執行效率更高。當然go在細節處理上也有很多改進,以後補坑。

二,順序編程

我覺得學習編程語言不要拿教科書照本宣科,要充分利用語言間的共通性,自己用實例觀察語言間的相同和不同,想不通的時候想想編譯原理,語言細節上的改變就很自然懂了。看順序編程前要先熟悉go的關鍵字和基本保留信息:https://www.runoob.com/go/go-program-structure.html

2.1 變量

2.1.1 變量聲明

以下是go語言的變量聲明格式

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    //函數 輸入int a,返回值爲int

聲明也可以這麼寫:

var {
    v1 int
    v2 string
    v3 [10]int
}

2.1.2 變量初始化

var v1 int = 10
var v2 = 10    //編譯器自動推導類型
v3 := 10    //編譯器自動推導類型,:=是go特有可以同時聲明變量和初始化
var v4 int //0

2.1.3 變量賦值

除了一般等號賦值之外,go還可以多重賦值:

//交換i,j兩個值
i , j = j , i

2.1.4 匿名變量

func GetName(b boolean) (firstName, lastName, nickName string) {
    return "James", "Lebron", "LJ"
}

//I just want the nickName
_, _, nickName := GetName()

2.2 常量

2.2.1 常量定義

const PI float64 = 3.14159265
//go的常量定義可以是一個編譯期運算的常量表達式
const mask = 1 << 3

2.2.2 預定義變量

/* 
go預定義常量:true,false,iota
其中iota比較特殊也比較好玩,是一個可被編譯期修改的常量
在每一個const出現時重置爲0,
隨後每出現一次iota,其所代表的數字就會增1
*/
 const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   //獨立值"ha",iota +=1
            e          //"ha"   iota +=1
            f = 100    //100    iota +=1
            g          //100    iota +=1
            h = iota   //7,恢復計數
            i          //8
    )

2.3 類型

2.3.1 布爾類型

//布爾型很常規,記住賦值和關鍵字
var v1 bool
v1 = true
v2 := (1 == 2)

2.3.2 整型

go的整型都有哪些類型?

這裏要注意,兩個不同類型的整型數不能直接比較和賦值,需要進行強制轉換

var i int 32
j := 64    //編譯識別爲int
i = int32(j)
if i == int32(j) {
    fmt.Println("i == j")
}

關於位運算,go和c的情況大致相同,只不過取反操作go語言是^x而不是~x

2.3.3 浮點型

go的浮點型都有那些數據類型?

go只有float32(相當於c的float)和float64(相當於c的double類型)

那go編譯自動推導的浮點型類型是哪個呢?

var value float32
value = 12
value2 := 12.0 //推導成float64
value = float32(value2)

另外,浮點數比較是一種不精確的表達方式(挖坑:浮點數是怎樣存儲的),因此不能用==直接比較

//一種替代方式
import "math"

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

2.3.4 複數類型

複數由實部和虛部組成,在計算機中用浮點數表示

go中複數初始化

var value1 complex64    //由兩個float32組成的複數
value1 = 3.2 + 12i
value2 := 3.2 + 12i
value3 := complex(3.2, 12)

2.3.5 字符串

在go語言中,字符串也是一種基本類型

聲明和初始化:

var str string
str = "Hello string"
ch := str[0]    //'H'

字符串的內容不能在初始化後被修改

字符串類型內置len()函數獲取字符串長度

字符串遍歷:

str := "Hello, world"
n := len(str)
for i := 0; i < n; i++ {
    ch := str[i]    //這裏注意是取字符串中的字符,類型爲byte
    fmt.Println(i, ch)
}

2.3.6 字符類型

go中支持兩個字符類型,一個是byte(uint8),另一個是rune,代表單個unicode字符

go中多數api都假設字符是utf8編碼,go也提供了unicode和utf8之間的轉換包

2.3.7 數組

數組聲明:

[32]byte    //長度爲32的byte數組
[2*N] struct {x, y int32}    //複雜類型數組 每個結構體是int類型的x和y組成
[1000]*float64    //指針數組
[3][5]int    //二維數組
[2][2][2]float64    //三維數組,相當於[2]([2]([2]float64))

數組訪問:

//1.常規訪問
for i := 0; i < len(array); i++ {
    fmt.Println(i + " " + array[i])
}

//2.range遍歷 range有兩個返回值 數組下標和對應的值
for i, v := range array {
    fmt.Println(i + " " + v)
}

值類型:

go中數組是一個值類型(挖坑,值類型與引用類型),因此函數體無法修改傳入數組的內容,因爲函數內操作的只是傳入數組的一個副本

package main

import "fmt"

func modify(array [10]int) {
    array[0] = 0
}

func main() {
    array := [5]int{1,2,3,4,5}
    modify(array)
    fmt.Println(array)    //1 2 3 4 5
}

2.3.8 數組切片

數組的特點是數組長度在定義之後無法修改,而go提供了數組切片來滿足這一需求,那什麼是數組切片呢?

數組切片的數據結構可以抽象爲3個變量:

1. 一個指向原生數組的指針

2. 數組切片中元素的個數

3. 數組切片已分配的存儲空間

它與數組間的關係就相當於C++的std::vector之於數組,可以動態擴放存儲空間

1. 創建與聲明

/*
根據數組創建切片
*/
var myArray [10]int = [10]int{1,2,3,4,5,6,7,8,9,0}
var mySlice []int = myArray[:5]    //前5個元素創建切片
// var mySlice []int = myArray[5:]    //後5個元素創建切片
// var mySlice []int = myArray[:]    //全部元素創建切片
for _, v := range myArray {
    fmt.Print(v, " ")
}

/*
直接創建
*/
mySlice := make([]int, 5)    //創建初始個數爲5的切片 元素值爲0
mySlice := make([]int, 5, 10)    //創建一個。。。並預留10個元素的存儲空間
mySlice := []int{1,2,3,4,5}    //直接初始化 和數組一樣 不過沒有size限制

/*
數組切片創建數組切片
*/
oldSlice := []int{1,2,3,4,5}
newSlice := oldSlice[:3]     //基於oldSlice前3個元素創建

2. 動態增減元素

mySlice := make([]int, 5, 10)
mySlice = append(mySlice, 1, 2, 3)
mySlice2 := []int{8,9,0}
mySlice.append(mySlice, mySlice2...)
//數組切片會自動處理存儲空間不足的情況

4. 內容複製

slice1 := []int{1,2,3,4,5}
slice2 := []int{6,7,8}

copy(slice2, slice1)    //複製slice1的前3個元素到slice2中
copy(slice1, slice2)    //複製slice2的元素到slice1的前3個位置

2.3.9 map

在C++/Java中map是引入庫的形式使用,而go直接定義成數據類型

package main

import "fmt"

type PersonInfo struct {
    ID string
    Name string
    Address string
}

func main() {
    //變量聲明及創建
    var personDB map[string] PersionInfo
    personDB = make(map[string] PersonInfo)
    
    //賦值
    personDB["2345"] = PersonInfo{"2345", "Tom", "Room 203"}
    personDB["1"] = PersonInfo{"1", "Jack", "Room 201"}
    
    //刪除
    delete(personDB, "1")

    //元素查找
    person, ok := personDB["1234"]
    
    //ok is a bool
    if ok {
        fmt.Println("Found Person", person.Name. "With ID 1234")
    } else {
        fmt.Println("Did not find person with ID 1234")
    }
}

2.4 流程控制

2.4.1 條件從句

關於條件語句,需要注意以下幾點:
1. 條件語句不需要使用括號將條件包含起來();
2.無論語句體內有幾條語句,花括號{}都是必須存在的;
3. 左花括號{必須與if或者else處於同一行;
4. 在if之後,條件語句之前,可以添加變量初始化語句,使用;間隔;
5. 在有返回值的函數中,不允許將“最終的”return語句包含在if...else...結構中

if a < 5 {
    return 0
} else {
    return 1
}

2.4.2 選擇語句

在使用switch結構時,我們需要注意以下幾點:
1. 左花括號{必須與switch處於同一行;
2. 條件表達式不限制爲常量或者整數;
3. 單個case中,可以出現多個結果選項;
4. 與C語言等規則相反,Go語言不需要用break來明確退出一個case;
5. 只有在case中明確添加fallthrough關鍵字,纔會繼續執行緊跟的下一個case; 可以不設定switch之後的條件表達式,在此種情況下,整個switch結構與多個if...else...的邏輯作用等同。

switch i {
    case 0:
        fmt.Println("1")
        fallthrough
    case 1:
        fmt.Println("2"
}

2.4.3 循環語句

使用循環語句時,需要注意的有以下幾點。
1. 左花括號{必須與for處於同一行。
2. Go語言中的for循環與C語言一樣,都允許在循環條件中定義和初始化變量,唯一的區別是,Go語言不支持以逗號爲間隔的多個賦值語句,必須使用平行賦值的方式來初始化多個變量。
 

a := []int{1,2,3,4,5,6}
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}

2.4.4跳轉語句

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

2.5 函數

函數的不定參數指函數傳入參數個數爲不定數量

func myFunc(args ...int) {
    for _, arg := range args {
        fmt.Println(args)
        //按原樣傳參
        myFunc2(args...)
        //傳遞片段
        myFunc3(args[1:]...)
    }
}

如上邊這個函數就可以接受不定數量的參數,但參數類型必須全部是int

如果你想傳遞任意類型,可以指定類型爲interface{}

下面是go語言標準庫fmt.Println()函數原型

func Printf(format string, args ...interface{}) {
    //...
}

2.6 錯誤處理 (golang的錯誤處理適合單出一篇)

2.6.1 error接口

error是go的一個關於錯誤處理的標準模式

type PathError struct {
    Op string
    Path string
    Err error
}

func Stat(name string) (fi FileInfo, err error) {
    var stat syscall.Stat_t
    err = syscall.Stat(name, &stat)
    if err != nil {
        return nil, &PathError{"stat", name, err}
    }
    return fileInfoFromStat(&stat, name), nil
}

2.6.2 defer

defer是go引入的的幫助程序員做複雜清理工作的工作

defer後面的語句會在defer所在函數結束後調用

一個應用場景是互斥鎖解鎖:

func foo(...) {
    mu.Lock()
    defer mu.Unlock()

    // code logic
}

2.6.3 panic 和 recover

內置函數panic()和recover()用於報告和處理運行時錯誤和程序錯誤的場景,處理錯誤流程

當在一個函數執行過程中調用panic()函數時,正常的函數執行流程將立即終止,但函數中 之前使用defer關鍵字延遲執行的語句將正常展開執行,之後該函數將返回到調用函數,並導致 逐層向上執行panic流程,直至所屬的goroutine中所有正在執行的函數被終止。錯誤信息將被報 告,包括在調用panic()函數時傳入的參數,這個過程稱爲錯誤處理流程。

recover()函數用於終止錯誤處理流程。一般情況下,recover()應該在一個使用defer 關鍵字的函數中執行以有效截取錯誤處理流程。如果調用了內置函數recover,並且定義該defer語句的函數發生了panic異常,recover會使程序從panic中恢復,並返回panic value。導致panic異常的函數不會繼續運行,但能正常返回。在未發生panic時調用recover,recover會返回nil。

下面是一個例子,猜猜輸出是什麼

package main
 
import "fmt"
 
func main() {
    f()
    fmt.Println("Returned normally from f.")
}
 
func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}
 
func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

補充:

go語言指針

我們都知道,變量是一種使用方便的佔位符,用於引用計算機內存地址。

Go 語言的取地址符是 &,放到一個變量前使用就會返回相應變量的內存地址。

一個指針變量指向了一個值的內存地址。

類似於變量和常量,在使用指針前你需要聲明指針。指針聲明格式如下:

var ip *int

指針使用流程:

  • 定義指針變量。
  • 爲指針變量賦值。
  • 訪問指針變量中指向地址的值。

在指針類型前面加上 * 號(前綴)來獲取指針所指向的內容。

 空指針在go裏是nil

 

參考:go語言編程-許式偉

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章