周末天氣不好,只能宅在家里,于是就順便看了一下Go語言,覺得比較有意思,所以寫篇文章介紹一下。我想寫一篇你可以在乘坐地鐵或公交車上下班時(shí)就可以初步了解一門語言的文章。所以,下面的文章主要是以代碼和注釋為主。只需要你對(duì)Java,Python,C等編程語言有一點(diǎn)基礎(chǔ),我相信你會(huì)在30分鐘左右讀完并對(duì)Go語言有一些初步了解的。
本文的唯一目的,就是希望大家閱讀之后,能夠了解go語言長什么樣子。。。
Hello World
package main //聲明本文件的package名 import "fmt" //import語言的fmt庫——用于輸出 func main() { fmt.Println("hello world") }
運(yùn)行
你可以有兩種運(yùn)行方式,
解釋執(zhí)行(實(shí)際是編譯成a.out再執(zhí)行. $go run hello.go hello world 編譯執(zhí)行 $go build hello.go $ls hello hello.go $./hello hello world
自己的package
你可以使用GOPATH環(huán)境變量,或是使用相對(duì)路徑來import你自己的package。
Go的規(guī)約是這樣的:
1)在import中,你可以使用相對(duì)路徑,如 ./或 ../ 來引用你的package
2)如果沒有使用相對(duì)路徑,那么,go會(huì)去找$GOPATH/src/目錄。
使用相對(duì)路徑
import "./haoel" //import當(dāng)前目錄里haoel子目錄里的所有的go文件
使用GOPATH路徑
import "haoel" //import 環(huán)境變量 $GOPATH/src/haoel子目錄里的所有的go文件
fmt輸出格式
fmt包和libc里的那堆使用printf, scanf,fprintf,fscanf 很相似。下面的東西對(duì)于C程序員不會(huì)陌生。
注意:Println不支持,Printf才支持%式的輸出:
package main import "fmt" import "math" func main() { fmt.Println("hello world") fmt.Printf("%t\n", 1==2) fmt.Printf("二進(jìn)制:%b\n", 255) fmt.Printf("八進(jìn)制:%o\n", 255) fmt.Printf("十六進(jìn)制:%X\n", 255) fmt.Printf("十進(jìn)制:%d\n", 255) fmt.Printf("浮點(diǎn)數(shù):%f\n", math.Pi) fmt.Printf("字符串:%s\n", "hello world") }
當(dāng)然,也可以使用如\n\t\r這樣的和C語言一樣的控制字符
變量和常量
變量的聲明很像 javascript,使用 var關(guān)鍵字。注意:go是靜態(tài)類型的語言,下面是代碼:
//聲明初始化一個(gè)變量 var x int = 100 var str string = "hello world"/pre> //聲明初始化多個(gè)變量 var i, j, k int = 1, 2, 3 //不用指明類型,通過初始化值來推導(dǎo) var b = true //bool型
還有一種定義變量的方式(這讓我想到了Pascal語言,但完全不一樣)
x := 100 //等價(jià)于 var x int = 100;
常量很簡單,使用const關(guān)鍵字:
const s string = "hello world" const pi float32 = 3.1415926
數(shù)組
直接看代碼(注意其中的for語句,和C很相似吧,就是沒有括號(hào)了)
func main() { var a [5]int fmt.Println("array a:", a) a[1] = 10 a[3] = 30 fmt.Println("assign:", a) fmt.Println("len:", len(a)) b := [5]int{1, 2, 3, 4, 5} fmt.Println("init:", b) var c [2][3]int for i := 0; i 2; i++ { for j := 0; j 3; j++ { c[i][j] = i + j } } fmt.Println("2d: ", c) }
運(yùn)行結(jié)果:
array a: [0 0 0 0 0]
assign: [0 10 0 30 0]
len: 5
init: [1 2 3 4 5]
2d: [[0 1 2] [1 2 3]]
數(shù)組的切片操作
這個(gè)很Python了。
a := [5]int{1, 2, 3, 4, 5} b := a[2:4] // a[2] 和 a[3],但不包括a[4] fmt.Println(b) b = a[:4] // 從 a[0]到a[4],但不包括a[4] fmt.Println(b) b = a[2:] // 從 a[2]到a[4],且包括a[2] fmt.Println(b)
分支循環(huán)語句
if語句
注意:if 語句沒有圓括號(hào),而必需要有花括號(hào)
//if 語句 if x % 2 == 0 { //... } //if - else if x % 2 == 0 { //偶數(shù)... } else { //奇數(shù)... } //多分支 if num 0 { //負(fù)數(shù) } else if num == 10 { //零 } else { //正數(shù) }
switch 語句
注意:switch語句沒有break,還可以使用逗號(hào)case多個(gè)值
switch i { case 1: fmt.Println("one") case 2: fmt.Println("two") case 3: fmt.Println("three") case 4,5,6: fmt.Println("four, five, six") default: fmt.Println("invalid value!") }
for 語句
前面你已見過了,下面再來看看for的三種形式:(注意:Go語言中沒有while)
//經(jīng)典的for語句 init; condition; post for i := 0; i10; i++{ fmt.Println(i) } //精簡的for語句 condition i := 1 for i10 { fmt.Println(i) i++ } //死循環(huán)的for語句 相當(dāng)于for(;;) i :=1 for { if i>10 { break } i++ }
關(guān)于分號(hào)
從上面的代碼我們可以看到代碼里沒有分號(hào)。其實(shí),和C一樣,Go的正式的語法使用分號(hào)來終止語句。和C不同的是,這些分號(hào)由詞法分析器在掃描源代碼過程中使用簡單的規(guī)則自動(dòng)插入分號(hào),因此輸入源代碼多數(shù)時(shí)候就不需要分號(hào)了。
規(guī)則是這樣的:如果在一個(gè)新行前方的最后一個(gè)標(biāo)記是一個(gè)標(biāo)識(shí)符(包括像int和float64這樣的單詞)、一個(gè)基本的如數(shù)值這樣的文字、或以下標(biāo)記中的一個(gè)時(shí),會(huì)自動(dòng)插入分號(hào):
break continue fallthrough return ++ -- ) }
通常Go程序僅在for循環(huán)語句中使用分號(hào),以此來分開初始化器、條件和增量單元。如果你在一行中寫多個(gè)語句,也需要用分號(hào)分開。
注意:無論任何時(shí)候,你都不應(yīng)該將一個(gè)控制結(jié)構(gòu)((if、for、switch或select)的左大括號(hào)放在下一行。如果這樣做,將會(huì)在大括號(hào)的前方插入一個(gè)分號(hào),這可能導(dǎo)致出現(xiàn)不想要的結(jié)果。
map
map在別的語言里可能叫哈希表或叫dict,下面是和map的相關(guān)操作的代碼,代碼很容易懂
func main(){ m := make(map[string]int) //使用make創(chuàng)建一個(gè)空的map m["one"] = 1 m["two"] = 2 m["three"] = 3 fmt.Println(m) //輸出 map[three:3 two:2 one:1] (順序在運(yùn)行時(shí)可能不一樣) fmt.Println(len(m)) //輸出 3 v := m["two"] //從map里取值 fmt.Println(v) // 輸出 2 delete(m, "two") fmt.Println(m) //輸出 map[three:3 one:1] m1 := map[string]int{"one": 1, "two": 2, "three": 3} fmt.Println(m1) //輸出 map[two:2 three:3 one:1] (順序在運(yùn)行時(shí)可能不一樣) for key, val := range m1{ fmt.Printf("%s => %d \n", key, val) /*輸出:(順序在運(yùn)行時(shí)可能不一樣) three => 3 one => 1 two => 2*/ } }
指針
Go語言一樣有指針,看代碼
var i int = 1 var pInt *int = i //輸出:i=1 pInt=0xf8400371b0 *pInt=1 fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt) *pInt = 2 //輸出:i=2 pInt=0xf8400371b0 *pInt=2 fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt) i = 3 //輸出:i=3 pInt=0xf8400371b0 *pInt=3 fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt)
Go具有兩個(gè)分配內(nèi)存的機(jī)制,分別是內(nèi)建的函數(shù)new和make。他們所做的事不同,所應(yīng)用到的類型也不同,這可能引起混淆,但規(guī)則卻很簡單。
內(nèi)存分配
new 是一個(gè)分配內(nèi)存的內(nèi)建函數(shù),但不同于其他語言中同名的new所作的工作,它只是將內(nèi)存清零,而不是初始化內(nèi)存。new(T)為一個(gè)類型為T的新項(xiàng)目分配了值為零的存儲(chǔ)空間并返回其地址,也就是一個(gè)類型為*T的值。用Go的術(shù)語來說,就是它返回了一個(gè)指向新分配的類型為T的零值的指針。
make(T, args)函數(shù)的目的與new(T)不同。它僅用于創(chuàng)建切片、map和chan(消息管道),并返回類型T(不是*T)的一個(gè)被初始化了的(不是零)實(shí)例。這種差別的出現(xiàn)是由于這三種類型實(shí)質(zhì)上是對(duì)在使用前必須進(jìn)行初始化的數(shù)據(jù)結(jié)構(gòu)的引用。例如,切片是一個(gè)具有三項(xiàng)內(nèi)容的描述符,包括指向數(shù)據(jù)(在一個(gè)數(shù)組內(nèi)部)的指針、長度以及容量,在這三項(xiàng)內(nèi)容被初始化之前,切片值為nil。對(duì)于切片、映射和信道,make初始化了其內(nèi)部的數(shù)據(jù)結(jié)構(gòu)并準(zhǔn)備了將要使用的值。如:
下面的代碼分配了一個(gè)整型數(shù)組,長度為10,容量為100,并返回前10個(gè)數(shù)組的切片
make([]int, 10, 100)
以下示例說明了new和make的不同。
var p *[]int = new([]int) // 為切片結(jié)構(gòu)分配內(nèi)存;*p == nil;很少使用 var v []int = make([]int, 10) // 切片v現(xiàn)在是對(duì)一個(gè)新的有10個(gè)整數(shù)的數(shù)組的引用 // 不必要地使問題復(fù)雜化: var p *[]int = new([]int) fmt.Println(p) //輸出:[] *p = make([]int, 10, 10) fmt.Println(p) //輸出:[0 0 0 0 0 0 0 0 0 0] fmt.Println((*p)[2]) //輸出: 0 // 習(xí)慣用法: v := make([]int, 10) fmt.Println(v) //輸出:[0 0 0 0 0 0 0 0 0 0]
函數(shù)
老實(shí)說,我對(duì)Go語言這種反過來聲明變量類型和函數(shù)返回值的做法有點(diǎn)不滿(保持和C一樣的不可以嗎? 呵呵)
package main import "fmt" func max(a int, b int) int { //注意參數(shù)和返回值是怎么聲明的 if a > b { return a } return b } func main(){ fmt.Println(max(4, 5)) }
函數(shù)返回多個(gè)值
Go中很多Package 都會(huì)返回兩個(gè)值,一個(gè)是正常值,一個(gè)是錯(cuò)誤,如下所示:
package main import "fmt" func main(){ v, e := multi_ret("one") fmt.Println(v,e) //輸出 1 true v, e = multi_ret("four") fmt.Println(v,e) //輸出 0 false //通常的用法(注意分號(hào)后有e) if v, e = multi_ret("four"); e { // 正常返回 }else{ // 出錯(cuò)返回 } } func multi_ret(key string) (int, bool){ m := map[string]int{"one": 1, "two": 2, "three": 3} var err bool var val int val, err = m[key] return val, err }
函數(shù)不定參數(shù)
例子很清楚了,我就不多說了
func sum(nums ...int) { fmt.Print(nums, " ") //輸出如 [1, 2, 3] 之類的數(shù)組 total := 0 for _, num := range nums { //要的是值而不是下標(biāo) total += num } fmt.Println(total) } func main() { sum(1, 2) sum(1, 2, 3) //傳數(shù)組 nums := []int{1, 2, 3, 4} sum(nums...) }
函數(shù)閉包
nextNum這個(gè)函數(shù)返回了一個(gè)匿名函數(shù),這個(gè)匿名函數(shù)記住了nextNum中i+j的值,并改變了i,j的值,于是形成了一個(gè)閉包的用法
func nextNum() func() int { i,j := 1,1 return func() int { var tmp = i+j i, j = j, tmp return tmp } } //main函數(shù)中是對(duì)nextNum的調(diào)用,其主要是打出下一個(gè)斐波拉契數(shù) func main(){ nextNumFunc := nextNum() for i:=0; i10; i++ { fmt.Println(nextNumFunc()) } }
函數(shù)的遞歸
和c基本是一樣的
func fact(n int) int { if n == 0 { return 1 } return n * fact(n-1) } func main() { fmt.Println(fact(7)) }
結(jié)構(gòu)體
Go的結(jié)構(gòu)體和C的基本上一樣,不過在初始化時(shí)有些不一樣,Go支持帶名字的初始化。
type Person struct { name string age int email string } func main() { //初始化 person := Person{"Tom", 30, "tom@gmail.com"} person = Person{name:"Tom", age: 30, email:"tom@gmail.com"} fmt.Println(person) //輸出 {Tom 30 tom@gmail.com} pPerson := person fmt.Println(pPerson) //輸出 {Tom 30 tom@gmail.com} pPerson.age = 40 person.name = "Jerry" fmt.Println(person) //輸出 {Jerry 40 tom@gmail.com} }
結(jié)構(gòu)體方法
不多說了,看代碼吧。
注意:Go語言中沒有public, protected, private的關(guān)鍵字,所以,如果你想讓一個(gè)方法可以被別的包訪問的話,你需要把這個(gè)方法的第一個(gè)字母大寫。這是一種約定。
type rect struct { width, height int } func (r *rect) area() int { //求面積 return r.width * r.height } func (r *rect) perimeter() int{ //求周長 return 2*(r.width + r.height) } func main() { r := rect{width: 10, height: 15} fmt.Println("面積: ", r.area()) fmt.Println("周長: ", r.perimeter()) rp := r fmt.Println("面積: ", rp.area()) fmt.Println("周長: ", rp.perimeter()) }
接口和多態(tài)
接口意味著多態(tài),下面是一個(gè)經(jīng)典的例子,不用多說了,自己看代碼吧。
//---------- 接 口 --------// type shape interface { area() float64 //計(jì)算面積 perimeter() float64 //計(jì)算周長 } //--------- 長方形 ----------// type rect struct { width, height float64 } func (r *rect) area() float64 { //面積 return r.width * r.height } func (r *rect) perimeter() float64 { //周長 return 2*(r.width + r.height) } //----------- 圓 形 ----------// type circle struct { radius float64 } func (c *circle) area() float64 { //面積 return math.Pi * c.radius * c.radius } func (c *circle) perimeter() float64 { //周長 return 2 * math.Pi * c.radius } // ----------- 接口的使用 -----------// func interface_test() { r := rect {width:2.9, height:4.8} c := circle {radius:4.3} s := []shape{r, c} //通過指針實(shí)現(xiàn) for _, sh := range s { fmt.Println(sh) fmt.Println(sh.area()) fmt.Println(sh.perimeter()) } }
錯(cuò)誤處理 – Error接口
函數(shù)錯(cuò)誤返回可能是C/C++時(shí)最讓人糾結(jié)的東西的,Go的多值返回可以讓我們更容易的返回錯(cuò)誤,其可以在返回一個(gè)常規(guī)的返回值之外,還能輕易地返回一個(gè)詳細(xì)的錯(cuò)誤描述。通常情況下,錯(cuò)誤的類型是error,它有一個(gè)內(nèi)建的接口。
type error interface {
Error() string
}
還是看個(gè)示例吧:
package main import "fmt" import "errors" //自定義的出錯(cuò)結(jié)構(gòu) type myError struct { arg int errMsg string } //實(shí)現(xiàn)Error接口 func (e *myError) Error() string { return fmt.Sprintf("%d - %s", e.arg, e.errMsg) } //兩種出錯(cuò) func error_test(arg int) (int, error) { if arg 0 { return -1, errors.New("Bad Arguments - negtive!") }else if arg >256 { return -1, myError{arg, "Bad Arguments - too large!"} } return arg*arg, nil } //相關(guān)的測試 func main() { for _, i := range []int{-1, 4, 1000} { if r, e := error_test(i); e != nil { fmt.Println("failed:", e) } else { fmt.Println("success:", r) } } }
程序運(yùn)行后輸出:
1 failed: Bad Arguments - negtive!
2 success: 16
3 failed: 1000 - Bad Arguments - too large!
錯(cuò)誤處理 – Defer
下面的程序?qū)τ诿恳粋€(gè)熟悉C語言的人來說都不陌生(有資源泄露的問題),C++使用RAII來解決這種問題。
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } dst, err := os.Create(dstName) if err != nil { return } written, err = io.Copy(dst, src) dst.Close() src.Close() return }
Go語言引入了Defer來確保那些被打開的文件能被關(guān)閉。如下所示:(這種解決方式還是比較優(yōu)雅的)
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close() dst, err := os.Create(dstName) if err != nil { return } defer dst.Close() return io.Copy(dst, src) }
Go的defer語句預(yù)設(shè)一個(gè)函數(shù)調(diào)用(延期的函數(shù)),該調(diào)用在函數(shù)執(zhí)行defer返回時(shí)立刻運(yùn)行。該方法顯得不同常規(guī),但卻是處理上述情況很有效,無論函數(shù)怎樣返回,都必須進(jìn)行資源釋放。
我們?cè)賮砜匆粋€(gè)defer函數(shù)的示例:
1 for i := 0; i 5; i++ {
2 defer fmt.Printf("%d ", i)
3 }
被延期的函數(shù)以后進(jìn)先出(LIFO)的順行執(zhí)行,因此以上代碼在返回時(shí)將打印4 3 2 1 0。
總之,我個(gè)人覺得defer的函數(shù)行為有點(diǎn)怪異,我現(xiàn)在還沒有完全搞清楚。
錯(cuò)誤處理 – Panic/Recover
對(duì)于不可恢復(fù)的錯(cuò)誤,Go提供了一個(gè)內(nèi)建的panic函數(shù),它將創(chuàng)建一個(gè)運(yùn)行時(shí)錯(cuò)誤并使程序停止(相當(dāng)暴力)。該函數(shù)接收一個(gè)任意類型(往往是字符串)作為程序死亡時(shí)要打印的東西。當(dāng)編譯器在函數(shù)的結(jié)尾處檢查到一個(gè)panic時(shí),就會(huì)停止進(jìn)行常規(guī)的return語句檢查。
下面的僅僅是一個(gè)示例。實(shí)際的庫函數(shù)應(yīng)避免panic。如果問題可以容忍,最好是讓事情繼續(xù)下去而不是終止整個(gè)程序。
var user = os.Getenv("USER") func init() { if user == "" { panic("no value for $USER") } }
當(dāng)panic被調(diào)用時(shí),它將立即停止當(dāng)前函數(shù)的執(zhí)行并開始逐級(jí)解開函數(shù)堆棧,同時(shí)運(yùn)行所有被defer的函數(shù)。如果這種解開達(dá)到堆棧的頂端,程序就死亡了。但是,也可以使用內(nèi)建的recover函數(shù)來重新獲得Go程的控制權(quán)并恢復(fù)正常的執(zhí)行。 對(duì)recover的調(diào)用會(huì)通知解開堆棧并返回傳遞到panic的參量。由于僅在解開期間運(yùn)行的代碼處在被defer的函數(shù)之內(nèi),recover僅在被延期的函數(shù)內(nèi)部才是有用的。
你可以簡單地理解為recover就是用來捕捉Painc的,防止程序一下子就掛掉了。
下面是一個(gè)例程,很簡單了,不解釋了
func g(i int) { if i>1 { fmt.Println("Panic!") panic(fmt.Sprintf("%v", i)) } } func f() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() for i := 0; i 4; i++ { fmt.Println("Calling g with ", i) g(i) fmt.Println("Returned normally from g.") } } func main() { f() fmt.Println("Returned normally from f.") }
運(yùn)行結(jié)果如下:(我們可以看到Painc后的for循環(huán)就沒有往下執(zhí)行了,但是main的程序還在往下走)
Calling g with 0 Returned normally from g. Calling g with 1 Returned normally from g. Calling g with 2 Panic! Recovered in f 2 Returned normally from f
總結(jié)
以上就是本文關(guān)于go語言基礎(chǔ)語法示例的全部內(nèi)容,希望對(duì)大家有所幫助。如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
標(biāo)簽:駐馬店 阿壩 滄州 瀘州 昭通 泰安 晉中 東營
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《go語言基礎(chǔ)語法示例》,本文關(guān)鍵詞 語言基礎(chǔ),語法,示例,語言基礎(chǔ),;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。