日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

第61篇 笔记-Go 基础

發布時間:2023/12/9 编程问答 76 豆豆
生活随笔 收集整理的這篇文章主要介紹了 第61篇 笔记-Go 基础 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

一、go原則

二、組織代碼項目

三、數據類型

四、fmt 格式

五、package 包

5.1 可見性與作用域

5.2 包的導入

六、函數

6.1 main()?函數

6.2 init() 初始化函數

6.3 自定義函數

6.4 更改函數參數值

七、控制語句

7.1 if 語句

7.2 switch 語句

7.3 select 語句

7.4 break?語句

7.5?continue 語句

7.6?goto 語句

7.7 defer 函數

7.8?panic 函數與 recover 函數

7.9 示例:用戶登錄(流程控制)

7.10 示例:錯誤處理(控制流中斷)

八、數組

8.1 聲明與初始化

8.2 賦值與訪問

8.3 range遍歷

九、切片

9.1 切片內部結構

9.2 切片項

9.3 append() 和 copy() 函數

9.4 容量cap變化規律

9.5 示例:斐波納契數列

十、映射

10.1 定義 Map

10.2 添加與刪除

10.3 判斷存在

十一、結構體

11.1?聲明和初始化

11.2?結構嵌入

11.3 JSON 序列化

十二、方法

12.1 聲明方法

12.2 方法的重載

12.3 方法的封裝

12.4 函數與方法的區別

十三、接口

13.1 示例:interface聲明與使用方法

13.2 示例:創建用于管理在線商店的程序包

13.3 示例:通過實現接口對結構體切片進行排序

十四、類型斷言

十五、隨機數

15.1 偽隨機示例:math/rand

15.2 真隨機示例:crypto/rand

十六、日期與時間

16.1 type Time

16.2 示例:日期與時間

16.3 type Duration

十七、cmd編譯

17.1 兩種編譯方法

17.2 go build命令

17.3 go build示例

17.4 go install命令

17.5 go install示例

十八、錯誤處理策略

18.1 示例

18.2 處理策略:

十九、日志記錄

19.1 log包

19.2 記錄到文件

二十、反射

二十一、nil


一、go原則

(1)Go 致力于使事情變得簡單,用更少的代碼行執行更多操作。

(2)并發是首選,函數可作為輕量線程運行。

(3)編譯和執行速度快,目標是與 C 一樣快。

(4)Go 要求強制轉換是顯式的,否則會引發編譯錯誤。

(5)未使用的代碼不是警告,而是錯誤,代碼將不會編譯。

(6)有一種官方格式設置,有助于保持項目之間的一致性。

(7)Go 并不適用于框架,因為它更傾向于使用標準庫。

(8)Go 確保向后兼容性。

(9)Go 許可證是完全開放源代碼。

?

二、組織代碼項目

Go 在組織項目文件方面與其他編程語言不同。 首先,Go 在工作區的概念之下工作,其中,工作區就是應用程序源代碼所在的位置。 在 Go 中,所有項目共享同一個工作區。 不過,從版本 1.11 開始,Go 開始更改此方法。? 現在,Go 工作區位于 $HOME/go,但如果需要,可以為所有項目設置其他位置。

若要定義其他工作區位置,請將值設置為 $GOPATH 環境變量。 開始創建更復雜的項目時,需要為環境變量設置一個值,以避免將來出現問題。

在 macOS 或 Linux 中,可以:

// 通過將以下命令添加到?~/.profile?來配置工作區: export GOPATH=$HOME/go// 然后運行以下命令以更新環境變量: source ~/.profile

在Windows 中,創建一個文件夾(例如?C:\Projects\Go),你將在其中創建所有 Go 項目。 打開 PowerShell 提示符,然后運行以下命令:

[Environment]::SetEnvironmentVariable("GOPATH", "C:\Projects\Go", "User")

在 Go 中,可以通過打印 $GOPATH 環境變量的值來獲取工作區位置,以供將來參考。 或者,可以通過運行以下命令獲取與 Go 相關的環境變量:

go env

在 Go 工作區中,可以找到以下文件夾:

  • bin:包含應用程序中的可執行文件。
  • src:包括位于工作站中的所有應用程序源代碼。
  • pkg:包含可用庫的已編譯版本。 編譯器可以鏈接這些庫,而無需重新編譯它們。

?

三、數據類型

Go 有四類數據類型:

  • 基本類型:數字、字符串和布爾值
  • 聚合類型:數組和結構
  • 引用類型:指針、切片、映射、函數和通道
  • 接口類型:接口
  • 值類型與引用類型用法的區別

  • 值類型包括基本類型聚合類型
  • 值類型內存中變量存儲的是具體的值,變量在內存中的地址可以通過 &num 來獲取;引用類型變量直接存放的就是一個地址值,這個地址值指向的空間存的才是值。
  • 值類型內存通常在棧中分配;引用類型變量存儲的地址(也就是通過指針訪問類型里面的數據),通常真正的值在堆上分配。
  • 函數引用時,作為參數的值類型不修改原始數據;引用類型修改數據內容。
  • 默認值:

    • int?類型的:?0(及其所有子類型,如?int64)
    • float32?和?float64?類型的:?+0.000000e+000
    • bool?類型的:?false
    • string?類型的:空值

    ?

    四、fmt 格式

    通用:%v 值的默認格式表示%+v 類似%v,但輸出結構體時會添加字段名%#v 值的Go語法表示%T 值的類型的Go語法表示%% 百分號布爾值:%t 單詞true或false整數:%b 表示為二進制%c 該值對應的unicode碼值%d 表示為十進制%o 表示為八進制%q 該值對應的單引號括起來的go語法字符字面值,必要時會采用安全的轉義表示%x 表示為十六進制,使用a-f%X 表示為十六進制,使用A-F%U 表示為Unicode格式:U+1234,等價于"U+%04X"浮點數與復數的兩個組分:%b 無小數部分、二進制指數的科學計數法,如-123456p-78;參見strconv.FormatFloat%e 科學計數法,如-1234.456e+78%E 科學計數法,如-1234.456E+78%f 有小數部分但無指數部分,如123.456%F 等價于%f%g 根據實際情況采用%e或%f格式(以獲得更簡潔、準確的輸出)%G 根據實際情況采用%E或%F格式(以獲得更簡潔、準確的輸出)字符串和[]byte%s 直接輸出字符串或者[]byte%q 該值對應的雙引號括起來的go語法字符串字面值,必要時會采用安全的轉義表示%x 每個字節用兩字符十六進制數表示(使用a-f)%X 每個字節用兩字符十六進制數表示(使用A-F) 指針:%p 表示為十六進制,并加上前導的0x 沒有%u。整數如果是無符號類型自然輸出也是無符號的。類似的,也沒有必要指定操作數的尺寸(int8,int64)。寬度與精度:%f: 默認寬度,默認精度%9f 寬度9,默認精度%.2f 默認寬度,精度2%9.2f 寬度9,精度2%9.f 寬度9,精度0

    ?

    五、package 包

    (1)包(package)是多個Go源碼的集合,go語言有很多內置包,比如fmt,os,io等;

    (2)一個文件夾下的所有源碼文件只能屬于同一個包,同樣屬于同一個包的源碼文件不能放在多個文件夾下;

    (3)包名一般是小寫的,使用一個簡短且有意義的名稱;

    (4)main包是一個可執行的包,是應用程序的入口包,編譯完會生成一個可執行文件;

    (5)編譯不包含 main 包的源碼文件時不會得到可執行文件;

    (6)包名一般要和所在的目錄同名,也可以不同,包名中不能包含 “-”等特殊符號;

    (7)包一般使用域名作為目錄名稱,這樣能保證包名的唯一性;

    (8)GitHub 項目的包一般會放到?GOPATH/src/github.com/userName/projectName?目錄下;

    (9)任何源代碼文件必須屬于某個包,同時源碼文件的第一行有效代碼必須是?package?語句,通過該語句聲明自己所在的包。

    ?

    5.1 可見性與作用域

    變量作用域的好處:

  • 可以在多個位置使用相同的變量名而不引起任何沖突;
  • 脫離作用域的變量將不再可見并且無法訪問
  • 幫助更好地閱讀代碼;
  • go的作用域通常隨著大括號{ }的出現而開啟和結束;
  • 雖然沒有使用大括號,但關鍵字 case 和 default 也都引入了新的作用域;
  • 在 for 語句、if 語句或 switch 語句所在行聲明的變量,作用域持續至該語句結束為止;
  • 聲明變量的位置決定了變量所處的作用域;
  • 如果想在一個包中引用另外一個包里的標識符(如變量、常量、類型、函數等)時,該標識符必須是對外可見的(public)。

    在Go語言中只需要將標識符的首字母大寫就可以。

    // 首字母小寫,外部包不可見,只能在當前包內使用 var num = 10//首字母大寫外部包可見,可在其他包中使用 const Name ?= "ares"// 首字母小寫,外部包不可見,只能在當前包內使用 type person struct {name string }type Student struct {Name ?string ? ? ? ? ? //可在包外訪問的方法class string ? ? ? ? ? //僅限包內訪問的字段 }type Payer interface {init() ? ? ? ? ? ? ? ? //僅限包內訪問的方法Pay() ? ? ? ? ? ? ? ? ?//可在包外訪問的方法 }// 首字母大寫,外部包可見,可在其他包中使用 func Add(x, y int) int {return x + y }func age() { ? ? ? ? ? ? ? // 首字母小寫,外部包不可見,只能在當前包內使用var Age = 18 ? ? ? ? ? // 函數局部變量,外部包不可見,只能在當前函數內使用fmt.Println(Age) }

    5.2 包的導入

    (1)使用import關鍵字;

    (2)import導入語句通常放在文件開頭包聲明語句的下面;

    (3)導入的包名需要使用雙引號包裹起來;

    (4)當你導入多個包時,導入的順序會按照字母排序;

    (5)導入包即等同于包含了這個包的所有的代碼對象;

    (6)導入的包必須要被使用,否則程序編譯時也會報錯。

    導入格式:

    Go包的引入格式常見的有四種,下面以引用"fmt"包為例說明:

    import "fmt" ? ? ? ? ? ? // 標準引用 import fmt_go "fmt" ? ? ?// 別名引用,fmt_go是引用fmt包的別名,此時使用fmt包的功能時需要用fmt_go.方法來使用 import . "fmt" ? ? ? ? ? // 省略方式,這種引用相當于把包fmt的命名空間合并到當前程序的命名空間了,因此可以直接引用,不用在加上前綴fmt. import _ "fmt" ? ? ? ? ? // 匿名引用,僅執行包的初始化函數,不使用包內數據

    導入路徑:

    import導入時,會從GO的安裝目錄(也就是GOROOT環境變量設置的目錄)和GOPATH環境變量設置的目錄中,檢索 src/package 來導入包。如果不存在,則導入失敗。

    • GOROOT,就是GO內置的包所在的位置;
    • GOPATH,就是我們自己定義的包的位置。

    關于我們自己定義的包的導入路徑有多種說法,本文進行了實際測試;

    假設文件結構:

    D:\golang\.|└──src├──add│ ? └───add.go└──main└───main.go└───sub└───sub.go

    包的引用策略:

    // 第一種情況 // 如果設置了 GOPATH = D:\golang // 包名默認是從 $GOPATH/src/ 后開始計算的 import ( ? ?"add""main/sub" )// 第二種情況 // 如果沒有設置 GOPATH = D:\golang // 只能使用相對路徑 import ( ? ?"../add" ? ? ? ? ? ? ? //上級目錄"./sub" ? ? ? ? ? ? ? ?//本級目錄往下 )

    特別注意:本文測試中,兩種方法與?GOPATH 的設置相對應;即:

    • 如果源碼在 GOPATH 目錄下,包的引用只能使用第一種方法,不能使用相對路徑(不知道這個是什么邏輯);
    • 如果源碼不在 GOPATH 目錄下,只能使用相對路徑,而不能使用第一種方法;
      ?

    六、函數

    6.1 main()?函數

    與之交互的函數是?main()?函數。 Go 中的所有可執行程序都具有此函數,因為它是程序的起點。

    你的程序中只能有一個?main()?函數。 如果創建的是 Go 包,則無需編寫?main()?函數。?

    main()?函數沒有任何參數,并且不返回任何內容。 但這并不意味著其不能從用戶讀取值,如命令行參數。

    6.2 init() 初始化函數

    在Go語言程序執行時導入包語句會自動觸發包內部init()函數的調用。需要注意的是: init()函數沒有參數也沒有返回值。

    import "fmt"var x = 100func init() {fmt.Println(x) //100 } func main() {fmt.Println("Hello!") //Hello! }

    包中init函數的執行時機:

    ? ? ? ? 全局聲明 ===> init() ====> main()

    init()函數執行順序:

    init()?函數與 main() 函數對比:

    (1)都是go語言中的保留函數。init()用于初始化信息,main()用于座位程序入口;

    (2)兩個函數定義的時候,不能有參數和返回值,只能由go程序自動調用,不能被引用;

    (3)init()函數可以定義在任意包中,可以有多個。main()函數只能在main包下,并且只能有一個;

    (4)存在依賴的包之間不能循環導入;

    (5)一個包可以被其他多個包import,但是只能被初始化一次。

    執行順序:

    • 先執行init()函數,后執行main()函數
    • 對于同一個go文件中,調用順序是從上向下的,也就是先寫的先被執行,后寫的后被執行
    • 對于同一個包下,將文件名稱按照字符串進行排序,之后順序調用哥哥文件中的init()函數
    • 不同包下,如果不存在依賴,按照main包中的import順序來調用對應包中的init()函數;如果存在依賴,最后被依賴 的最先被初始化,導入順序:main-->A-->B-->C,執行順序,C-->B-->A--main


    6.3 自定義函數

    下面是用于創建函數的語法:

    func name(parameters) (results) {body-content }
  • 使用?func?關鍵字來定義函數,然后為其指定名稱。
  • 在命名后,指定函數的參數列表。 你可以指定零個或多個參數。
  • 你還可以定義函數的返回類型,該函數也可以是零個或多個。只需在末尾添加?return?行。
  • 在 Go 中,函數可以返回多個值。 你可以采用類似于定義函數參數的方式來定義這些值。 換句話說,你可以指定一個類型和名稱,但該名稱是可選的。
  • 如果不需要函數的某個返回值,可以通過將返回值分配給?_?變量來放棄該函數。?_?變量是 Go 忽略返回值的慣用方式。 它允許程序進行編譯。?
  • 示例:

    func main() {sum, _ := calc(os.Args[1], os.Args[2]) // 放棄calc函數的第 2 個返回值println("Sum:", sum) }

    6.4 更改函數參數值

    將值傳遞給函數時,該函數中的每個更改都不會影響調用方。

    Go 是“按值傳遞”編程語言。 這意味著每次向函數傳遞值時,Go 都會使用該值并創建本地副本(內存中的新變量)。

    在函數中對該變量所做的更改都不會影響你向函數發送的更改。

    指針:

    如果你希望在?自定義?函數中進行的更改會影響?main?函數中的變量,則需要使用指針。?

    指針是包含另一個變量的內存地址的變量。 當你發送指向某個函數的指針時,不會傳遞值,而是傳遞地址內存。 因此,對該變量所做的每個更改都會影響調用方。

    在 Go 中,有兩個運算符可用于處理指針:

    • &?運算符使用其后對象的地址。
    • *?運算符取消引用指針。 也就是說,你可以前往指針中包含的地址訪問其中的對象。

    示例:

    package mainfunc main() {firstName := "John"updateName(&firstName)println(firstName) }func updateName(name *string) {*name = "David" }

    運行前面的代碼。 請注意,輸出現在顯示的是?David,而不是?John。

    首先要做的就是修改函數的參數,以指明你要接收指針。 為此,請將參數類型從?string?更改為?*string。(后者仍是字符串,但現在它是指向字符串的指針。)然后,將新值分配給該變量時,需要在該變量的左側添加星號 (*) 以暫停該變量的值。 調用?updateName?函數時,系統不會發送值,而是發送變量的內存地址。 這就是前面的代碼在變量左側帶有?&?符號的原因。

    ?

    七、控制語句

    7.1 if 語句

    (1)不需使用括號將條件包含起來;

    (2)大括號{}必須存在,即使只有一行語句;

    (3)左括號必須在if或else的同一行;

    (4)在if之后,條件語句之前,可以添加變量初始化語句,使用";"進行分隔;

    (5)在有返回值的函數中,最終的return不能在條件語句中。

    ?

    7.2 switch 語句

    (1)switch 語句用于基于不同條件執行不同動作,每一個 case 分支都是唯一的,從上至下逐一測試,直到匹配為止;

    (2)case 后的各個表達式的值的數據類型,必須和 switch 的表達式數據類型一致;

    (3)case 后的表達式如果是常量(字面量),則要求不能重復;

    (4)switch 語句執行的過程從上至下,直到找到匹配項,匹配項后面也不需要再加 break;

    (5)switch 默認情況下 case 最后自帶 break 語句,匹配成功后就不會執行其他 case,如果我們需要執行后面的 case,可以使用?fallthrough ;

    (6)switch 從第一個判斷表達式為 true 的 case 開始執行,如果 case 帶有 fallthrough,程序會繼續執行下一條 case,且它不會去判斷下一個 case 的表達式是否為 true。

    (7)switch 后面可以不帶表達式,類似 if-else 分支來使用;

    ?

    7.3 select 語句

    (1)每個 case 都必須是一個通信;

    (2)所有 channel 表達式都會被求值;

    (3)所有被發送的表達式都會被求值;

    (4)如果任意某個通信可以進行,它就執行,其他被忽略;

    (5)如果有多個 case 都可以運行,Select 會隨機公平地選出一個執行。其他不會執行;

    (6)如果有 default 子句,則執行該語句。如果沒有 default 子句,select 將阻塞,直到某個通信可以運行;Go 不會重新對 channel 或值進行求值。

    ?

    7.4 break?語句

    (1)用于循環語句中跳出循環,并開始執行循環之后的語句;

    (2)break 在 switch(開關語句)中在執行一條 case 后跳出語句的作用;

    (3)在多重循環中,可以用標號 label 標出想 break 的循環。

    ?

    7.5?continue 語句

    (1)有點像 break 語句。但是 continue 不是跳出循環,而是跳過當前循環執行下一次循環語句;

    (2)for 循環中,執行 continue 語句會觸發 for 增量語句的執行;

    (3)在多重循環中,可以用標號 label 標出想 continue 的循環。

    ?

    7.6?goto 語句

    (1)無條件地轉移到過程中指定的行。

    (2)goto 語句通常與條件語句配合使用。可用來實現條件轉移, 構成循環,跳出循環體等功能。

    (3)但是,在結構化程序設計中一般不主張使用 goto 語句, 以免造成程序流程的混亂,使理解和調試程序都產生困難。

    ?

    7.7 defer 函數

    (1)在 Go 中,defer?語句會推遲函數(包括任何參數)的運行,直到包含?defer?語句的函數完成。

    (2)通常情況下,當你想要避免忘記任務(例如關閉文件或運行清理進程)時,可以推遲某個函數的運行。

    (3)可以根據需要推遲任意多個函數。

    (4)defer 語句按逆序運行(后進先出),先運行最后一個,最后運行第一個。

    ?

    7.8?panic 函數與 recover 函數

    panic 函數:

    (1)運行時錯誤會使 Go 程序進入緊急狀態。 可以強制程序進入緊急狀態,但運行時錯誤(例如數組訪問超出范圍、取消對空指針的引用)也可能會導致進入緊急狀態。

    (2)內置?panic()?函數會停止正常的控制流。 所有推遲的函數調用都會正常運行。 進程會在堆棧中繼續,直到所有函數都返回。 然后,程序會崩潰并記錄日志消息。 此消息包含錯誤和堆棧跟蹤,有助于診斷問題的根本原因。

    (3)調用?panic()?函數時,可以添加任何值作為參數。 通常,你會發送一條錯誤消息,說明為什么會進入緊急狀態。

    recover 函數:

    (1)有時,你可能想要避免程序崩潰,改為在內部報告錯誤。 或者,你可能想要先清理混亂情況,然后再讓程序崩潰。 例如,你可能想要關閉與某個資源的連接,以免出現更多問題。

    (2)Go 提供內置函數?recover(),允許你在出現緊急狀況之后重新獲得控制權。

    (3)只能在已推遲的函數中使用此函數。

    (4)如果調用?recover()?函數,則在正常運行的情況下,它會返回?nil,沒有任何其他作用。

    兩者的關系:panic 與 recover 是 Go 的兩個內置函數,這兩個內置函數用于處理 Go 運行時的錯誤,panic 用于主動拋出錯誤,recover 用來捕獲 panic 拋出的錯誤。

    (1)引發?panic?有兩種情況,一是程序主動調用,二是程序產生運行時錯誤,由運行時檢測并退出;

    (2)發生?panic?后,程序會從調用?panic?的函數位置或發生?panic?的地方立即返回,逐層向上執行函數的?defer?語句,然后逐層打印函數調用堆棧,直到被?recover?捕獲或運行到最外層函數;

    (3)panic?不但可以在函數正常流程中拋出,在?defer?邏輯里也可以再次調用?panic?或拋出?panic;defer?里面的?panic?能夠被后續執行的?defer?捕獲;

    (4)recover?用來捕獲?panic,阻止?panic?繼續向上傳遞;

    (5)recover?和?defer?一起使用,但是?defer?只有在后面的函數體內直接被調用才能捕獲?panic?來終止異常,否則返回?nil,異常繼續向外傳遞。

    //以下三種方法捕獲失敗 defer recover() //無效 defer fmt.Prinntln(recover) //無效defer func(){func(){recover() //無效,嵌套兩層}() }()//以下三種捕獲有效 defer func(){recover() }()func except(){recover() }func test(){defer except()panic("runtime error") }

    7.9 示例:用戶登錄(流程控制)

    package main import "fmt" func main() {// 實現登錄驗證,有三次機會,如果用戶名和密碼正確提示登錄成功// 否則提示還有幾次機會var name string var pwd stringvar loginChance = 3for i := 1 ; i <= 3; i++ {fmt.Println("請輸入用戶名")fmt.Scanln(&name)fmt.Println("請輸入密碼")fmt.Scanln(&pwd)if name == "user" && pwd == "888888" {fmt.Println("恭喜你登錄成功!")break} else {loginChance--fmt.Printf("你還有%v次登錄機會,請珍惜\n", loginChance)}}if loginChance == 0 {fmt.Println("機會用完,沒有登錄成功!")} }

    7.10 示例:錯誤處理(控制流中斷)

    package mainimport "fmt"func main() {defer func() {if r := recover(); r != nil {fmt.Println("Recovered in main", r)}}()g(0)fmt.Println("Program finished successfully!") }func g(i int) {if i > 3 {fmt.Println("Panicking!")panic("Panic in g() (major)")}defer fmt.Println("Defer in g()", i)fmt.Println("Printing in g()", i)g(i + 1) }/* Printing in g() 0 Printing in g() 1 Printing in g() 2 Printing in g() 3 Panicking! Defer in g() 3 Defer in g() 2 Defer in g() 1 Defer in g() 0 Recovered in main Panic in g() (major) */

    下面是運行代碼時會發生的情況:

  • ?一切正常運行。 程序輸出?g()?函數接收的值。
  • 當?i?大于 3 時,程序會進入緊急狀態。 會顯示“Panicking!”消息。 此時,控制流中斷,所有推遲的函數都開始輸出“Defer in g()”消息。
  • 程序崩潰,并顯示?recover()?消息。?
  • 總結:

  • 在發生未預料到的嚴重錯誤時,系統通常會運行對?panic()?的調用。 若要避免程序崩潰,可以使用?recover()?函數。
  • 在?main()?函數中,你會將一個可以調用?recover()?函數的匿名函數推遲。
  • 當程序處于緊急狀態時,對?recover()?的調用無法返回?nil。 你可以在此處執行一些操作來清理混亂,但在這種情況下,你可以直接輸出一些內容。
  • panic()?和?recover()?的組合是 Go 處理異常的慣用方式。
  • ?

    八、數組

    數組是具有相同唯一類型的一組已編號且長度固定的數據項序列,這種類型可以是任意的原始類型例如整型、字符串或者自定義類型。

    8.1 聲明與初始化

    // Go 語言數組聲明需要指定元素類型及元素個數,語法格式如下: var variable_name [SIZE] variable_type// 一維數組的定義方式。例如以下定義了數組 balance 長度為 10 類型為 float32: var balance [10] float32// 數組初始化,初始化數組中 {} 中的元素個數不能大于 [] 中的數字: var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}// 如果數組長度不確定,可以使用 ... 代替數組的長度,編譯器會根據元素個數自行推斷數組的長度: var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} //或 balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}// 如果設置了數組的長度,我們還可以通過指定下標來初始化元素: // 將索引為 1 和 3 的元素初始化 balance := [5]float32{1:2.0,3:7.0}// 常用的多維數組聲明方式: var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type// 以下實例聲明了三維的整型數組: var threedim [5][10][4]int// 二維數組初始化 a := [3][4]int{ {0, 1, 2, 3} , /* 第一行索引為 0 */{4, 5, 6, 7} , /* 第二行索引為 1 */{8, 9, 10, 11}, /* 第三行索引為 2 */ //注意:必須要有逗號,因為后面一行的?}?不能單獨一行。 }

    8.2 賦值與訪問

    數組元素可以通過索引(位置)來讀取。格式為數組名后加中括號,中括號中為索引的值。例如:

    package mainimport "fmt"func main() {var n [10]int /* n 是一個長度為 10 的數組 */var i,j int/* 為數組 n 初始化元素 */ for i = 0; i < 10; i++ {n[i] = i + 100 /* 設置元素為 i + 100 */}/* 輸出每個數組元素的值 */for j = 0; j < 10; j++ {fmt.Printf("Element[%d] = %d\n", j, n[j] )} }/* Element[0] = 100 Element[1] = 101 Element[2] = 102 Element[3] = 103 Element[4] = 104 Element[5] = 105 Element[6] = 106 Element[7] = 107 Element[8] = 108 Element[9] = 109 */

    二維數組:

    package mainimport "fmt"func main() {// Step 1: 創建數組values := [][]int{}// Step 2: 使用 appped() 函數向空的二維數組添加兩行一維數組row1 := []int{1, 2, 3}row2 := []int{4, 5, 6}values = append(values, row1)values = append(values, row2)// Step 3: 顯示兩行數據fmt.Println("Row 1")fmt.Println(values[0])fmt.Println("Row 2")fmt.Println(values[1])// Step 4: 訪問第一個元素fmt.Println("第一個元素為:")fmt.Println(values[0][0]) }/* Row 1 [1 2 3] Row 2 [4 5 6] 第一個元素為: 1 */

    使用函數修改數組的值:

    package main import ("fmt" )// 函數(數組) func test01(arr01 [3]int) {fmt.Println("arr01 arr old = ", arr01)arr01[0] = 88fmt.Println("arr01 arr new = ", arr01) } // 函數(數組指針) func test02(arr02 *[3]int) {fmt.Printf("arr02指針的值 = %p\n", arr02)fmt.Printf("arr02指針的地址 = %p\n", &arr02)fmt.Println("arr02 arr old = ", *arr02) (*arr02)[0] = 88 fmt.Println("arr02 arr new = ", *arr02) } func main() {arr := [3]int{11, 22, 33}fmt.Println("main arr old =", arr)test01(arr)fmt.Println("main arr from test01 =", arr)fmt.Println("----------以上傳的是數組,數組值沒有變化----------")fmt.Println("----------如果想修改數組,需要傳數組指針----------")fmt.Printf("arr 的地址 = %p\n", &arr)test02(&arr)fmt.Println("main arr from test02 =", arr) }/* main arr old = [11 22 33] arr01 arr old = [11 22 33] arr01 arr new = [88 22 33] main arr from test01 = [11 22 33] ----------以上傳的是數組,數組值沒有變化---------- ----------如果想修改數組,需要傳數組指針---------- arr 的地址 = 0xc0000a8120 arr02指針的值 = 0xc0000a8120 arr02指針的地址 = 0xc0000d4020 arr02 arr old = [11 22 33] arr02 arr new = [88 22 33] main arr from test02 = [88 22 33] */

    創建各個維度元素數量不一致的多維數組:

    package?mainimport?"fmt"func?main()?{// 創建空的二維數組animals?:=?[][]string{}// 創建三一維數組,各數組長度不同row1?:=?[]string{"fish",?"shark",?"eel"}row2?:=?[]string{"bird"}row3?:=?[]string{"lizard",?"salamander"}// 使用 append() 函數將一維數組添加到二維數組中animals?=?append(animals,?row1)animals?=?append(animals,?row2)animals?=?append(animals,?row3)// 循環輸出for?i?:=?range?animals?{fmt.Printf("Row: %v\n",?i)fmt.Println(animals[i])} }/* Row: 0 [fish shark eel] Row: 1 [bird] Row: 2 [lizard salamander] */

    8.3 range遍歷

    package main import ( "fmt" ) func main() { // 二維數組 var value = [3][2]int{{1, 2}, {3, 4}, {5, 6}} // 遍歷二維數組,使用 range // 其實,這里的 i, j 表示行游標和列游標 // v2 就是具體的每一個元素 // v 就是每一行的所有元素 for i, v := range value {for j, v2 := range v { fmt.Printf("value[%v][%v]=%v \t ", i, j, v2) } fmt.Print(v) fmt.Println() } }/* value[0][0]=1 value[0][1]=2 [1 2] value[1][0]=3 value[1][1]=4 [3 4] value[2][0]=5 value[2][1]=6 [5 6] */

    ?

    九、切片

    9.1 切片內部結構

    struct Slice { ??byte* ? ?array; ? ? ? // actual datauintgo ? ?len; ? ? ? ?// number of elementsuintgo ? ?cap; ? ? ? ?// allocated number of elements? };

    切片有 3 個組件:

    • 指針,指向基礎數組可訪問的第一個元素(并非一定是數組的第一個元素)。
    • 長度,指示切片中的元素數目,表示 slice 的長度。
    • 容量,顯示切片開頭與基礎數組結束之間的元素數目。

    下圖顯示了什么是切片:

    當把 slice 作為參數,本身傳遞的是值,但其內容就?byte* array,實際傳遞的是引用,所以可以在函數內部修改,但如果對 slice 本身做 append,而且導致 slice 進行了擴容,實際擴容的是函數內復制的一份切片,對于函數外面的切片沒有變化。

    9.2 切片項

    Go 支持切片運算符?s[i:j],其中:

    • s?表示數組。
    • i?表示指向它將使用的數組(或另一切片)的第一個元素的指針。
    • j?表示切片將使用的最后一個元素的位置。

    換句話說,切片只能引用元素的子集。

    例如,假設需要 4 個變量來表示一年的每個季度。 下圖說明了它在 Go 中的顯示效果:

    若要用代碼表示在上圖中看到的內容,可使用以下代碼:

    package mainimport "fmt"func main() {months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}quarter1 := months[0:3]quarter2 := months[3:6]quarter3 := months[6:9]quarter4 := months[9:12]fmt.Println(quarter1, len(quarter1), cap(quarter1))fmt.Println(quarter2, len(quarter2), cap(quarter2))fmt.Println(quarter3, len(quarter3), cap(quarter3))fmt.Println(quarter4, len(quarter4), cap(quarter4)) }/* [January February March] 3 12 [April May June] 3 9 [July August September] 3 6 [October November December] 3 3 */

    示例代碼:

    package mainimport "fmt"func main() {/* 創建切片 */numbers := []int{0,1,2,3,4,5,6,7,8} printSlice(numbers)/* 打印原始切片 */fmt.Println("numbers ==", numbers)/* 打印子切片從索引1(包含) 到索引4(不包含)*/fmt.Println("numbers[1:4] ==", numbers[1:4])/* 默認下限為 0*/fmt.Println("numbers[:3] ==", numbers[:3])/* 默認上限為 len(s)*/fmt.Println("numbers[4:] ==", numbers[4:])numbers1 := make([]int,0,5)printSlice(numbers1)/* 打印子切片從索引 0(包含) 到索引 2(不包含) */number2 := numbers[:2]printSlice(number2)/* 打印子切片從索引 2(包含) 到索引 5(不包含) */number3 := numbers[2:5]printSlice(number3)}func printSlice(x []int){fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) }/* len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8] numbers == [0 1 2 3 4 5 6 7 8] numbers[1:4] == [1 2 3] numbers[:3] == [0 1 2] numbers[4:] == [4 5 6 7 8] len=0 cap=5 slice=[] len=2 cap=9 slice=[0 1] len=3 cap=7 slice=[2 3 4] */

    9.3 append() 和 copy() 函數

    package mainimport "fmt"func main() {var numbers []intprintSlice(numbers)/* 允許追加空切片 */numbers = append(numbers, 0)printSlice(numbers)/* 向切片添加一個元素 */numbers = append(numbers, 1)printSlice(numbers)/* 同時添加多個元素 */numbers = append(numbers, 2,3,4)printSlice(numbers)/* 創建切片 numbers1 是之前切片的兩倍容量*/numbers1 := make([]int, len(numbers), (cap(numbers))*2)/* Go 具有內置函數 copy(dst, src []Type) 用于創建切片的副本。 * 創建一個切片副本,它會在后臺生成新的基礎數組,與原數組互不影響* 你需要發送目標切片和源切片,拷貝 numbers 的內容到 numbers1*/copy(numbers1,numbers)printSlice(numbers1) }func printSlice(x []int){fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) }/* len=0 cap=0 slice=[] len=1 cap=1 slice=[0] len=2 cap=2 slice=[0 1] len=5 cap=6 slice=[0 1 2 3 4] len=5 cap=12 slice=[0 1 2 3 4] */

    9.4 容量cap變化規律

    /*每次cap改變的時候指向array內存的指針都在變化。當在使用 append 的時候,如果 cap==len 了這個時候就會新開辟一塊更大內存,然后把之前的數據復制過去。實際go在append的時候放大cap是有規律的。在 cap 小于1024的情況下是每次擴大到 2 * cap ,當大于1024之后就每次擴大到 1.25 * cap 。 */package mainimport ("fmt""unsafe" )func main() { // 每次cap改變,指向array的ptr就會變化一次s := make([]int, 1)fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))for i := 0; i < 5; i++ {s = append(s, i)fmt.Printf("len:%d cap: %d array ptr: %v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))}fmt.Println("Array:", s) }/* len:1 cap: 1 array ptr: 0xc0000aa058 len:2 cap: 2 array ptr: 0xc0000aa0a0 len:3 cap: 4 array ptr: 0xc0000a8140 len:4 cap: 4 array ptr: 0xc0000a8140 len:5 cap: 8 array ptr: 0xc0000c20c0 len:6 cap: 8 array ptr: 0xc0000c20c0 Array: [0 0 1 2 3 4] */

    9.5 示例:斐波納契數列

    斐波那契數列(Fibonacci sequence),又稱黃金分割數列,因數學家萊昂納多·斐波那契(Leonardoda Fibonacci)以引入;

    指的是這樣一個數列:0、1、1、2、3、5、8、13、21、34、55、89、……

    在數學上,斐波那契數列以如下被以遞推的方法定義:F(0)=0,F(1)=1,?F(n)=F(n - 1)+F(n - 2)(n?≥ 2,n?∈ N*)
    ?

    package mainimport "fmt"func fibonacci(n int) []int {if n < 2 {return make([]int, 0)}nums := make([]int, n)nums[0], nums[1] = 1, 1for i := 2; i < n; i++ {nums[i] = nums[i-1] + nums[i-2]}return nums }func main() {var num intfmt.Print("What's the Fibonacci sequence you want? ")fmt.Scanln(&num)fmt.Println("The Fibonacci sequence is:", fibonacci(num)) }

    ?

    十、映射

    (1)Map 是一種無序的鍵值對的集合。

    (2)Map 最重要的一點是通過 key 來快速檢索數據,key 類似于索引,指向數據的值。

    (3)Map 是一種集合,可以像迭代數組和切片那樣迭代它。

    (4)Map 是無序的,我們無法決定它的返回順序,這是因為 Map 是使用 hash 表來實現的。

    10.1 定義 Map

    可以使用內建函數 make 也可以使用 map 關鍵字來定義 Map:

    /* 聲明變量,默認 map 是 nil ,nil map 不能用來存放鍵值對*/ var map_variable map[key_data_type]value_data_type/* 使用 make 函數 ,創建空映射,可以用來存放鍵值對*/ map_variable := make(map[key_data_type]value_data_type)/* 定義并初始化*/ studentsAge := map[string]int{"john": 32, "bob": 31,}

    10.2 添加與刪除

    delete() 函數用于刪除集合的元素, 參數為 map 和其對應的 key。實例如下:

    package mainimport "fmt"func main() {/* 創建map */countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome"}/* 添加映射項 */countryCapitalMap ["Japan"] = "Tokyo"countryCapitalMap ["India"] = "New delhi"fmt.Println("原始地圖")/* 打印地圖 *//* 方法1:可以直接打印輸出映射fmt.Println(countryCapitalMap ) *//* 方法2:可使用for range打印輸出映射 */ for country := range countryCapitalMap {fmt.Println(country, "首都是", countryCapitalMap [ country ])}/* for range雙參數輸出for country , capital:= range countryCapitalMap {fmt.Printf("%s\t%s\n", country , capital)}*//*刪除元素*/ delete(countryCapitalMap, "France")fmt.Println("法國條目被刪除")fmt.Println("刪除元素后地圖")/*打印地圖*/for country := range countryCapitalMap {fmt.Println(country, "首都是", countryCapitalMap [ country ])} }/* 原始地圖 India 首都是 New delhi France 首都是 Paris Italy 首都是 Rome Japan 首都是 Tokyo 法國條目被刪除 刪除元素后地圖 Italy 首都是 Rome Japan 首都是 Tokyo India 首都是 New delhi */

    10.3 判斷存在

    訪問映射中沒有的項時 Go 不會返回錯誤,這是正常的。

    但有時需要知道某個項是否存在。 在 Go 中,映射的下標表示法可生成兩個值。 第一個是項的值。 第二個是指示鍵是否存在的布爾型標志。

    package mainimport "fmt"func main() {studentsAge := make(map[string]int)studentsAge["john"] = 32studentsAge["bob"] = 31age, exist := studentsAge["christy"]if exist {fmt.Println("Christy's age is", age)} else {fmt.Println("Christy's age couldn't be found")} }

    ?

    十一、結構體

    11.1?聲明和初始化

    /* 使用 struct 關鍵字,還要使用希望新的數據類型具有的字段及其類型的列表*/ type Employee struct {ID intFirstName stringLastName stringAddress string }/* 可像操作其他類型一樣使用新類型聲明一個變量*/ var john Employee/* * 可像操作其他類型一樣使用新類型聲明一個變量;* 請注意,該方式必須為結構中的每個字段指定一個值。 */ employee01 := Employee{1001, "John", "Doe", "Doe's Street"}/* * 可更具體地了解要在結構中初始化的字段。* 為每個字段分配值的順序不重要。 * 如果未指定任何其他字段的值,也并不重要。 Go 將根據字段數據類型分配默認值。 */ employee02 := Employee{LastName: "Doe", FirstName: "John"}

    11.2?結構嵌入

    有時,你需要減少重復并重用一種常見的結構。通過 Go 中的結構,可將某結構嵌入到另一結構中。

    package mainimport "fmt"type Person struct {ID intFirstName stringLastName stringAddress string }/* 第1種方法:重構結構體*/ type Employee struct {PersonManagerID int }/* 第2種方法:創建新字段*/ type Contractor struct {Information PersonCompanyID int }func main() {/** 第1種方法定義的使用* 無需指定 Person 字段的情況下訪問 Employee 結構中的 FirstName 字段,因為它會自動嵌入其所有字段。 * 但在你初始化結構時,必須明確要給哪個字段分配值*/employee := Employee{Person: Person{FirstName: "John",},}employee.LastName = "Doe"fmt.Println(employee.FirstName)/** 第2種方法定義的使用* 若要引用 Person 結構中的字段,你需要包含員工變量中的 Information 字段*/var contractor Contractor contractor.Information.FirstName = "John"fmt.Println(contractor.FirstName) }

    11.3 JSON 序列化

    可使用結構來對 JSON 中的數據進行編碼和解碼。 Go 對 JSON 格式提供很好的支持,該格式已包含在標準庫包中。

    JSON 編碼示例:

    package main import ("fmt""encoding/json" )type Monster struct {Name string `json:"monster_name"` // 結構體的 tag 標簽,反射機制Age int `json:"monster_age"`Birthday stringSal float64Skill string }// 將 struct 進行序列化 func testStruct() {monster := Monster{Name :"牛魔王",Age : 500 ,Birthday : "2011-11-11",Sal : 8000.0,Skill : "牛魔拳",}// 將 monster 序列化data, err := json.Marshal(&monster)if err != nil {fmt.Printf("序列號錯誤 err=%v\n", err)}// 輸出序列化后的結果fmt.Printf("monster序列化后 = %v\n", string(data)) }// 將 map 進行序列化 func testMap() {var a map[string]interface{}a = make(map[string]interface{})a["name"] = "紅孩兒"a["age"] = 30a["address"] = "洪崖洞"//將a這個 map 進行序列化data, err := json.Marshal(a)if err != nil {fmt.Printf("序列化錯誤 err=%v\n", err)}// 輸出序列化后的結果fmt.Printf("a map 序列化后 = %v\n", string(data)) }// 將切片進行序列化, []map[string]interface{} func testSlice() {var slice []map[string]interface{}var m1 map[string]interface{}m1 = make(map[string]interface{})m1["name"] = "jack"m1["age"] = "7"m1["address"] = "北京"slice = append(slice, m1)var m2 map[string]interface{}m2 = make(map[string]interface{})m2["name"] = "tom"m2["age"] = "20"m2["address"] = [2]string{"上海","深圳"}slice = append(slice, m2)// 將切片進行序列化操作data, err := json.Marshal(slice)if err != nil {fmt.Printf("序列化錯誤 err=%v\n", err)}// 輸出序列化后的結果fmt.Printf("slice 序列化后 = %v\n", string(data)) }// 對基本數據類型序列化,對基本數據類型進行序列化意義不大 func testFloat64() {var num1 float64 = 2345.67data, err := json.Marshal(num1)if err != nil {fmt.Printf("序列化錯誤 err=%v\n", err)}// 輸出序列化后的結果fmt.Printf("num1 序列化后 = %v\n", string(data)) }func main() {// 演示將結構體, map , 切片進行序列化testStruct()testMap()testSlice()testFloat64() }/* monster序列化后 = {"monster_name":"牛魔王","monster_age":500,"Birthday":"2011-11-11","Sal":8000,"Skill":"牛魔拳"} a map 序列化后 = {"address":"洪崖洞","age":30,"name":"紅孩兒"} slice 序列化后 = [{"address":"北京","age":"7","name":"jack"},{"address":["上海","深圳"],"age":"20","name":"tom"}] num1 序列化后 = 2345.67 */

    JSON 解碼示例:

    package main import ("fmt""encoding/json" )// 定義一個結構體 type Monster struct {Name string Age int Birthday string Sal float64Skill string }// 將 json 字符串,反序列化成 struct func unmarshalStruct() {// json str 一般不是直接寫入,在項目開發中,是通過網絡傳輸獲取到,或者是讀取文件獲取到str := "{\"Name\":\"牛魔王\",\"Age\":500,\"Birthday\":\"2011-11-11\",\"Sal\":8000,\"Skill\":\"牛魔拳\"}"var monster Monstererr := json.Unmarshal([]byte(str), &monster)if err != nil {fmt.Printf("unmarshal err=%v\n", err)}fmt.Printf("反序列化后 monster = %v monster.Name = %v \n", monster, monster.Name) }// 將 json 字符串,反序列化成 map func unmarshalMap() {str := "{\"address\":\"洪崖洞\",\"age\":30,\"name\":\"紅孩兒\"}"// 定義一個 mapvar a map[string]interface{} // 注意:反序列化 map,不需要 make,因為 make 操作被封裝到 Unmarshal 函數err := json.Unmarshal([]byte(str), &a)if err != nil {fmt.Printf("unmarshal err=%v\n", err)}fmt.Printf("反序列化后 map a = %v\n", a)}// 將 json 字符串,反序列化成切片 func unmarshalSlice() {str := "[{\"address\":\"北京\",\"age\":\"7\",\"name\":\"jack\"}," + "{\"address\":[\"上海\",\"深圳\"],\"age\":\"20\",\"name\":\"tom\"}]" //定義一個slicevar slice []map[string]interface{}//反序列化,不需要make,因為make操作被封裝到 Unmarshal函數err := json.Unmarshal([]byte(str), &slice)if err != nil {fmt.Printf("unmarshal err=%v\n", err)}fmt.Printf("反序列化后 slice = %v\n", slice) }func main() {unmarshalStruct()unmarshalMap()unmarshalSlice() }/* 反序列化后 monster = {牛魔王 500 2011-11-11 8000 牛魔拳} monster.Name = 牛魔王 反序列化后 map a = map[address:洪崖洞 age:30 name:紅孩兒] 反序列化后 slice = [map[address:北京 age:7 name:jack] map[address:[上海 深圳] age:20 name:tom]] */

    類型示例:

    package mainimport ("encoding/json""fmt" )/* 敲黑板,劃重點:當要將結構體對象轉換為 JSON 時,對象中的屬性首字母必須是大寫,才能正常轉換為 JSON。*/ type Person struct {ID intFirstName string `json:"name"` // JSON 輸出顯示 name 而不是 FirstName LastName stringAddress string `json:"address,omitempty"` // 忽略空字段 }type Employee struct {PersonManagerID int }func main() {employees := []Employee{Employee{Person: Person{LastName: "Doe", FirstName: "John",},},Employee{Person: Person{LastName: "Campbell", FirstName: "David",},},}data, _ := json.Marshal(employees) // 若要將結構編碼為 JSON,請使用 json.Marshal 函數fmt.Printf("%s\n", data)var decoded []Employeejson.Unmarshal(data, &decoded) // 若要將 JSON 字符串解碼為數據結構,請使用 json.Unmarshal 函數fmt.Printf("%v", decoded) }/* [{"ID":0,"name":"John","LastName":"Doe","ManagerID":0},{"ID":0,"name":"David","LastName":"Campbell","ManagerID":0}] [{{0 John Doe } 0} {{0 David Campbell } 0}] */

    ?

    十二、方法

    Go 中的方法是一種特殊類型的函數,但存在一個簡單的區別:你必須在函數名稱之前加入一個額外的參數。 此附加參數稱為?接收方

    如你希望分組函數并將其綁定到自定義類型,則方法非常有用。 Go 中的這一方法類似于在其他編程語言中創建類,因為它允許你實現面向對象編程 (OOP) 模型中的某些功能,例如嵌入、重載和封裝。

    12.1 聲明方法

    package mainimport "fmt"/* 在聲明方法之前,必須先創建結構*/ type triangle struct {size int }type square struct {size int }/* 附加的額外參數(t triangle)就是接收方*/ func (t triangle) perimeter() int {return t.size * 3 }/* 此方法屬于不同的結構,可以為其指定相同的名稱*/ func (s square) perimeter() int {return s.size * 4 }func main() {/* 如果嘗試按平常的方式調用 perimeter() 函數,則此函數將無法正常工作,因為此函數的簽名表明它需要接收方。* 因此,調用此方法的唯一方式是先聲明一個結構,獲取此方法的訪問權限。* 通過對 perimeter() 函數的兩次調用,編譯器將根據接收方類型來確定要調用的函數。* 這有助于在各程序包之間保持函數的一致性和名稱的簡短,并避免將包名稱作為前綴。*/t := triangle{3}s := square{4}fmt.Println("Perimeter (triangle):", t.perimeter())fmt.Println("Perimeter (square):", s.perimeter()) }/* Perimeter (triangle): 9 Perimeter (square): 16 */

    方法的一個關鍵方面在于,可以為任何類型定義方法,而不只是針對自定義類型(如結構)進行定義。 但是,你不能通過屬于其他包的類型來定義結構。 因此,不能在基本類型(如?string)上創建方法。

    12.2 方法的重載

    package mainimport "fmt"/* 結構體:三角形*/ type triangle struct {size int }/* 結構體:彩色三角形* 方法可以重用來自一個結構的屬性,以避免出現重復并保持代碼庫的一致性。 * 即使接收方不同,也可以調用已嵌入結構的方法。 */ type coloredTriangle struct {trianglecolor string }/* 三角形大小增加3倍*/ func (t triangle) perimeter() int {return t.size * 3 }/* 重載方法* 可以在 coloredTriangle 結構中更改 perimeter() 方法的實現* 因為方法需要額外參數(接收方),所以,可以使用一個同名的方法,只要此方法專門用于要使用的接收方即可。 */ func (t coloredTriangle) perimeter() int {return t.size * 3 * 2 }func main() {t := triangle{3} // 定義一個三角形fmt.Println("Perimeter (triangle):", t.perimeter())t1 := coloredTriangle{triangle{5}, "blue"} // 定義一個彩色三角形fmt.Println("Size:", t1.size)/* 如果沒有寫重載方法 func (t coloredTriangle) perimeter() int,此處會從 triangle 結構調用 perimeter() 方法,而不必重新創建彩色三角形的方法* 如果寫了重載方法 func (t coloredTriangle) perimeter() int,此處會從 coloredTriangle 結構調用 perimeter() 方法*/fmt.Println("Perimeter", t1.perimeter()) /* 如果你仍需要從 triangle 結構調用 perimeter() 方法,則可通過對其進行顯示訪問來執行此操作。*/fmt.Println("Perimeter (normal)", t1.triangle.perimeter()) }/* Perimeter (triangle): 9 Size: 5 Perimeter 30 Perimeter (normal) 15 */

    在 Go 中,你可以?重載?方法,并在需要時仍訪問?原始?方法。

    12.3 方法的封裝

    在 Go 中,只需使用大寫標識符,即可公開方法(public),使用非大寫的標識符將方法設為私有方法(private)。

    Go 中的封裝僅在程序包之間有效。 換句話說,你只能隱藏來自其他程序包的實現詳細信息,而不能隱藏程序包本身。

    比如,創建新程序包?geometry?:

    package geometrytype Triangle struct {size int }func (t *Triangle) doubleSize() {t.size *= 2 }func (t *Triangle) SetSize(size int) {t.size = size }func (t *Triangle) Perimeter() int {t.doubleSize()return t.size * 3 }

    你可以使用上述程序包,具體如下所示:

    func main() {t := geometry.Triangle{}t.SetSize(3)fmt.Println("Perimeter", t.Perimeter()) }

    此時你應獲得以下輸出:

    Perimeter 18

    如要嘗試從?main()?函數中調用?size?字段或?doubleSize()?方法,程序將死機,如下所示:

    func main() {t := geometry.Triangle{}t.SetSize(3)fmt.Println("Size", t.size)fmt.Println("Perimeter", t.Perimeter()) }

    在運行前面的代碼時,你將看到以下錯誤:

    ./main.go:12:23: t.size undefined (cannot refer to unexported field or method size)

    12.4 函數與方法的區別

    (1)含義不同

    • 函數function是?段具有獨?功能的代碼,可以被反復多次調?,從?實現代碼復?。??法method是?個類的?為功能,只有該類的對象才能調?。

    (2)?法有接受者,?函數?接受者

    • Go語?的?法method是?種作?于特定類型變量的函數,這種特定類型變量叫做Receiver(接受者、接收者、接收器);
    • 接受者的概念類似于傳統?向對象語?中的this或self關鍵字;
    • Go語?的接受者強調了?法具有作?對象,?函數沒有作?對象;
    • ?個?法就是?個包含了接受者的函數;
    • Go語?中, 接受者的類型可以是任何類型,不僅僅是結構體, 也可以是struct類型外的其他類型。

    (3)函數不可以重名,??法可以重名

    • 只要接受者不同,則?法名可以?樣。

    (4)調用方式不一樣

    • 方法由struct對象通過(.點號)調用,而函數是直接調用。
      ?

    十三、接口

    與其他編程語言中的接口不同,Go 中的接口是滿足隱式實現的。?

    示例:編寫自定義?String()?方法來打印自定義字符串,具體如下所示:

    package mainimport "fmt"type Person struct {Name, Country string }func (p Person) String() string {return fmt.Sprintf("%v is from %v", p.Name, p.Country) }func main() {rs := Person{"John Doe", "USA"}ab := Person{"Mark Collins", "United Kingdom"}fmt.Printf("%s\n%s\n", rs, ab) }/* John Doe is from USA Mark Collins is from United Kingdom */

    如你所見,你已使用自定義類型(結構)來寫入?String()?方法的自定義版本。 這是在 Go 中實現接口的一種常用方法。

    13.1 示例:interface聲明與使用方法

    package main import "fmt"// 聲明/定義一個接口 type Usb interface { Start() Stop() }type Phone struct {} // 讓Phone 實現 Usb接口的方法 func (p Phone) Start() {fmt.Println("手機開始工作。。。") } func (p Phone) Stop() {fmt.Println("手機停止工作。。。") }type Camera struct {}// 讓Camera 實現 Usb接口的方法 func (c Camera) Start() {fmt.Println("相機開始工作。。。") } func (c Camera) Stop() {fmt.Println("相機停止工作。。。") }// Computer,可以識別 Phone 和 Camera 的接口 type Computer struct {} /* 編寫一個 Working 方法,接收一個Usb接口類型變量* 該變量必須實現了 Usb 接口聲明的所有方法* 通過usb接口變量來調用Start和Stop方法* 這里實際體現了多態特性,因為同一個參數可以接收多個類型變量 */ func (c Computer) Working(usb Usb) {usb.Start() usb.Stop() }func main() {// 創建結構體變量computer := Computer{}phone := Phone{}camera := Camera{}// 接口的使用computer.Working(phone)computer.Working(camera) }/* 手機開始工作。。。 手機停止工作。。。 相機開始工作。。。 相機停止工作。。。*/

    13.2 示例:創建用于管理在線商店的程序包

    編寫一個程序,此程序使用自定義程序包來管理在線商店的帳戶。

  • 創建一個名為?Account?的自定義類型,此類型包含帳戶所有者的名字和姓氏。 此類型還必須加入?ChangeName?的功能。

  • 創建另一個名為?Employee?的自定義類型,此類型包含用于將貸方數額存儲為類型?float64?并嵌入?Account?對象的變量。 類型還必須包含?AddCredits、RemoveCredits?和?CheckCredits?的功能。 你需要展示你可以通過?Employee?對象更改帳戶名稱。

  • 將字符串方法寫入?Account?對象,以便按包含名字和姓氏的格式打印?Employee?名稱。

  • 最后,編寫使用已創建程序包的程序,并測試此挑戰中列出的所有功能。 也就是說,主程序應更改名稱、打印名稱、添加貸方、刪除貸方以及檢查余額。

  • 下方是適用于商店程序包的代碼:

    package storeimport ("errors""fmt" )type Account struct {FirstName stringLastName string }type Employee struct {AccountCredits float64 }func (a *Account) ChangeName(newname string) {a.FirstName = newname }func (e Employee) String() string {return fmt.Sprintf("Name: %s %s\nCredits: %.2f\n", e.FirstName, e.LastName, e.Credits) }func CreateEmployee(firstName, lastName string, credits float64) (*Employee, error) {return &Employee{Account{firstName, lastName}, credits}, nil }func (e *Employee) AddCredits(amount float64) (float64, error) {if amount > 0.0 {e.Credits += amountreturn e.Credits, nil}return 0.0, errors.New("Invalid credit amount.") }func (e *Employee) RemoveCredits(amount float64) (float64, error) {if amount > 0.0 {if amount <= e.Credits {e.Credits -= amountreturn e.Credits, nil}return 0.0, errors.New("You can't remove more credits than the account has.")}return 0.0, errors.New("You can't remove negative numbers.") }func (e *Employee) CheckCredits() float64 {return e.Credits }

    下方是主程序用于測試所有功能的代碼:

    package mainimport ("fmt""store" )func main() {bruce, _ := store.CreateEmployee("Bruce", "Lee", 500)fmt.Println(bruce.CheckCredits())credits, err := bruce.AddCredits(250)if err != nil {fmt.Println("Error:", err)} else {fmt.Println("New Credits Balance = ", credits)}_, err = bruce.RemoveCredits(2500)if err != nil {fmt.Println("Can't withdraw or overdrawn!", err)}bruce.ChangeName("Mark")fmt.Println(bruce) }

    13.3 示例:通過實現接口對結構體切片進行排序

    package main import ("fmt""sort""math/rand" )type Hero struct{ // 聲明Hero結構體Name stringAge int }type HeroSlice []Hero // 聲明一個Hero結構體切片類型// 實現Interface 接口1(sort.Sort要求) func (hs HeroSlice) Len() int {return len(hs) }// 實現Interface 接口2(sort.Sort要求) // Less方法就是決定你使用什么標準進行排序 func (hs HeroSlice) Less(i, j int) bool {return hs[i].Age < hs[j].Age // 按Hero的Age從小到大排序//return hs[i].Name < hs[j].Name // 按Hero的Name排序,實際上可以按結構體的任意字段進行排序 }// 實現Interface 接口3(sort.Sort要求) func (hs HeroSlice) Swap(i, j int) {hs[i], hs[j] = hs[j], hs[i] // 交換 }func main() { var intSlice = []int{0, -1, 10, 7, 90} // 定義一個數組/切片sort.Ints(intSlice) // 對 intSlice切片進行排序,可以使用系統提供的一般方法fmt.Println(intSlice)/* 對結構體切片進行排序,本代碼重點內容 */var heroes HeroSlice // 定義一個切片for i := 0; i < 10 ; i++ { // 給切片賦值hero := Hero{Name : fmt.Sprintf("英雄|%d", rand.Intn(100)),Age : rand.Intn(100),}heroes = append(heroes, hero) // 將 hero append到 heroes切片}for _ , v := range heroes { // 排序前的順序fmt.Println(v)}sort.Sort(heroes) // 調用sort.Sort,必須實現interface,才可以調用fmt.Println("-----------排序后------------")for _ , v := range heroes { // 排序后的順序fmt.Println(v)}}/* [-1 0 7 10 90] {英雄|81 87} {英雄|47 59} {英雄|81 18} {英雄|25 40} {英雄|56 0} {英雄|94 11} {英雄|62 89} {英雄|28 74} {英雄|11 45} {英雄|37 6} -----------排序后------------ {英雄|56 0} {英雄|37 6} {英雄|94 11} {英雄|81 18} {英雄|25 40} {英雄|11 45} {英雄|47 59} {英雄|28 74} {英雄|81 87} {英雄|62 89} */

    sort.Interface接口的說明:

    // https://studygolang.com/pkgdoc type Interface interface {// Len方法返回集合中的元素個數Len() int// Less方法報告索引i的元素是否比索引j的元素小Less(i, j int) bool// Swap方法交換索引i和j的兩個元素Swap(i, j int) }

    一個滿足sort.Interface接口的(集合)類型可以被本包的函數進行排序。方法要求集合中的元素可以被整數索引。

    ?

    十四、類型斷言

    golang中的所有程序都實現了interface{}的接口,這意味著,所有的類型如 string , int , int64 甚至是自定義的 struct 類型都就此擁有了 interface{} 的接口。

    類型斷言(Type Assertion)是一個使用在接口值上的操作,用于檢查接口類型變量所持有的值是否實現了期望的接口或者具體的類型。

    在Go語言中類型斷言的語法格式如下:

    value, ok := x.(T)

    其中,x 表示一個接口的類型,T 表示一個具體的類型(也可為接口類型)。

    該斷言表達式會返回 x 的值(也就是 value)和一個布爾值(也就是 ok),可根據該布爾值判斷 x 是否為 T 類型:

    • 如果 T 是具體某個類型,類型斷言會檢查 x 的動態類型是否等于具體類型 T。如果檢查成功,類型斷言返回的結果是 x 的動態值,其類型是 T。
    • 如果 T 是接口類型,類型斷言會檢查 x 的動態類型是否滿足 T。如果檢查成功,x 的動態值不會被提取,返回值是一個類型為 T 的接口值。
    • 無論 T 是什么類型,如果 x 是 nil 接口值,類型斷言都會失敗。
    package main import "fmt"type Point struct {x inty int }func main() {var a interface{}var point Point = Point{1, 2}a = point var b Pointb = a.(Point) // 直接進行類型斷言fmt.Println(b) var x interface{}var b2 float32 = 2.1x = b2 // 空接口,可以接收任意類型// 類型斷言(帶檢測的)if y, ok := x.(float32); ok {fmt.Println("convert success")fmt.Printf("y 的類型是, %T 值是 %v\n", y, y)} else {fmt.Println("convert fail")}s := "hello world"if v, ok := interface{}(s).(string); ok {fmt.Println(v)}fmt.Println("繼續執行...") }/* {1 2} convert success y 的類型是 float32, 值是 2.1 hello world 繼續執行... */

    接口變量的類型也可以使用一種特殊形式的 swtich 來檢測。下面的代碼片段展示了一個類型分類函數,它有一個可變長度參數,可以是任意類型的數組,它會根據數組元素的實際類型執行不同的動作:

    func classifier(items ...interface{}) {for i, x := range items {switch x.(type) {case bool:fmt.Printf("Param #%d is a bool\n", i)case float64:fmt.Printf("Param #%d is a float64\n", i)case int, int64:fmt.Printf("Param #%d is a int\n", i)case nil:fmt.Printf("Param #%d is a nil\n", i)case string:fmt.Printf("Param #%d is a string\n", i)default:fmt.Printf("Param #%d is unknown\n", i)}} }

    ?

    十五、隨機數

    真隨機和偽隨機概念:

  • 統計學偽隨機性:在給定的隨機比特流樣本中,1 的數量大致等于 0 的數量,也就是說,“10”“01”“00”“11” 四者數量大致相等。說人話就是:“一眼看上去是隨機的”。
  • 密碼學安全偽隨機性:就是給定隨機樣本的一部分和隨機算法,不能有效的演算出隨機樣本的剩余部分。
  • 真隨機性:其定義為隨機樣本不可重現。
  • 根據以上幾個標準,其對應的隨機數也就分為以下幾類:

  • 偽隨機數:滿足第一個條件的隨機數。
  • 密碼學安全的偽隨機數:同時滿足前兩個條件的隨機數。可以通過密碼學安全偽隨機數生成器計算得出
  • 真隨機數:同時滿足三個條件的隨機數
  • 15.1 偽隨機示例:math/rand

    rand包實現了偽隨機數生成器。

    隨機數從資源生成。包水平的函數都使用的默認的公共資源。該資源會在程序每次運行時都產生確定的序列。

    如果需要每次運行產生不同的序列,應使用Seed函數進行初始化。

    默認資源可以安全的用于多go程并發。

    package main import ("fmt""math/rand""time" )func main() {// 返回一個取值范圍在[0,n)的偽隨機int值,如果n<=0會panic // func Intn(n int) intrand1 := rand.Intn(100) //每次生成一個確定的值fmt.Println("rand1 = ", rand1)// 設置種子,使用給定的seed將默認資源初始化到一個確定的狀態;如未調用Seed,默認資源的行為就好像調用了Seed(1)// func Seed(seed int64)// Unix將t表示為Unix時間,即從時間點January 1, 1970 UTC到時間點t所經過的時間(單位秒)// func (t Time) Unix() int64for i:=1; i<=10; i++{rand.Seed(time.Now().Unix())rand2 := rand.Intn(100)fmt.Println("rand2 = ", rand2)}// UnixNano將t表示為Unix時間,即從時間點January 1, 1970 UTC到時間點t所經過的時間(單位納秒)// func (t Time) UnixNano() int64for i:=1; i<=10; i++{rand.Seed(time.Now().UnixNano())rand3 := rand.Intn(100)fmt.Println("rand3 = ", rand3)}}/* rand1 = 81 //每次運行均輸出該值 rand2 = 61 //每秒內輸出相同的值 rand2 = 61 rand2 = 61 rand2 = 61 rand2 = 61 rand2 = 61 rand2 = 61 rand2 = 61 rand2 = 61 rand2 = 61 rand3 = 95 //每納秒內輸出相同的值 rand3 = 95 rand3 = 95 rand3 = 82 rand3 = 82 rand3 = 82 rand3 = 82 rand3 = 82 rand3 = 82 rand3 = 93 */

    15.2 真隨機示例:crypto/rand

    package mainimport ("crypto/rand""fmt""math/big" )// crypto/rand包實現了用于加解密的更安全的隨機數生成器 // 返回一個在[0, max)區間服從均勻分布的隨機值,如果max<=0則會panic // func Int(rand io.Reader, max *big.Int) (n *big.Int, err error)func main() {// 生成 10 個 [0, 100) 范圍的真隨機數for i := 0; i < 10; i++ {result, _ := rand.Int(rand.Reader, big.NewInt(100))fmt.Println(result)} }/* 36 45 17 24 63 19 28 66 38 50 */

    ?

    十六、日期與時間

    time包提供了時間的顯示和測量用的函數。日歷的計算采用的是公歷。

    16.1 type Time

    type Time struct {// 內含隱藏或非導出字段 }

    (1)Time代表一個納秒精度的時間點;

    (2)程序中應使用Time類型值來保存和傳遞時間,而不能用指針。就是說,表示時間的變量和字段,應為 time.Time 類型,而不是 *time.Time. 類型;

    (3)一個Time類型值可以被多個go程同時使用;

    (4)時間點可以使用 Before、After 和 Equal 方法進行比較;

    (5)Sub方法讓兩個時間點相減,生成一個 Duration 類型值(代表時間段);

    (6)Add方法給一個時間點加上一個時間段,生成一個新的Time類型時間點;

    (7)Time零值代表時間點January 1, year 1, 00:00:00.000000000 UTC;因為本時間點一般不會出現在使用中,IsZero方法提供了檢驗時間是否顯式初始化的一個簡單途徑。

    (8)每一個時間都具有一個地點信息(及對應地點的時區信息),當計算時間的表示格式時,如 Format、Hour 和 Year 等方法,都會考慮該信息。Local、UTC和In方法返回一個指定時區(但指向同一時間點)的Time。修改地點/時區信息只是會改變其表示;不會修改被表示的時間點,因此也不會影響其計算。

    16.2 示例:日期與時間

    package main import ("fmt""time" )func main() {// 日期和時間相關函數和方法使用// 1. 獲取當前時間now := time.Now()fmt.Printf("now=%v now type=%T\n", now, now)// 2.通過now可以獲取到年月日,時分秒fmt.Printf("年=%v\n", now.Year())fmt.Printf("月=%v\n", now.Month()) //月份默認顯示類型fmt.Printf("月=%v\n", int(now.Month())) //月份類型轉換fmt.Printf("日=%v\n", now.Day())fmt.Printf("時=%v\n", now.Hour())fmt.Printf("分=%v\n", now.Minute())fmt.Printf("秒=%v\n", now.Second())fmt.Printf("星期=%v\n", now.Weekday())fmt.Println(now.Date()) //日期,返回三個參數fmt.Println(now.Clock()) //時間,返回三個參數// 3.格式化日期時間fmt.Printf("當前日期和時間 %d-%d-%d %d:%d:%d \n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())dateStr := fmt.Sprintf("當前日期和時間 %d-%d-%d %d:%d:%d \n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())fmt.Printf("dateStr=%v\n", dateStr)// 格式化日期時間的第二種方式// Format根據layout指定的格式返回t代表的時間點的格式化文本表示。layout定義了參考時間:// Mon Jan 2 15:04:05 -0700 MST 2006fmt.Printf(now.Format("2006-01-02 15:04:05"))fmt.Println()fmt.Printf(now.Format("2006-01-02"))fmt.Println()fmt.Printf(now.Format("15:04:05"))fmt.Println()fmt.Printf(now.Format("2006"))fmt.Println()// 4.每隔0.1秒中打印一個數字,打印到10時就退出i := 0for {i++fmt.Println(i)// time.Sleep(time.Second) // 休眠,每1秒time.Sleep(time.Millisecond * 100) // 休眠,每0.1秒if i == 10 {break}}// 5.Unix和UnixNano的使用fmt.Printf("unix時間戳 = %v\nunixnano時間戳 = %v\n", now.Unix(), now.UnixNano())}

    16.3 type Duration

    type Duration int64

    Duration類型代表兩個時間點之間經過的時間,以納秒為單位。可表示的最長時間段大約290年。

    const (Nanosecond Duration = 1Microsecond = 1000 * NanosecondMillisecond = 1000 * MicrosecondSecond = 1000 * MillisecondMinute = 60 * SecondHour = 60 * Minute )

    常用的時間段。沒有定義一天或超過一天的單元,以避免夏時制的時區切換的混亂。

    要將Duration類型值表示為某時間單元的個數,用除法:

    second := time.Second fmt.Print(int64(second/time.Millisecond)) // prints 1000

    要將整數個某時間單元表示為Duration類型值,用乘法:

    seconds := 10 fmt.Print(time.Duration(seconds)*time.Second) // prints 10s

    ?

    十七、cmd編譯

    17.1 兩種編譯方法

    主要有兩種 cmd 編譯方法:

    (1)go build:用于測試編譯包,在項目目錄下生成可執行文件(有main包)

    (2)go install:主要用來生成庫和工具

    • 一是編譯包文件(無main包),將編譯后的包文件放到 pkg 目錄下($GOPATH/pkg)。
    • 二是編譯生成可執行文件(有main包),將可執行文件放到 bin 目錄($GOPATH/bin)。

    相同點

    • 都能生成可執行文件

    不同點

    • go build 不能生成包文件, go install 可以生成包文件
    • go build 生成可執行文件默認在當前目錄下(可以通過參數指定生成目錄), go install 生成可執行文件默認在bin目錄下($GOPATH/bin)

    17.2 go build命令

    go build [-o 輸出名] [-i] [編譯標記] [包名]

    (1)如果參數為***.go文件或文件列表,則編譯為一個個單獨的包;

    (2)當編譯單個main包(文件),則生成可執行文件;

    (3)當編譯單個或多個包非主包時,只構建編譯包,但丟棄生成的對象(.a),僅用作檢查包可以構建;

    (4)當編譯包時,會自動忽略'_test.go'的測試文件。

    17.3 go build示例

    代碼相對于 GOPATH 的目錄關系如下:

    D:\golang\.|└── src├─── chapter11| └──── utils| └──── gobuild| ├──── lib.go| └──── main.go└─── chapter12└──── event└──── main.go

    (1)如果源碼中沒有依賴 GOPATH 的包引用,那么這些源碼可以使用無參數 go build

    // 在代碼所在目錄(./src/chapter11/gobuild)下使用 go build 命令 > cd src/chapter11/gobuild/ > go build
    • go build 在編譯開始時,會搜索當前目錄的 go 源碼。這個例子中,go build 會找到 lib.go 和 main.go 兩個文件。
    • 編譯這兩個文件后,生成當前目錄名的可執行文件并放置于當前目錄下,這里生成的可執行文件是 gobuild.exe 。

    (2)編譯同目錄的多個源碼文件時,可以在 go build 的后面提供多個文件名,go build 會編譯這些源碼,輸出可執行文件

    > cd src/chapter11/gobuild/> go build main.go lib.go //編譯結果,生成main.exe > go build lib.go main.go //編譯結果,生成lib.exe > go build -o test.exe main.go lib.go //編譯結果,生成test.exe
    • 使用“go build+文件列表”方式編譯時,可執行文件默認選擇文件列表中第一個源碼文件作為可執行文件名輸出。
    • 使用“go build+文件列表”方式編譯時,文件列表中的每個文件必須是同一個包的 Go 源碼。

    (3)“go build+包”方式編譯;在設置 GOPATH 后,可以直接根據包名進行編譯

    D:\> cd golangD:\golang> go build chapter11/gobuild // 后面接要編譯的包名;包名是相對于 GOPATH 下的 src 目錄開始的,生成默認的文件名 main.exe,保存在目錄 GOPATH 下 D:\golang> go build -o test.exe chapter11/gobuild // -o執行指定輸出文件名為 test.exe,保存在目錄 GOPATH 下 D:\golang> go build -o bin/test.exe chapter11/gobuild // -o執行指定輸出目錄bin(GOPATH/bin),輸出文件名為 test.exe

    注意 :GOPATH 下的目錄結構,源碼必須放在 GOPATH 下的 src 目錄下。所有目錄中不要包含中文。

    17.4 go install命令

    (1)go install 命令的功能和?go build 命令類似,附加參數絕大多數都可以與 go build 通用。

    (2)go install 只是將編譯的中間文件放在 GOPATH 的 pkg 目錄下,以及固定地將編譯結果放在 GOPATH 的 bin 目錄下。

    (3)這個命令在內部實際上分成了兩步操作:

    • 第一步是生成結果文件(可執行文件或者 .a 包),
    • 第二步會把編譯好的結果移到 $GOPATH/pkg 或者 $GOPATH/bin。
    go install [包名]

    go install 的編譯過程有如下規律:

    (1)go install 是建立在 GOPATH 上的,無法在獨立的目錄里使用 go install;

    (2)GOPATH 下的 bin 目錄放置的是使用 go install 生成的可執行文件,可執行文件的名稱來自于編譯時的包名;

    (3)go install 輸出目錄始終為 GOPATH 下的 bin 目錄,無法使用?-o?附加參數進行自定義;

    (4)GOPATH 下的 pkg 目錄放置的是編譯期間的中間文件。

    17.5 go install示例

    D:\> cd golangD:\golang> go install chapter11/gobuild

    編譯完成后的目錄結構如下:

    D:\golang\.|├── bin│ └─── gobuild.exe|├── pkg│ └─── chapter11│ └─── gobuild│ └── lib.a|└── src├─── chapter11| └──── utils| └──── gobuild| ├──── lib.go| └──── main.go└─── chapter12└──── event└──── main.go

    ?

    十八、錯誤處理策略

    Go 的錯誤處理方法只是一種只需要?if?和?return?語句的控制流機制。

    18.1 示例

    package mainimport ("fmt""os" )type Employee struct {ID intFirstName stringLastName stringAddress string }func main() {employee, err := getInformation(1001)if err != nil {// Something is wrong. Do something.} else {fmt.Print(employee)} }func getInformation(id int) (*Employee, error) {employee, err := apiCallEmployee(1000)return employee, err }func apiCallEmployee(id int) (*Employee, error) {employee := Employee{LastName: "Doe", FirstName: "John"}return &employee, nil }

    18.2 處理策略:

    當函數返回錯誤時,該錯誤通常是最后一個返回值。 正如上一部分所介紹的那樣,調用方負責檢查是否存在錯誤并處理錯誤。 因此,一個常見策略是繼續使用該模式在子例程中傳播錯誤。 例如,子例程(如上一示例中的?getInformation)可能會將錯誤返回給調用方,而不執行其他任何操作,如下所示:

    func getInformation(id int) (*Employee, error) {employee, err := apiCallEmployee(1000)if err != nil {return nil, err // Simply return the error to the caller.}return employee, nil }

    你可能還需要在傳播錯誤之前添加更多信息。 為此,可以使用?fmt.Errorf()?函數,該函數與我們之前看到的函數類似,但它返回一個錯誤。 例如,你可以向錯誤添加更多上下文,但仍返回原始錯誤,如下所示:

    func getInformation(id int) (*Employee, error) {employee, err := apiCallEmployee(1000)if err != nil {return nil, fmt.Errorf("Got an error when getting the employee information: %v", err)}return employee, nil }

    另一種策略是在錯誤為暫時性錯誤時運行重試邏輯。 例如,可以使用重試策略調用函數三次并等待兩秒鐘,如下所示:

    func getInformation(id int) (*Employee, error) {for tries := 0; tries < 3; tries++ {employee, err := apiCallEmployee(1000)if err == nil {return employee, nil}fmt.Println("Server is not responding, retrying ...")time.Sleep(time.Second * 2)}return nil, fmt.Errorf("server has failed to respond to get the employee information") }

    最后,可以記錄錯誤并對最終用戶隱藏任何實現詳細信息,而不是將錯誤打印到控制臺。

    創建可重用的錯誤:

    有時錯誤消息數會增加,你需要維持秩序。 或者,你可能需要為要重用的常見錯誤消息創建一個庫。 在 Go 中,你可以使用 errors.New() 函數創建錯誤并在若干部分中重復使用這些錯誤,如下所示:

    var ErrNotFound = errors.New("Employee not found!")func getInformation(id int) (*Employee, error) {if id != 1001 {return nil, ErrNotFound}employee := Employee{LastName: "Doe", FirstName: "John"}return &employee, nil }

    在 Go 中處理錯誤時,請記住下面一些推薦做法:

    • 始終檢查是否存在錯誤,即使預期不存在。 然后正確處理它們,以免向最終用戶公開不必要的信息。
    • 在錯誤消息中包含一個前綴,以便了解錯誤的來源。 例如,可以包含包和函數的名稱。
    • 創建盡可能多的可重用錯誤變量。
    • 了解使用返回錯誤和 panic 之間的差異。 不能執行其他操作時再使用 panic。 例如,如果某個依賴項未準備就緒,則程序運行無意義(除非你想要運行默認行為)。
    • 在記錄錯誤時記錄盡可能多的詳細信息,并打印出最終用戶能夠理解的錯誤。

    ?

    十九、日志記錄

    19.1 log包

    Go 提供了一個用于處理日志的簡單標準包。 可以像使用?fmt?包一樣使用此包。 該標準包不提供日志級別,且不允許為每個包配置單獨的記錄器。 如果需要編寫更復雜的日志記錄配置,可以使用記錄框架執行此操作。?

    log.Print()?函數將日期和時間添加為日志消息的前綴。

    import ("log" )func main() {log.Print("Hey, I'm a log!") }/* 2020/12/19 13:39:17 Logging in Go! */

    log.Fatal()?函數記錄錯誤并結束程序,就像使用?os.Exit(1)?一樣。

    package mainimport ("fmt""log" )func main() {log.Fatal("Hey, I'm an error log!")fmt.Print("Can you see me?") }/* 2020/12/19 13:53:19 Hey, I'm an error log! exit status 1 */

    注意最后一行?fmt.Print("Can you see me?")?未運行。 這是因為?log.Fatal()?函數調用停止了該程序。

    在使用?log.Panic()?函數時會出現類似行為,該函數也調用?panic()?函數,如下所示:

    package mainimport ("fmt""log" )func main() {log.Panic("Hey, I'm an error log!")fmt.Print("Can you see me?") }/* 2020/12/19 13:53:19 Hey, I'm an error log! panic: Hey, I'm an error log!goroutine 1 [running]: log.Panic(0xc000060f58, 0x1, 0x1)/usr/local/Cellar/go/1.15.5/libexec/src/log/log.go:351 +0xae main.main()/Users/christian/go/src/helloworld/logs.go:9 +0x65 exit status 2 */

    你仍獲得日志消息,但現在還會獲得錯誤堆棧跟蹤。

    另一重要函數是?log.SetPrefix()。 可使用它向程序的日志消息添加前綴。 例如,可以使用以下代碼片段:

    package mainimport ("log" )func main() {log.SetPrefix("main(): ")log.Print("Hey, I'm a log!")log.Fatal("Hey, I'm an error log!") }/* main(): 2021/01/05 13:59:58 Hey, I'm a log! main(): 2021/01/05 13:59:58 Hey, I'm an error log! exit status 1 */

    19.2 記錄到文件

    除了將日志打印到控制臺之外,你可能還希望將日志發送到文件,以便稍后或實時處理這些日志。

    為什么想要將日志發送到文件? 首先,你可能想要對最終用戶隱藏特定信息。 他們可能對這些信息不感興趣,或者你可能公開了敏感信息。 在文件中添加日志后,可以將所有日志集中在一個位置,并將它們與其他事件關聯。 此模式為典型模式:具有可能是臨時的分布式應用程序,例如容器。

    讓我們使用以下代碼測試將日志發送到文件:

    package mainimport ("log""os" )func main() {file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)if err != nil {log.Fatal(err)}defer file.Close()log.SetOutput(file)log.Print("Hey, I'm a log!") }

    運行前面的代碼時,在控制臺中看不到任何內容。 在目錄中,你應看到一個名為 info.log 的新文件,其中包含使用?log.Print()?函數發送的日志。 請注意,需要首先創建或打開文件,然后將?log?包配置為將所有輸出發送到文件。 然后,可以像通常做法那樣繼續使用?log.Print()?函數。

    ?

    二十、反射

    示例:

    package main import ("fmt""reflect" )// 定義了一個Monster結構體 type Monster struct {Name string `json:"name"`Age int `json:"monster_age"`Score float32 Sex string}// 方法的排序默認是按照函數名的排序(ASCII碼) // 方法,返回兩個數的和,在 3 個方法中排第1,標號0 func (s Monster) GetSum(n1, n2 int) int {return n1 + n2 } // 方法, 接收四個值,給s賦值,在 3 個方法中排第3,標號2 func (s Monster) Set(name string, age int, score float32, sex string) {s.Name = names.Age = ages.Score = scores.Sex = sex }// 方法,顯示s的值,在 3 個方法中排第2,標號1 func (s Monster) Print() {fmt.Println("------start------")fmt.Println(s)fmt.Println("-------end-------") }func TestStruct(a interface{}) {typ := reflect.TypeOf(a) // 獲取reflect.Type 類型fmt.Println("reflect.TypeOf",typ)val := reflect.ValueOf(a) // 獲取reflect.Value 類型fmt.Println("reflect.ValueOf",val)kd := val.Kind() // 獲取到a對應的類別fmt.Println("val.Kind",kd)fmt.Println()if kd != reflect.Struct { // 如果傳入的不是struct,就退出fmt.Println("expect struct")return}num := val.NumField() // 獲取到該結構體有幾個字段fmt.Printf("struct has %d fields\n", num) // 變量結構體的所有字段for i := 0; i < num; i++ {fmt.Printf("Field %d: 值為=%v\n", i, val.Field(i)) // 獲取到該結構體的字段tagVal := typ.Field(i).Tag.Get("json") // 獲取到struct標簽, 注意需要通過reflect.Type來獲取tag標簽的值if tagVal != "" { // 如果該字段有tag標簽就顯示,否則就不顯示fmt.Printf("Field %d: tag為=%v\n", i, tagVal)}} fmt.Println()numOfMethod := val.NumMethod() // 獲取到該結構體有多少個方法fmt.Printf("struct has %d methods\n", numOfMethod)val.Method(1).Call(nil) // 調用第 2 個方法;方法的排序默認是按照 函數名的排序(ASCII碼)// 調用結構體的第1個方法Method(0)var params []reflect.Value // 聲明了 []reflect.Valueparams = append(params, reflect.ValueOf(10))params = append(params, reflect.ValueOf(40))res := val.Method(0).Call(params) // 傳入的參數是 []reflect.Value, 返回[]reflect.Valuefmt.Println("res=", res[0].Int()) // 返回結果, 返回的結果是 []reflect.Value }func main() {//創建了一個Monster實例var a Monster = Monster{Name: "huangshulang",Age: 400,Score: 30.8,}//將Monster實例傳遞給TestStruct函數TestStruct(a) }/* reflect.TypeOf main.Monster reflect.ValueOf {huangshulang 400 30.8 } val.Kind structstruct has 4 fields Field 0: 值為=huangshulang Field 0: tag為=name Field 1: 值為=400 Field 1: tag為=monster_age Field 2: 值為=30.8 Field 3: 值為=struct has 3 methods ------start------ {huangshulang 400 30.8 } -------end------- res= 50 */

    ?

    二十一、nil

    (1)nil 指針解引用會令程序崩潰;

    (2)方法可以通過簡單的措施類防范接收 nil 值;

    示例:

    package mainimport "fmt"// 定義一個結構體 type person struct{age int }// 結構體方法中,處理nil func (p *person) birthday(){if p != nil {p.age++} }func main(){// 普通指針var nowhere *intfmt.Println(nowhere) // nil//fmt.Println(*nowhere) // panic.go //go:nosplitif nowhere != nil{ // 解決方法fmt.Println(*nowhere)} // 結構體var nobody *personfmt.Println(nobody) // nilnobody.birthday() // 如果方法中沒有處理nil,panic.go// 函數var fn func(a,b int) intfmt.Println(fn == nil) // true,因為 fn 沒有被賦予任何函數// 切片var soup []stringfmt.Println(soup == nil) // true fmt.Println(len(soup)) // len 可以處理 nil 切片for _,ingredient := range soup{ // range 可以處理 nil 切片fmt.Println(ingredient) // 0}soup = append(soup,"onion","celery") // append 可以處理 nil 切片fmt.Println(soup) // [onion celery]// 接口var v interface{} // 接口變量的值和類型都是 nil,該變量是 nilfmt.Printf("%T %v %v \n",v,v,v == nil) // nil nil truevar p *intv = p // 接口變量的值是 nil,但類型不是nil,該變量就不是 nilfmt.Printf("%T %v %v \n",v,v,v == nil) // *int nil false }/* <nil> <nil> true true 0 [onion celery] <nil> <nil> true *int <nil> false */

    ?

    總結

    以上是生活随笔為你收集整理的第61篇 笔记-Go 基础的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    中文字幕精品久久 | 激情五月播播久久久精品 | 色噜噜日韩精品欧美一区二区 | 亚洲日本va在线观看 | 99久免费精品视频在线观看 | 亚洲国产电影在线观看 | 久草在线欧美 | 欧美另类xxxx | 日韩在线观看一区二区三区 | 成人超碰97| 精品国模一区二区 | 成人h视频 | 2021国产在线视频 | 黄色大片免费网站 | 一区二区三区免费在线观看视频 | 国产一区二区不卡视频 | 日韩毛片在线播放 | 91av欧美| 国产亚洲视频在线免费观看 | 亚洲激情校园春色 | 夜夜躁日日躁狠狠躁 | 99热官网| 国产日韩欧美在线免费观看 | 国内精品久久久久久中文字幕 | 一本到视频在线观看 | 黄p在线播放 | 在线黄网站 | av线上看 | 国产 一区二区三区 在线 | 国产 字幕 制服 中文 在线 | 久草国产视频 | 日日躁天天躁 | 欧美精品乱码久久久久久按摩 | av资源免费在线观看 | 狠狠干美女 | 久草免费电影 | 天天碰天天操视频 | 日韩av快播电影网 | 久久tv | 久久精品这里精品 | 一级黄色免费网站 | 欧美与欧洲交xxxx免费观看 | 国产又粗又硬又爽视频 | 国产特级毛片aaaaaaa高清 | 中文在线最新版天堂 | 69国产精品成人在线播放 | 人人爽人人爽人人片 | 美女视频黄在线 | 成 人 黄 色 视频播放1 | www一起操 | 中文字幕之中文字幕 | 五月婷婷激情六月 | 五月天最新网址 | 免费91麻豆精品国产自产在线观看 | 日韩专区 在线 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 操一草 | 欧美福利片在线观看 | 精品国产美女在线 | 欧美福利片在线观看 | 五月天视频网站 | 欧美激情第一页xxx 午夜性福利 | 中文字幕日韩国产 | 国产精品综合久久久久久 | 黄色毛片视频免费观看中文 | 国产精品久久久久免费 | 粉嫩av一区二区三区四区在线观看 | 午夜精品久久久久久久久久久久久久 | 精品视频网站 | 色婷婷国产精品 | 亚洲精品国产综合99久久夜夜嗨 | 久久国产91| 久久久国产日韩 | 天天干天天拍天天操天天拍 | 中文字幕亚洲字幕 | 在线国产视频 | 日本中文不卡 | 日韩欧美在线高清 | 日本最新高清不卡中文字幕 | 国产999视频 | 久久伊人91 | 男女激情网址 | 精品久久久久亚洲 | 亚洲丝袜一区 | 九九久久久 | 中文字幕有码在线观看 | 夜夜爽www | 人人干人人草 | 日韩精品资源 | 国产精品久久精品 | 国产中文字幕免费 | 国产亚洲精品xxoo | 丁香五月亚洲综合在线 | 国产麻豆精品久久一二三 | 日韩视频1 | 在线观看日韩精品 | 亚洲午夜久久久久久久久电影网 | 国际精品久久 | 狠狠色丁香久久综合网 | 黄色免费视频在线观看 | 亚洲成人av在线 | 亚洲va韩国va欧美va精四季 | www91在线观看 | 又黄又爽又刺激视频 | 在线中文字幕一区二区 | 欧美激情精品久久久久久免费印度 | 亚洲黄色免费电影 | 碰超在线观看 | 国产剧情一区 | 视频在线精品 | 成人影片在线播放 | 99精品国产免费久久久久久下载 | 精品久久久久久综合 | 成人污视频在线观看 | 国产精品久久久久久久久久久久午夜片 | 99一级片| 亚洲男女精品 | 免费av网址在线观看 | 亚洲精品视频一二三 | 国产v在线播放 | 9色在线视频 | 日韩一级黄色av | 青草视频在线免费 | 深爱激情亚洲 | 午夜精品视频福利 | 久久精品www人人爽人人 | 欧美一级视频在线观看 | 中文字幕日韩av | 中文字幕精品在线 | 久久免费一级片 | 久久久国产精品网站 | 婷婷在线视频 | 久久久免费精品 | 亚洲精品视频在线播放 | 国产一区免费观看 | 国产精品丝袜在线 | 91精品久久香蕉国产线看观看 | 深爱开心激情网 | 天天操天天添 | 亚洲成人二区 | 亚洲欧美综合精品久久成人 | 91在线视频在线 | 色播五月婷婷 | 中文字幕久久精品亚洲乱码 | 久久最新| 97综合在线| 亚洲精品色婷婷 | 日韩成人高清在线 | 最新av免费在线 | 亚洲在线免费视频 | 天天色 天天 | 国产精品欧美日韩在线观看 | 色婷婷av一区 | 亚洲视屏 | 夜夜躁日日躁狠狠躁 | 91精品爽啪蜜夜国产在线播放 | 色综合天天狠天天透天天伊人 | 黄色精品一区 | 国产色视频一区 | 99热网站 | 久草在线视频首页 | 97超碰伊人 | 热久久免费视频精品 | 一区二区三区在线观看中文字幕 | 亚洲视频电影在线 | 91视频在线网址 | 狠狠伊人 | 在线免费成人 | 国产999精品久久久久久 | 欧美精品国产精品 | 日韩欧美视频一区二区 | 国产一级二级三级在线观看 | 成人午夜黄色影院 | 久久高视频 | 免费午夜av | 色视频成人在线观看免 | 网站在线观看日韩 | 国产成人高清在线 | 81国产精品久久久久久久久久 | 激情综合色综合久久综合 | 欧美一级片免费在线观看 | 丁香综合激情 | 国产在线97 | 91男人影院 | 国产精品久久久久久五月尺 | 国产精品成久久久久三级 | 欧美日韩精品在线免费观看 | 久久中文欧美 | 国产精品永久在线 | 亚洲精品美女在线观看 | 中文字幕一区二区三区乱码在线 | 手机av电影在线观看 | 九九热久久久 | a级国产乱理伦片在线观看 亚洲3级 | 成人综合免费 | 麻豆视频在线看 | 五月天中文字幕mv在线 | 国产精品va在线观看入 | 欧美老人xxxx18 | 黄色一级动作片 | 成人啪啪18免费游戏链接 | 日本在线h| 久久久精品日本 | 91大神精品视频在线观看 | 久久国产影院 | 国产手机精品视频 | 亚洲综合精品视频 | 国产精品九九久久99视频 | 国产一级精品绿帽视频 | 91天天操 | 国产视频在线免费 | 欧美国产日韩激情 | 亚洲精品99久久久久中文字幕 | 91精品小视频 | 456免费视频 | 午夜免费在线观看 | 又黄又爽又色无遮挡免费 | 国产真实在线 | 亚洲情影院 | 欧美日韩午夜爽爽 | 色噜噜日韩精品欧美一区二区 | 欧美色婷 | 国产亚洲精品女人久久久久久 | 天天操夜夜拍 | 天天舔夜夜操 | 国产小视频免费观看 | 免费av大片 | 成人一区二区在线 | 欧美激情综合五月色丁香小说 | 色综合色综合久久综合频道88 | 国产精品久久久久久久久久久久久 | 成人av在线播放网站 | av电影一区| 精品国内自产拍在线观看视频 | 成片免费观看视频999 | 婷婷久久综合网 | 免费av大片 | 77国产精品| 香蕉免费 | 精品一区二区免费在线观看 | 黄色av网站在线观看 | 尤物一区二区三区 | 免费看一级黄色大全 | 97视频在线免费播放 | 字幕网在线观看 | 91超在线| 亚洲综合视频网 | 久久免费视频1 | 在线免费观看羞羞视频 | 亚洲三级av | 97人人爽人人 | 欧美日韩国产二区三区 | 中文字幕日本电影 | 不卡日韩av | 97在线观看免费高清 | 国产 在线观看 | 91视频91色 | www.色婷婷.com | 黄色成人免费电影 | 91看片一区二区三区 | 天天综合成人 | 精品影院 | 色婷婷伊人 | 日精品| 久久综合亚洲鲁鲁五月久久 | 天天操天天干天天玩 | 欧美在线a视频 | 激情五月激情综合网 | 国产又粗又猛又黄又爽 | 精品国产三级 | 国语自产偷拍精品视频偷 | 色欧美成人精品a∨在线观看 | 欧美一级免费在线 | 久久新 | 久草精品免费 | 亚洲国产精品成人精品 | 日韩三级在线观看 | 久久亚洲电影 | 欧美在线观看小视频 | 国产麻豆剧传媒免费观看 | 欧美一级裸体视频 | 激情视频免费在线 | 久久天堂网站 | 日韩videos | 热久久在线视频 | 91视频下载| 欧美永久视频 | 国内精品久久久久久久久久久久 | 992tv人人草 黄色国产区 | 久久久九色精品国产一区二区三区 | av电影在线观看完整版一区二区 | 黄色大全在线观看 | 97av视频| 国产精品久久久久久久7电影 | 在线观看www91 | 91九色老| 天天射天天做 | 婷婷5月激情5月 | 91伊人影院 | 日韩在线视频不卡 | 国产精品久久久久久一区二区三区 | 一 级 黄 色 片免费看的 | 超碰在线99 | 久久99精品久久久久久 | 国产精品九九九九九九 | 五月天婷婷狠狠 | 国产九九热视频 | 亚洲精品字幕在线观看 | 欧美精品在线免费 | 亚洲女人天堂成人av在线 | 91久久精品日日躁夜夜躁国产 | 人人干人人草 | 国产a免费 | 久久精品之 | 亚洲精品乱码白浆高清久久久久久 | av免费观看网址 | 免费视频a | 日本精品一 | 天天综合网天天 | 久草在线免费资源站 | 午夜视频免费在线观看 | 涩涩网站在线看 | 天堂av免费 | 国产免费xvideos视频入口 | 亚州av免费 | 久久综合久色欧美综合狠狠 | 99se视频在线观看 | 日韩高清成人在线 | 国产精品九九九九九 | www黄色com | 国产精品美女久久久网av | 日日夜夜婷婷 | 国产日韩精品久久 | 国产精品午夜免费福利视频 | 中文字幕2021 | 免费久久片 | 97人人网 | ,久久福利影视 | 伊人婷婷激情 | 亚洲欧美色婷婷 | 在线探花 | 日韩一区正在播放 | 91免费在线视频 | 成人免费在线视频观看 | 国产精品一码二码三码在线 | 日韩在线观看网站 | 激情视频在线观看网址 | 中文字幕亚洲欧美日韩 | 91色在线观看视频 | 91自拍视频在线 | 日韩日韩日韩日韩 | 国产精品12345 | 中文不卡视频 | 正在播放 国产精品 | 国产精品久久久久国产a级 激情综合中文娱乐网 | 日韩成人精品一区二区三区 | 久久精品aaa | www免费看 | aaa免费毛片 | 日韩激情中文字幕 | 亚洲国产精品一区二区尤物区 | 日韩精品一二三 | 91在线一区 | 欧美极品一区二区三区 | 一区电影 | 免费久久99精品国产 | 日本一区二区三区视频在线播放 | 国产精品一区二区三区免费看 | 人人干人人艹 | 在线观看成人小视频 | 成人一级片免费看 | 中文字幕第一页在线播放 | 亚洲综合在线五月天 | av九九九 | 超碰在线最新地址 | 毛片在线播放网址 | 久久久久高清 | 国产一区麻豆 | 亚洲国产伊人 | 欧美性生活久久 | 日韩av三区 | 亚洲aⅴ一区二区三区 | 亚洲第一成网站 | 国产一线二线三线在线观看 | 久久er99热精品一区二区 | 婷婷在线精品视频 | 日韩性xxxx | 91麻豆精品国产自产在线 | 亚洲国产精品成人女人久久 | a视频免费 | 五月婷婷激情综合网 | 亚洲国产wwwccc36天堂 | 国产精品剧情在线亚洲 | 国产亚洲欧美日韩高清 | 福利网在线 | 日本在线免费看 | 99免费看片 | 国产精品岛国久久久久久久久红粉 | 中文字幕国语官网在线视频 | 99精品视频观看 | www.夜夜爽| www欧美日韩 | 国产麻豆精品久久一二三 | 色国产精品一区在线观看 | 欧美日韩一区二区三区免费视频 | 亚洲成人精品在线 | 久久99久久99精品免费看小说 | 91精品婷婷国产综合久久蝌蚪 | 天天视频色 | 天天干天天操天天操 | 欧美资源 | 国产一区二区三区 在线 | 国产一区二区播放 | 午夜久久成人 | 久草国产精品 | av电影在线不卡 | 久久黄色精品视频 | 三日本三级少妇三级99 | 亚洲电影第一页av | 麻豆传媒视频在线 | 97在线免费| 免费视频你懂得 | 精品久久久久久久久亚洲 | 91在线精品一区二区 | 欧美精品一区二区性色 | 久久在线一区 | 欧美色综合天天久久综合精品 | 四虎成人精品永久免费av | 99精品视频免费看 | 亚洲精品在线视频观看 | 91在线91拍拍在线91 | 黄色三级免费看 | 国产美女视频一区 | 日韩亚洲国产中文字幕 | 欧美一区二区三区免费看 | 久久视 | 最新亚洲视频 | 97看片吧 | 亚洲五月六月 | 国产精选在线观看 | 99亚洲视频 | 国产在线观看h | 成人黄色电影免费观看 | 日韩综合视频在线观看 | 91爱爱免费观看 | 欧美日韩一区二区三区在线观看视频 | 成人午夜黄色 | 国产精品va最新国产精品视频 | 在线观看www视频 | 91精品视频在线免费观看 | 91黄在线看| 色网站视频 | 玖玖视频精品 | 午夜精品视频一区二区三区在线看 | 国产手机免费视频 | 黄色网址在线播放 | 国产精品激情偷乱一区二区∴ | 最新国产福利 | 亚洲精品国产精品乱码不99热 | 免费网站观看www在线观看 | 夜夜骑日日操 | 99riav1国产精品视频 | 99在线精品视频观看 | 成人av资源网 | 成年人免费av网站 | 国产午夜av | 日韩av电影国产 | 欧美黄色免费 | 天天爽天天爽天天爽 | 免费视频黄色 | 特级免费毛片 | 五月天婷婷视频 | 久久8精品| 丁香婷婷电影 | 久久69av| 91香蕉视频色版 | 国产免费人人看 | 天天爱天天操 | 最新91在线视频 | a级片久久久 | 亚洲天天在线日亚洲洲精 | 国产精品久久久久婷婷二区次 | 中文字幕免费高 | 成片视频免费观看 | 成人资源在线播放 | 五月花婷婷 | 日韩电影久久久 | 最近中文国产在线视频 | 99久久这里只有精品 | 日韩天堂在线观看 | 99久久精品日本一区二区免费 | 欧美大片在线观看一区 | 日韩中文三级 | 天天av在线播放 | 91九色最新 | 伊人影院在线观看 | 国产成人一区二区啪在线观看 | 国产美女精品久久久 | 在线成人免费电影 | 日韩免费一级a毛片在线播放一级 | 五月婷香蕉久色在线看 | 黄色一级免费网站 | 国产在线精品区 | 亚洲国产影院av久久久久 | 91爱爱网址| 久草爱视频 | a久久免费视频 | 91av看片 | 五月天综合| 91精品国产综合久久久久久久 | 色婷婷国产精品一区在线观看 | 亚洲日韩欧美视频 | 美女视频久久黄 | 蜜臀av性久久久久蜜臀aⅴ涩爱 | 97超碰站| 免费黄色在线播放 | 久久精品黄 | 福利一区二区在线 | 日日夜夜91 | 丁香婷婷激情五月 | 国产成人一区二区啪在线观看 | 玖玖精品视频 | 毛片精品免费在线观看 | 欧美色图p| 人人cao | 亚洲伊人网在线观看 | 摸bbb搡bbb搡bbbb| 日韩毛片在线一区二区毛片 | 免费av电影网站 | 久久国产精品一区二区 | 国产亚州精品视频 | 亚洲永久字幕 | 99热这里只有精品8 久久综合毛片 | 激情视频免费在线观看 | 国产中文字幕视频在线观看 | 久久激五月天综合精品 | 天天躁天天狠天天透 | 99亚洲视频 | 日韩欧美在线不卡 | 午夜色婷婷 | 国产免费亚洲 | 成人作爱视频 | 一区二区三区四区在线 | 日韩av一卡二卡三卡 | 精品美女久久久久 | 日韩v在线 | 久久激情小视频 | 狠狠夜夜| 国产成人区 | 97在线免费 | 久久免费精品一区二区三区 | 美女免费黄视频网站 | 日本最大色倩网站www | 天天操天天干天天爱 | 久久综合狠狠综合久久激情 | 国产成人精品在线观看 | 精品国产免费人成在线观看 | 国内视频一区二区 | 狠狠地操 | 欧美日韩国产精品爽爽 | 成人免费xxx在线观看 | 中文字幕在线播放第一页 | 久久字幕精品一区 | 亚洲综合色av | 黄色在线视频网址 | 天天操操操操操操 | 这里只有精品视频在线观看 | 又黄又爽又刺激视频 | 国产精品av免费 | 国产麻豆果冻传媒在线观看 | 欧美少妇18p| 久久国语 | 国产精品videossex国产高清 | 成人一级电影在线观看 | 国产人成在线视频 | 亚洲成人精品在线观看 | 国产剧情一区在线 | 久草免费资源 | 国产网站av | 人人澡人摸人人添学生av | 99久热精品 | 欧美一区二区三区激情视频 | 有没有在线观看av | 在线观看国产91 | 日韩中文在线观看 | 精品久久久久久久久久久久久久久久 | 九九久久精品 | 国产99久久久国产精品成人免费 | 91成人免费视频 | 91成人亚洲| 精品自拍av | 欧美一级小视频 | 九九在线精品视频 | 精品国产乱码 | 欧美日韩国产二区 | 综合天天网| 日韩一区二区三区在线观看 | 国产在线不卡 | 在线观看完整版免费 | 69视频网站 | 久草精品视频 | 免费观看9x视频网站在线观看 | 亚洲成熟女人毛片在线 | 日韩欧美精品在线 | 日韩av一卡二卡三卡 | 天天躁日日躁狠狠躁av中文 | 国产精品久久毛片 | 深夜免费小视频 | 日韩av偷拍| 日韩免费一级a毛片在线播放一级 | 97在线观看免费观看高清 | 天天做日日做天天爽视频免费 | 一级欧美日韩 | 91麻豆视频网站 | 91精品1区2区 | 中文字幕在线第一页 | 日韩中文字幕视频在线 | wwwwwww色 | 美女免费电影 | 亚洲高清资源 | 久久在线免费视频 | 日韩一区二区三区在线观看 | 国产高清专区 | bbbbb女女女女女bbbbb国产 | 黄色成年片 | 9在线观看免费高清完整版 玖玖爱免费视频 | 久久99国产精品久久99 | 91精品国产成人 | 亚洲激情久久 | 麻豆视频大全 | 国产又粗又硬又爽的视频 | 久久狠狠干 | 欧美另类一二三四区 | 欧美成人h版电影 | 国产一级片免费观看 | 久久久精品国产一区二区三区 | 一级片免费观看 | 一级片在线 | 五月精品| 91精品国产福利在线观看 | 久久精品伊人 | 天天干天天做 | 久久综合免费视频影院 | 欧美午夜剧场 | 黄色电影网站在线观看 | 激情狠狠干 | 在线探花| 国产精品久久久久久爽爽爽 | 国产a级片免费观看 | 波多野结衣在线播放一区 | 国产一及片 | 日韩在线播放欧美字幕 | www黄色com| 97香蕉久久超级碰碰高清版 | 欧美精品999 | 在线国产视频 | 国产午夜精品久久 | 天堂av免费 | 久久99亚洲精品久久久久 | 色综合久久综合中文综合网 | 久久精品国产免费看久久精品 | 精品一二三四五区 | 91大神免费视频 | 日韩在线二区 | 欧美日韩亚洲一 | 精品在线观看一区二区三区 | 成人免费中文字幕 | 日本女人b| 久一在线| 亚洲欧美婷婷六月色综合 | 超碰日韩在线 | 超碰在线最新地址 | 99视频一区二区 | av在线免费在线 | 欧美日韩69 | 精产嫩模国品一二三区 | 亚洲精品天天 | 婷婷综合久久 | 久久久福利视频 | 中文字幕成人网 | 一本一本久久a久久精品牛牛影视 | 美女黄频在线观看 | 最新不卡av| 日韩高清免费电影 | 久久草精品 | 欧美片网站yy | 日韩av一区二区在线 | 久久久久福利视频 | 日韩精品一区二区三区免费观看视频 | 日韩成人免费电影 | 久久午夜影院 | 成人日批视频 | 国产 日韩 欧美 中文 在线播放 | 日本黄区免费视频观看 | 国产一区二区精品久久91 | 久久久免费播放 | 99在线精品视频在线观看 | 免费看黄的 | 国产成年人av | 91香蕉视频在线下载 | 免费av在线网 | 在线观看午夜av | 青青视频一区 | 日韩av成人在线 | 96超碰在线 | 91视频最新网址 | 亚洲精品高清视频在线观看 | 久久久久激情 | 日韩精品国产一区 | 超碰在线个人 | 国产一区影院 | 亚洲黄在线观看 | 天天综合91 | 亚洲免费一级 | 国产亚洲欧美在线视频 | av一级在线观看 | 91最新网址在线观看 | 亚洲精品在线视频播放 | 日韩一二三在线 | 国产成人三级三级三级97 | 美女一级毛片视频 | 成年人在线观看免费视频 | 91aaa在线观看| 欧美三级高清 | 中日韩男男gay无套 日韩精品一区二区三区高清免费 | 国产成人久久77777精品 | 国产精国产精品 | 久久精品国产一区二区三区 | 天天色天天艹 | 国产精品国产精品 | 中文字幕中文字幕在线一区 | www色网站 | 国产一区二区免费看 | 日韩三区在线观看 | 精品久久精品久久 | 国产在线观看你懂得 | 亚洲人成精品久久久久 | 国产精品麻豆果冻传媒在线播放 | 久久福利 | 国内揄拍国内精品 | 久久精品国产免费 | a级国产乱理伦片在线播放 久久久久国产精品一区 | 久久草在线免费 | 中文字幕第一页在线视频 | 香蕉视频久久久 | 免费黄a大片 | 日日操狠狠干 | 91麻豆精品国产91久久久久 | 亚洲精品乱码久久久一二三 | 91试看| 一二三区av | 在线观看视频99 | 婷婷去俺也去六月色 | 欧美日韩视频在线观看免费 | 国产视频在线免费 | 国产xxxx性hd极品 | 五月婷婷丁香在线观看 | 玖玖在线免费视频 | 日韩在线不卡视频 | 成人在线免费小视频 | 亚洲综合涩 | 国产视频一级 | 免费情趣视频 | 亚洲激情网站免费观看 | 日韩精品免费一区 | 久久在线视频在线 | 成人免费视频网站在线观看 | 亚洲综合欧美激情 | 天天操人人要 | 日韩精品中文字幕在线播放 | 成人免费视频网站在线观看 | 国产香蕉97碰碰碰视频在线观看 | 成人久久免费 | 国产视频在线观看一区 | 麻豆视频免费在线 | 成人久久久久 | 麻豆视频在线免费观看 | 久草在线免费播放 | 男女拍拍免费视频 | 免费观看黄 | 99在线热播 | 美女久久久久久久久久 | 国产精品一区在线观看你懂的 | 欧美国产日韩一区二区 | 超碰资源在线 | 久色小说 | 亚洲在线视频免费 | 91视频com | 五月天色综合 | 99久久这里只有精品 | 91资源在线免费观看 | 人人爽人人澡人人添人人人人 | 人人插人人爱 | 97视频免费 | 日日碰狠狠添天天爽超碰97久久 | 亚洲va综合va国产va中文 | 在线观看亚洲国产精品 | 国产91精品久久久久久 | 日本精品一区二区 | 99在线热播| 狠狠狠狠狠狠狠干 | 在线黄色免费av | 亚洲 欧洲 国产 日本 综合 | 日韩四虎 | 一区二区三区四区影院 | 久久av观看 | 日韩欧美专区 | 中文日韩在线 | av高清在线观看 | 免费黄色特级片 | 午夜久久成人 | 亚洲最新精品 | 国产91亚洲| 一区二区三区在线看 | 天天草天天干天天 | 国产美女网站在线观看 | 成人一级片视频 | 国产精品久久久av | 91精品在线观看入口 | 欧美日韩精品在线播放 | 丰满少妇对白在线偷拍 | 婷婷丁香激情 | 国产91电影在线观看 | 中文字幕大全 | 久久黄色影院 | 在线观看一区视频 | 天天射天 | 91中文在线观看 | 日韩免费视频线观看 | www.福利 | 肉色欧美久久久久久久免费看 | 天天射天| 国产精品久久久久久久久岛 | 精品视频在线看 | 92国产精品久久久久首页 | 免费视频网 | 狠狠色噜噜狠狠狠合久 | 国产精品黄色av | 欧美日韩国产一区二区三区在线观看 | 超碰97av在线| 国内视频一区二区 | 69视频网站 | 精品久久久久久一区二区里番 | 日韩三级免费观看 | 天天色天天干天天 | 一区二区精 | 国产麻豆视频免费观看 | 免费视频一二三区 | 国产 精品 资源 | 国产91勾搭技师精品 | 国产最新在线观看 | 中文字幕丝袜制服 | 成人午夜网址 | 操高跟美女 | 91在线国产观看 | 国产精品一区二区久久久久 | 丁香激情综合国产 | 日本久久影视 | 国产小视频网站 | 91麻豆精品国产91久久久无需广告 | 欧美在线视频一区二区三区 | 国产黄视频在线观看 | 免费网站在线观看人 | 精品久久五月天 | 丝袜美腿亚洲综合 | 久久免费精品一区二区三区 | 久久精品视频免费播放 | 一区二区免费不卡在线 | 中文字幕av全部资源www中文字幕在线观看 | 人人草人| 日韩天堂在线观看 | 色综合天天色 | 草莓视频在线观看免费观看 | 福利视频午夜 | 日韩免费一区二区在线观看 | 国产精品久久一区二区三区不卡 | www免费黄色 | 99久热在线精品 | 亚洲综合视频在线播放 | 激情网五月| 成年人免费看的视频 | 国产成人在线精品 | 免费看一级特黄a大片 | 日韩欧美视频在线观看免费 | 国产 日韩 在线 亚洲 字幕 中文 | 狠狠色噜噜狠狠狠 | 91麻豆精品国产自产在线游戏 | 99精品视频播放 | 久久久九色精品国产一区二区三区 | 麻豆国产精品va在线观看不卡 | 国产日韩精品在线观看 | 成人一区电影 | 日韩精品一区在线播放 | 五月天com | 中文字幕在线不卡国产视频 | 成人黄色小视频 | 欧美日韩不卡一区二区 | 亚洲日本色| 亚洲高清国产视频 | 国产精品欧美久久久久三级 | 97国产在线观看 | 免费高清无人区完整版 | 亚洲视频电影在线 | 国产精品久久久久久69 | 亚洲va在线va天堂va偷拍 | 日韩精品在线视频 | 99精品国产99久久久久久97 | 婷婷中文在线 | 婷婷久久精品 | 国产123区在线观看 国产精品麻豆91 | 欧美国产日韩在线视频 | 欧美激情视频一二三区 | 伊人官网 | 一区二区三区四区影院 | 久久视频在线看 | av解说在线观看 | 中文字幕精品一区二区三区电影 | 国产手机视频精品 | 国产色黄网站 | 亚洲精品久久久久久久不卡四虎 | 超级碰碰碰碰 | 成人av免费在线观看 | 国产精品大片免费观看 | 欧美日韩在线网站 | 97超碰影视 | 91精品国产一区二区在线观看 | 免费情趣视频 | 色综合天天 | www.亚洲激情.com | 亚洲黄网址 | 国产99久久久国产精品成人免费 | 亚洲午夜激情网 | 97操碰| 国产原创在线观看 | av黄色在线| 国产美女视频一区 | 久久久影院一区二区三区 | 日韩av一区二区三区四区 | 久热免费在线观看 | 亚洲精品久久久久久久不卡四虎 | 成人免费观看完整版电影 | 天天操天天干天天玩 | 91精品国产99久久久久 | 国产老太婆免费交性大片 | 国产护士av| 国产黄色精品在线 | 久久综合狠狠综合久久狠狠色综合 | 91毛片视频 | 欧美久久久久久久 | 国产精品毛片久久久久久 | 久久免费视频在线观看6 | 一区二区国产精品 | 国产成人黄色网址 | 97精品国产91久久久久久 | 日韩黄色免费 | 狠狠色伊人亚洲综合网站野外 | 日韩r级在线 | 九九99视频 | 国产高清久久 | 欧美在线久久 | 国产丝袜在线 | 日韩偷拍精品 | 99热日本 | 欧美日韩免费网站 | 激情影院在线 | 欧美激情片在线观看 | 精品国产aⅴ麻豆 | 日韩在线视频播放 | 97在线免费视频 | 久久成人麻豆午夜电影 | 久久av中文字幕片 | 九九热精| 国产精品一区二区久久精品爱涩 | 一区二区三区四区影院 | 新版资源中文在线观看 | 97视频在线看 | 在线视频观看亚洲 | 国产精品一区二区美女视频免费看 | wwxxx日本| 蜜臀av性久久久久蜜臀av | 久久久久久久影院 | 成人动漫视频在线 | 激情五月开心 | 亚洲激精日韩激精欧美精品 | 亚洲精选99 | 免费看的黄色 | 99这里只有久久精品视频 | 国产一区二区视频在线播放 | 国产一区 在线播放 | 久久99视频精品 | 激情久久伊人 | 一色av| 亚洲一区精品人人爽人人躁 | 国产精品免费一区二区三区 | 天天操天天摸天天爽 | 四虎国产精品免费观看视频优播 | 日韩欧美99| 国产 一区二区三区 在线 |