<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                [TOC] ## 使用`new`進行分配 Go有兩個分配原語,內建函數`new`和`make`。它們所做的事情有所不同,并且用于不同的類型。這會有些令人混淆,但規則其實很簡單。我們先講下`new`。這是一個用來分配內存的內建函數,但是不像在其它語言中,它并不*初始化*內存,只是將其*置零*。也就是說,`new(T)`會為`T`類型的新項目,分配被置零的存儲,并且返回它的地址,一個類型為`*T`的值。在Go的術語中,其返回一個指向新分配的類型為`T`,值為零的指針。 由于`new`返回的內存是被置零的,這會有助于你將數據結構設計成,每個類型的零值都可以使用,而不需要進一步初始化。這意味著,數據結構的用戶可以使用`new`來創建數據,并正確使用。例如,`bytes.Buffer`的文檔說道,"`Buffer`的零值是一個可以使用的空緩沖"。類似的,`sync.Mutex`沒有顯式的構造器和`Init`方法。相反的,`sync.Mutex`的零值被定義為一個未加鎖的互斥。 “零值可用”的屬性是可以傳遞的。考慮這個類型聲明。 ~~~ type SyncedBuffer struct { lock sync.Mutex buffer bytes.Buffer } ~~~ `SyncedBuffer`類型的值也可以在分配或者聲明之后直接使用。在下一個片段中,`p`和`v`都不需要進一步的處理便可以正確地工作。 ~~~ p := new(SyncedBuffer) // type *SyncedBuffer var v SyncedBuffer // type SyncedBuffer ~~~ ## 構造器和復合文字 有時候零值并不夠好,需要一個初始化構造器(constructor),正如這個源自程序包`os`的例子。 ~~~ func NewFile(fd int, name string) *File { if fd < 0 { return nil } f := new(File) f.fd = fd f.name = name f.dirinfo = nil f.nepipe = 0 return f } ~~~ 有許多這樣的模版。我們可以使用*復合文字(composite literal)*進行簡化,其為一個表達式,在每次求值的時候會創建一個新實例。 ~~~ func NewFile(fd int, name string) *File { if fd < 0 { return nil } f := File{fd, name, nil, 0} return &f } ~~~ 注意,不像C,返回一個局部變量的地址是絕對沒有問題的;變量關聯的存儲在函數返回之后依然存在。實際上,使用復合文字的地址也會在每次求值時分配一個新的實例,所以,我們可以將最后兩行合并起來。 ~~~ return &File{fd, name, nil, 0} ~~~ 復合文字的域按順序排列,并且必須都存在。然而,通過*field*`:`*value*顯式地為元素添加標號,則初始化可以按任何順序出現,沒有出現的則對應為零值。因此,我們可以寫成 ~~~ return &File{fd: fd, name: name} ~~~ 作為一種極端情況,如果復合文字根本不包含域,則會為該類型創建一個零值。表達式`new(File)`和`&File{}`是等價的。 復合文字還可用于arrays,slices和maps,域標號使用適當的索引或者map key。下面的例子中,不管`Enone`,`Eio`和`Einval`的值是什么,只要它們不同,初始化就可以工作。 ~~~ a := [...]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} s := []string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"} ~~~ ## 使用`make`進行分配 回到分配的話題。內建函數`make(T,?`*args*`)`與`new(T)`的用途不一樣。它只用來創建slice,map和channel,并且返回一個*初始化的*(而不是*置零*),類型為`T`的值(而不是`*T`)。之所以有所不同,是因為這三個類型的背后是象征著,對使用前必須初始化的數據結構的引用。例如,slice是一個三項描述符,包含一個指向數據(在數組中)的指針,長度,以及容量,在這些項被初始化之前,slice都是`nil`的。對于slice,map和channel,`make`初始化內部數據結構,并準備好可用的值。例如, ~~~ make([]int, 10, 100) ~~~ 分配一個有100個int的數組,然后創建一個長度為10,容量為100的slice結構,并指向數組前10個元素上。(當創建slice時,容量可以省略掉,更多信息參見slice章節。)對應的,`new([]int)`返回一個指向新分配的,被置零的slice結構體的指針,即指向`nil`slice值的指針。 這些例子闡釋了`new`和`make`之間的差別。 ~~~ var p *[]int = new([]int) // allocates slice structure; *p == nil; rarely useful var v []int = make([]int, 100) // the slice v now refers to a new array of 100 ints // Unnecessarily complex: var p *[]int = new([]int) *p = make([]int, 100, 100) // Idiomatic: v := make([]int, 100) ~~~ 記住`make`只用于map,slice和channel,并且不返回指針。要獲得一個顯式的指針,使用`new`進行分配,或者顯式地使用一個變量的地址。 ## 數組 數組可以用于規劃內存的精細布局,有時利于避免分配,不過從根本上講,它們是切片的基本構件,這是下一章節的話題。作為鋪墊,這里介紹一下數組。 在Go和C中,數組的工作方式有幾個重要的差別。在Go中, * 數組是值。將一個數組賦值給另一個,會拷貝所有的元素。 * 特別是,如果你給函數傳遞一個數組,其將收到一個數組的*拷貝*,而不是它的指針。 * 數組的大小是其類型的一部分。類型`[10]int`和`[20]int`是不同的。 數組為值這樣的屬性,可以很有用處,不過也會有代價;如果你希望類C的行為和效率,可以傳遞一個數組的指針。 ~~~ func Sum(a *[3]float64) (sum float64) { for _, v := range *a { sum += v } return } array := [...]float64{7.0, 8.5, 9.1} x := Sum(&array) // Note the explicit address-of operator ~~~ 不過,這種風格并不符合Go的語言習慣。相反的,應該使用切片。 ## 切片 切片(slice)對數組進行封裝,提供了一個針對串行數據,更加通用,強大和方便的接口。除了像轉換矩陣這樣具有顯式維度的項,Go中大多數的數組編程都是通過切片完成,而不是簡單數組。 切片持有對底層數組的引用,如果你將一個切片賦值給另一個,二者都將引用同一個數組。如果函數接受一個切片參數,那么其對切片的元素所做的改動,對于調用者是可見的,好比是傳遞了一個底層數組的指針。因此,`Read`函數可以接受一個切片參數,而不是一個指針和一個計數;切片中的長度已經設定了要讀取的數據的上限。這是程序包`os`中,`File`類型的`Read`方法的簽名: ~~~ func (file *File) Read(buf []byte) (n int, err error) ~~~ 該方法返回讀取的字節數和一個錯誤值,如果存在的話。要讀取一個大緩沖`b`中的前32個字節,可以將緩沖進行*切片*(這里是動詞)。 ~~~ n, err := f.Read(buf[0:32]) ~~~ 這種切片很常見,而且有效。實際上,如果先不考慮效率,下面的片段也可以讀取緩沖的前32個字節。 ~~~ var n int var err error for i := 0; i < 32; i++ { nbytes, e := f.Read(buf[i:i+1]) // Read one byte. if nbytes == 0 || e != nil { err = e break } n += nbytes } ~~~ 只要還符合底層數組的限制,切片的長度就可以進行改變;直接將其賦值給它自己的切片。切片的*容量*,可以通過內建函數`cap`訪問,告知切片可以獲得的最大長度。這里有一個函數可以為切片增加數據。如果數據超出了容量,則切片會被重新分配,然后返回新產生的切片。該函數利用了一個事實,即當用于`nil`切片時,`len`和`cap`是合法的,并且返回0. ~~~ func Append(slice, data[]byte) []byte { l := len(slice) if l + len(data) > cap(slice) { // reallocate // Allocate double what's needed, for future growth. newSlice := make([]byte, (l+len(data))*2) // The copy function is predeclared and works for any slice type. copy(newSlice, slice) slice = newSlice } slice = slice[0:l+len(data)] for i, c := range data { slice[l+i] = c } return slice } ~~~ 我們必須在后面返回切片,盡管`Append`可以修改`slice`的元素,切片本身(持有指針,長度和容量的運行時數據結構)是按照值傳遞的。 為切片增加元素的想法非常有用,以至于實現了一個內建的`append`函數。不過,要理解該函數的設計,我們還需要一些更多的信息,所以我們放到后面再說。 ## 二維切片 Go的數組和切片都是一維的。要創建等價的二維數組或者切片,需要定義一個數組的數組或者切片的切片,類似這樣: ~~~ type Transform [3][3]float64 // A 3x3 array, really an array of arrays. type LinesOfText [][]byte // A slice of byte slices. ~~~ 因為切片是可變長度的,所以可以將每個內部的切片具有不同的長度。這種情況很常見,正如我們的`LinesOfText`例子中:每一行都有一個獨立的長度。 ~~~ text := LinesOfText{ []byte("Now is the time"), []byte("for all good gophers"), []byte("to bring some fun to the party."), } ~~~ 有時候是需要分配一個二維切片的,例如這種情況可見于當掃描像素行的時候。有兩種方式可以實現。一種是獨立的分配每一個切片;另一種是分配單個數組,為其 指定單獨的切片們。使用哪一種方式取決于你的應用。如果切片們可能會增大或者縮小,則它們應該被單獨的分配以避免覆寫了下一行;如果不會,則構建單個分配 會更加有效。作為參考,這里有兩種方式的框架。首先是一次一行: ~~~ // Allocate the top-level slice. picture := make([][]uint8, YSize) // One row per unit of y. // Loop over the rows, allocating the slice for each row. for i := range picture { picture[i] = make([]uint8, XSize) } ~~~ 然后是分配一次,被切片成多行: ~~~ // Allocate the top-level slice, the same as before. picture := make([][]uint8, YSize) // One row per unit of y. // Allocate one large slice to hold all the pixels. pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8. // Loop over the rows, slicing each row from the front of the remaining pixels slice. for i := range picture { picture[i], pixels = pixels[:XSize], pixels[XSize:] } ~~~ ## Maps Map是一種方便,強大的內建數據結構,其將一個類型的值(*key*)與另一個類型的值(*element*或*value*) 關聯一起。key可以為任何定義了等于操作符的類型,例如整數,浮點和復數,字符串,指針,接口(只要其動態類型支持等于操作),結構體和數組。切片不能 作為map的key,因為它們沒有定義等于操作。和切片類似,map持有對底層數據結構的引用。如果將map傳遞給函數,其對map的內容做了改變,則這 些改變對于調用者是可見的。 Map可以使用通常的復合文字語法來構建,使用分號分隔key和value,這樣很容易在初始化的時候構建它們。 ~~~ var timeZone = map[string]int{ "UTC": 0*60*60, "EST": -5*60*60, "CST": -6*60*60, "MST": -7*60*60, "PST": -8*60*60, } ~~~ 賦值和獲取map的值,在語法上看起來跟數組和切片類似,只不過索引不需要為一個整數。 ~~~ offset := timeZone["EST"] ~~~ 嘗試使用一個不在map中的key來獲取map值,將會返回map中元素相應類型的零值。例如,如果map包含的是整數,則查找一個不存在的key將會返回`0`。可以通過值類型為`bool`的map來實現一個集合。將map項設置為`true`,來將值放在集合中,然后通過簡單的索引來進行測試。 ~~~ attended := map[string]bool{ "Ann": true, "Joe": true, ... } if attended[person] { // will be false if person is not in the map fmt.Println(person, "was at the meeting") } ~~~ 有時你需要區分開沒有的項和值為零的項。是否有一個項為`"UTC"`,或者由于其根本不在map中,所以為空字符串?你可以通過多賦值的形式來進行辨別。 ~~~ var seconds int var ok bool seconds, ok = timeZone[tz] ~~~ 這被形象的稱作為“comma ok”用法。在這個例子中,如果`tz`存在,`seconds`將被設置為適當的值,`ok`將為真;如果不存在,`seconds`將被設置為零,`ok`將為假。這有個例子,并增加了一個友好的錯誤報告: ~~~ func offset(tz string) int { if seconds, ok := timeZone[tz]; ok { return seconds } log.Println("unknown time zone:", tz) return 0 } ~~~ 如果只測試是否在map中存在,而不關心實際的值,你可以將通常使用變量的地方換成[空白標識符](http://www.hellogcc.org/effective_go.html#blank)(`_`) ~~~ _, present := timeZone[tz] ~~~ 要刪除一個map項,使用`delete`內建函數,其參數為map和要刪除的key。即使key已經不在map中,這樣做也是安全的。 ~~~ delete(timeZone, "PDT") // Now on Standard Time ~~~ ## 打印輸出 Go中的格式化打印使用了與C中`printf`家族類似的風格,不過更加豐富和通用。這些函數位于`fmt`程序包中,并具有大寫的名字:`fmt.Printf`,`fmt.Fprintf`,`fmt.Sprintf`等等。字符串函數(`Sprintf`等)返回一個字符串,而不是填充到提供的緩沖里。 你不需要提供一個格式串。對每個`Printf`,`Fprintf`和`Sprintf`,都有另外一對相應的函數,例如`Print`和`Println`。這些函數不接受格式串,而是為每個參數生成一個缺省的格式。`Println`版本還會在參數之間插入一個空格,并添加一個換行,而`Print`版本只有當兩邊的操作數都不是字符串的時候才增加一個空格。在這個例子中,每一行都會產生相同的輸出。 ~~~ fmt.Printf("Hello %d\n", 23) fmt.Fprint(os.Stdout, "Hello ", 23, "\n") fmt.Println("Hello", 23) fmt.Println(fmt.Sprint("Hello ", 23)) ~~~ 格式化打印函數`fmt.Fprint`等,接受的第一個參數為任何一個實現了`io.Writer`接口的對象;變量`os.Stdout`和`os.Stderr`是常見的實例。 接下來這些就和C不同了。首先,數字格式,像`%d`,并不接受正負號和大小的標記;相反的,打印程序使用參數的類型來決定這些屬性。 ~~~ var x uint64 = 1<<64 - 1 fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x)) ~~~ 會打印出 ~~~ 18446744073709551615 ffffffffffffffff; -1 -1 ~~~ 如果只是想要缺省的轉換,像十進制整數,你可以使用通用格式`%v`(代表“value”);這正是`Print`和`Println`所產生的結果。而且,這個格式可以打印*任意的*的值,甚至是數組,切片,結構體和map。這是一個針對前面章節中定義的時區map的打印語句 ~~~ fmt.Printf("%v\n", timeZone) // or just fmt.Println(timeZone) ~~~ 其會輸出 ~~~ map[CST:-21600 PST:-28800 EST:-18000 UTC:0 MST:-25200] ~~~ 當然,map的key可能會按照任意順序被輸出。當打印一個結構體時,帶修飾的格式`%+v`會將結構體的域使用它們的名字進行注解,對于任意的值,格式`%#v`會按照完整的Go語法打印出該值。 ~~~ type T struct { a int b float64 c string } t := &T{ 7, -2.35, "abc\tdef" } fmt.Printf("%v\n", t) fmt.Printf("%+v\n", t) fmt.Printf("%#v\n", t) fmt.Printf("%#v\n", timeZone) ~~~ 會打印出 ~~~ &{7 -2.35 abc def} &{a:7 b:-2.35 c:abc def} &main.T{a:7, b:-2.35, c:"abc\tdef"} map[string] int{"CST":-21600, "PST":-28800, "EST":-18000, "UTC":0, "MST":-25200} ~~~ (注意符號&)還可以通過`%q`來實現帶引號的字符串格式,用于類型為`string`或`[]byte`的值。格式`%#q`將盡可能的使用反引號。(格式`%q`還用于整數和符文,產生一個帶單引號的符文常量。)還有,`%x`用于字符串,字節數組和字節切片,以及整數,生成一個長的十六進制字符串,并且如果在格式中有一個空格(`%?x`),其將會在字節中插入空格。 另一個方便的格式是`%T`,其可以打印出值的*類型*。 ~~~ fmt.Printf("%T\n", timeZone) ~~~ 會打印出 ~~~ map[string] int ~~~ 如果你想控制自定義類型的缺省格式,只需要對該類型定義一個簽名為`String() string`的方法。對于我們的簡單類型`T`,看起來可能是這樣的。 ~~~ func (t *T) String() string { return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c) } fmt.Printf("%v\n", t) ~~~ 會按照如下格式打印 ~~~ 7/-2.35/"abc\tdef" ~~~ (如果你需要打印類型為`T`的*值*,同時需要指向`T`的指針,那么`String`的接收者必須為值類型的;這個例子使用了指針,是因為這對于結構體類型更加有效和符合語言習慣。更多信息參見下面的章節[pointers vs. value receivers](http://www.hellogcc.org/effective_go.html#pointers_vs_values)) 我們的`String`方法可以調用`Sprintf`,是因為打印程序是完全可重入的,并且可以按這種方式進行包裝。然而,對于這種方式,有一個重要的細節需要明白:不要將調用`Sprintf`的`String`方法構造成無窮遞歸。如果`Sprintf`調用嘗試將接收者直接作為字符串進行打印,就會導致再次調用該方法,發生這樣的情況。這是一個很常見的錯誤,正如這個例子所示。 ~~~ type MyString string func (m MyString) String() string { return fmt.Sprintf("MyString=%s", m) // Error: will recur forever. } ~~~ 這也容易修改:將參數轉換為沒有方法函數的,基本的字符串類型。 ~~~ type MyString string func (m MyString) String() string { return fmt.Sprintf("MyString=%s", string(m)) // OK: note conversion. } ~~~ 在[初始化章節](http://www.hellogcc.org/effective_go.html#initialization),我們將會看到另一種避免該遞歸的技術。 另一種打印技術,是將一個打印程序的參數直接傳遞給另一個這樣的程序。`Printf`的簽名使用了類型`...interface{}`作為最后一個參數,來指定在格式之后可以出現任意數目的(任意類型的)參數。 ~~~ func Printf(format string, v ...interface{}) (n int, err error) { ~~~ 在函數`Printf`內部,`v`就像是一個類型為`[]interface{}`的變量,但是如果其被傳遞給另一個可變參數的函數,其就像是一個正常的參數列表。這里有一個對我們上面用到的函數`log.Println`的實現。其將參數直接傳遞給`fmt.Sprintln`來做實際的格式化。 ~~~ // Println prints to the standard logger in the manner of fmt.Println. func Println(v ...interface{}) { std.Output(2, fmt.Sprintln(v...)) // Output takes parameters (int, string) } ~~~ 我們在嵌套調用`Sprintln`中`v`的后面使用了`...`來告訴編譯器將`v`作為一個參數列表;否則,其會只將`v`作為單個切片參數進行傳遞。 除了我們這里講到的之外,還有很多有關打印的技術。詳情參見`godoc`文檔中對`fmt`的介紹。 順便說下,`...`參數可以為一個特定的類型,例如`...int`,可以用于最小值函數,來選擇整數列表中的最小值: ~~~ func Min(a ...int) int { min := int(^uint(0) >> 1) // largest int for _, i := range a { if i < min { min = i } } return min } ~~~ ## append內建函數 現在,我們需要解釋下`append`內建函數的設計了。`append`的簽名與我們上面定制的`Append`函數不同。簡略地講,類似于這樣: ~~~ func append(slice []T, elements ...T) []T ~~~ 其中*T*為任意給定類型的占位符。你在Go中是無法寫出一個類型`T`由調用者來確定的函數。這就是為什么`append`是內建的:它需要編譯器的支持。 `append`所做的事情是將元素添加到切片的結尾,并返回結果。需要返回結果,是因為和我們手寫的`Append`一樣,底層的數組可能會改變。這個簡單的例子 ~~~ x := []int{1,2,3} x = append(x, 4, 5, 6) fmt.Println(x) ~~~ 會打印出`[1 2 3 4 5 6]`。所以`append`的工作方式有點像`Printf`,搜集任意數目的參數。 但是,如果我們想按照我們的`Append`那樣做,給切片增加一個切片,那么該怎么辦?簡單:在調用點使用`...`,就像我們在上面調用`Output`時一樣。這個片段會產生和上面相同的輸出。 ~~~ x := []int{1,2,3} y := []int{4,5,6} x = append(x, y...) fmt.Println(x) ~~~ 如果沒有`...`,則會因為類型錯誤而無法編譯;`y`不是`int`型的。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看