變量聲明
var a
a=100
//或
var b = 100
//或
var c int = 100
// := 是聲明并賦值帅腌,并且系統(tǒng)自動推斷類型,不需要var關(guān)鍵字
d := 100
package main
import "fmt"
func main() {
// 切片的一種定義方式是 用 make
// var x = make([]float64, 5) ,切片用make
//另外一種是通過數(shù)組切片賦值礁凡,采用[low_index:high_index]的方式獲取數(shù)值切片庐冯,其中切片元素包括low_index的元素孽亲,但是不包括high_index的元素。
ar := new([3]int)// 指針
ar1 := [3]int{0, 0, 0}
var ar2 = [3]int{0, 0, 0}
var ar3 [3]int
var ar4 = [3]int{0: 1, 1: 2, 2: 3}
fp(ar)
fp(&ar1)
fp(&ar2)
fp(&ar3)
fp(&ar4)
fp1(ar)
fp1(&ar1)
fp1(&ar2)
fp1(&ar3)
fp1(&ar4)
}
func fp(a *[3]int) { print(*a) }
func fp1(a *[3]int) { print(a) }
與 Python不同
任何空值(nil)或者零值(0, 0.0, "")都不能作為布爾型來直接判斷展父。
func main() {
if 0 {
fmt.Println("hello world")
}
if nil {
fmt.Println("hello world")
}
if "" {
fmt.Println("hello world")
}
}
常用命令
go build: 編譯出可執(zhí)行文件
go install: go build + 把編譯后的可執(zhí)行文件放到GOPATH/bin目錄下
go get: git clone + go install
何時使用指針
指針的一大用途就是可以將變量的指針作為實參傳遞給函數(shù)返劲,從而在函數(shù)內(nèi)部能夠直接修改實參所指向的變量值。
Go的變量傳遞都是值傳遞栖茉。
package main
import (
"fmt"
)
func change(x int) {
x = 200
}
func main() {
var x int = 100
fmt.Println(x)
change(x)
fmt.Println(x)
}
上面的例子輸出結(jié)果為
100
100
很顯然篮绿,change函數(shù)改變的僅僅是內(nèi)部變量x的值,而不會改變傳遞進(jìn)去的實參吕漂。其實亲配,也就是說Go的函數(shù)一般關(guān)心的是輸出結(jié)果,而輸入?yún)?shù)就相當(dāng)于信使跑到函數(shù)門口大叫惶凝,你們這個參數(shù)是什么值吼虎,那個是什么值,然后就跑了梨睁。你函數(shù)根本就不能修改它的值鲸睛。不過如果是傳遞的實參是指針變量,那么函數(shù)一看坡贺,小子這次你地址我都知道了官辈,哪里跑。那么就是下面的例子:
package main
import (
"fmt"
)
func change(x *int) {
*x = 200
}
func main() {
var x int = 100
fmt.Println(x)
change(&x)
fmt.Println(x)
}
上面的例子中遍坟,change函數(shù)的虛參為整型指針變量拳亿,所以在main中調(diào)用的時候傳遞的是x的地址。然后在change里面使用*x=200修改了這個x的地址的值愿伴。所以x的值就變了肺魁。這個輸出是:
100
200
一個函數(shù)何時該用指針類型做receiver對初學(xué)者而言一直是個頭疼的問題。如果不知道該如何取舍隔节,選擇指針類型的receiver鹅经。但有些時候value receiver更加合適,比如對象是一些輕量級的不變的structs怎诫,使用value receiver會更加高效瘾晃。下面是列舉了一些常用的判斷指導(dǎo)。
如果receiver是map幻妓、func或者chan蹦误,不要使用指針
如果receiver是slice并且該函數(shù)并不會修改此slice,不要使用指針
如果該函數(shù)會修改receiver,此時一定要用指針
如果receiver是struct并且包含互斥類型sync.Mutex强胰,或者是類似的同步變量舱沧,receiver必須是指針,這樣可以避免對象拷貝
如果receiver是較大的struct或者array偶洋,使用指針則更加高效熟吏。多大才算大?假設(shè)struct內(nèi)所有成員都要作為函數(shù)變量傳進(jìn)去涡真,如果覺得這時數(shù)據(jù)太多分俯,就是struct太大
如果receiver是struct,array或者slice哆料,并且其中某個element指向了某個可變量缸剪,則這個時候receiver選指針會使代碼的意圖更加明顯
如果receiver使較小的struct或者array,并且其變量都是些不變量东亦、常量杏节,例如time.Time,value receiver更加適合典阵,因為value receiver可以減少需要回收的垃圾量奋渔。
最后,如果不確定用哪個壮啊,使用指針類的receiver
常量
變量定義的類型推斷方式 := 不能夠用來定義常量
快速聲明
Go還提供了一種同時定義多個變量或者常量的快捷方式嫉鲸。
import (
"fmt"
)
func main() {
var (
a int = 10
b float64 = 32.45
c bool = true
)
const (
Pi float64 = 3.14
True bool = true
)
fmt.Println(a, b, c)
fmt.Println(Pi, True)
}
if 判斷的()
package main
import (
"fmt"
)
func main() {
const Male = 'M'
const Female = 'F'
var dog_age = 10
var dog_sex = 'M'
if (dog_age == 10 && dog_sex == 'M') {
fmt.Println("dog")
}
}
但是如果你使用Go提供的格式化工具來格式化這段代碼的話,Go會智能判斷你的括號是否必須有歹啼,否則的話玄渗,會幫你去掉的。你可以試試狸眼。
go fmt test_bracket.go
然后你會發(fā)現(xiàn)藤树,咦?拓萌!果真被去掉了岁钓。
# for 循環(huán)
package main
import (
"fmt"
)
在Go里面沒有提供while關(guān)鍵字,如果你懷念while的寫法也可以這樣:
func main() {
var i int = 1
for i <= 100 {
fmt.Println(i)
i++
}
}
或許你會問微王,如果我要死循環(huán)呢屡限?是不是for true?呵呵炕倘,不用了钧大,直接這樣。
for{
}
切片
我們發(fā)現(xiàn)arr1的長度變?yōu)?1激才,因為元素個數(shù)現(xiàn)在為11個。另外我們發(fā)現(xiàn)arr1的容量也變了,變?yōu)樵瓉淼膬杀丁?/p>
這是因為Go在默認(rèn)的情況下瘸恼,如果追加的元素超過了容量大小劣挫,Go會自動地重新為切片分配容量,容量大小為原來的兩倍东帅。
總結(jié)一下压固,數(shù)組和切片的區(qū)別就在于[]里面是否有數(shù)字或者...!!!!!!!!!!!
因為數(shù)值長度是固定的,而切片是可變的靠闭。
函數(shù)預(yù)定義
命名返回值
Go的函數(shù)很有趣帐我,你甚至可以為返回值預(yù)先定義一個名稱,在函數(shù)結(jié)束的時候愧膀,直接一個return就可以返回所有的預(yù)定義返回值拦键。例如上面的例子,我們將sum作為命名返回值檩淋。
package main
import (
"fmt"
)
func slice_sum(arr []int) (sum int) {
sum = 0
for _, elem := range arr {
sum += elem
}
return
}
func main() {
var arr1 = []int{1, 3, 2, 3, 2}
var arr2 = []int{3, 2, 3, 1, 6, 4, 8, 9}
fmt.Println(slice_sum(arr1))
fmt.Println(slice_sum(arr2))
}
這里要注意的是芬为,如果你定義了命名返回值,那么在函數(shù)內(nèi)部你將不能再重復(fù)定義一個同樣名稱的變量蟀悦。比如第一個例子中我們用sum := 0來定義和初始化變量sum媚朦,而在第二個例子中,我們只能用sum = 0初始化這個變量了日戈。因為 := 表示的是定義并且初始化變量询张。
異常處理
panic & recover
當(dāng)你周末走在林蔭道上,聽著小歌浙炼,哼著小曲份氧,很是愜意。突然之間鼓拧,從天而降瓢潑大雨半火,你頓時慌張(panic)起來,
沒有帶傘啊季俩,淋著雨感冒就不好了钮糖。于是你四下張望,忽然發(fā)現(xiàn)自己離地鐵站很近酌住,那里有很多賣傘的店归,心中頓時又安定了下來(recover),于是你飛奔過去買了一把傘(defer )
Go語言提供了關(guān)鍵字defer來在函數(shù)運(yùn)行結(jié)束的時候運(yùn)行一段代碼或調(diào)用一個清理函數(shù)酪我。上面的例子中消痛,雖然second()函數(shù)寫在first()函數(shù)前面,但是由于使用了defer標(biāo)注都哭,所以它是在main函數(shù)執(zhí)行結(jié)束的時候才調(diào)用的秩伞。
package main
import (
"fmt"
)
func main() {
//defer一定是在函數(shù)執(zhí)行結(jié)束的時候運(yùn)行的逞带。不管是正常結(jié)束還是異常終止,相當(dāng)于finally
defer func() {
//panic用來觸發(fā)異常,而recover用來終止異常并且返回傳遞給panic的值纱新。(注意recover并不能處理異常展氓,而且recover只能在defer里面使用,否則無效脸爱。)
msg := recover()
fmt.Println(msg)
}()
fmt.Println("I am walking and singing...")
panic("It starts to rain cats and dogs")
}
指針
定義
Go函數(shù)的參數(shù)傳遞方式是值傳遞
而指針的主要作用就是在函數(shù)內(nèi)部改變傳遞進(jìn)來變量的值
至于使不使用結(jié)構(gòu)體指針和使不使用指針的出發(fā)點是一樣的遇汞,那就是你是否試圖在函數(shù)內(nèi)部改變傳遞進(jìn)來的參數(shù)的值。再舉個例子如下:
在學(xué)Python的時候簿废,經(jīng)常要注意不小心修改了可變類型空入,如list。事實上這種可變和不可變了類型族檬,是Python為了簡化你的操作歪赢。
但是在Golang中,數(shù)組和切片导梆,你可以當(dāng)做可變類型轨淌,也可以當(dāng)做不可變類型,來使用看尼,通過使用*递鹉,&指針。
所謂指針其實你可以把它想像成一個箭頭藏斩,這個箭頭指向(存儲)一個變量的地址躏结。
因為這個箭頭本身也需要變量來存儲,所以也叫做指針變量狰域。
new 的使用發(fā)現(xiàn)
package main
import (
"fmt"
)
func set_value(x_ptr *int) {
//指針指向的地址內(nèi)容設(shè)置為100
*x_ptr = 100
}
func main() {
//開辟一塊內(nèi)存媳拴,用于存貯指針地址,且此指針地址的變量名為x_ptr
x_ptr := new(int)
set_value(x_ptr)
//指針變量指向的地址,因為本身就是指針變量兆览,存貯的就是指向的地址
fmt.Println(x_ptr)
//指針變量本身的地址
fmt.Println(&x_ptr)
//打印指針變量指向的地址內(nèi)容
fmt.Println(*x_ptr)
}
0xc084000040
0xc084000038
100
結(jié)構(gòu)體組合函數(shù)
上面我們在main函數(shù)中計算了矩形的面積屈溉,但是我們覺得矩形的面積如果能夠作為矩形結(jié)構(gòu)體的“內(nèi)部函數(shù)”提供會更好。這樣我們就可以直接說這個矩形面積是多少抬探,而不用另外去取寬度和長度去計算∽咏恚現(xiàn)在我們看看結(jié)構(gòu)體“內(nèi)部函數(shù)”定義方法:
package main
import (
"fmt"
)
type Rect struct {
width, length float64
}
func (rect Rect) area() float64 {
return rect.width * rect.length
}
func main() {
var rect = Rect{100, 200}
fmt.Println("Width:", rect.width, "Length:", rect.length,
"Area:", rect.area())
}
咦?這個是什么“內(nèi)部方法”小压,根本沒有定義在Rect數(shù)據(jù)類型的內(nèi)部跋吖!?
確實如此怠益,我們看到仪搔,雖然main函數(shù)中的rect變量可以直接調(diào)用函數(shù)area()來獲取矩形面積,但是area()函數(shù)確實沒有定義在Rect結(jié)構(gòu)體內(nèi)部蜻牢,這點和C語言的有很大不同烤咧。Go使用組合函數(shù)的方式來為結(jié)構(gòu)體定義結(jié)構(gòu)體方法馍盟。我們仔細(xì)看一下上面的area()函數(shù)定義耘拇。
首先是關(guān)鍵字func表示這是一個函數(shù)琢感,第二個參數(shù)是結(jié)構(gòu)體類型和實例變量葡兑,第三個是函數(shù)名稱,第四個是函數(shù)返回值立膛。這里我們可以看出area()函數(shù)和普通函數(shù)定義的區(qū)別就在于area()函數(shù)多了一個結(jié)構(gòu)體類型限定。這樣一來Go就知道了這是一個為結(jié)構(gòu)體定義的方法梯码。
這里需要注意一點就是定義在結(jié)構(gòu)體上面的函數(shù)(function)一般叫做方法(method)
接口和鴨子類型
package main
import (
"fmt"
)
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
以前我們說過宝泵,Go語言式靜態(tài)類型語言,變量的類型在運(yùn)行過程中不能改變轩娶。但是在上面的例子中儿奶,phone變量好像先定義為Phone類型,然后是NokiaPhone類型鳄抒,最后成為了IPhone類型闯捎,真的是這樣嗎?
在Go語言里面许溅,一個類型A只要實現(xiàn)了接口X所定義的全部方法瓤鼻,那么A類型的變量也是X類型的變量。
就是贤重,你能叫茬祷,能游,你就是鴨子
數(shù)組和指針
package main
import "fmt"
func main() {
array := [3]float64{7.0, 8.5, 9.1}
x := Sum(&array) // Note the explicit address-of operator
// to pass a pointer to the array
fmt.Printf("The sum of the array is: %f", x)
}
func Sum(a *[3]float64) (sum float64) {
for _, v := range a { // derefencing *a to get back to the array is not necessary!
sum += v
}
return
}
*表示并蝗,你不用來祭犯,我去干你,我只接受你的地址滚停,不接受你沃粗。
&表示我告訴你的我的地址
不用*指針的方式的話,會復(fù)制一份數(shù)組键畴,浪費內(nèi)存