golang快速入門[8.1]-變量類型、聲明賦值、作用域聲明週期與變量內存分配

前文

題記

  • 在上一篇文章中,我們介紹了吸心大法—go module的技巧來引用第三方代碼,但是看過武俠小說的同學都知道,只有內力沒有招式那也是花架子。正所謂"巧婦難爲無米之炊",我們將在後面幾章鞏固基本功,介紹go語言的語法、基本概念和性質。

前言

我們將在本文中學習到:

  • 變量的內涵
  • 變量的數據類型
  • 變量的多種聲明與賦值
  • 變量的命名
  • 變量的作用域與示例
  • 變量的內存分配方式

變量是什麼

  • 在計算機編程中,變量(Variable)是與關聯的符號名配對的存儲地址(內存地址標識)
  • 變量用於存儲要在計算機程序中引用和操作的信息。變量還提供了一種使用描述性名稱標記數據的方法,因此讀者和開發人員都可以更清楚地理解程序。可以將將變量視爲保存信息的空間。
  • 編譯器必須用數據的實際地址替換變量的符號名。儘管變量的名稱,類型和地址通常保持不變,但存儲在地址中的數據可能會在程序執行期間發生更改。

變量的數據類型

go語言是靜態類型的語言,需要在運行前明確變量的數據類型以及大小。如下圖是靜態語言與動態語言的區別。動態語言可以在運行時擴展變量的大小。

  • 數據類型是數據的屬性,它告訴編譯器打算如何使用數據
  • 大多數編程語言都支持基本數據類型,包括整數,浮點數,字符和布爾值等。數據類型定義了可以對數據執行的操作,數據的含義以及該類型值的存儲方式
  • Go語言的數值類型包括幾種不同大小的整數、浮點數和複數。每種數值類型都決定了對應的大小範圍和是否支持正負符號。讓我們先從整型數類型開始介紹
  • Go語言同時提供了有符號和無符號類型的整數運算,有int8、int16、int32和int64四種截然不同大小的有符號整型數類型,分別對應8、16、32、64bit大小的有符號整型數,與此對應的是uint8、uint16、uint32和uint64四種無符號整型數類型。
  • 還有兩種一般對應特定CPU平臺機器字大小的有符號和無符號整數int和uint。其中int是應用最廣泛的數值類型。這兩種類型都有同樣的大小,32或64bit,但是我們不能對此做任何的假設;因爲不同的編譯器卽使在相同的硬件平臺上可能產生不同的大小。
  • Unicode字符rune類型是和int32等價的類型,通常用於表示一個Unicode碼點。這兩個名稱可以互換使用。同樣byte也是uint8類型的等價類型,byte類型一般用於強調數值是一個原始的數據而不是一個小的整數。
  • 最後,還有一種無符號的整數類型uintptr,沒有指定具體的bit大小但是足以容納指針。uintptr類型只有在底層編程時才需要,特別是Go語言和C語言函數庫或操作系統接口相交互的地方。在介紹指針時,會詳細介紹它。

變量的聲明與賦值

變量的聲明使用var來標識,變量聲明的通用格式如下:

var name type = expression

 

  • 函數體外部:變量聲明方式1
var i int
  • 函數體外部:變量聲明方式2
// 外部連續聲明
var U, V, W float64
  • 函數體外部:變量聲明方式3
// 賦值不帶類型,自動推斷
var k = 0
  • 函數體外部:變量聲明方式4
// 外部連續聲明+賦值
var x, y float32 = -1, -2
  • 函數體外部:變量聲明方式5
// 外部var括號內部
var (
    g       int
    u, v, s = 2.0, 3.0, "bar"
)
  • 函數體內部:變量聲明方式6
func main() {
    //函數內部的變量聲明  聲明的變量類型必須使用 否則報錯
    var x string
  • 函數體內部:變量聲明方式7
// 只限函數內部 自動推斷類型
y := "jonson"

變量的命名

  • 名字的長度沒有邏輯限制,但是Go語言的風格是儘量使用短小的名字,對於局部變量尤其是這樣;你會經常看到i之類的短名字,而不是冗長的theLoopIndex命名。通常來說,如果一個名字的作用域比較大,生命週期也比較長,那麼用長的名字將會更有意義。
  • 在習慣上,Go語言程序員推薦使用 駝峯式 命名,當名字由幾個單詞組成時優先使用大小寫分隔,而不是優先用下劃線分隔。因此,在標準庫有QuoteRuneToASCII和parseRequestLine這樣的函數命名,但是一般不會用quote_rune_to_ASCII和parse_request_line這樣的命名。而像ASCII和HTML這樣的縮略詞則避免使用大小寫混合的寫法,它們可能被稱爲htmlEscape、HTMLEscape或escapeHTML,但不會是escapeHtml。

作用域

  • 在程序設計中,一段程序代碼中所用到的標識符並不總是有效/可用的,作用域就是標識符有效可用的代碼範圍。
  • 在go語言中,作用域可以分爲
全局作用域 > 包級別作用域 > 文件級別作用域 > 函數作用域 > 內部作用域
universe block > package block > file block > function block > inner block

全局作用域

  • 全局作用域主要是go語言預聲明的標識符,所有go文件都可以使用。主要包含了如下的標識符
內建類型: int int8 int16 int32 int64
          uint uint8 uint16 uint32 uint64 uintptr
          float32 float64 complex128 complex64
          bool byte rune string error

內建常量: true false iota nil

內建函數: make len cap new append copy close delete
          complex real imag
          panic recover

包級別作用域

  • 全局(任何函數之外)聲明的常量,類型,變量或函數的標識符是包級別作用域
  • 如下例中的變量x 以及 fmt包中的函數println 就是包級別作用域
package main

import "fmt"

var x int=5

func main(){

    fmt.Println("mainx:",x)
}
  • 調用例子1
// f1.go
package main

var x int
//-------------------------------------
// f2.go
package main

func f() {
  fmt.Println(x)
}
  • 調用例子2:調用另一個包中的函數和屬性:
//testdemo/destdemo.go
package testdemo

import "fmt"

var Birth uint = 23
func Haha(){
    fmt.Println("lalalal")
}
//-------------------------------------
package main  // main/scope.go

import (
    "testdemo"
    "fmt"
)

func main(){

    testdemo.Haha()
    fmt.Println(testdemo.Birth)
}
  • 注意:如果要讓包中的屬性和變量被外部包調用,必須要首字母大寫。

文件級別作用域

  • import包的標識符是文件級別作用域的,只能夠在本文件中使用
  • 例如下面的代碼無效,因爲import 是file block,不能跨文件
// f1.go
package main

import "fmt"
//-------------------------------------
// f2.go  無效
package main

func f() {
  fmt.Println("Hello World")
}

函數級別作用域

  • 方法接收者(後面介紹),函數參數和結果變量的標識符的範圍是函數級別作用域,在函數體外部無效,在內部任何位置可見
  • 例如下面函數中的a,b,c就是函數級別作用域
func  add(a int,b int)(c int) {
  fmt.Println("Hello World")
  x := 5
  fmt.Println(x)
}

內部作用域

  • 函數聲明的常量和變量是函數內部作用域,其作用域從聲明開始,到最近的一個花括號結束。
  • 例子1:注意參數的前後順序
//下面的代碼無效:
func main() {
  fmt.Println("Hello World")

  fmt.Println(x)
    x := 5
}
  • 例子2:參數不能跨函數使用
//下面的代碼無效2:
func main() {
  fmt.Println("Hello World")
  x := 5
  fmt.Println(x)
}
//
func test(){
    fmt.Println(x)
}
  • 例子3:函數內部變量與外部變量重名,使用就近原則
package main

import "fmt"

var x int=5

func test(){

    var x int = 99;
    x = 100;
    // 下面的代碼輸出結果爲: 100
    fmt.Println("testx",x)
}
  • 例子4:內部花括號
  • 變量x的作用域是從scope3到scope5爲止
func main() {
    fmt.Println("Hello World")  // scope1
    {                           // scope2
        x := 5                  // scope3
        fmt.Println(x)          // scope4
    }                           // scope5
}

變量的內存分配

我們在前文go語言是如何運行的-內存概述 與 go語言是如何運行的-內存分配 中,詳細介紹了在虛擬內存角度其不同的及其功能

  • 對於全局變量,其存儲在.data 和.bss段。我們可以用下面的實例來驗證
// main.go
package main

var aaa int64 = 8
var ddd int64
func main() {
}
  • 在終端中輸入如下指令打印彙編代碼
$ go tool compile -S main.go

...
"".aaa SNOPTRDATA size=8
        0x0000 08 00 00 00 00 00 00 00
"".ddd SNOPTRBSS size=8
...
  • 從上面的彙編輸出中可以看出, 變量aaa位於 .data段中, 變量ddd位於.bss
  • 對於函數的內部變量,在go語言的編程規範中並沒有明確的劃分,變量是分配在棧中還是堆中,簡單來說,Go語言的逃逸分析(escape analysis)會分析各個變量的使用狀況,來決定他要放在stack還是heap段。
  • 一般的變量會在運行時在棧中創建,隨着函數的調用而產生,隨着函數的結束而消亡。如果編譯器無法證明函數返回後未引用該變量,則編譯器必須在堆上分配該變量,以避免懸空指針錯誤。另外,如果局部變量很大,則將其存儲在堆而不是堆棧上可能更有意義
  • 有一些初始化的情況會被分配到.data段中,例如長度大於4的數組字面量、字符串 等,如下所示
func main() {
    var vvv  = [5]int{1,2,3,4,5}
    var bbb string = "hello"
}
  • 在終端中輸入如下指令打印彙編代碼 即可驗證其存在於.data段中
$ go tool compile -S main.go
...
go.string."hello" SRODATA dupok size=5
    0x0000 68 65 6c 6c 6f                                   hello
type.[5]int SRODATA dupok size=72
"".ddd SNOPTRBSS size=8
...

總結

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章