?年學 go 1:變數&函式

goodspeed發表於2018-02-22

變數&函式

最近在學習golang,寫下學習筆記提升記憶。 為了看起來不是那麼枯燥,本學習筆記採用分析程式碼的形式。

首先搬出我們最經典的第一段程式碼:

hello world

    package main // 0

    import "fmt" // 1實現格式化的 I/O

    /* Print something */ // 2
    func main() { // 3
    	fmt.Println("Hello, world; or καλημε ́ρα κóσμε; orこんにちは 世界") // 4
    }
複製程式碼

首先我們要認識到

每個Go 程式都是由包組成,程式的執行入口是包main

  1. 首行這個是必須的。所有的 Go 檔案以 package 開頭,對於獨立執行的執行檔案必須是 package main;
  2. 這是說需要將fmt加入到main。不是main 的包被稱為庫 末尾以 // 開頭的內容是單行註釋 Package fmt包含有格式化I/O函式,類似於C語言的printf和scanf
  3. 這也是註釋,表示多行註釋。
  4. package main 必須首先出現,緊跟著是 import。在 Go 中,package 總是首先出現, 然後是 import,然後是其他所有內容。當 Go 程式在執行的時候,首先呼叫的函式 是 main.main(),這是從 C 中繼承而來。這裡定義了這個函式
  5. 呼叫了來自於 fmt 包的函式列印字串到螢幕。字串由 " 包裹,並且可以包含非 ASCII 的字元。這裡使用了希臘文和日文、中文"

編譯和執行程式碼

構建 Go 程式的最佳途徑是使用 go 工具。 構建 helloworld 只需要:

    1. go build helloworld.go
    # 結果是叫做 helloworld 的可執行檔案。
    2. ./helloworld
    # Hello, world; or καλημε ́ρα κóσμε; or こんにちは世界
複製程式碼

變數

Go 是靜態型別語言,不能在執行期改變變數型別。

變數如果不提供初始化值將自動初始化為零值。如果提供初始化值,可省略變數型別,由編譯器自動推斷。

    var x int
    // 使用關鍵字 var 定義變數, 跟函式的引數列表一樣,型別在後面。
    var c, python, java bool
    // 多個相同型別的變數可以寫在一行。
    var f float32 = 1.6
    var i, j int = 1, 2
    // 變數定義可以包含初始值,每個變數對應一個。
    var s = "abc"
    // 如果初始化是使用表示式,則可以省略型別;變數從初始值中獲得型別。
複製程式碼

變數在定義時沒有明確的初始化時會賦值為零值

零值是:

  • 數值型別為 0 ,
  • 布林型別為 false ,
  • 字串為 "" (空字串)。

在函式內部,可用更簡略的 ":=" 式定義變數。

    func main() {
        n, s := 12, "Hello, World!"
        println(s, n)
    }
複製程式碼

函式外的每個語句都必須以關鍵字開始( var 、 func 、等等), := 結構不能使用在函式外。

可一次定義多個變數。

    var x, y, z int
    var s, n = "abc", 123
    var (
    	a int
    	b float32
    )

    func main() {
        n, s := 0x1234, "Hello, World!"
        println(x, s, n)
    }
複製程式碼

一個特殊的變數名是 _(下劃線)。任何賦給它的值都被丟棄。在這個例子中,將 35 賦值給 b,同時丟棄 34。

    _, b := 34, 35
複製程式碼

Go 的編譯器會對宣告卻未使用的變數報錯

    var s string // 全域性變數沒問題。

    func main() {
        i := 0 // Error: i declared and not used。(可使  "_ = i" 規避)
    }
複製程式碼

定義完之後的變數可以被重新賦值 比如第8行,將計算結果賦值給result。

常量

常量值必須是編譯期可確定的數字、字串、布林值。

常量的定義與變數類似,只不過使用 const 關鍵字

    const x, y int = 1, 2
    const s = "Hello, World!"
    // 多常量初始化 // 型別推斷
    // 常量組
    const (
    	a, b = 10, 100
    	c bool = false
    )

    func main() _{
    	const x = 'xxx'      // 未使用區域性常量不會引發編譯錯誤
    }
複製程式碼

在常量中,如果不提供型別和初始化值,那麼被看作和上一常量相同

    const (
		s = "abc"
		x           // x = "abc"
	)
複製程式碼

變數值的引用

通常情況下 go 語言的變數持有相應的值。 對於通道函式方法對映以及切片的引用變數,它們持有的都是引用,也既是儲存指標的變數

值在傳遞給函式或者方法的時候會被複制一次

不同型別引數所佔空間如下:

型別 佔用空間
bool 型別佔1~8個位元組
傳遞字串 佔 16個位元組(64位)或者8個位元組(32位)
傳遞切片 佔 16個位元組(64位)或者12個位元組(32位)
傳遞指標 佔 8個位元組(64位)或者4個位元組(32位)

陣列是按值傳遞的,所以傳遞大陣列代價較大 可用切片代替

變數是賦給記憶體塊的名字,該記憶體塊用於儲存特定的資料型別

指標是指儲存了另一個變數記憶體地址的變數。建立的指標用來指向另一個某種型別的變數。 為了便於理解,我們看以下兩段程式碼。

x := 3    y := 22
// 變數 x, y 為int型 分別賦值 3   22  記憶體地址 0xf840000148   0xf840000150
x == 3  &&  y == 22
複製程式碼
pi := &x

// 變數pi 為 *int(指向int型變數的指標)   在這裡我們將變數x的記憶體地址賦值給pi,即pi 儲存了另一個變數的記憶體地址(這也是指標定義)

pi == 3 && x == 3 && y == 22  
x++

// x + 1 此時 x==4 pi 指向x的記憶體地址 所以

pi == 4 && x == 4 && y == 22

*pi++

// *pi ++ 意為著pi指向的值增加
*pi == 5 & x == 5 && y == 22

pi := &y

//pi 指向y的記憶體地址
*pi == 22 && x == 5 && y == 22

*pi++

// *pi++ 意為著pi指向的值增加

*pi == 23 && x == 5 && y == 23
複製程式碼

基本型別

Go 有明確的數字型別命名, 支援 Unicode, 支援常用資料結構

型別 長度 預設值 說明
bool 1 false
byte 1 0 unit8
rune 4 0 int32 的別名 代表一個Unicode 碼
int, unit 4 或 8 0 32 或 64
int8, unit8 1 0 -128 ~ 127, 0~255
int16, unit16 2 0 -32768 ~ 32767, 0 ~ 65535
int32, unit32 4 0 -21億~ 21億, 0 ~ 42億
int64, unit64 8 0
float32 4 0.0
float64 8 0.0
complex64 8
complex128 16
unitptr 4或8 足以儲存指標的unit32 或unit64 整數
array 值型別
struct 值型別
string "" UTF-8 字串
slice nil 引用型別
map nil 引用型別
channel nil 引用型別
interface nil 介面
function nil 函式

intuintuintptr 型別在32位的系統上一般是32位,而在64位系統上是64位。當你需要使用一個整數型別時,你應該首選 int,僅當有特別的理由才使用定長整數型別或者無符號整數型別。 引用型別包括 slicemapchannel。它們有複雜的內部結構,除了申請記憶體外,還需要初始化相關屬性

型別轉換

go 不支援 隱式的型別轉換

使用表示式 T(v) 將值 v 轉換為型別 T 。

var b byte = 100
// var n int = b // Error: cannot use b (type byte) as type int in assignment
var n int = int(b) // 顯式轉換

複製程式碼

不能將其他型別當 bool 值使用

a := 100
if a {                  // Error: non-bool a (type int) used as if condition
    println("true")
}
複製程式碼

函式

首先看下面這段程式碼

    package main

    import "fmt"

    func add(x int, y int) int {
    	return x + y
    }

    func main() {
    	fmt.Println(add(42, 13))
    }
複製程式碼

函式定義

使用關鍵字 func 定義函式,左大括號不能另起一行

golang中符合規範的函式一般寫成如下的形式:

    func functionName(parameter_list) (return_value_list) {
       …
    }

    // parameter_list 是引數列表
    // return_value_list 是返回值列表 下邊有詳細的講解
複製程式碼

函式的特性

  • 無需宣告原型。 (1)
  • 支援不定長變參。
  • 支援多返回值。
  • 支援命名返回引數。
  • 支援匿名函式和閉包。
  • 不支援 巢狀 (nested)、過載 (overload) 和 預設引數 (default parameter)
    func test(x int, y int, s string) (r int, s string) { // 型別相同的相鄰引數可合併
        n := x + y                                    // 多返回值必須用括號。
        return n, fmt.Sprintf(s, n)
    }
複製程式碼

關鍵字 func 用於定義一個函式 test 是你函式的名字 int 型別的變數 x, y 和 string 型別的變數 s 作為輸入引數引數用pass-by-value方式傳遞,意味著它們會被複制 當兩個或多個連續的函式命名引數是同一型別,則除了最後一個型別之外,其他都可以省略。

在這個例子中:

x int, y int
複製程式碼

被縮寫為

x, y int
複製程式碼

變數 r 和 s 是這個函式的命名返回值。在 Go 的函式中可以返回多個值。 如果不想對返回的引數命名,只需要提供型別:(int, string)。 如果只有一個返回值,可以省略圓括號。如果函式是一個子過程,並且沒有任何返回值,也可以省略這些內容。 函式體。注意 return 是一個語句,所以包裹引數的括號是可選的。 不定長引數其實就是slice,只能有一個,且必須是最後一個。

    func test(s string, n ...int) string {
        var x int
    		for _, i := range n {
    			 x += i
    		}
    	return fmt.Sprintf(s, x)
    }
    // 使用slice 做變參時,必須展開
    func main() {
        s := []int{1, 2, 3}
        println(test("sum: %d", s...))
    }
複製程式碼

函式是第一類物件,可作為引數傳遞

就像其他在 Go 中的其他東西一樣,函式也是值而已。它們可以像下面這樣賦值給變數:

    func main() {
        a := func() {                  // 定義一個匿名函式,並且賦值給 a
    		println("Hello")
    	}                              // 這裡沒有 ()
        a()                            // 呼叫函式
    }
複製程式碼

如果使用 fmt.Printf("%T\n", a) 列印 a 的型別,輸出結果是 func()

返回值

函式可以返回任意數量返回值

Go 函式的返回值或者結果引數可以指定一個名字,並且像原始的變數那樣使用,就像 輸入引數那樣。如果對其命名,在函式開始時,它們會用其型別的零值初始化

    package main

    import "fmt"

    func swap(x, y string) (string, string) {
    	return y, x
    }

    func main() {
    	a, b := swap("hello", "world")
    	fmt.Println(a, b)
    }

    /*
       函式可以返回任意數量返回值
       swap 函式返回了兩個字串
    */

複製程式碼

Go 的返回值可以被命名,並且就像在函式體開頭宣告的變數那樣使用。

    package main

    import "fmt"

    func split(sum int) (x, y int) { // 初始化返回值為 x,y
    	x = sum * 4 / 9              // x,y 已經初始化,可以直接賦值使用
    	y = sum - x
    	return                       // 隱式返回x,y(裸返回)
    }

    func main() {
    	fmt.Println(split(17))
    }

    /*
       在長的函式中這樣的裸返回會影響程式碼的可讀性。
    */
複製程式碼

有返回值的函式,必須有明確的return 語句,否則會引發編譯錯誤

名詞解釋

函式原型

函式宣告由函式返回型別、函式名和形參列表組成。形參列表必須包括形參型別,但是不必對形參命名。這三個元素被稱為函式原型,函式原型描述了函式的介面 函式原型類似函式定義時的函式頭,又稱函式宣告。為了能使函式在定義之前就能被呼叫,C++規定可以先說明函式原型,然後就可以呼叫函式。函式定義可放在程式後面。 由於函式原型是一條語句,因此函式原型必須以分號結束。函式原型由函式返回型別、函式名和參數列組成,它與函式定義的返回型別、函式名和參數列必須一致。函式原型必須包含引數的識別符號(對函式宣告而言是可選的) 注意:函式原型與函式定義必須一致,否則會引起連線錯誤。

下節預告

變數和函式部分暫時這些,有更新還會補充。下一篇將會是控制流。 將會用到的程式碼為:

    package main

    import "fmt"

    func main() {
        result := 0
        for i := 0; i <= 10; i++ {
          result = fibonacci(i)
          fmt.Printf("fibonacci(%d) is: %d\n", i, result)
       }
    }

    func fibonacci(n int) (res int) {
        if n <= 1 {
            res = 1
    	   } else {
    	       res = fibonacci(n-1) + fibonacci(n-2)
    	   }
    	return
    }
複製程式碼

參考連結


最後,感謝女朋友支援和包容,比❤️

想了解以下內容可以在公號輸入相應關鍵字獲取歷史文章: 公號&小程式 | 設計模式 | 併發&協程

歡迎讚賞關注

相關文章