1. 程式人生 > >golang筆記2_程序結構

golang筆記2_程序結構

lookup 2.3 判斷 sin ive 整數 程序結構 cycle 轉換

golang程序結構

2.1 命名

Golang中的命名遵循這樣一個簡單原則,名字的開頭必須是字母或者下劃線,後面跟字母、數字或者下劃線(這裏與C語言中是一致的)。

在函數內部聲明的實體,即局部變量,只在函數內部有效。在函數外定義的變量,在整個包內有效(註意是包,不是文件,多個文件可以屬於同一個包)。

首字母的大小寫決定了是否對其他包可見,首字母小寫只對本包內有效,首字母大寫對於其他包可見。比如,fmt.Println()函數名字的首字母大寫,可以在其他包內引用該函數。

名字中出現多個單詞時,習慣上使用大寫單詞首字母以便於閱讀(一般不用下劃線分割),比如parseRequestLine()

2.2 聲明

聲明命名了一個程序實體,並制定了它的一些特性。有四種主要聲明,分別是varconsttypefunc

go程序的結構遵循如下的結構:

// package name
package main

// import other packages
import (
    "fmt"
)

type student struct {
    name string
    age  int
}

var (
    x int     = 0
    y float64 = 1.1
    z bool    = false
)

const (
    dayOfWeek = 7
)

func
main() { gexin := student{name: "gexin", age: 27} fmt.Println(gexin, x, y, z, dayOfWeek) }

2.3 變量

一個變量聲明創建了一個特定類型的變量,並給變量一個名字,給變量賦初值。

var  name type = expression

var  x int  = 10
var  x      = 10
var  x int

聲明變量時,typeexpression至少存在一個。如果不存在type,則根據expression的類型來確定變量類型;如果不存在expression,則將其初始化為0。

對於數值型變量其默認初始化為0,布爾變量默認初始化為false,字符串默認初始化為空字符串"",引用類型(slice, pointer, map, channel, function)默認初始化為nil,組合類型(array、struct)所有的成員都默認初始化為0.

0初始化機制使得任何變量始終都有一個正確的值(不像C語言中,對於未初始化的某些變量可能會造成問題,尤其是內存相關的)。

顯式初始化的值可以是字符串值,亦可以是任意表達式。包級別的變量(在函數體外部聲明的變量)實在main()函數開始之前初始化的,局部變量是在函數運行時在其聲明的地方初始化的。

多個變量可以同時聲明並用函數初始化,如下所示:

var f, err = os.Open(arg)

2.3.1 短變量聲明

在函數體內部,可以使用另一種聲明格式,稱之為"短變量聲明",采用如下的格式:

x := 10
y := 3.14
f, err := os.Open(arg)

多數情況下局部變量使用短變量聲明,變量聲明用在那些需要顯示表明變量類型的地方,或者是變量初值不重要,只是需要一個類型的變量的地方。

短變量聲明也支持多變量同時聲明,如下所示:

i, j := 0, 1

短變量聲明也可在函數調用時聲明一個變量作為函數返回值,需要始終註意的是:=是聲明,其左側的多個變量至少有一個是新聲明的變量,對於已存在的變量其作用相當於賦值。

f, err := os.Open(fileName)

2.3.2 指針

變量是一段保存了一個值得存儲空間,變量是在聲明過程中創建的,並且給了一個名字用來訪問變量。也有一些變量是通過表達式來訪問的,比如x[i]x.f,這類表達式讀取變量的值,當出現在賦值號左邊時,就是給變量賦值。

一個指針的值是一個變量的地址,指針是一個值在內存中存儲的位置。不是每個值都有地址,但是每個變量都有地址,變量也可以成為可以被尋址的值。通過指針,我們可以訪問或者修改某個變量的值(直接通過變量地址,不用知道變量名字)。

“變量”與“值”這倆概念有點拗口,聽起來有點別扭。變量是一段存儲空間,裏面的實際內容就是值。變量也可以成為可以被尋址的值。變量的內容可以通過變量名字訪問,亦可以直接通過指針訪問,指針就是變量的存儲地址。

對於一個已經聲明的變量x int&x表示地址可以賦值給指針,這個地址值得類型是*int

x := 1
p := &x

fmt.Println(*p) // 1 

*p=2
fmt.Println(x) // 2 

指針的默認初始化0值時nil,指針時可以比較的,當兩個指針指向同一個變量時,他們是相等的。

在golang中,函數返回局部變量的地址是安全的(這一點比C好),比如下面的代碼的代碼中,在函數f1返回後,變量v的地址仍然是有效的,其值為10;

func main(){
    p := f1()
    fmt.Println(*p) // 10
}

func f1() *int {
    v := 10
    return &v
}

在函數調用中,亦可用傳地址方式修改參數,函數的參數設置為指針類型,就可以了。這點與C語言一致。

2.3.3 new()函數

new(T)會創建一個變量,0初始化並返回其地址,但是沒有給這個變量名字。

p := new(int)   // p是*int類型的指針,*p值初始化為0
fmt.Println(*p) // 0

*p = 2
fmt.Println(*p) // 2

需要註意的是,通過new來創建的變量與普通創建的變量並無什麽不同,只是沒有給變量起名字罷了,所以下面代碼段中的兩個函數本質是一樣的。

func newInt() *int{
    return new(int)
}
// **************************
func newInt() *int{
    var dummy int
    return &dummy
}

2.3.4 變量的生命周期

包級別變量存在於程序的整個執行期間,局部變量有著動態的聲明周期。局部變量在聲明時被創建,直到變量不被訪問才會被銷毀。函數參數及返回結果也是局部變量,當函數調用時被創建。

for t := 0.0; t < cycles*2*math.Pi; t += res {
    x := math.Sin(t)
    y := math.Sin(t*freq + phase)
    img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex)
}

在上面的代碼段中,t是在循環開始時被創建,x,y在每次循環中創建。

垃圾收集機制是如何判斷何時收回變量呢?這是個比較復雜的問題,這裏簡單介紹一下其原理。每次創建一個變量時,該變量都作為一個根路徑來被他的指針或者其他引用跟蹤。如果這樣的路徑都不存在了,那麽這個變量就不可訪問了,這時就可以回收了。

變量的生命周期取決於它是否可以被訪問,一個局部變量可以存在於他的代碼段之外,所以函數返回局部變量的地址也是安全的。

2.4 賦值

變量的值是通過賦值語句來更新的,如下所示:

x = 1
*p = true
person.name = "gexin"
count[x] = count[x] * scale

跟C語言中一樣,以下的形式也是正確的

count[x] *= 2

v := 1
v++
v--

2.4.1 Tuple(元組)賦值

元組賦值是說多個變量同時賦值

// 交換變量的值
x, y = y, x
a[i], a[j] = a[j], a[i]

求兩個整數的最大公約數

func gdc(x, y int) int {
    for y != 0{
        x, y = y, x%y
    }
    return x
}

求第n個菲波那切數列

func fib(n int) int{
    x, y := 0, 1
    for i:=0; i<n; i++{
        x, y = y, x+y
    }
    return x
}

一些函數需要返回額外的錯誤碼以表明程序執行的狀態,比如之前用的到os.Open(),這時就需要元組賦值了,如下所示:

f, err = os.Open("file.txt")

有三個操作符有時也表現出相同的方式,如下所示:

v, ok = m[key]      // map lookup
v, ok = x.(T)       // type assertion
v, ok = <-ch        // chanel receive

就像變量聲明一樣,我們也可以用下劃線來賦值不想用的值,如下所示:

_, err = io.Copy(dst, src)
_, ok  = x.(T)

2.4.2 可賦值性

除了顯示的賦值,還有很多地方會有隱式賦值。程序調用時,會隱式的給參數變量賦值;程序返回時,隱式的給結果變量賦值;符合結構的數據使用字符常量,默認給每個成員賦值,如下所示:

medals := []string{"gold", "silver", "bronze"}

// 對每個元素隱式賦值,相當於如下三個賦值
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"

一個賦值操作,不管是顯式的還是隱式的,只要兩側的類型一致,該操作就是合法的。

兩個值是否相等,==或者!=,與可賦值性相關。在比較操作中,第一個操作數必須可以被第二個的數據類型賦值,反之亦然。

2.5 類型聲明

變量或者表達式的類型決定了值得表現形式, 比如值得size,如何表示,支持的運算,與之關聯的操作方法等等。

type name underlying_type

一個類型聲明定義了一個名為"name"的類型,它與 "underlying_type"有著相同的類型。

```go
type Celsius float64
type student struct {
underlying_type
age int
}name string

age int
對於每個類型T,都有一個轉換操作,T(x),該操作將x值轉換為T類型。轉換在以下幾種情況下才是允許的:

  • x的類型和T都有著相同的"underlying_type"
  • 都是未命名的指針類型,而且指向相同的"underlying_type"數據
  • 雖然改變的類型,但是不影響值得表達

轉換在數值類型間是可以轉換的,字符串和一些slice類型間也是可以轉換的。這些轉換可能會影響值得表達,比如將一個float64類型裝換為int。

2.6 包和文件

go中的pacakge就跟C語言中的庫是一樣的,包的源碼分布在一個或者多個.go文件中,每個包給其中的聲明都提供了一個獨立的命名空間,比如utf16.Decode()image.Decode()就是兩個不同的函數。

包用簡單的方式決定一個變量是否可以被包外訪問,首字母大寫的才可以在包外訪問,首字母小寫的只能在包內訪問。

我們在這裏實現溫度轉換的例子,該包使用兩個文件來實現,一個包用來聲明類型、常量等信息,另一個包用來實現方法。

// file tempconv.go
package tempconv

import (
    "fmt"
)

type Celsius float64
type Faherenheit float64

const (
    AbsoluteZeroC Celsius = -273.15
    FreezingC     Celsius = 0
    BoilingC      Celsius = 100
)

func (c Celsius) String() string     { return fmt.Sprintf("%g°C".c) }
func (f Faherenheit) String() string { return fmt.Sprintf("%g°F", f) }

// file conv.go
package tempconv

func CToF(c Celsius) Faherenheit { return Faherenheit(c*9/5 + 32) }
func FToC(f Faherenheit) Celsius { return Celsius((f - 32) * 5 / 9) }

golang筆記2_程序結構