Go語言的變量定義詳情
一、變量
聲明變量
go定義變量的方式和c,c++,java語法不一樣,如下:
var 變量名 類型, 比如 : var a int
var在前,變量名在中間,類型在后面
我們以代碼舉例,如下:
var i int = 0 var i = 0 var i int
以上三個(gè)表達(dá)式均是合法的,第三個(gè)表達(dá)式會(huì)將i初始化為int類型的零值,0;如果i是bool類型,則為false;i是float64類型,則為0.0;i為string類型,則為"";i為interface類型,則為nil;i為引用類型,則為nil;如果i是struct,則是將struct中所有的字段初始化為對(duì)應(yīng)類型的零值。
這種初始化機(jī)制可以保證任何一個(gè)變量都是有初始值的,這樣在做邊界條件條件檢查時(shí)不需要擔(dān)心值未初始化,可以避免一些潛在的錯(cuò)誤,相信C和C++程序員的體會(huì)更加深入。
fmt.Println(s) // ""
這里的s是可以正常打印的,而不是導(dǎo)致某種不可預(yù)期的錯(cuò)誤。
可以在同一條語句中聲明多個(gè)變量:
var b, f, s = true, 2.3, "four"http:// bool, float64, string
包內(nèi)可見的變量在main函數(shù)開始執(zhí)行之前初始化,本地變量在函數(shù)執(zhí)行到對(duì)應(yīng)的聲明語句時(shí)初始化。
變量也可以通過函數(shù)的返回值來初始化:
var f, err = os.Open(name) // os.Open returns a file and an error
二、短聲明
在函數(shù)內(nèi)部,有一種短聲明的方式,形式是name := expression,這里,變量的類型是由編譯器自動(dòng)確定的。
anim := gif.GIF{LoopCount: nframes}
freq := rand.Float64() * 3.0
t := 0.0因?yàn)檫@種形式非常簡潔,因此在函數(shù)內(nèi)部(本地變量)大量使用。如果需要為本地變量顯式的指定類型,或者先聲明一個(gè)變量后面再賦值,那么應(yīng)該使用var:
i := 100// an int var boiling float64 = 100// a float64 var names []string var err error var p Point
就像var聲明一樣,短聲明也可以并行初始化。
i, j := 0, 1
要謹(jǐn)記的是,:=是一個(gè)聲明,=是一個(gè)賦值,因此在需要賦值的場所不能使用 :=
var i int i := 10//panic : no new variables on left side of :=
可以利用并行賦值的特性來進(jìn)行值交換:
i, j = j, i // swap values of i and j
有一點(diǎn)需要注意的:短聲明左邊的變量未必都是新聲明的!:
//... out, err := os.Create(path2)?
/因?yàn)閑rr已經(jīng)聲明過,因此這里只新聲明一個(gè)變量out。
雖然這里使用:=,但是err是在上個(gè)語句聲明的,這里僅僅是賦值/
而且,短聲明的左邊變量必須有一個(gè)是新的,若都是之前聲明過的,會(huì)報(bào)編譯錯(cuò)誤:
f, err := os.Open(infile) // ... f, err := os.Create(outfile) // compile error: no new variables
正確的寫法是這樣的:
f, err := os.Open(infile) // ... f, err = os.Create(outfile) // compile ok
指針
值變量的存儲(chǔ)地址存的是一個(gè)值。例如 x = 1 就是在x的存儲(chǔ)地址存上1這個(gè)值; x[i] = 1代表在數(shù)組第i + 1的位置存上1這個(gè)值;x.f = 1,代表struct x中的f字段所在的存儲(chǔ)位置存上1這個(gè)值。
指針值是一個(gè)變量的存儲(chǔ)地址。注意:不是所有的值都有地址,但是變量肯定是有地址的!這個(gè)概念一定要搞清楚! 通過指針,我們可以間接的去訪問一個(gè)變量,甚至不需要知道變量名。
var x int = 10
p := &x?
/*&x是取x變量的地址,因此p是一個(gè)指針,指向x變量.
這里p的類型是*int,意思是指向int的指針*/
fmt.Printf("addr:%p, value:%d\n", p, *p)
//output: addr:0xc820074d98, value:10
*p = 20// 更新x到20上面的代碼中,我們說p指向x或者p包含了x的地址。p的意思是從p地址中取出對(duì)應(yīng)的變量值,因此p就是x的值:10。因?yàn)閜是一個(gè)變量,因此可以作為左值使用,p = 20,這時(shí)代表p地址中的值更新為20,因此這里x會(huì)變?yōu)?0。下面的例子也充分解釋了指針的作用:
x := 1 p := &x ? ? ? ? // p類型:*int,指向x fmt.Println(*p) // "1" *p = 2// 等價(jià)于x = 2 fmt.Println(x) ?// "2"
聚合類型struct或者array中的元素也是變量,因此是可以通過尋址(&)獲取指針的。
若一個(gè)值是變量,那么它就是可尋址的,因此若一個(gè)表達(dá)式可以作為一個(gè)變量使用時(shí),意味著該表達(dá)式可以尋址,也可以被使用&操作符。
`指針的零值是nil(記得之前的內(nèi)容嗎?go的所有類型在沒有初始值時(shí)都默認(rèn)會(huì)初始化為該類型的零值)。若p指向一個(gè)變量,那么p != nil 就是true,因?yàn)閜會(huì)被賦予變量的地址。指針是可以比較的,兩個(gè)指針相等意味著兩個(gè)指針都指向同一個(gè)變量或者兩個(gè)指針都為nil。
var x, y int fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false" ? ?
在函數(shù)中返回一個(gè)本地變量的地址是很安全的。例如以下代碼,本地變量v是在f中創(chuàng)建的,從f返回后依然會(huì)存在,指針p仍然會(huì)去引用v:
var p = f()
fmt.Println(*p) //output:1
func f() *int{
? ? v := 1
return &v
}每次調(diào)用f都會(huì)返回不同的指針,因?yàn)閒會(huì)創(chuàng)建新的本地變量并返回指針:
fmt.Println(f() == f()) // "false"
把變量的指針傳遞給函數(shù),即可以在函數(shù)內(nèi)部修改該變量(go的函數(shù)默認(rèn)是值傳遞,所有的值類型都會(huì)進(jìn)行內(nèi)存拷貝)。
func incr(p *int)int{
? ? ? ? *p++ // increments what p points to; does not change p
return *p
}
v := 1
incr(&v) ? ? ? ? ? ? ?// v現(xiàn)在是2
fmt.Println(incr(&v)) // "3" (and v is 3)指針在flag包中是很重要的。flag會(huì)讀取程序命令行的參數(shù),然后設(shè)置程序內(nèi)部的變量。下面的例子中,我們有兩個(gè)命令行參數(shù):-n,不打印換行符;-s sep,使用自定義的字符串分隔符進(jìn)行打印。
package main
import(
"flag"
"fmt"
"strings"
)
var n = flag.Bool("n", false, "忽略換行符")
var sep = flag.String("s", " ", "分隔符")
func main(){
? ? flag.Parse()
? ? fmt.Print(strings.Join(flag.Args(), *sep))
if !*n {
? ? ? ? ? fmt.Println()
? ? }
}flag.Bool會(huì)創(chuàng)建一個(gè)bool類型的flag變量,flag.Bool有三個(gè)參數(shù):flag的名字,命令行沒有傳值時(shí)默認(rèn)的flag值(false),flag的描述信息( 當(dāng)用戶傳入一個(gè)非法的參數(shù)或者-h、 -help時(shí),會(huì)打印該描述信息)。變量sep和n 都是flag變量的指針,因此要通過sep和n來訪問原始的flag值。
當(dāng)程序運(yùn)行時(shí),在使用flag值之前首先要調(diào)用flag.Parse。非flag參數(shù)可以通過args := flag.Args()來訪問,args的類型是[]string(見后續(xù)章節(jié))。如果flag.Parse報(bào)錯(cuò),那么程序就會(huì)打印出一個(gè)使用說明,然后調(diào)用os.Exit(2)來結(jié)束。
讓我們來測試一下上面的程序:
$ go build gopl.io/ch2/echo4 $ ./echo4 a bc def a bc def $ ./echo4 -s / a bc def a/bc/def $ ./echo4 -n a bc def a bc def$ $ ./echo4 -help Usage of ./echo4: ? ? ?-n ? ?忽略換行符 ? ? ?-s string ? ? ?分隔符 (default" ")
三、new函數(shù)
還可以通過內(nèi)建(built-in)函數(shù)new來創(chuàng)建變量。new(T)會(huì)初始化一個(gè)類型為T的變量,值為類型T對(duì)應(yīng)的零值,然后返回一個(gè)指針:*T。
p := new(int) ? // p,類型*int,指向一個(gè)沒有命名的int變量 fmt.Println(*p) // "0" *p = 2 fmt.Println(*p) // "2"
這種聲明方式和普通的var聲明再取地址沒有區(qū)別。如果不想絞盡腦汁的去思考一個(gè)變量名,那么就可以使用new:
func newInt() *int{ ? ? ? ? ? ?func newInt() *int{
returnnew(int) ? ? ? ? ? ? ? ? var dummy int
} ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return &dummy
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }每次調(diào)用new都會(huì)返回一個(gè)唯一的地址:
p := new(int) q := new(int) fmt.Println(p == q) // "false"
但是有一個(gè)例外:比如struct{}或[0]int,這種類型的變量沒有包含什么信息且為零值,可能會(huì)有同樣的地址。
new函數(shù)相對(duì)來說是較少使用的,因?yàn)樽畛S玫奈淳呙兞渴?code>struct類型,對(duì)于這種類型而言,相應(yīng)的struct語法更靈活也更適合。
因?yàn)閚ew是預(yù)定義的函數(shù)名(參見上一節(jié)的保留字),不是語言關(guān)鍵字,因此可以用new做函數(shù)內(nèi)的變量名:
func delta(old, new int)int{ returnnew - old }當(dāng)然,在delta函數(shù)內(nèi)部,是不能再使用new函數(shù)了!
四、變量的生命期
變量的生命期就是程序執(zhí)行期間變量的存活期。包內(nèi)可見的變量的生命期是固定的:程序的整個(gè)執(zhí)行期。作為對(duì)比,本地變量的生命期是動(dòng)態(tài)的:每次聲明語句執(zhí)行時(shí),都會(huì)創(chuàng)建一個(gè)新的變量實(shí)例,變量的生命期就是從創(chuàng)建到不可到達(dá)狀態(tài)(見下文)之間的時(shí)間段,生命期結(jié)束后變量可能會(huì)被回收。
函數(shù)的參數(shù)和本地變量都是動(dòng)態(tài)生命期,在每次函數(shù)調(diào)用和執(zhí)行的時(shí)候,這些變量會(huì)被創(chuàng)建。例如下面的代碼:
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)
? ? }每次for循環(huán)執(zhí)行時(shí),t,x,y都會(huì)被重新創(chuàng)建。
那么GC是怎么判斷一個(gè)變量應(yīng)該被回收呢?完整的機(jī)制是很復(fù)雜的,但是基本的思想是尋找每個(gè)變量的過程路徑,如果找不到這樣的路徑,那么變量就是不可到達(dá)的,因此就是可以被回收的。
一個(gè)變量的生命期只取決于變量是否是可到達(dá)的,因此一個(gè)本地變量可以在循環(huán)之外依然存活,甚至可以在函數(shù)return后依然存活。編譯器會(huì)選擇在堆上或者棧上去分配變量,但是請(qǐng)記?。壕幾g器的選擇并不是由var或者new這樣的聲明方式?jīng)Q定的。
var global *int
func f() { ? ? ? ? ? ? ? ? ? ? ?func g(){
? ? var x int ? ? ? ? ? ? ? ? ? ? ? y := new(int)
? ? x = 1 ? ? ? ? ? ? ? ? ? ? ? ? ? *y = 1
? ? global = &x ? ? ? ? ? ? ? ? }
}上面代碼中,x是在堆上分配的變量,因?yàn)樵趂返回后,x也是可到達(dá)的(global指針)。這里x是f的本地變量,因此,這里我們說x從f中逃逸了。相反,當(dāng)g返回時(shí),變量y就變?yōu)椴豢傻竭_(dá)的,然后會(huì)被垃圾回收。因?yàn)閥沒有從g中逃逸,所以編譯器將*y分配在棧上(即使是用new分配的)。在絕大多數(shù)情況下,我們都不用擔(dān)心變量逃逸的問題,只要在做性能優(yōu)化時(shí)意識(shí)到:每一個(gè)逃逸的變量都需要進(jìn)行一次額外的內(nèi)存分配。
盡管自動(dòng)GC對(duì)于寫現(xiàn)代化的程序來說,是一個(gè)巨大的幫助,但是我們也要理解go語言的內(nèi)存機(jī)制。程序不需要顯式的內(nèi)存分配或者回收,可是為了寫出高效的程序,我們?nèi)匀恍枰宄闹雷兞康纳凇@?,在長期對(duì)象(特別是全局變量)中持有指向短期對(duì)象的指針,會(huì)阻止GC回收這些短期對(duì)象,因?yàn)樵谶@種情況下,短期對(duì)象是可以到達(dá)的?。?/p>
五、變量的作用域
如果你有c,c++,java的經(jīng)驗(yàn),那么go語言的變量使用域名和這幾門語言是一樣的
一句話: 就近原則,定義在作用域用的變量只能在函數(shù)中使用。
如果外面有定義的同名變量,則就近原則。
到此這篇關(guān)于Go語言的變量定義詳情的文章就介紹到這了,更多相關(guān)Go變量定義內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang協(xié)程關(guān)閉踩坑實(shí)戰(zhàn)記錄
協(xié)程(coroutine)是Go語言中的輕量級(jí)線程實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于golang協(xié)程關(guān)閉踩坑的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03
golang API開發(fā)過程的中的自動(dòng)重啟方式(基于gin框架)
這篇文章主要介紹了golang API開發(fā)過程的中的自動(dòng)重啟方式(基于gin框架),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12

