go學習(二)Go語言基礎


1、語法

(1)變量定義和初始化

var a int //標准,類型放后面:var 變量名 類型
a = 10

var a int = 10 //標准

var a = 10 //省略type

a,b := 10,"s" //短變量聲明,最簡化,【只能在函數內用,函數外不可以】。注意:直接a=10會報錯undefined,:=左側是新變量,=左側是已聲明變量

(2)bool類型

與C或Java不同,Go的整型和布爾型之間壓根就沒關系。
Go語言中不允許將整型強制轉換為布爾型
布爾值並不會隱式轉換為數字值 0 或 1,反之亦然

(3)整型

a := 3.1415e2 // 314.15,科學計數法表示
b := int64(a) // 強轉語法

(4)交換

a,b = b,ap

(5)defer

https://www.cnblogs.com/phpper/p/11984161.html

defer特性:

1. 關鍵字 defer 用於注冊延遲調用。
2. 這些調用直到 return 前才被執。因此,可以用來做資源清理。
3. 多個defer語句,按[先進后出]的方式執行。
4. defer語句中的變量,在defer聲明時就決定了。

defer用途:

1. 關閉文件句柄
2. 鎖資源釋放
3. 數據庫連接釋放

潛在問題

https://www.jianshu.com/p/f897e69f5504

defer x.Close() 會忽略它的返回值,但在執行 x.Close() 時,我們並不能保證 x 一定能正常關閉,萬一它返回錯誤應該怎么辦?這種寫法,會讓程序有可能出現非常難以排查的錯誤。

(6)Go 語言 for 循環

Go 語言的 For 循環有 3 種形式,只有其中的一種使用分號。

和 C 語言的 for 一樣:

包括for i 和 for range兩種形式。

// for i
for init; condition; post { }

// for range
// for 循環的 range 格式可以對 slice、map、數組、字符串等進行迭代循環。格式如下:
for key, value := range oldMap {
    newMap[key] = value
}

和 C 的 while 一樣:

for condition { }

和 C 的 for( ; ; ) 一樣:

for { }
  • init: 一般為賦值表達式,給控制變量賦初值;
  • condition: 關系表達式或邏輯表達式,循環控制條件;
  • post: 一般為賦值表達式,給控制變量增量或減量。

Go語言類型別名 vs 類型定義

參考:

https://studygolang.com/articles/22667?fr=sidebar

http://www.weixueyuan.net/a/481.html

  1. 類型別名是 Go1.9 版本添加的新功能。之前都是類型定義。
  2. 類型定義會形成一種新的類型,編譯后依然有該類型。
  3. 別名類型只會在代碼中存在,編譯后不會有別名類型。
type myInt int // 類型定義,a 的類型是 main.myInt,表示main 包下定義的myInt 類型。
type intAlias = int // 類型別名,b 的類型是 int 。intAlias 類型只會在代碼中存在,編譯完成時,不會有 intAlias 類型

2、復合數據類型

(1)數組

數組和Slice區別:指定長度為數組,不指定長度為切片。

定義數組:

var variable_name [SIZE] variable_type // 格式
var balance [10] float32

(2)Slice

定義切片,兩種方式:

var identifier []type // 不指定長度。identifier := []int,會報錯!

var slice1 []type = make([]type, len)
slice1 := make([]type, len) // make創建,指定長度
slice2 := make([]T, length, capacity) // 也可以指定容量capacity,可選。  默認初始為[0,0,...]。

// 補充:
capacity vs length:slice是個fat array,正是因為它有len和cap屬性才能實現動態擴縮。
  length:已使用的長度
  capacity:容量
    假如沒有cap,只有len,怎么知道是否需要擴容呢?
    但是一般都不怎么需要關注cap。
    特殊場景關注cap:https://github.com/valyala/bytebufferpool

初始化:

s :=[] int {1,2,3} 
s := arr[:] // 初始化切片 s,是數組 arr 的引用。

s := arr[startIndex:endIndex] // 將 arr 中從下標 startIndex 到 endIndex-1 下的元素創建為一個新的切片。
s := arr[startIndex:] // 默認 endIndex 時將表示一直到 arr 的最后一個元素。
s := arr[:endIndex] // 默認 startIndex 時將表示從 arr 的第一個元素開始。

初始化地址:

a := make([]int, 2)
println(a) // 實質就是&a[0],前面加個len
println(&a[0])
println(&a[1])
println(&a)

輸出結果:
[2/2]0xc00003ff48
0xc00003ff48
0xc00003ff50
0xc00003ff60

Slice傳參

(3)Map

  1. map類型可以寫為map[K]V

  2. 其中K對應的key必須是支持==比較運算符的數據類型,所以map可以通過測試key是否相等來判斷是否已經存在

  3. 浮點數最好不要作為key:類型也是支持相等運算符比較的,但是將浮點數用做key類型則是一個壞的想法,正如第三章提到的,最壞的情況是可能出現的NaN和任何浮點數都不相等

  4. 創建map

    // 1. 創建+賦值:內置的make函數
    ages := make(map[string]int)
    ages["alice"] = 31
    ages["charlie"] = 34
    // 2. 創建+初始化:
    age2 := map[string]int{
    	"alice":   31,
    	"charlie": 34,
    }
    //3. 創建空map
    age3 := map[string]int{}
    
  5. 訪問map

    // Map中的元素通過key對應的下標語法訪問:
    ages["alice"] = 32
    fmt.Println(ages["alice"]) 
    // 使用內置的delete函數可以刪除元素:
    delete(ages, "alice")
    

(4)結構體

一個命名為S的結構體類型將不能再包含S類型的成員:因為一個聚合的值不能包含它自身。(該限制同樣適用於數組)
但是S類型的結構體可以包含*S指針類型的成員,這可以讓我們創建遞歸的數據結構,比如鏈表和樹結構等。

創建一個結構體變量

// 方法1。 &Point{1, 2}寫法可以直接在表達式中使用,比如一個函數調用。
pp := &Point{1,2}

// 方法2
pp := new(Point)
*pp = Point{1,2}

結構體賦值,如果換行,最后要有",",如果不換行,最后不要有","

type a struct {
	AA string
	BB string
}

var S1 = a{
	AA: "ddd",
	BB: "sss", // 最后有逗號
}
var S2 = a{AA: "ddd", BB: "sss"}// 最后沒有逗號

結構體作為參數

結構體可以作為函數的參數和返回值。
如果要在函數內部修改結構體成員的話,用指針傳入是必須的;因為在Go語言中,所有的函數參數都是值拷貝傳入的,函數參數將不再是函數調用時的原始變量。

(5)JSON

(6)文本和HTML模板

3、函數

func name(parameter-list) (result-list) {
    body
}

(1)Go語言沒有默認參數值

(2)沒有函數體的函數聲明,這表示該函數不是以Go實現的。這樣的聲明定義了函數標識符。

package math

func Sin(x float64) float //implemented in assembly language

(3)大寫字母開頭是公有,小寫字母開頭為私有。

4、方法

(1)接收器(receiver):相當於Java中的this或Python中的self,可以任意的選擇接收器的名字。

(2)方法就是在函數名前加個該類的接收器:

type Point struct{ X, Y float64 }

// traditional function
func Distance(p, q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

// Point類型的方法,p是接收器,一般就用類型的首字母
func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

(3)函數調用,會對每一個參數值進行拷貝,參數太大可以用指針避免這種默認拷貝。接收器也是參數。當接收器太大時,可以用其指針而不是對象來聲明方法:

func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
}

(4)在現實的程序里,一般會約定如果Point這個類有一個指針作為接收器的方法,那么所有Point的方法都必須有一個指針接收器,即使是那些並不需要這個指針接收器的函數。

一個方法用的是指針接收器,所有方法都要是指針接收器。

5、接口interface

data := map[string]interface{}{
    "lang": "GO語言",
    "tag":  "<br>",
}

6、第八章 Goroutines和Channels

6.1引言

  1. Go語言中的並發程序可以用兩種手段來實現,分別是第八章和第九章。
  2. 本章講解goroutine和channel,其支持“順序通信進程”(communicating sequential processes)或被簡稱為CSP。
  3. CSP是一種現代的並發編程模型,在這種編程模型中值會在不同的運行實例(goroutine)中傳遞,盡管大多數情況下仍然是被限制在單一實例中
  4. 第9章覆蓋更為傳統的並發模型:多線程共享內存,如果你在其它的主流語言中寫過並發程序的話可能會更熟悉一些。

6.2

6.3

6.4Channels

創建channel

ch := make(chan int) // make函數,我們可以創建一個channel

操作channel

發送和接收兩個操作都使用<-運算符

ch <- x // 把x發送到channel中
x = <- ch // x接收channel中的值
<- ch // 接收channel值,匿名,eg, fmt.Println(<- ch)

channel死鎖(deadlock)的情況

(1)聲明channel時,沒有說明具體的大小,會導致在存儲數據時死鎖

//make(chan type ,size)//第二個參數為存儲的大小

//正確情況:指定尺寸為1,同一個線程就可以讀寫同一個channel,不會死鎖。
func main() {
  ch := make(chan int, 1)
  ch <- 1
  fmt.Println(<-ch)
  fmt.Println("lalalala")
  time.Sleep(3 * time.Second)
}
// 運行結果:
1
lalalala

下面是不指定size時,幾種死鎖的情況,並且采用多線程解決:

func main() {
  //情況1:不用多線程,報錯
  ch := make(chan int) // 沒有指定size,導致死鎖。
  //ch <- 10 //直接報錯,因為只有一個主線程,ch存入一個元素后就阻塞了主線程。
}
//運行結果:
fatal error: all goroutines are asleep - deadlock!
func main() {
  //情況2:1個多線程寫入
  ch2 := make(chan int) //1   沒有指定size,導致死鎖。
  go func() {           //2 4
    fmt.Println("開啟協程1")            //5
    ch2 <- 10                       //7 6 寫入channel死鎖,協程1被阻塞。
    fmt.Println("協程1復活")            //8  從這行往后都無法輸出了
    fmt.Println("協程1從ch2讀:", <-ch2) //9 10 8
  }() //13 12
  //雖然上面的協程已經死鎖,但是主線程沒有死鎖,所以仍然執行,並且繼續輸出。
  fmt.Println("主線程不受影響")          //3
  fmt.Println("主線程從ch2讀:", <-ch2) //6 7  主線程可以直接讀channel
  ch2 <- 5                        //10 9 7   主線程寫入channel,主線程被阻塞
  fmt.Println("主線程最后一行")          //11 9
  time.Sleep(3 * time.Second)     //12 13
} //14
//運行結果:
開啟協程1
主線程不受影響
主線程從ch2讀: 10
協程1復活
主線程最后一行
協程1從ch2讀: 5

或者:
主線程不受影響
開啟協程1
協程1復活
主線程從ch2讀: 10
主線程最后一行
協程1從ch2讀: 5
func main() {
  //情況3:1個線程寫入,1個線程讀出。這樣即使寫線程阻塞,讀線程仍不受影響,一旦channel被讀出了,寫入線程就可以繼續寫入了!
  ch := make(chan int) // 沒有指定size,導致死鎖。
  
  time.Sleep(3 * time.Second)

(2)channel存儲滿后,如果再進行存儲,會導致線程鎖住,只能等到channel將數據取出之后,才能進行正常存儲

a:=make(chan int ,1)
a<-1
a<-2
<-a
1
2
3
4
此時,如果沒有<-a,會導致通道的阻塞,只能等到<-a才能再存儲
3:channel中沒有值時,進行讀取數據,不能正常讀取,造成通道的阻塞

<-a
如果不賦值,直接從channel中讀取數據,會造成通道的阻塞

原文鏈接:https://blog.csdn.net/Xiang_lhh/article/details/108779630

關閉channel

(1)關閉后,對基於該channel的任何發送操作都將導致panic異常。

(2)對一個已經被close過的channel進行接收操作依然可以接受到之前已經成功發送的數據;如果channel中已經沒有數據的話將產生一個零值的數據。

close(ch) // 使用內置的close函數關閉channel

打開channel

參考:https://www.jianshu.com/p/bc7aa77a609c

go語言原生語法並沒有提供方法打開channel,只能自己寫方法,利用指針再次打開。

channel的結構體在chan.go中:

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    //... 以下字段沒有用上,先省略
}

Channel是否關閉取決於hchan.closed,0是打開,1是關閉。
方法:讓指針指向hchan.closed直接修改它的值。

代碼實現【不理解!!!】

//go:linkname lock runtime.lock
func lock(l *mutex)

//go:linkname unlock runtime.unlock
func unlock(l *mutex)

func open(c interface{}) error {
    v := reflect.ValueOf(c)
    if v.Type().Kind() != reflect.Chan {
        return errors.New("type must be channel")
    }
    i := (*[2]uintptr)(unsafe.Pointer(&c)) //定位c所在數據空間,這里的c是個指針所以要進行一步取值
    var closedOffset, lockOffset uintptr = 28, 88
    closed := (*uint32)(unsafe.Pointer(i[1] + closedOffset)) //指向closed的地址
    if *closed == 1 {
        lockPtr := (*mutex)(unsafe.Pointer(i[1] + lockOffset)) //指向lock地址
        lock(lockPtr) //上鎖
        if *closed == 1 {
            *closed = 0 //直接修改值
        }
        unlock(lockPtr) //解鎖
    }
    return nil
}

7、環境搭建

(1)安裝和配置SDK

go1.16.5.windows-amd64.msi

/bin 下面是
……

(2)GOROOT和GOPATH

Go開發相關的環境變量如下:
GOROOT:GOROOT就是Go的安裝目錄,(類似於java的JDK)。不用往環境變量配置。包管理方式變成Go Module之后就用處不大了。
如,C:\Program Files\Go
GOPATH:GOPATH是我們的工作空間,保存go項目代碼和第三方依賴包。安裝后,在環境變量中有。
如,GOPATH = %USERPROFILE%\go

參考:https://blog.csdn.net/qq_38151401/article/details/105729884

8、結構

(1)package包聲明

package main //執行程序必須叫main,下面必須有main函數,作為入口
package test // 其他的包,不一定要與目錄同名,但最好同名

(2)import引入包

// 方式1
import "fmt"
import "demo1/test" // 項目名/包名
// 方式2
import (
    "fmt"
    "demo1/test" // 項目名/包名
)

(3)函數

大寫字母開頭是公有,小寫字母開頭為私有。

(4)變量

(5)語句 & 表達式

(6)注釋

單行注釋://

多行注釋:/**/

9、大小寫命名規范和訪問權限

(1)首字母大寫是公有的,首字母小寫是私有的,因此,變量或函數只需要首字母大寫即可跨文件訪問

(2)golang的命名需要使用駝峰命名法,且不能出現下划線

(3)結構體中屬性名最好大寫:如果屬性名小寫則在數據解析(如json解析,或將結構體作為請求或訪問參數)時無法解析

10、運行

(1)直接運行

go run hello.go

(2)編譯運行

go build hello.go
hello


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM