Go語(yǔ)言的介紹
- go語(yǔ)言的歷史:2007年,Rob Pike、Robert Griesmier 和 Ken Thompson 三人(他們 3 個(gè)人負(fù)責(zé)構(gòu)建過(guò) UNIX、 Plan 9、 B、 Java的 JVM HotSpot、 V8、 Strongtalk、 Sawzall、 Ed、 Acme 和 UTF8)綜合他們多年的經(jīng)驗(yàn),借鑒已有的語(yǔ)言,創(chuàng)建了一門與眾不同的、全新的系統(tǒng)語(yǔ)言,并命名為“Go”。如果按照現(xiàn)在的路線發(fā)展下去,這門語(yǔ)言將是這 3 個(gè)人最有影響的一項(xiàng)創(chuàng)造。
- Go語(yǔ)言的特點(diǎn):1. Go 語(yǔ)言是一種讓代碼分享更容易的編程語(yǔ)言。 Go 語(yǔ)言自帶一些工具,讓使用別人寫的包更容易,并且 Go 語(yǔ)言也讓分享自己寫的包更容易。提供了更高效的復(fù)用代碼的手段。 2. Go 語(yǔ)言還讓用戶能更高效地利用昂貴服務(wù)器上的所有核心,而且它編譯大型項(xiàng)目的速度也很快。
總結(jié):C 和 C++這類語(yǔ)言提供了很快的執(zhí)行速度,而 Ruby 和 Python 這類語(yǔ)言則擅長(zhǎng)快速開發(fā)。 Go 語(yǔ)言在這兩者間架起了橋梁,不僅提供了高性能的語(yǔ)言,同時(shí)也讓開發(fā)更快速。
- Go語(yǔ)言快速編譯的原因:編譯 Go 程序時(shí),編譯器只會(huì)關(guān)注那些直接被引用的庫(kù),而不是像 Java、 C 和 C++那樣,要遍歷依賴鏈中所有依賴的庫(kù)。因此,很多 Go 程序可以在 1 秒內(nèi)編譯完。在現(xiàn)代硬件上,編譯整個(gè) Go語(yǔ)言的源碼樹只需要 20 秒。
Go語(yǔ)言的基本語(yǔ)法
- 一、 包、變量和函數(shù)
- 二、 流程控制語(yǔ)句:for 、if 、 else 、 switch 和 defer
- 三、 更多類型:struct 、 slice 和 映射
一、 包、變量和函數(shù)
(1) 包
//舉個(gè)例子
package main
import (
"fmt"
"math/rand"
//包名與導(dǎo)入路徑的最后一個(gè)元素一致。例如,"math/rand" 包中的源碼均以 package rand 語(yǔ)句開始
)
func main( ) {
fmt.Println(math.Pi)
//此處若為math.pi 則輸出錯(cuò)誤,原因:pi未以大寫字母開頭,是未導(dǎo)出的,而未導(dǎo)出的名字在該包外無(wú)法訪問
}
//運(yùn)行結(jié)果: 3.141592653589793
*每個(gè) Go 程序都是由包構(gòu)成的。 程序從 main 包開始運(yùn)行。
btw:按照約定,包名與導(dǎo)入路徑的最后一個(gè)元素一致。例如,"math/rand" 包中的源碼均以 package rand 語(yǔ)句開始*
*在 Go 中,如果一個(gè)名字以大寫字母開頭,那么它就是已導(dǎo)出的。例如,Pizza 就是個(gè)已導(dǎo)出名,Pi 也同樣,它導(dǎo)出自 math 包。
在導(dǎo)入一個(gè)包時(shí),你只能引用其中已導(dǎo)出的名字。任何“未導(dǎo)出”的名字在該包外均無(wú)法訪問。
(2) 函數(shù)
例一
package main
import "fmt"
func add(x, y int) int{ //注意類型在變量名之后
return x + y
}
func main( ) {
fmt.Println(add(42, 13))
}
*函數(shù)可以沒有參數(shù)或接受多個(gè)參數(shù)。
在本例中,add 接受兩個(gè) int 類型的參數(shù)。 ! 注意類型在變量名之后
*當(dāng)連續(xù)兩個(gè)或多個(gè)函數(shù)的已命名形參類型相同時(shí),除最后一個(gè)類型以外,其它都可以省略。
在本例中, x int, y int 被縮寫為 x, y int 。
例二
// 多值返回 函數(shù)可以返回任意數(shù)量的返回值。
package main
import "fmt"
func swap(x, y string) (string, string) {
// swap 函數(shù)返回了兩個(gè)字符串。
return y, x
}
func main( ) {
a, b := swap("hello", "world")
fmt.Println(a,b)
}
//運(yùn)行結(jié)果:world hello
例三
//命名值返回
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
//運(yùn)行結(jié)果:7 10
*Go 的返回值可被命名,它們會(huì)被視作定義在函數(shù)頂部的變量。
*沒有參數(shù)的 return 語(yǔ)句返回已命名的返回值。也就是 直接 返回。
btw, 直接返回語(yǔ)句應(yīng)當(dāng)僅用在下面這樣的短函數(shù)中。在長(zhǎng)的函數(shù)中它們會(huì)影響代碼的可讀性。
(3) 變量
var 語(yǔ)句用于聲明一個(gè)變量列表,跟函數(shù)的參數(shù)列表一樣,類型在最后。
package main
import "fmt"
var c, python, java bool // c,python,java 都是 bool 類型變量
func main() {
var i int
fmt.Println(i, c, python, java)
}
// 運(yùn)行結(jié)果:0 false false false
btw, 沒有明確初始值的變量明會(huì)被賦予它們的零值。
零值是: 數(shù)值類型為 0,布爾類型為 false,字符串為 ""(空字符串)。
例一
//變量的初始化
package main
import "fmt"
var i, j int = 1, 2
func main() {
var c, python, java = true, false, "no!" /// var =
fmt.Println(i, j, c, python, java)
}
//運(yùn)行結(jié)果:1 2 true false no!
//短變量的聲明
package main
import "fmt"
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!" /// :=
fmt.Println(i, j, k, c, python, java)
}
//運(yùn)行結(jié)果: 1 2 3 true false no!
*變量聲明可以包含初始值,每個(gè)變量對(duì)應(yīng)一個(gè)。
如果初始化值已存在,則可以省略類型;變量會(huì)從初始值中獲得類型。
*在函數(shù)中,簡(jiǎn)潔賦值語(yǔ)句 := 可在類型明確的地方代替 var 聲明。
函數(shù)外的每個(gè)語(yǔ)句都必須以關(guān)鍵字開始(var, func 等等),因此 := 結(jié)構(gòu)不能在函數(shù)外使用。
例二 // Go的基本類型
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
fmt.Printf("Type: %T Value: %v\n", z, z)
}
//////
//運(yùn)行結(jié)果:
Type: bool Value: false
Type: uint64 Value: 18446744073709551615
Type: complex128 Value: (2+3i)
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的別名
rune // int32 的別名
// 表示一個(gè) Unicode 碼點(diǎn)
float32 float64
complex64 complex128
*同導(dǎo)入語(yǔ)句一樣,變量聲明也可以“分組”成一個(gè)語(yǔ)法塊。
*int, uint 和 uintptr 在 32 位系統(tǒng)上通常為 32 位寬,在 64 位系統(tǒng)上則為 64 位寬。 當(dāng)你需要一個(gè)整數(shù)值時(shí)應(yīng)使用 int 類型,除非你有特殊的理由使用固定大小或無(wú)符號(hào)的整數(shù)類型。
例三:類型轉(zhuǎn)換
package main
import (
"fmt"
"math"
)
func main() {
var x, y int = 3, 4
var f float64 = math.Sqrt(float64(x*x + y*y))
var z uint = uint(f)
fmt.Println(x, y, z)
}
表達(dá)式 T(v) 將值 v 轉(zhuǎn)換為類型 T。
一些關(guān)于數(shù)值的轉(zhuǎn)換:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
或者,更加簡(jiǎn)單的形式:
i := 42
f := float64(i)
u := uint(f)
與 C 不同的是,Go 在不同類型的項(xiàng)之間賦值時(shí)需要顯式轉(zhuǎn)換。試著移除例子中 float64 或 uint 的轉(zhuǎn)換看看會(huì)發(fā)生什么。
例四: 類型推導(dǎo)
package main
import "fmt"
func main() {
v := 42 // 修改這里!
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
fmt.Printf("v, f, g is of type %T,%T,%T\n", v,f,g)
}
---------------------------------------------------------------------------------------
// 運(yùn)行結(jié)果: v, f, g is of type int,float64,complex128
在聲明一個(gè)變量而不指定其類型時(shí)(即使用不帶類型的 := 語(yǔ)法或 var = 表達(dá)式語(yǔ)法),變量的類型由右值推導(dǎo)得出。
當(dāng)右值聲明了類型時(shí),新變量的類型與其相同:
var i int
j := i // j 也是一個(gè) int
不過(guò)當(dāng)右邊包含未指明類型的數(shù)值常量時(shí),新變量的類型就可能是 int, float64 或 complex128 了,這取決于常量的精度:
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
例五:常量和數(shù)值常量
package main
import "fmt"
const Pi = 3.14
func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
-----------------------------------------------------------------------------------
//運(yùn)行結(jié)果:
Hello 世界
Happy 3.14 Day
Go rules? true
*常量的聲明與變量類似,只不過(guò)是使用 const 關(guān)鍵字。 常量可以是字符、字符串、布爾值或數(shù)值。
****常量不能用 := 語(yǔ)法聲明。
package main
import "fmt"
const (
// 將 1 左移 100 位來(lái)創(chuàng)建一個(gè)非常大的數(shù)字
// 即這個(gè)數(shù)的二進(jìn)制是 1 后面跟著 100 個(gè) 0
Big = 1 << 100
// 再往右移 99 位,即 Small = 1 << 1,或者說(shuō) Small = 2
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
//fmt.Println(needInt(Big)) 運(yùn)行錯(cuò)誤
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
--------------------------------------------------------------------------------
//運(yùn)行結(jié)果:
21
0.2
1.2676506002282295e+29
*數(shù)值常量是高精度的值。
一個(gè)未指定類型的常量由上下文來(lái)決定其類型。
*(int 類型最大可以存儲(chǔ)一個(gè) 64 位的整數(shù),有時(shí)會(huì)更小。)
*(int 可以存放最大64位的整數(shù),根據(jù)平臺(tái)不同有時(shí)會(huì)更少。)
二、 流程控制語(yǔ)句:for 、if 、 else 、 switch 和 defer
(1) for
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
//和 C、Java、JavaScript 之類的語(yǔ)言不同,Go 的 for 語(yǔ)句后面的三個(gè)構(gòu)成部分外沒有小括號(hào), 大括號(hào) { } 則是必須的。
sum += i
}
fmt.Println(sum)
}
---------------------------------------------------------------------------------------
//運(yùn)行結(jié)果:45
***Go 只有一種循環(huán)結(jié)構(gòu):for 循環(huán)。
*初始化語(yǔ)句通常為一句短變量聲明,該變量聲明僅在 for 語(yǔ)句的作用域中可見。
*基本的 for 循環(huán)由三部分組成,它們用分號(hào)隔開:
初始化語(yǔ)句:在第一次迭代前執(zhí)行
條件表達(dá)式:在每次迭代前求值
后置語(yǔ)句:在每次迭代的結(jié)尾執(zhí)行
//btw,初始化語(yǔ)句和后置語(yǔ)句是可選的
/* package main
import "fmt"
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
//運(yùn)行結(jié)果:1024
*/
//此時(shí)你可以去掉分號(hào),因?yàn)?C 的 while 在 Go 中叫做 for。
/* package main
import "fmt"
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
} */
(2) if
- Go 的 if 語(yǔ)句與 for 循環(huán)類似,表達(dá)式外無(wú)需小括號(hào) ( ) ,而大括號(hào) { } 則是必須的。
*同 for 一樣, if 語(yǔ)句可以在條件表達(dá)式前執(zhí)行一個(gè)簡(jiǎn)單的語(yǔ)句。
該語(yǔ)句聲明的變量作用域僅在 if 之內(nèi)。
*在 if 的簡(jiǎn)短語(yǔ)句中聲明的變量同樣可以在任何對(duì)應(yīng)的 else 塊中使用。
//ex1
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
///ex2
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// 這里開始就不能使用 v 了
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
//練習(xí):循環(huán)與函數(shù)
//用牛頓法實(shí)現(xiàn)平方根函數(shù)
//先聲明和初始化一個(gè)浮點(diǎn)數(shù) 然后根據(jù) z -= (z*z - x) / (2*z) 重復(fù)迭代10次 最后再與標(biāo)準(zhǔn)庫(kù)中的math.Sqrt進(jìn)行比較
package main
import (
"fmt"
"math"
)
func SqrtDefine(x float64) float64 {
z := float64(1)
for i := 0; i < 10; i++ {
z -= (z*z - x) / (2*z)
}
return z
}
func main() {
fmt.Println(SqrtDefine(2))
fmt.Println(math.Sqrt(2))
}
//運(yùn)行結(jié)果:1.414213562373095
//1.4142135623730951
(3) switch
- switch 語(yǔ)句用于基于不同條件執(zhí)行不同動(dòng)作,每一個(gè) case 分支都是唯一的,從上至下逐一測(cè)試,直到匹配為止。
- switch 語(yǔ)句執(zhí)行的過(guò)程從上至下,直到找到匹配項(xiàng),匹配項(xiàng)后面也不需要再加 break。
- switch 默認(rèn)情況下 case 最后自帶 break 語(yǔ)句,匹配成功后就不會(huì)執(zhí)行其他 case,如果我們需要執(zhí)行后面的 case,可以使用 fallthrough 。Go 只運(yùn)行選定的 case,而非之后所有的 case。
原因:Go 自動(dòng)提供了在這些語(yǔ)言中每個(gè) case 后面所需的 break 語(yǔ)句。 除非以 fallthrough 語(yǔ)句結(jié)束,否則分支會(huì)自動(dòng)終止。
例一
package main
import "fmt"
func main() {
/* 定義局部變量 */
var grade string = "B"
var marks int = 90
switch marks {
case 90: grade = "A"
case 80: grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
}
switch {
case grade == "A" :
fmt.Printf("優(yōu)秀!\n" )
case grade == "B", grade == "C" :
fmt.Printf("良好\n" )
case grade == "D" :
fmt.Printf("及格\n" )
case grade == "F":
fmt.Printf("不及格\n" )
default:
fmt.Printf("差\n" );
}
fmt.Printf("你的等級(jí)是 %s\n", grade );
}
--------------------------------------------------------------------------------------
//運(yùn)行結(jié)果:
優(yōu)秀!
你的等級(jí)是 A
ps1: switch 的 case 語(yǔ)句從上到下順次執(zhí)行,直到匹配成功時(shí)停止。
(例如,
switch i {
case 0:
case f():
}
在 i==0 時(shí) f 不會(huì)被調(diào)用。)
ps2: 沒有條件的 switch 同 switch true 一樣。 這種形式能將一長(zhǎng)串 if-then-else 寫得更加清晰
btw,switch 從第一個(gè)判斷表達(dá)式為 true 的 case 開始執(zhí)行,如果 case 帶有 fallthrough,
程序會(huì)繼續(xù)執(zhí)行下一條 case,且它不會(huì)去判斷下一個(gè) case 的表達(dá)式是否為 true
(4) defer
defer 語(yǔ)句會(huì)將函數(shù)推遲到外層函數(shù)返回之后執(zhí)行。
推遲調(diào)用的函數(shù)其參數(shù)會(huì)立即求值,但直到外層函數(shù)返回前該函數(shù)都不會(huì)被調(diào)用。
- 推遲的函數(shù)調(diào)用會(huì)被壓入一個(gè)棧中。當(dāng)外層函數(shù)返回時(shí),被推遲的函數(shù)會(huì)按照后進(jìn)先出的順序調(diào)用。
//舉個(gè)例子
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
// 運(yùn)行結(jié)果:
counting
done
9
8
7
6
5
4
3
2
1
0
- 函數(shù)返回過(guò)程中,有一個(gè)事實(shí)必須要了解,關(guān)鍵字return不是一個(gè)原子操作,實(shí)際上return只代理匯編指令ret,即將跳轉(zhuǎn)程序執(zhí)行。比如語(yǔ)句return i,實(shí)際上分兩步進(jìn)行,即將i值存入棧中作為返回值,然后執(zhí)行跳轉(zhuǎn),而defer的執(zhí)行時(shí)機(jī)正是跳轉(zhuǎn)前,所以說(shuō)defer執(zhí)行時(shí)還是有機(jī)會(huì)操作返回值的。
//主函數(shù)擁有具名返回值
主函聲明語(yǔ)句中帶名字的返回值,會(huì)被初始化成一個(gè)局部變量,函數(shù)內(nèi)部可以像使用局部變量一樣使用該返回值。如果defer語(yǔ)句操作該返回值,可能會(huì)改變返回結(jié)果。
一個(gè)影響函返回值的例子:
---------------------------------------------------------------------------------------
func foo() (ret int) {
defer func() {
ret++
}()
return 0
}
--------------------------------------------------------------------------------------
上面的函數(shù)拆解出來(lái),如下所示:
ret = 0
ret++
return
函數(shù)真正返回前,在defer中對(duì)返回值做了+1操作,所以函數(shù)最終返回1。
總結(jié):
*defer定義的延遲函數(shù)參數(shù)在defer語(yǔ)句出時(shí)就已經(jīng)確定下來(lái)了
*defer定義順序與實(shí)際執(zhí)行順序相反
*return不是原子操作,執(zhí)行過(guò)程是: 保存返回值(若有)-->執(zhí)行defer(若有)-->執(zhí)行ret跳轉(zhuǎn)
*申請(qǐng)資源后立即使用defer關(guān)閉資源是好習(xí)慣