?年學 go 1:變量&函數

變量&函數

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

首先搬出咱們最經典的第一段代碼:java

hello world

package main // 0

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

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

首先咱們要認識到python

每一個Go 程序都是由包組成,程序的運行入口是包main
  1. 首行這個是必須的。全部的 Go 文件以 package <something> 開頭,對於獨立運行的執行文件必須是 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 只須要:git

1. go build helloworld.go
    # 結果是叫作 helloworld 的可執行文件。
    2. ./helloworld
    # Hello, world; or καλημε ́ρα κóσμε; or こんにちは世界

變量

Go 是靜態類型語言,不能在運行期改變變量類型。github

變量若是不提供初始化值將自動初始化爲零值。若是提供初始化值,可省略變量類型,由編譯器自動推斷。golang

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("%Tn", 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
    }

參考連接


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

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

相關文章
相關標籤/搜索