基本語(yǔ)法
常量
常量可以類比于java中的final變量绰咽,必須在初始化時(shí)復(fù)制隅津,不可以修改未斑,不可以使用 :=
package main
import "fmt"
//全局常量
const a = "hello"
const b = 1
func main() {
//局部常量
const c = true
fmt.Println(a)
test()
fmt.Println(c)
}
func test(){
fmt.Println(b)
}
變量
變量的聲明方式有一下幾種方式:
var a int
var b string
c := 12
var d = "hello"
可以使用var關(guān)鍵字或者使用 :=的方式聲名并賦值
package basics
import "fmt"
//函數(shù)體外的全局變量要遵循閉包規(guī)則,不可以使用 := 或者 var c
var a string
var b int
var c [] int
var d [5] int
// e := 12 golang具有閉包規(guī)則虹统。 := 其實(shí)是兩步操作, var e int + e = 12 而 e = 12 是不能在函數(shù)體外執(zhí)行的
func test() {
a = "hello"
e := "world"
var f = "just"
fmt.Println(a)
fmt.Println(e)
fmt.Println(f)
}
基本數(shù)據(jù)類型
類型 | 描述 |
---|---|
浮點(diǎn)型 | float32,float64,complex32 |
整型 | byte,int,int8,int16,int32,uint |
布爾型 | bool |
字符串 | “ ”里的字符串可以進(jìn)行標(biāo)量替換, ``里的字符串是什么就是什么 |
字符串支持切片
package main
import "fmt"
func main(){
a := "hesitate"
for _,char := range a{
fmt.Printf("%c",char)
fmt.Printf("--")
}
fmt.Println()
fmt.Printf(a[2:])
fmt.Println()
b:= "hello"
c:= "world"
b+=c
fmt.Printf(b)
fmt.Println(len(a))
}
數(shù)組
數(shù)組是一個(gè)定長(zhǎng)的序列染簇,可以使用以下語(yǔ)法來(lái)創(chuàng)建
[length]Type
[length]Type{v1,v2,v3...}
-
[...]Type{v1,v2,v3...}
使用[...]系統(tǒng)會(huì)自動(dòng)計(jì)算數(shù)組長(zhǎng)度
切片
由于數(shù)組是定長(zhǎng)的对室,實(shí)際使用中更多用到的還是切片模燥,切片是可以調(diào)整長(zhǎng)度的咖祭,切片的創(chuàng)建語(yǔ)法如下:
- []Type{}
- []Type{v1,v2,v3...}
- make([]Type,length,capacity)
- make([]Type,length)
切片的一些操作:
s1 := []int{1, 2, 3, 4, 5}
s2 := make([]int, 2, 4) //make語(yǔ)法聲明 ,初始化len為2涧窒,cap為4
s2 = []int{5, 6}
s3 := append(s2, 7) //append一個(gè)元素
fmt.Println(s3, s2) //[5 6 7] [5 6]
s4 := append(s2, s1...) //append 一個(gè)切片所有的元素
fmt.Println(s4) //[5 6 3 4]
//return
copy(s1, s2) // 復(fù)制心肪,用s2的元素填充s1里去,改變?cè)璼lice,覆蓋對(duì)應(yīng)的key
fmt.Println(s1) //[5 6 3 4]
s1[0], s1[1] = 1, 2
copy(s2, s1)
fmt.Println(s2) //[1 2] 目標(biāo)slice len不夠時(shí)纠吴,只填滿len
s5 := s1[1:4]
s6 := s5[0:4] //不會(huì)報(bào)錯(cuò)硬鞍,因?yàn)閏ap為4,從底層取得最后一位
fmt.Println(s5, s6, cap(s6)) //[2 3 4] [2 3 4 5] 4
//刪除第三個(gè)元素
s7 := append(s1[:1], s1[3:]...)
fmt.Println(s7) //[1 4 5]
包
GOPATH
工程源文件存放于$GOPATH/src 目錄下戴已,不過(guò)gopath的方式慢慢被廢棄固该,才是go module的方式
流程控制
switch
switch的表達(dá)式如下:
switch optionalStatement; optionalExpression {
case expression1: block1
...
case expressionN: blockN
default: blockD
}
但是需要注意的是 Go 語(yǔ)言的 switch
語(yǔ)句不會(huì)自動(dòng)貫穿,相反糖儡,如果想要貫穿需要添加 fallthrough
語(yǔ)句伐坏。
demo:
package main
import "fmt"
func main(){
num := 3
//switch不含expression
switch {
case num > 0:
fmt.Println("num is gt 0")
case num > 1:
fmt.Println("num is gt 1")
case num < 0:
fmt.Println("num is lt 0")
default :
fmt.Println("wrong num")
}
fmt.Println("----------")
//先執(zhí)行前置運(yùn)算,再賦值
switch a:= get();a{
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
}
fmt.Println("----------")
b := "i"
//多情況匹配
switch b{
case "a","e","i","o":
fmt.Println("this is vo")
case "b":
fmt.Println("this is b")
}
}
func get() int{
return 3
}
if
if的寫法如下:
if condition {
} else if condition {
} else {
}
同switch一樣握联,也有如下變體:這種形式的 if
語(yǔ)句先執(zhí)行 statement
桦沉,然后再判斷 conditon
。
if statement; condition {
}
上面的變體中的變量?jī)H作用于該if語(yǔ)句金闽。
函數(shù)
函數(shù)的定義包括: func關(guān)鍵字纯露,函數(shù)名,參數(shù)代芜,返回值埠褪,方法體,方法出口挤庇。
// Switch 函數(shù)包含 func 關(guān)鍵字, 方法名,參數(shù),返回值,方法體,return
func Switch(a int)(b bool){
switch a {
case 1:
return true
case 2:
return false
default:
return false
}
}
同java不同的是钞速,go的函數(shù)可以有多個(gè)返回值,具體如下:
func Divide(a int,b int)(c int,err error){
if b==0{
err = errors.New("被除數(shù)不能為0")
return
}
return a/b,nil
}
// Add 返回值可以只寫類型
func Add(a,b int)(int,string){
return a+b,"success"
}
匿名函數(shù):可以定義一個(gè)沒(méi)有名字的函數(shù),然后賦值給一個(gè)變量,或者直接運(yùn)行:
//匿名函數(shù)
f := func(a, b, c int) (result bool) {
if a+b+c > 10 {
result = true
} else {
result = false
}
return result
}
fmt.Println(f(1,2,3))
//也可以不定義變量,直接在結(jié)尾拼上參數(shù)直接運(yùn)行該函數(shù)
defer
defer
可以使棧幀中的代碼執(zhí)行完后嫡秕,以出棧的方式來(lái)執(zhí)行渴语。程序異常并不會(huì)影響defer運(yùn)行,因此可以使用defer做資源的釋放昆咽,或者是異常的捕獲遵班。
//測(cè)試一下defer函數(shù)
func Delay(){
fmt.Println("start ...")
defer foo1()
defer foo2()
defer foo3()
fmt.Println("end ...")
}
func foo1(){
fmt.Println("do 001...")
}
func foo2(){
fmt.Println("do 002...")
}
func foo3(){
fmt.Println("do 003...")
}
以上代碼輸出:
start ...
end ...
do 003...
do 002...
do 001...
類型斷言
類型斷言針對(duì)interface{}
類型的變量,其使用方法有以下兩種
- 安全斷言:s,ok := s.(T)
- 非安全斷言:s:=s.(T)
其中s表示interface{}類型的變量潮改,ok為是否斷言成功狭郑,T為斷言的類型。
//interface{} 包含所有類型,類似于object
var i interface{} = 99
//類型斷言
j:=i.(int)
fmt.Printf("type of j is %T,value is %d \n",j,j)
var s interface{} = []string{"left","right"}
if s,ok:=s.([]string);ok{
fmt.Printf("s is a string slice,value is %s",s)
}
異常
使用panic(interface{})
來(lái)拋出異常汇在,類似于java中的throw
使用recover()
可以捕獲異常,類似于java中的catch翰萨。
func foo(){
panic(errors.New("i`m a bug"))
return
}
func Wrong() int{
defer func(){ //捕獲異常
if r:=recover();r!=nil{
err:=r.(error)
fmt.Println("catch an exception",err)
}
}()
foo()
return 10
}
面向?qū)ο缶幊?/h4>
指針
go中的指針表示指向內(nèi)存地址的一種數(shù)據(jù)。使用方法:
v:="cat"
ptr:=&v //使用&來(lái)獲得v變量的指針
s:=*ptr //使用*來(lái)獲得指針?biāo)赶虻淖兞康闹?
&
用于變量糕殉,來(lái)取得指針
*
用于指針亩鬼,來(lái)取得變量
func Point(){
a:="nothing to fear"
ptr:=&a //&表示取得變量的內(nèi)存地址
//變量ptr為 *string 類型
fmt.Printf("a的內(nèi)存地址為%p \n",ptr)
fmt.Printf("prt的類型為%T \n",ptr)
//使用*來(lái)獲得指針指向的內(nèi)存值
fmt.Println(*ptr)
}
也可以使用new關(guān)鍵字來(lái)創(chuàng)建一個(gè)指定類型的指針:
func Create(){
//使用new來(lái)創(chuàng)建指定類型的指針
ptr:=new(string)
fmt.Println(ptr)
*ptr = "range your dream"
fmt.Println(*ptr)
}
當(dāng)一個(gè)指針沒(méi)有指向任何內(nèi)存地址時(shí),其值為nil
結(jié)構(gòu)體
go中結(jié)構(gòu)體為一系列數(shù)據(jù)類型(基本數(shù)據(jù)類型,interface,自定義結(jié)構(gòu)體)的組合殖告,eg:
type ColorPoint struct {
color.Color //匿名字段
x,y int //具名字段
r Print //interface類型
m Man //自定義結(jié)構(gòu)體
}
type Print interface {
red() string
}
type Man struct {
name string
age int
}
接口同java中的接口類似,為一系列方法的集合。
方法
方法是特殊的函數(shù)雳锋,定義在某一特定的類型上黄绩,通過(guò)這個(gè)類型的實(shí)例(接收者)來(lái)調(diào)用。
go中方法不像其他語(yǔ)言一樣玷过,是定義在類中爽丹,而是通過(guò)接收者來(lái)關(guān)聯(lián)。
接收者必須顯式地?fù)碛幸粋€(gè)名字辛蚊,在方法中必須被使用粤蝎。
type Count int //go不允許為內(nèi)置的數(shù)據(jù)類型添加方法,這里自定義了一個(gè)類型
func (c Count) Add(i Count){
fmt.Printf("add result is : %d",c+i)
}
func (c *Count) Increment() {
*c++
}
func (c *Count) IsZero() bool{
return *c==0
}
方法相比于函數(shù),在func關(guān)鍵字和方法名中間增加了接收者袋马,接收者可以是指針類型初澎,也可以是接收者本身
type Cat struct {
Name string
}
func (c *Cat) Tell(){
fmt.Println("喵喵")
}
func (c Cat) Catch(){
fmt.Println("抓老鼠")
}
func (c *Cat) HisName() {
fmt.Println(c.Name)
}
組合
go中可以使用組合的方式來(lái)實(shí)現(xiàn)繼承
type Base struct {
Name string
}
func (b *Base) Foo(){
fmt.Println("foo...")
}
func (b *Base) Bar(){
fmt.Println("bar...")
}
type Speed struct {
Base
}
func (s *Speed) Foo(){
fmt.Println("重寫foo...")
}
func main(){
s:=new(Speed)
s.Base.Foo() //可以調(diào)用base中的所有方法
s.Foo() //s對(duì)原方法進(jìn)行了重寫
}
接口
go中的接口定義了函數(shù)名,參數(shù)及返回類型虑凛,不進(jìn)行函數(shù)的實(shí)現(xiàn)碑宴。以java為例,類實(shí)現(xiàn)接口是通過(guò)implements關(guān)鍵字桑谍,并對(duì)接口中的函數(shù)進(jìn)行實(shí)現(xiàn)墓懂。在go中也是以結(jié)構(gòu)體來(lái)對(duì)接口進(jìn)行實(shí)現(xiàn)的,并不需要implements或者其他的關(guān)鍵字霉囚,而是只要實(shí)現(xiàn)了接口中定義的所有的函數(shù),就表示這個(gè)結(jié)構(gòu)體實(shí)現(xiàn)了接口匕积。
type Sharp interface{
Area() float64
Perimeter() float64
}
type Rect struct {
width float64
height float64
}
func (r Rect) Area() float64{
return r.width * r.height
}
func (r Rect) Perimeter() float64{
return (r.width+r.height)*2
}
// Rect實(shí)現(xiàn)了Sharp接口
一個(gè)結(jié)構(gòu)體也可以實(shí)現(xiàn)多接口:
type Sharp interface{
Area() float64
Perimeter() float64
}
type Detail interface {
Desc()
}
type Rect struct {
Width float64
Height float64
}
func (r Rect) Area() float64{
return r.Width * r.Height
}
func (r Rect) Perimeter() float64{
return (r.Width +r.Height)*2
}
// Rect實(shí)現(xiàn)了Detail接口
func (r Rect) Desc(){
fmt.Println("this is a rect")
}
func main(){
r:=Rect{Width: 10, Height: 20}
var s Sharp = r
var d Detail = r
fmt.Printf("type of s is %T,type of d is %T \n",s,d)
//類型斷言
sharp,ok := s.(Sharp)
if ok {
fmt.Println(sharp.Perimeter())
}
}
使用接口可以達(dá)到多態(tài)的效果盈罐。
接口也可以嵌入到接口中,實(shí)現(xiàn)接口的組合:
type Interface1 interface {
Send()
Receive()
}
type Interface2 interface {
Interface1
Close()
}
//Interface2隱式地包含了Interface1
內(nèi)存分配
go中的內(nèi)存分配的關(guān)鍵字使用make
和new
闪唆,make
用于對(duì)slice盅粪,map,channel分配內(nèi)存
并發(fā)編程
協(xié)程
提到高并發(fā)的解決方案便會(huì)想到多線程悄蕾,多線程其實(shí)是以獲得cpu時(shí)間片的方式來(lái)解決單線程的阻塞等待的問(wèn)題票顾。顯而易見(jiàn)如果一個(gè)任務(wù)持續(xù)需要cpu的計(jì)算能力,那么多線程上下文的切換反而會(huì)讓效率變低帆调。所以對(duì)于高密度計(jì)算的程序的線程數(shù)一般設(shè)置為cpu的核心數(shù)奠骄。而對(duì)于io密集型的程序,由于請(qǐng)求會(huì)阻塞到io番刊,這時(shí)線程切換來(lái)處理其他請(qǐng)求含鳞,可以極大的提高系統(tǒng)的吞吐量。
但是系統(tǒng)內(nèi)存資源有限芹务,目前操作系統(tǒng)支持的最大線程數(shù)為千量級(jí)蝉绷,那么可以使用比線程更細(xì)粒度的協(xié)程
協(xié)程是在用戶態(tài)來(lái)實(shí)現(xiàn)的鸭廷,所以和線程以及進(jìn)程是兩個(gè)層面的概念。操作系統(tǒng)無(wú)法感知協(xié)程熔吗,所以一個(gè)線程內(nèi)的協(xié)程在某一時(shí)刻只會(huì)有一個(gè)在運(yùn)行辆床。但由于是在用戶態(tài),所以不再存在線程切換帶來(lái)的時(shí)間損失桅狠。golang的并發(fā)正是基于協(xié)程來(lái)實(shí)現(xiàn)的讼载。
goroutine
Go 程序中使用 go 關(guān)鍵字為一個(gè)函數(shù)創(chuàng)建一個(gè)goroutine
。一個(gè)函數(shù)可以被創(chuàng)建多個(gè) goroutine
垂攘,一個(gè) goroutine
必定對(duì)應(yīng)一個(gè)函數(shù)维雇。
func GoAdder(){
var times int
for true {
times++
fmt.Println("tick",times)
time.Sleep(time.Second)
}
}
func main(){
go GoAdder()
var string input
fmt.Scanln(&input)
}
channel
我們無(wú)法直接取得goroutine
函數(shù)的結(jié)果,可以通過(guò)channel
晒他。
channel
是goroutine
之間互相通訊的東西吱型。channel
是類型相關(guān)的,也就是說(shuō)一個(gè) channel
只能傳遞一種類型的值陨仅,這個(gè)類型需要在 channel
聲明時(shí)指定津滞。
channe
l是一個(gè)FIFO的隊(duì)列,在任何時(shí)候灼伤,同時(shí)只能有一個(gè) goroutine
訪問(wèn)通道進(jìn)行發(fā)送和獲取數(shù)據(jù)
channel
的一般聲明形式:var chanName chan ElementType
即在普通變量的基礎(chǔ)上增加了chan
這個(gè)關(guān)鍵字
聲明的變量需要使用make分配內(nèi)存空間后才能使用:
通道實(shí)例 := make(chan 數(shù)據(jù)類型)
channel的讀寫操作
c <- x //向一個(gè)Channel發(fā)送一個(gè)值
<- c //從一個(gè)Channel中接收一個(gè)值
x = <- c //從Channel c接收一個(gè)值并將其存儲(chǔ)到x中
x, ok = <- c //從Channel接收一個(gè)值触徐,如果channel關(guān)閉了或沒(méi)有數(shù)據(jù),那么ok將被置為false
默認(rèn)情況下狐赡,通信是同步且無(wú)緩沖的:在有接受者接收數(shù)據(jù)之前撞鹉,發(fā)送不會(huì)結(jié)束∮敝叮可以想象一個(gè)無(wú)緩沖的通道在沒(méi)有空間來(lái)保存數(shù)據(jù)的時(shí)候:必須要一個(gè)接收者準(zhǔn)備好接收通道的數(shù)據(jù)然后發(fā)送者可以直接把數(shù)據(jù)發(fā)送給接收者鸟雏。所以通道的發(fā)送/接收操作在對(duì)方準(zhǔn)備好之前是阻塞的:
1)對(duì)于同一個(gè)通道,發(fā)送操作(協(xié)程或者函數(shù)中的)览祖,在接收者準(zhǔn)備好之前是阻塞的:如果ch中的數(shù)據(jù)無(wú)人接收孝鹊,就無(wú)法再給通道傳入其他數(shù)據(jù):新的輸入無(wú)法在通道非空的情況下傳入。所以發(fā)送操作會(huì)等待 ch 再次變?yōu)榭捎脿顟B(tài):就是通道值被接收時(shí)(可以傳入變量)展蒂。
2)對(duì)于同一個(gè)通道又活,接收操作是阻塞的(協(xié)程或函數(shù)中的),直到發(fā)送者可用:如果通道中沒(méi)有數(shù)據(jù)锰悼,接收者就阻塞了柳骄。
func main(){
m:=make(chan string)
//開啟一個(gè)routine做通道的發(fā)送方
go func() {
time.Sleep(time.Second*2)
m <- "hello"
}()
//main routine做通道的接收方,該語(yǔ)句會(huì)阻塞直到接收到數(shù)據(jù)
s:= <- m
fmt.Println(s)
}
循環(huán)接收
通道中的數(shù)據(jù)每次只能接收一個(gè)箕般,但是通道是可以遍歷的夹界,遍歷的結(jié)果就是接收到的數(shù)據(jù):
func main(){
m:=make(chan int)
go func() {
for i:=3;i>=0;i-- {
m <- i
}
}()
for k:= range m {
//打印通道數(shù)據(jù)
fmt.Println(k)
if k == 0 {
break
}
}
}
deadLock
fatal error: all goroutines are asleep - deadlock!
學(xué)習(xí)channel時(shí)這個(gè)錯(cuò)誤并不陌生,下面通過(guò)一段程序來(lái)分析一下:
func TestDeadLock(a chan int){
fmt.Println( <- a)
}
func main(){
m:=make(chan int)
m<-0
go base.TestDeadLock(m)
time.Sleep(time.Second*2)
}
執(zhí)行上面程序會(huì)出現(xiàn)deadlock的異常,我們分析在m<-0時(shí)可柿,由于還沒(méi)有接收者鸠踪,程序在此阻塞,導(dǎo)致無(wú)法執(zhí)行之后的代碼复斥,拋出deadlock異常营密。那么先調(diào)換一下順序:
func TestDeadLock(a chan int){
fmt.Println( <- a)
}
func main(){
m:=make(chan int)
go base.TestDeadLock(m)
m<-0
time.Sleep(time.Second*2)
}
發(fā)現(xiàn)程序正常執(zhí)行。證實(shí)了非緩沖通道的阻塞性目锭。
帶緩沖的通道
如何創(chuàng)建帶緩沖的通道呢评汰?參見(jiàn)如下代碼:
通道實(shí)例 := make(chan 通道類型, 緩沖大小)
- 通道類型:和無(wú)緩沖通道用法一致,影響通道發(fā)送和接收的數(shù)據(jù)類型痢虹。
- 緩沖大斜蝗ァ:決定通道最多可以保存的元素?cái)?shù)量。
- 通道實(shí)例:被創(chuàng)建出的通道實(shí)例奖唯。
func ChannelBuffer() {
//創(chuàng)建一個(gè)包含3個(gè)元素緩沖區(qū)的通道
c:=make(chan int,3)
//不會(huì)再有deadlock的異常
c <- 0
c <- 1
c <- 2
//查看當(dāng)前通道的大小
fmt.Println(len(c))
}
這就是我們常見(jiàn)的生產(chǎn)者/消費(fèi)者模式了
channel的關(guān)閉使用內(nèi)置的close(ch)函數(shù)
select
java nio中的核心實(shí)現(xiàn)在于Selector來(lái)實(shí)現(xiàn)多路復(fù)用惨缆,golang則是使用select關(guān)鍵字。
func GoSelector() {
n:=time.Now()
c1:=make(chan interface{})
c2:=make(chan int)
c3:=make(chan string)
go func() {
time.Sleep(time.Second*4)
close(c1)
}()
go func() {
time.Sleep(time.Second*3)
c2 <- 1
}()
go func() {
time.Sleep(time.Second*3)
c3 <- "hello"
}()
fmt.Println("wait to read...")
for {
select {
case <-c1:
fmt.Printf("Unblocked %v later.\n", time.Since(n))
case m2 := <-c2:
fmt.Printf("read from c2:%d .\n", m2)
case m3 := <-c3:
fmt.Println("read from c3:", m3)
default:
fmt.Println("exec default ...")
}
}
}