亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

go語言的數據類型有什么用

發布時間:2022-12-29 10:23:35 來源:億速云 閱讀:82 作者:iii 欄目:編程語言

這篇文章主要講解了“go語言的數據類型有什么用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“go語言的數據類型有什么用”吧!

go語言是谷歌2009發布的開源編程語言。Go語言最初由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三個大牛于2007年開始設計發明,并于2009年正式對外發布;三名初始人最終的目標是設計一種適應網絡和多核時代的C語言,所以Go語言很多時候被描述為“類C語言”,或者是“21世紀的C語言”,Go從C繼承了相似的語法、編程思想等。

1. Go語言的出現

在具體學習go語言的基礎語法之前,我們來了解一下go語言出現的時機及其特點。

Go語言最初由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三個大牛于2007年開始設計發明,于2009年11月開源,一開始在google內部作為一個20%的項目運行。

Go 語言起源 2007 年,并于 2009 年正式對外發布。它從 2009 年 9 月 21 日開始作為谷歌公司 20% 兼職項目,即相關員工利用 20% 的空余時間來參與 Go 語言的研發工作。

三名初始人最終的目標是設計一種適應網絡和多核時代的C語言,所以Go語言很多時候被描述為“類C語言”,或者是“21世紀的C語言”,當然從各種角度看,Go語言確實是從C語言繼承了相似的表達式語法、控制流結構、基礎數據類型、調用參數傳值、指針等諸多編程思想。但是Go語言更是對C語言最徹底的一次揚棄,它舍棄了C語言中靈活但是危險的指針運算,還重新設計了C語言中部分不太合理運算符的優先級,并在很多細微的地方都做了必要的打磨和改變。

2. go版本的hello world

在這一部分我們只是使用“hello world”的程序來向大家介紹一下go語言的所編寫的程序的基本組成。

package main
import "fmt"
func main() {
	// 終端輸出hello world
	fmt.Println("Hello world!")
}

和C語言相似,go語言的基本組成有:

  • 包聲明,編寫源文件時,必須在非注釋的第一行指明這個文件屬于哪個包,如package main

  • 引入包,其實就是告訴Go 編譯器這個程序需要使用的包,如import "fmt"其實就是引入了fmt包。

  • 函數,和c語言相同,即是一個可以實現某一個功能的函數體,每一個可執行程序中必須擁有一個main函數。

  • 變量,Go 語言變量名由字母、數字、下劃線組成,其中首個字符不能為數字。

  • 語句/表達式,在 Go 程序中,一行代表一個語句結束。每個語句不需要像 C 家族中的其它語言一樣以分號 ; 結尾,因為這些工作都將由 Go 編譯器自動完成。

  • 注釋,和c語言中的注釋方式相同,可以在任何地方使用以 // 開頭的單行注釋。以 /* 開頭,并以 */ 結尾來進行多行注釋,且不可以嵌套使用,多行注釋一般用于包的文檔描述或注釋成塊的代碼片段。

需要注意的是:標識符是用來命名變量、類型等程序實體。一個標識符實際上就是一個或是多個字母和數字、下劃線_組成的序列,但是第一個字符必須是字母或下劃線而不能是數字。

  • 當標識符(包括常量、變量、類型、函數名、結構字段等等)以一個大寫字母開頭,如:Group1,那么使用這種形式的標識符的對象就可以被外部包的代碼所使用(客戶端程序需要先導入這個包),這被稱為導出(像面向對象語言中的 public);

  • 標識符如果以小寫字母開頭,則對包外是不可見的,但是他們在整個包的內部是可見并且可用的(像面向對象語言中的 protected)。

3. 數據類型

在 Go 編程語言中,數據類型用于聲明函數和變量。

數據類型的出現是為了把數據分成所需內存大小不同的數據,編程的時候需要用大數據的時候才需要申請大內存,就可以充分利用內存。具體分類如下:

類型詳解
布爾型布爾型的值只可以是常量 true 或者 false。
數字類型整型 int 和浮點型 float。Go 語言支持整型和浮點型數字,并且支持復數,其中位的運算采用補碼。
字符串類型字符串就是一串固定長度的字符連接起來的字符序列。Go 的字符串是由單個字節連接起來的。Go 語言的字符串的字節使用 UTF-8 編碼標識 Unicode 文本。
派生類型(a) 指針類型(Pointer)(b) 數組類型? 結構化類型(struct)(d) Channel 類型(e) 函數類型(f) 切片類型(g) 接口類型(interface)(h) Map 類型

3.0 定義變量

聲明變量的一般形式是使用 var 關鍵字,具體格式為:var identifier typename。如下的代碼中我們定義了一個類型為int的變量。

package main
import "fmt"
func main() {
	var a int = 27
	fmt.Println(a);
}

3.0.1 如果變量沒有初始化

在go語言中定義了一個變量,指定變量類型,如果沒有初始化,則變量默認為零值。零值就是變量沒有做初始化時系統默認設置的值

類型零值
數值類型0
布爾類型false
字符串“”(空字符串)

3.0.2 如果變量沒有指定類型

在go語言中如果沒有指定變量類型,可以通過變量的初始值來判斷變量類型。如下代碼

package main
import "fmt"
func main() {
    var d = true
    fmt.Println(d)
}

3.0.3 :=符號

當我們定義一個變量后又使用該符號初始化變量,就會產生編譯錯誤,因為該符號其實是一個聲明語句。

使用格式:typename := value

也就是說intVal := 1相等于:

var intVal int 
intVal =1

3.0.4 多變量聲明

可以同時聲明多個類型相同的變量(非全局變量),如下圖所示:

var x, y int
var c, d int = 1, 2
g, h := 123, "hello"

關于全局變量的聲明如下:
var ( vname1 v_type1 vname2 v_type2 )
具體舉例如下:

var ( 
    a int
    b bool
)

3.0.5 匿名變量

匿名變量的特點是一個下畫線_,這本身就是一個特殊的標識符,被稱為空白標識符。它可以像其他標識符那樣用于變量的聲明或賦值(任何類型都可以賦值給它),但任何賦給這個標識符的值都將被拋棄,因此這些值不能在后續的代碼中使用,也不可以使用這個標識符作為變量對其它變量進行賦值或運算。

使用匿名變量時,只需要在變量聲明的地方使用下畫線替換即可。

示例代碼如下:

    func GetData() (int, int) {
        return 10, 20
    }
    func main(){
        a, _ := GetData()
        _, b := GetData()
        fmt.Println(a, b)
    }

需要注意的是匿名變量不占用內存空間,不會分配內存。匿名變量與匿名變量之間也不會因為多次聲明而無法使用。

3.0.6 變量作用域

作用域指的是已聲明的標識符所表示的常量、類型、函數或者包在源代碼中的作用范圍,在此我們主要看一下go中變量的作用域,根據變量定義位置的不同,可以分為一下三個類型:

  • 函數內定義的變量為局部變量,這種局部變量的作用域只在函數體內,函數的參數和返回值變量都屬于局部變量。這種變量在存在于函數被調用時,銷毀于函數調用結束后。

  • 函數外定義的變量為全局變量,全局變量只需要在一個源文件中定義,就可以在所有源文件中使用,甚至可以使用import引入外部包來使用。全局變量聲明必須以 var 關鍵字開頭,如果想要在外部包中使用全局變量的首字母必須大寫

  • 函數定義中的變量成為形式參數,定義函數時函數名后面括號中的變量叫做形式參數(簡稱形參)。形式參數只在函數調用時才會生效,函數調用結束后就會被銷毀,在函數未被調用時,函數的形參并不占用實際的存儲單元,也沒有實際值。形式參數會作為函數的局部變量來使用

3.1 基本類型

類型描述
uint8 / uint16 / uint32 / uint64無符號 8 / 16 / 32 / 64位整型
int8 / int16 / int32 / int64有符號8 / 16 / 32 / 64位整型
float32 / float64IEEE-754 32 / 64 位浮點型數
complex64 / complex12832 / 64 位實數和虛數
byte類似 uint8
rune類似 int32
uintptr無符號整型,用于存放一個指針

以上就是go語言基本的數據類型,有了數據類型,我們就可以使用這些類型來定義變量,Go 語言變量名由字母、數字、下劃線組成,其中首個字符不能為數字。

3.2 指針

與C相同,Go語言讓程序員決定何時使用指針。變量其實是一種使用方便的占位符,用于引用計算機內存地址。Go 語言中的的取地址符是&,放到一個變量前使用就會返回相應變量的內存地址。

指針變量其實就是用于存放某一個對象的內存地址。

3.2.1 指針聲明和初始化

和基礎類型數據相同,在使用指針變量之前我們首先需要申明指針,聲明格式如下:var var_name *var-type,其中的var-type 為指針類型,var_name 為指針變量名,* 號用于指定變量是作為一個指針。

代碼舉例如下:

var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮點型 */

指針的初始化就是取出相對應的變量地址對指針進行賦值,具體如下:

   var a int= 20   /* 聲明實際變量 */
   var ip *int        /* 聲明指針變量 */

   ip = &a  /* 指針變量的存儲地址 */

3.2.2 空指針

當一個指針被定義后沒有分配到任何變量時,它的值為 nil,也稱為空指針。它概念上和其它語言的null、NULL一樣,都指代零值或空值。

3.3 數組

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

3.3.1 聲明數組

Go 語言數組聲明需要指定元素類型及元素個數,語法格式如下:

var variable_name [SIZE] variable_type

以上就可以定一個一維數組,我們舉例代碼如下:

var balance [10] float32

3.3.2 初始化數組

數組的初始化方式有不止一種方式,我們列舉如下:

  • 直接進行初始化:var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

  • 通過字面量在聲明數組的同時快速初始化數組: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}

  • 數組長度確定,指定下標進行部分初始化:balanced := [5]float32(1:2.0, 3:7.0)

注意:

  • 初始化數組中 {} 中的元素個數不能大于 [] 中的數字。
    如果忽略 [] 中的數字不設置數組大小,Go 語言會根據元素的個數來設置數組的大小。

3.3.3 go中的數組名意義

在c語言中我們知道數組名在本質上是數組中第一個元素的地址,而在go語言中,數組名僅僅表示整個數組,是一個完整的值,一個數組變量即是表示整個數組。

所以在go中一個數組變量被賦值或者被傳遞的時候實際上就會復制整個數組。如果數組比較大的話,這種復制往往會占有很大的開銷。所以為了避免這種開銷,往往需要傳遞一個指向數組的指針,這個數組指針并不是數組。關于數組指針具體在指針的部分深入的了解。

3.3.4 數組指針

通過數組和指針的知識我們就可以定義一個數組指針,代碼如下:

var a = [...]int{1, 2, 3} // a 是一個數組
var b = &a                // b 是指向數組的指針

數組指針除了可以防止數組作為參數傳遞的時候浪費空間,還可以利用其和for range來遍歷數組,具體代碼如下:

for i, v := range b {     // 通過數組指針迭代數組的元素
    fmt.Println(i, v)
}

具體關于go語言的循環語句我們在后文中再進行詳細介紹。

3.4 結構體

通過上述數組的學習,我們就可以直接定義多個同類型的變量,但這往往也是一種限制,只能存儲同一種類型的數據,而我們在結構體中就可以定義多個不同的數據類型。

3.4.1 聲明結構體

在聲明結構體之前我們首先需要定義一個結構體類型,這需要使用type和struct,type用于設定結構體的名稱,struct用于定義一個新的數據類型。具體結構如下:

type struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}

定義好了結構體類型,我們就可以使用該結構體聲明這樣一個結構體變量,語法如下:

variable_name := structure_variable_type {value1, value2...valuen}

variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

3.4.2 訪問結構體成員

如果要訪問結構體成員,需要使用點號 . 操作符,格式為:結構體變量名.成員名。舉例代碼如下:

package main

import "fmt"

type Books struct {
   title string
   author string
}

func main() {
	var book1 Books
	Book1.title = "Go 語言入門"
	Book1.author = "mars.hao"	
}

3.4.3 結構體指針

關于結構體指針的定義和申明同樣可以套用前文中講到的指針的相關定義,從而使用一個指針變量存放一個結構體變量的地址。

定義一個結構體變量的語法:var struct_pointer *Books

這種指針變量的初始化和上文指針部分的初始化方式相同struct_pointer = &Book1,但是和c語言中有所不同,使用結構體指針訪問結構體成員仍然使用.操作符。格式如下:struct_pointer.title

3.5 字符串

一個字符串是一個不可改變的字節序列,字符串通常是用來包含人類可讀的文本數據。和數組不同的是,字符串的元素不可修改,是一個只讀的字節數組。每個字符串的長度雖然也是固定的,但是字符串的長度并不是字符串類型的一部分。

3.5.1 字符串定義和初始化

Go語言字符串的底層結構在reflect.StringHeader中定義,具體如下:

type StringHeader struct {
    Data uintptr
    Len  int
}

也就是說字符串結構由兩個信息組成:第一個是字符串指向的底層字節數組,第二個是字符串的字節的長度。

字符串其實是一個結構體,因此字符串的賦值操作也就是reflect.StringHeader結構體的復制過程,并不會涉及底層字節數組的復制,所以我們也可以將字符串數組看作一個結構體數組。

字符串和數組類似,內置的len函數返回字符串的長度。

3.5.2 字符串UTF8編碼

根據Go語言規范,Go語言的源文件都是采用UTF8編碼。因此,Go源文件中出現的字符串面值常量一般也是UTF8編碼的(對于轉義字符,則沒有這個限制)。提到Go字符串時,我們一般都會假設字符串對應的是一個合法的UTF8編碼的字符序列。

Go語言的字符串中可以存放任意的二進制字節序列,而且即使是UTF8字符序列也可能會遇到壞的編碼。如果遇到一個錯誤的UTF8編碼輸入,將生成一個特別的Unicode字符‘\uFFFD’,這個字符在不同的軟件中的顯示效果可能不太一樣,在印刷中這個符號通常是一個黑色六角形或鉆石形狀,里面包含一個白色的問號‘?’。

下面的字符串中,我們故意損壞了第一字符的第二和第三字節,因此第一字符將會打印為“?”,第二和第三字節則被忽略;后面的“abc”依然可以正常解碼打印(錯誤編碼不會向后擴散是UTF8編碼的優秀特性之一)。代碼如下:

fmt.Println("\xe4\x00\x00\xe7\x95\x8cabc") // ?界abc

不過在for range迭代這個含有損壞的UTF8字符串時,第一字符的第二和第三字節依然會被單獨迭代到,不過此時迭代的值是損壞后的0:

// 0 65533  // \uFFFD, 對應 ?
// 1 0      // 空字符
// 2 0      // 空字符
// 3 30028  // 界
// 6 97     // a
// 7 98     // b
// 8 99     // c

3.5.3 字符串的強制類型轉換

在上文中我們知道源代碼往往會采用UTF8編碼,如果不想解碼UTF8字符串,想直接遍歷原始的字節碼:

  • 可以將字符串強制轉為[]byte字節序列后再行遍歷(這里的轉換一般不會產生運行時開銷):

  • 采用傳統的下標方式遍歷字符串的字節數組

除此以外,字符串相關的強制類型轉換主要涉及到[]byte和[]rune兩種類型。每個轉換都可能隱含重新分配內存的代價,最壞的情況下它們的運算時間復雜度都是O(n)。

不過字符串和[]rune的轉換要更為特殊一些,因為一般這種強制類型轉換要求兩個類型的底層內存結構要盡量一致,顯然它們底層對應的[]byte和[]int32類型是完全不同的內部布局,因此這種轉換可能隱含重新分配內存的操作。

3.6 slice

簡單地說,切片就是一種簡化版的動態數組。因為動態數組的長度不固定,切片的長度自然也就不能是類型的組成部分了。數組雖然有適用它們的地方,但是數組的類型和操作都不夠靈活,而切片則使用得相當廣泛。

切片高效操作的要點是要降低內存分配的次數,盡量保證append操作(在后續的插入和刪除操作中都涉及到這個函數)不會超出cap的容量,降低觸發內存分配的次數和每次分配內存大小。

3.6.1 slice定義

我們先看看切片的結構定義,reflect.SliceHeader:

type SliceHeader struct {
    Data uintptr   // 指向底層的的數組指針
    Len  int	   // 切片長度
    Cap  int	   // 切片最大長度
}

和數組一樣,內置的len函數返回切片中有效元素的長度,內置的cap函數返回切片容量大小,容量必須大于或等于切片的長度。

切片可以和nil進行比較,只有當切片底層數據指針為空時切片本身為nil,這時候切片的長度和容量信息將是無效的。如果有切片的底層數據指針為空,但是長度和容量不為0的情況,那么說明切片本身已經被損壞了

只要是切片的底層數據指針、長度和容量沒有發生變化的話,對切片的遍歷、元素的讀取和修改都和數組是一樣的。在對切片本身賦值或參數傳遞時,和數組指針的操作方式類似,只是復制切片頭信息(reflect.SliceHeader),并不會復制底層的數據。對于類型,和數組的最大不同是,切片的類型和長度信息無關,只要是相同類型元素構成的切片均對應相同的切片類型。

當我們想定義聲明一個切片時可以如下:

在對切片本身賦值或參數傳遞時,和數組指針的操作方式類似,只是復制切片頭信息·(reflect.SliceHeader),并不會復制底層的數據。對于類型,和數組的最大不同是,切片的類型和長度信息無關,只要是相同類型元素構成的切片均對應相同的切片類型

3.6.2 添加元素

append() :內置的泛型函數,可以向切片中增加元素。

  • 在切片尾部追加N個元素

var a []int
a = append(a, 1)               // 追加1個元素
a = append(a, 1, 2, 3)         // 追加多個元素, 手寫解包方式
a = append(a, []int{1,2,3}...) // 追加一個切片, 切片需要解包

注意:尾部添加在容量不足的條件下需要重新分配內存,可能導致巨大的內存分配和復制數據代價。即使容量足夠,依然需要用append函數的返回值來更新切片本身,因為新切片的長度已經發生了變化。

  • 在切片開頭位置添加元素

var a = []int{1,2,3}
a = append([]int{0}, a...)        // 在開頭位置添加1個元素
a = append([]int{-3,-2,-1}, a...) // 在開頭添加1個切片

注意:在開頭一般都會導致內存的重新分配,而且會導致已有的元素全部復制1次。因此,從切片的開頭添加元素的性能一般要比從尾部追加元素的性能差很多。

  • append鏈式操作

var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...)     // 在第i個位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i個位置插入切片

每個添加操作中的第二個append調用都會創建一個臨時切片,并將a[i:]的內容復制到新創建的切片中,然后將臨時創建的切片再追加到a[:i]。

  • append和copy組合

a = append(a, 0)     // 切片擴展1個空間
copy(a[i+1:], a[i:]) // a[i:]向后移動1個位置
a[i] = x             // 設置新添加的元素

第三個操作中會創建一個臨時對象,我們可以借用copy函數避免這個操作,這種方式操作語句雖然冗長了一點,但是相比前面的方法,可以減少中間創建的臨時切片。

3.6.3 刪除元素

根據要刪除元素的位置有三種情況:

1、從開頭位置刪除;

  • 直接移動數據指針,代碼如下:

a = []int{1, 2, 3, ...}
a = a[1:]                       // 刪除開頭1個元素
a = a[N:]                       // 刪除開頭N個元素

  • 將后面的數據向開頭移動,使用append原地完成(所謂原地完成是指在原有的切片數據對應的內存區間內完成,不會導致內存空間結構的變化)

a = []int{1, 2, 3, ...}
a = append(a[:0], a[1:]...) // 刪除開頭1個元素
a = append(a[:0], a[N:]...) // 刪除開頭N個元素

  • 使用copy將后續數據向前移動,代碼如下:

a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 刪除開頭1個元素
a = a[:copy(a, a[N:])] // 刪除開頭N個元素

2、從中間位置刪除;
對于刪除中間的元素,需要對剩余的元素進行一次整體挪動,同樣可以用append或copy原地完成:

  • append刪除操作如下:

a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1], ...)
a = append(a[:i], a[i+N:], ...)

  • copy刪除操作如下:

a = []int{1, 2, 3}
a = a[:copy(a[:i], a[i+1:])] // 刪除中間1個元素
a = a[:copy(a[:i], a[i+N:])] // 刪除中間N個元素

3、從尾部刪除。

代碼如下所示:

a = []int{1, 2, 3, ...}

a = a[:len(a)-1]   // 刪除尾部1個元素
a = a[:len(a)-N]   // 刪除尾部N個元素

刪除切片尾部的元素是最快的

3.7 函數

為完成某一功能的程序指令(語句)的集合,稱為函數。

3.7.1 函數分類

在Go語言中,函數是第一類對象,我們可以將函數保持到變量中。函數主要有具名匿名之分,包級函數一般都是具名函數,具名函數是匿名函數的一種特例,當匿名函數引用了外部作用域中的變量時就成了閉包函數,閉包函數是函數式編程語言的核心。

舉例代碼如下:

  • 具名函數:就和c語言中的普通函數意義相同,具有函數名、返回值以及函數參數的函數。

func Add(a, b int) int {
    return a+b
}

  • 匿名函數:指不需要定義函數名的一種函數實現方式,它由一個不帶函數名的函數聲明和函數體組成。

var Add = func(a, b int) int {
    return a+b
}

解釋幾個名詞如下:

  1. 閉包函數:返回為函數對象,不僅僅是一個函數對象,在該函數外還包裹了一層作用域,這使得,該函數無論在何處調用,優先使用自己外層包裹的作用域。

  2. 一級對象:支持閉包的多數語言都將函數作為第一級對象,就是說函數可以存儲到變量中作為參數傳遞給其他函數,最重要的是能夠被函數動態創建和返回。

  3. 包:go的每一個文件都是屬于一個包的,也就是說go是以包的形式來管理文件和項目目錄結構的。

3.7.2 函數聲明和定義

Go 語言函數定義格式如下:

func fuction_name([parameter list])[return types]{
	函數體
}


解析
func函數由func開始聲明
function_name函數名稱
parameter list參數列表
return_types返回類型
函數體函數定義的代碼集合

3.7.3 函數傳參

Go語言中的函數可以有多個參數和多個返回值,參數和返回值都是以傳值的方式和被調用者交換數據。在語法上,函數還支持可變數量的參數,可變數量的參數必須是最后出現的參數,可變數量的參數其實是一個切片類型的參數。

當可變參數是一個空接口類型時,調用者是否解包可變參數會導致不同的結果,我們解釋一下解包的含義,代碼如下:

func main(){
	var a = []int{1, 2, 3}
	Print(a...)   // 解包
	Print(a)	  // 未解包
}

func Print(a ...int{}) {
	fmt.Println(a...)
}

以上當傳入參數為a...時即是對切片a進行了解包,此時其實相當于直接調用Print(1,2,3)。當傳入參數直接為 a時等價于直接調用Print([]int{}{1,2,3})

3.7.4 函數返回值

不僅函數的參數可以有名字,也可以給函數的返回值命名。

舉例代碼如下:

func Find(m map[int]int, key int)(value int, ok bool) {
	value,ok = m[key]
	return
}

如果返回值命名了,可以通過名字來修改返回值,也可以通過defer語句在return語句之后修改返回值,舉例代碼如下:

func mian() {
	for i := 0 ; i<3; i++ {
		defer func() { println(i) }
	}
}

// 該函數最終的輸出為:
// 3
// 3
// 3

以上代碼中如果沒有defer其實返回值就是0,1,2,但defer語句會在函數return之后才會執行,也就是或只有以上函數在執行結束return之后才會執行defer語句,而該函數return時的i值將會達到3,所以最終的defer語句執行printlin的輸出都是3。

defer語句延遲執行的其實是一個匿名函數,因為這個匿名函數捕獲了外部函數的局部變量v,這種函數我們一般叫閉包。閉包對捕獲的外部變量并不是傳值方式訪問,而是以引用的方式訪問。

這種方式往往會帶來一些問題,修復方法為在每一輪迭代中都為defer函數提供一個獨有的變量,修改代碼如下:

func main() {
    for i := 0; i < 3; i++ {
        i := i // 定義一個循環體內局部變量i
        defer func(){ println(i) } ()
    }
}

func main() {
    for i := 0; i < 3; i++ {
        // 通過函數傳入i
        // defer 語句會馬上對調用參數求值
        // 不再捕獲,而是直接傳值
        defer func(i int){ println(i) } (i)
    }
}

3.7.5 遞歸調用

Go語言中,函數還可以直接或間接地調用自己,也就是支持遞歸調用。Go語言函數的遞歸調用深度邏輯上沒有限制,函數調用的棧是不會出現溢出錯誤的,因為Go語言運行時會根據需要動態地調整函數棧的大小。這部分的知識將會涉及goroutint和動態棧的相關知識,我們將會在之后的博文中向大家解釋。

它的語法和c很相似,格式如下:

func recursion() {
   recursion() /* 函數調用自身 */
}

func main() {
   recursion()
}

感謝各位的閱讀,以上就是“go語言的數據類型有什么用”的內容了,經過本文的學習后,相信大家對go語言的數據類型有什么用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

高雄县| 高雄市| 曲水县| 元氏县| 三门县| 巨鹿县| 磐石市| 巩留县| 武平县| 伊宁市| 金寨县| 德钦县| 牡丹江市| 道孚县| 海盐县| 鹤山市| 若尔盖县| 略阳县| 宜君县| 阿坝| 平罗县| 灵武市| 疏勒县| 南丹县| 万源市| 禹城市| 双鸭山市| 乐亭县| 祥云县| 昭觉县| 大余县| 辽阳市| 林芝县| 九江县| 台中市| 永定县| 秦安县| 惠安县| 大姚县| 牙克石市| 榆中县|