golang 入門極簡教程

基本語法

常量

常量可以類比于java中的final變量,必須在初始化時復(fù)制,不可以修改,不可以使用 :=

package main

import "fmt"

//全局常量
const a = "hello"
const b = 1
func main() {
    //局部常量
    const c = true
    fmt.Println(a)
    test()
    fmt.Println(c)
}

func test(){
    fmt.Println(b)
}
變量

變量的聲明方式有一下幾種方式:

var a int

var b string

c := 12

var d = "hello"

可以使用var關(guān)鍵字或者使用 :=的方式聲名并賦值

package basics

import "fmt"
//函數(shù)體外的全局變量要遵循閉包規(guī)則,不可以使用 := 或者 var c
var a string
var b int
var c [] int
var d [5] int
// e := 12  golang具有閉包規(guī)則。 := 其實(shí)是兩步操作, var e int +  e = 12  而 e = 12 是不能在函數(shù)體外執(zhí)行的
func test() {
    a = "hello"
    e := "world"
    var f = "just"
    
    fmt.Println(a)
    fmt.Println(e)
    fmt.Println(f)
}
基本數(shù)據(jù)類型
類型 描述
浮點(diǎn)型 float32,float64,complex32
整型 byte,int,int8,int16,int32,uint
布爾型 bool
字符串 “ ”里的字符串可以進(jìn)行標(biāo)量替換, ``里的字符串是什么就是什么

字符串支持切片

package main
import "fmt"
func main(){
    a := "hesitate"
    for _,char := range a{
        fmt.Printf("%c",char)
        fmt.Printf("--")
    }

    fmt.Println()

    fmt.Printf(a[2:])

    fmt.Println()

    b:= "hello"
    c:= "world"
    b+=c
    fmt.Printf(b)

    fmt.Println(len(a))

}
數(shù)組

數(shù)組是一個定長的序列,可以使用以下語法來創(chuàng)建

  1. [length]Type

  2. [length]Type{v1,v2,v3...}

  3. [...]Type{v1,v2,v3...}

    使用[...]系統(tǒng)會自動計算數(shù)組長度

切片

由于數(shù)組是定長的,實(shí)際使用中更多用到的還是切片,切片是可以調(diào)整長度的,切片的創(chuàng)建語法如下:

  • []Type{}
  • []Type{v1,v2,v3...}
  • make([]Type,length,capacity)
  • make([]Type,length)

切片的一些操作:

s1 := []int{1, 2, 3, 4, 5} 
s2 := make([]int, 2, 4)    //make語法聲明 ,初始化len為2,cap為4
s2 = []int{5, 6}

s3 := append(s2, 7) //append一個元素
fmt.Println(s3, s2) //[5 6 7] [5 6]

s4 := append(s2, s1...) //append  一個切片所有的元素
fmt.Println(s4)         //[5 6 3 4]

    //return
copy(s1, s2)    //  復(fù)制,用s2的元素填充s1里去,改變原slice,覆蓋對應(yīng)的key
fmt.Println(s1) //[5 6 3 4]

s1[0], s1[1] = 1, 2
copy(s2, s1)
fmt.Println(s2) //[1 2] 目標(biāo)slice len不夠時,只填滿len

s5 := s1[1:4]
s6 := s5[0:4] //不會報錯,因?yàn)閏ap為4,從底層取得最后一位

fmt.Println(s5, s6, cap(s6)) //[2 3 4] [2 3 4 5] 4

//刪除第三個元素
s7 := append(s1[:1], s1[3:]...)
fmt.Println(s7) //[1 4 5]

GOPATH

工程源文件存放于$GOPATH/src 目錄下,不過gopath的方式慢慢被廢棄,才是go module的方式

流程控制

switch

switch的表達(dá)式如下:

switch optionalStatement; optionalExpression {
    case expression1: block1
    ...
    case expressionN: blockN
    default: blockD
}

但是需要注意的是 Go 語言的 switch 語句不會自動貫穿,相反,如果想要貫穿需要添加 fallthrough 語句。

demo:

package main
import "fmt"
func main(){
    num := 3
    //switch不含expression
    switch  {
    case num > 0:
        fmt.Println("num is gt 0")

    case num > 1:
        fmt.Println("num is gt 1")

    case num < 0:
        fmt.Println("num is lt 0")  
    
    default :
        fmt.Println("wrong num")
    }
    fmt.Println("----------")
    //先執(zhí)行前置運(yùn)算,再賦值
    switch a:= get();a{
    case 1:
        fmt.Println("one")
    case 2:
        fmt.Println("two")
    case 3:
        fmt.Println("three")        
    }
    fmt.Println("----------")
    b := "i"
    //多情況匹配
    switch b{
    case "a","e","i","o":
        fmt.Println("this is vo")
    case "b":
        fmt.Println("this is b")    
    }
}

func get() int{
    return 3
}

if

if的寫法如下:

if condition {  
} else if condition {
} else {
}

同switch一樣,也有如下變體:這種形式的 if 語句先執(zhí)行 statement,然后再判斷 conditon 。

if statement; condition {  
}

上面的變體中的變量僅作用于該if語句。

函數(shù)

函數(shù)的定義包括: func關(guān)鍵字,函數(shù)名,參數(shù),返回值,方法體,方法出口。

// Switch 函數(shù)包含 func 關(guān)鍵字, 方法名,參數(shù),返回值,方法體,return
func Switch(a int)(b bool){
    switch a {
    case 1:
        return true
    case 2:
        return false
    default:
        return false
    }
}

同java不同的是,go的函數(shù)可以有多個返回值,具體如下:

func Divide(a int,b int)(c int,err error){
    if b==0{
        err = errors.New("被除數(shù)不能為0")
        return
    }
    return a/b,nil
}

// Add 返回值可以只寫類型
func Add(a,b int)(int,string){
    return a+b,"success"
}

匿名函數(shù):可以定義一個沒有名字的函數(shù),然后賦值給一個變量,或者直接運(yùn)行:

//匿名函數(shù)
    f := func(a, b, c int) (result bool) {
        if a+b+c > 10 {
            result = true
        } else {
            result = false
        }
        return result
    }
    fmt.Println(f(1,2,3))
//也可以不定義變量,直接在結(jié)尾拼上參數(shù)直接運(yùn)行該函數(shù) 
defer

defer可以使棧幀中的代碼執(zhí)行完后,以出棧的方式來執(zhí)行。程序異常并不會影響defer運(yùn)行,因此可以使用defer做資源的釋放,或者是異常的捕獲。

//測試一下defer函數(shù)

func Delay(){
    fmt.Println("start ...")
    defer foo1()
    defer foo2()
    defer foo3()
    fmt.Println("end ...")

}

func foo1(){
    fmt.Println("do 001...")
}

func foo2(){
    fmt.Println("do 002...")
}

func foo3(){
    fmt.Println("do 003...")
}

以上代碼輸出:

start ...
end ...
do 003...
do 002...
do 001...

類型斷言

類型斷言針對interface{}類型的變量,其使用方法有以下兩種

  1. 安全斷言:s,ok := s.(T)
  2. 非安全斷言:s:=s.(T)

其中s表示interface{}類型的變量,ok為是否斷言成功,T為斷言的類型。

//interface{} 包含所有類型,類似于object
    var i interface{} = 99
    //類型斷言
    j:=i.(int)
    fmt.Printf("type of j is %T,value is %d \n",j,j)
    var s interface{} = []string{"left","right"}
    if s,ok:=s.([]string);ok{
        fmt.Printf("s is a string slice,value is %s",s)
    }
異常

使用panic(interface{})來拋出異常,類似于java中的throw

使用recover()可以捕獲異常,類似于java中的catch。


func foo(){
    panic(errors.New("i`m a bug"))
    return
}
func Wrong() int{
    defer func(){ //捕獲異常
        if r:=recover();r!=nil{
            err:=r.(error)
            fmt.Println("catch an exception",err)
        }
    }()
    foo()
    return 10
}

面向?qū)ο缶幊?/h4>

指針

go中的指針表示指向內(nèi)存地址的一種數(shù)據(jù)。使用方法:

v:="cat"
ptr:=&v //使用&來獲得v變量的指針
s:=*ptr //使用*來獲得指針?biāo)赶虻淖兞康闹?

&用于變量,來取得指針

*用于指針,來取得變量

func Point(){
    a:="nothing to fear"
    ptr:=&a  //&表示取得變量的內(nèi)存地址
    //變量ptr為 *string 類型
    fmt.Printf("a的內(nèi)存地址為%p \n",ptr)
    fmt.Printf("prt的類型為%T \n",ptr)
    //使用*來獲得指針指向的內(nèi)存值
    fmt.Println(*ptr)

}

也可以使用new關(guān)鍵字來創(chuàng)建一個指定類型的指針:

func Create(){
    //使用new來創(chuàng)建指定類型的指針
    ptr:=new(string)
    fmt.Println(ptr)
    *ptr = "range your dream"
    fmt.Println(*ptr)
}

當(dāng)一個指針沒有指向任何內(nèi)存地址時,其值為nil

結(jié)構(gòu)體

go中結(jié)構(gòu)體為一系列數(shù)據(jù)類型(基本數(shù)據(jù)類型,interface,自定義結(jié)構(gòu)體)的組合,eg:

type ColorPoint struct {
    color.Color  //匿名字段
    x,y int //具名字段
    r Print //interface類型
    m Man //自定義結(jié)構(gòu)體
}

type Print interface {
    red() string
}
type Man struct {
    name string
    age int
}

接口同java中的接口類似,為一系列方法的集合。

方法

方法是特殊的函數(shù),定義在某一特定的類型上,通過這個類型的實(shí)例(接收者)來調(diào)用。

go中方法不像其他語言一樣,是定義在類中,而是通過接收者來關(guān)聯(lián)。

接收者必須顯式地?fù)碛幸粋€名字,在方法中必須被使用。

type Count int //go不允許為內(nèi)置的數(shù)據(jù)類型添加方法,這里自定義了一個類型

func (c Count) Add(i Count){
    fmt.Printf("add result is : %d",c+i)
}

func (c *Count) Increment() {
    *c++
}

func (c *Count) IsZero() bool{
    return *c==0
}

方法相比于函數(shù),在func關(guān)鍵字和方法名中間增加了接收者,接收者可以是指針類型,也可以是接收者本身

type Cat struct {
    Name string
}

func (c *Cat) Tell(){
    fmt.Println("喵喵")
}
func (c Cat) Catch(){
    fmt.Println("抓老鼠")
}
func (c *Cat) HisName() {
    fmt.Println(c.Name)
}
組合

go中可以使用組合的方式來實(shí)現(xiàn)繼承

type Base struct {
    Name string
}

func (b *Base) Foo(){
    fmt.Println("foo...")
}
func (b *Base) Bar(){
    fmt.Println("bar...")
}

type Speed struct {
    Base
}

func (s *Speed) Foo(){
    fmt.Println("重寫foo...")
}

func main(){
    s:=new(Speed)
    s.Base.Foo() //可以調(diào)用base中的所有方法
    s.Foo() //s對原方法進(jìn)行了重寫
}
接口

go中的接口定義了函數(shù)名,參數(shù)及返回類型,不進(jìn)行函數(shù)的實(shí)現(xiàn)。以java為例,類實(shí)現(xiàn)接口是通過implements關(guān)鍵字,并對接口中的函數(shù)進(jìn)行實(shí)現(xiàn)。在go中也是以結(jié)構(gòu)體來對接口進(jìn)行實(shí)現(xiàn)的,并不需要implements或者其他的關(guān)鍵字,而是只要實(shí)現(xiàn)了接口中定義的所有的函數(shù),就表示這個結(jié)構(gòu)體實(shí)現(xiàn)了接口。

type Sharp interface{
    Area() float64
    Perimeter() float64
}

type Rect struct {
    width float64
    height float64
}

func (r Rect) Area() float64{
    return r.width * r.height
}

func (r Rect) Perimeter() float64{
    return (r.width+r.height)*2
}
// Rect實(shí)現(xiàn)了Sharp接口

一個結(jié)構(gòu)體也可以實(shí)現(xiàn)多接口:

type Sharp interface{
    Area() float64
    Perimeter() float64
}

type Detail interface {
    Desc()
}
type Rect struct {
    Width  float64
    Height float64
}

func (r Rect) Area() float64{
    return r.Width * r.Height
}

func (r Rect) Perimeter() float64{
    return (r.Width +r.Height)*2
}
// Rect實(shí)現(xiàn)了Detail接口
func (r Rect) Desc(){
    fmt.Println("this is a rect")
}
func main(){
    r:=Rect{Width: 10, Height: 20}
    var s Sharp = r
    var d Detail = r
    fmt.Printf("type of s is %T,type of d is %T \n",s,d)
    //類型斷言
    sharp,ok := s.(Sharp)
    if ok {
        fmt.Println(sharp.Perimeter())
    }
}

使用接口可以達(dá)到多態(tài)的效果。

接口也可以嵌入到接口中,實(shí)現(xiàn)接口的組合:

type Interface1 interface {
    Send()
    Receive()
}

type Interface2 interface {
    Interface1
    Close()
}
//Interface2隱式地包含了Interface1
內(nèi)存分配

go中的內(nèi)存分配的關(guān)鍵字使用makenew,make用于對slice,map,channel分配內(nèi)存

并發(fā)編程

協(xié)程

提到高并發(fā)的解決方案便會想到多線程,多線程其實(shí)是以獲得cpu時間片的方式來解決單線程的阻塞等待的問題。顯而易見如果一個任務(wù)持續(xù)需要cpu的計算能力,那么多線程上下文的切換反而會讓效率變低。所以對于高密度計算的程序的線程數(shù)一般設(shè)置為cpu的核心數(shù)。而對于io密集型的程序,由于請求會阻塞到io,這時線程切換來處理其他請求,可以極大的提高系統(tǒng)的吞吐量。

但是系統(tǒng)內(nèi)存資源有限,目前操作系統(tǒng)支持的最大線程數(shù)為千量級,那么可以使用比線程更細(xì)粒度的協(xié)程

協(xié)程是在用戶態(tài)來實(shí)現(xiàn)的,所以和線程以及進(jìn)程是兩個層面的概念。操作系統(tǒng)無法感知協(xié)程,所以一個線程內(nèi)的協(xié)程在某一時刻只會有一個在運(yùn)行。但由于是在用戶態(tài),所以不再存在線程切換帶來的時間損失。golang的并發(fā)正是基于協(xié)程來實(shí)現(xiàn)的。

goroutine

Go 程序中使用 go 關(guān)鍵字為一個函數(shù)創(chuàng)建一個goroutine。一個函數(shù)可以被創(chuàng)建多個 goroutine,一個 goroutine 必定對應(yīng)一個函數(shù)。

func GoAdder(){
    var times int
    for true {
        times++
        fmt.Println("tick",times)
        time.Sleep(time.Second)
    }
}
func main(){
    go GoAdder()
    var string input
    fmt.Scanln(&input)
}
channel

我們無法直接取得goroutine函數(shù)的結(jié)果,可以通過channel。

channelgoroutine 之間互相通訊的東西。channel 是類型相關(guān)的,也就是說一個 channel 只能傳遞一種類型的值,這個類型需要在 channel 聲明時指定。

channel是一個FIFO的隊列,在任何時候,同時只能有一個 goroutine 訪問通道進(jìn)行發(fā)送和獲取數(shù)據(jù)

channel 的一般聲明形式:var chanName chan ElementType

即在普通變量的基礎(chǔ)上增加了chan這個關(guān)鍵字

聲明的變量需要使用make分配內(nèi)存空間后才能使用:

通道實(shí)例 := make(chan 數(shù)據(jù)類型)

channel的讀寫操作
c <- x        //向一個Channel發(fā)送一個值
<- c          //從一個Channel中接收一個值
x = <- c      //從Channel c接收一個值并將其存儲到x中
x, ok = <- c  //從Channel接收一個值,如果channel關(guān)閉了或沒有數(shù)據(jù),那么ok將被置為false

默認(rèn)情況下,通信是同步且無緩沖的:在有接受者接收數(shù)據(jù)之前,發(fā)送不會結(jié)束。可以想象一個無緩沖的通道在沒有空間來保存數(shù)據(jù)的時候:必須要一個接收者準(zhǔn)備好接收通道的數(shù)據(jù)然后發(fā)送者可以直接把數(shù)據(jù)發(fā)送給接收者。所以通道的發(fā)送/接收操作在對方準(zhǔn)備好之前是阻塞的:

1)對于同一個通道,發(fā)送操作(協(xié)程或者函數(shù)中的),在接收者準(zhǔn)備好之前是阻塞的:如果ch中的數(shù)據(jù)無人接收,就無法再給通道傳入其他數(shù)據(jù):新的輸入無法在通道非空的情況下傳入。所以發(fā)送操作會等待 ch 再次變?yōu)榭捎脿顟B(tài):就是通道值被接收時(可以傳入變量)。

2)對于同一個通道,接收操作是阻塞的(協(xié)程或函數(shù)中的),直到發(fā)送者可用:如果通道中沒有數(shù)據(jù),接收者就阻塞了。

func main(){
    m:=make(chan string)
    //開啟一個routine做通道的發(fā)送方
    go func() {
        time.Sleep(time.Second*2)
        m <- "hello"
    }()
    //main routine做通道的接收方,該語句會阻塞直到接收到數(shù)據(jù)
    s:= <- m
    fmt.Println(s)
}
循環(huán)接收

通道中的數(shù)據(jù)每次只能接收一個,但是通道是可以遍歷的,遍歷的結(jié)果就是接收到的數(shù)據(jù):

func main(){
    m:=make(chan int)
    go func() {
        for i:=3;i>=0;i-- {
            m <- i
        }
    }()

    for k:= range m {
        //打印通道數(shù)據(jù)
        fmt.Println(k)
        if k == 0 {
            break
        }
    }
}
deadLock

fatal error: all goroutines are asleep - deadlock!

學(xué)習(xí)channel時這個錯誤并不陌生,下面通過一段程序來分析一下:

func TestDeadLock(a chan int){
    fmt.Println( <- a)
}
func main(){
    m:=make(chan int)
    m<-0
    go base.TestDeadLock(m)
    time.Sleep(time.Second*2)
}

執(zhí)行上面程序會出現(xiàn)deadlock的異常,我們分析在m<-0時,由于還沒有接收者,程序在此阻塞,導(dǎo)致無法執(zhí)行之后的代碼,拋出deadlock異常。那么先調(diào)換一下順序:

func TestDeadLock(a chan int){
    fmt.Println( <- a)
}
func main(){
    m:=make(chan int)
    go base.TestDeadLock(m)
    m<-0
    time.Sleep(time.Second*2)
}

發(fā)現(xiàn)程序正常執(zhí)行。證實(shí)了非緩沖通道的阻塞性。

帶緩沖的通道

如何創(chuàng)建帶緩沖的通道呢?參見如下代碼:

通道實(shí)例 := make(chan 通道類型, 緩沖大小)

  • 通道類型:和無緩沖通道用法一致,影響通道發(fā)送和接收的數(shù)據(jù)類型。
  • 緩沖大?。簺Q定通道最多可以保存的元素數(shù)量。
  • 通道實(shí)例:被創(chuàng)建出的通道實(shí)例。
func ChannelBuffer() {
    //創(chuàng)建一個包含3個元素緩沖區(qū)的通道
    c:=make(chan int,3)
    //不會再有deadlock的異常
    c <- 0
    c <- 1
    c <- 2
    //查看當(dāng)前通道的大小
    fmt.Println(len(c))
}

這就是我們常見的生產(chǎn)者/消費(fèi)者模式了

channel的關(guān)閉使用內(nèi)置的close(ch)函數(shù)

select

java nio中的核心實(shí)現(xiàn)在于Selector來實(shí)現(xiàn)多路復(fù)用,golang則是使用select關(guān)鍵字。


func GoSelector() {
    n:=time.Now()
    c1:=make(chan interface{})
    c2:=make(chan int)
    c3:=make(chan string)

    go func() {
        time.Sleep(time.Second*4)
        close(c1)
    }()

    go func() {
        time.Sleep(time.Second*3)
        c2 <- 1
    }()

    go func() {
        time.Sleep(time.Second*3)
        c3 <- "hello"
    }()

    fmt.Println("wait to read...")
    for {
        select {
        case <-c1:
            fmt.Printf("Unblocked %v later.\n", time.Since(n))
        case m2 := <-c2:
            fmt.Printf("read from c2:%d .\n", m2)
        case m3 := <-c3:
            fmt.Println("read from c3:", m3)
        default:
            fmt.Println("exec default ...")
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 11.1 概述 11.1.1 并行和并發(fā) 并行(parallel):指在同一時刻,有多條指令在多個處理器上同時執(zhí)行...
    小黑胖_閱讀 1,442評論 0 7
  • 語言介紹 Go(又稱Golang)是Google開發(fā)的一種靜態(tài)強(qiáng)類型、編譯型、并發(fā)型,并具有垃圾回收功能的編程語言...
    hewolf閱讀 1,332評論 0 0
  • Go入門 Go介紹 部落圖鑒之Go:爹好還這么努力? 環(huán)境配置 安裝 下載源碼編譯安裝 下載相應(yīng)平臺的安裝包安裝 ...
    齊天大圣李圣杰閱讀 4,758評論 0 26
  • Golang從09年發(fā)布,中間經(jīng)歷了多個版本的演進(jìn),已經(jīng)漸漸趨于成熟,并且出現(xiàn)了很多優(yōu)秀的開源項目,比如我們熟知的...
    隨風(fēng)遣入夜閱讀 4,181評論 0 5
  • Go語言特性 Go語言是Google公司開發(fā)的一種靜態(tài)的,編譯型并自帶垃圾回收和并發(fā)的變成語言.Go語言的風(fēng)格類似...
    Clark_new閱讀 32,999評論 2 10

友情鏈接更多精彩內(nèi)容