溫馨提示×

溫馨提示×

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

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

go語言的數(shù)據(jù)類型有什么用

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

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

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

1. Go語言的出現(xiàn)

在具體學(xué)習(xí)go語言的基礎(chǔ)語法之前,我們來了解一下go語言出現(xiàn)的時機(jī)及其特點。

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

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

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

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包。

  • 函數(shù),和c語言相同,即是一個可以實現(xiàn)某一個功能的函數(shù)體,每一個可執(zhí)行程序中必須擁有一個main函數(shù)。

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

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

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

需要注意的是:標(biāo)識符是用來命名變量、類型等程序?qū)嶓w。一個標(biāo)識符實際上就是一個或是多個字母和數(shù)字、下劃線_組成的序列,但是第一個字符必須是字母或下劃線而不能是數(shù)字。

  • 當(dāng)標(biāo)識符(包括常量、變量、類型、函數(shù)名、結(jié)構(gòu)字段等等)以一個大寫字母開頭,如:Group1,那么使用這種形式的標(biāo)識符的對象就可以被外部包的代碼所使用(客戶端程序需要先導(dǎo)入這個包),這被稱為導(dǎo)出(像面向?qū)ο笳Z言中的 public);

  • 標(biāo)識符如果以小寫字母開頭,則對包外是不可見的,但是他們在整個包的內(nèi)部是可見并且可用的(像面向?qū)ο笳Z言中的 protected)。

3. 數(shù)據(jù)類型

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

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

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

3.0 定義變量

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

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

3.0.1 如果變量沒有初始化

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

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

3.0.2 如果變量沒有指定類型

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

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

3.0.3 :=符號

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

使用格式: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"

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

var ( 
    a int
    b bool
)

3.0.5 匿名變量

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

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

示例代碼如下:

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

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

3.0.6 變量作用域

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

  • 函數(shù)內(nèi)定義的變量為局部變量,這種局部變量的作用域只在函數(shù)體內(nèi),函數(shù)的參數(shù)和返回值變量都屬于局部變量。這種變量在存在于函數(shù)被調(diào)用時,銷毀于函數(shù)調(diào)用結(jié)束后。

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

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

3.1 基本類型

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

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

3.2 指針

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

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

3.2.1 指針聲明和初始化

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

代碼舉例如下:

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

指針的初始化就是取出相對應(yīng)的變量地址對指針進(jìn)行賦值,具體如下:

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

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

3.2.2 空指針

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

3.3 數(shù)組

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

3.3.1 聲明數(shù)組

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

var variable_name [SIZE] variable_type

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

var balance [10] float32

3.3.2 初始化數(shù)組

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

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

  • 通過字面量在聲明數(shù)組的同時快速初始化數(shù)組:balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

  • 數(shù)組長度不確定,編譯器通過元素個數(shù)自行推斷數(shù)組長度,在[ ]中填入...,舉例如下: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}

  • 數(shù)組長度確定,指定下標(biāo)進(jìn)行部分初始化:balanced := [5]float32(1:2.0, 3:7.0)

注意:

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

3.3.3 go中的數(shù)組名意義

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

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

3.3.4 數(shù)組指針

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

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

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

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

具體關(guān)于go語言的循環(huán)語句我們在后文中再進(jìn)行詳細(xì)介紹。

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

通過上述數(shù)組的學(xué)習(xí),我們就可以直接定義多個同類型的變量,但這往往也是一種限制,只能存儲同一種類型的數(shù)據(jù),而我們在結(jié)構(gòu)體中就可以定義多個不同的數(shù)據(jù)類型。

3.4.1 聲明結(jié)構(gòu)體

在聲明結(jié)構(gòu)體之前我們首先需要定義一個結(jié)構(gòu)體類型,這需要使用type和struct,type用于設(shè)定結(jié)構(gòu)體的名稱,struct用于定義一個新的數(shù)據(jù)類型。具體結(jié)構(gòu)如下:

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

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

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

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

3.4.2 訪問結(jié)構(gòu)體成員

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

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 結(jié)構(gòu)體指針

關(guān)于結(jié)構(gòu)體指針的定義和申明同樣可以套用前文中講到的指針的相關(guān)定義,從而使用一個指針變量存放一個結(jié)構(gòu)體變量的地址。

定義一個結(jié)構(gòu)體變量的語法:var struct_pointer *Books。

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

3.5 字符串

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

3.5.1 字符串定義和初始化

Go語言字符串的底層結(jié)構(gòu)在reflect.StringHeader中定義,具體如下:

type StringHeader struct {
    Data uintptr
    Len  int
}

也就是說字符串結(jié)構(gòu)由兩個信息組成:第一個是字符串指向的底層字節(jié)數(shù)組,第二個是字符串的字節(jié)的長度。

字符串其實是一個結(jié)構(gòu)體,因此字符串的賦值操作也就是reflect.StringHeader結(jié)構(gòu)體的復(fù)制過程,并不會涉及底層字節(jié)數(shù)組的復(fù)制,所以我們也可以將字符串?dāng)?shù)組看作一個結(jié)構(gòu)體數(shù)組。

字符串和數(shù)組類似,內(nèi)置的len函數(shù)返回字符串的長度。

3.5.2 字符串UTF8編碼

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

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

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

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

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

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

3.5.3 字符串的強(qiáng)制類型轉(zhuǎn)換

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

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

  • 采用傳統(tǒng)的下標(biāo)方式遍歷字符串的字節(jié)數(shù)組

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

不過字符串和[]rune的轉(zhuǎn)換要更為特殊一些,因為一般這種強(qiáng)制類型轉(zhuǎn)換要求兩個類型的底層內(nèi)存結(jié)構(gòu)要盡量一致,顯然它們底層對應(yīng)的[]byte和[]int32類型是完全不同的內(nèi)部布局,因此這種轉(zhuǎn)換可能隱含重新分配內(nèi)存的操作。

3.6 slice

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

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

3.6.1 slice定義

我們先看看切片的結(jié)構(gòu)定義,reflect.SliceHeader:

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

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

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

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

當(dāng)我們想定義聲明一個切片時可以如下:

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

3.6.2 添加元素

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

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

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

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

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

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

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

  • append鏈?zhǔn)讲僮?/p>

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調(diào)用都會創(chuàng)建一個臨時切片,并將a[i:]的內(nèi)容復(fù)制到新創(chuàng)建的切片中,然后將臨時創(chuàng)建的切片再追加到a[:i]。

  • append和copy組合

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

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

3.6.3 刪除元素

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

1、從開頭位置刪除;

  • 直接移動數(shù)據(jù)指針,代碼如下:

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

  • 將后面的數(shù)據(jù)向開頭移動,使用append原地完成(所謂原地完成是指在原有的切片數(shù)據(jù)對應(yīng)的內(nèi)存區(qū)間內(nèi)完成,不會導(dǎo)致內(nèi)存空間結(jié)構(gòu)的變化)

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

  • 使用copy將后續(xù)數(shù)據(jù)向前移動,代碼如下:

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

2、從中間位置刪除;
對于刪除中間的元素,需要對剩余的元素進(jìn)行一次整體挪動,同樣可以用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 函數(shù)

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

3.7.1 函數(shù)分類

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

舉例代碼如下:

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

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

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

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

解釋幾個名詞如下:

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

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

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

3.7.2 函數(shù)聲明和定義

Go 語言函數(shù)定義格式如下:

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


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

3.7.3 函數(shù)傳參

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

當(dāng)可變參數(shù)是一個空接口類型時,調(diào)用者是否解包可變參數(shù)會導(dǎo)致不同的結(jié)果,我們解釋一下解包的含義,代碼如下:

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

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

以上當(dāng)傳入?yún)?shù)為a...時即是對切片a進(jìn)行了解包,此時其實相當(dāng)于直接調(diào)用Print(1,2,3)。當(dāng)傳入?yún)?shù)直接為 a時等價于直接調(diào)用Print([]int{}{1,2,3})

3.7.4 函數(shù)返回值

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

舉例代碼如下:

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) }
	}
}

// 該函數(shù)最終的輸出為:
// 3
// 3
// 3

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

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

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

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

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

3.7.5 遞歸調(diào)用

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

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

func recursion() {
   recursion() /* 函數(shù)調(diào)用自身 */
}

func main() {
   recursion()
}

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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI