基本語法
常量
常量可以類比于java中的final變量,必須在初始化時復(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ù)組是一個定長的序列,可以使用以下語法來創(chuàng)建
[length]Type
[length]Type{v1,v2,v3...}
-
[...]Type{v1,v2,v3...}
使用[...]系統(tǒng)會自動計算數(shù)組長度
切片
由于數(shù)組是定長的,實(shí)際使用中更多用到的還是切片,切片是可以調(diào)整長度的,切片的創(chuàng)建語法如下:
- []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語法聲明 ,初始化len為2,cap為4
s2 = []int{5, 6}
s3 := append(s2, 7) //append一個元素
fmt.Println(s3, s2) //[5 6 7] [5 6]
s4 := append(s2, s1...) //append 一個切片所有的元素
fmt.Println(s4) //[5 6 3 4]
//return
copy(s1, s2) // 復(fù)制,用s2的元素填充s1里去,改變原slice,覆蓋對應(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不夠時,只填滿len
s5 := s1[1:4]
s6 := s5[0:4] //不會報錯,因?yàn)閏ap為4,從底層取得最后一位
fmt.Println(s5, s6, cap(s6)) //[2 3 4] [2 3 4 5] 4
//刪除第三個元素
s7 := append(s1[:1], s1[3:]...)
fmt.Println(s7) //[1 4 5]
包
GOPATH
工程源文件存放于$GOPATH/src 目錄下,不過gopath的方式慢慢被廢棄,才是go module的方式
流程控制
switch
switch的表達(dá)式如下:
switch optionalStatement; optionalExpression {
case expression1: block1
...
case expressionN: blockN
default: blockD
}
但是需要注意的是 Go 語言的 switch 語句不會自動貫穿,相反,如果想要貫穿需要添加 fallthrough 語句。
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 語句先執(zhí)行 statement,然后再判斷 conditon 。
if statement; condition {
}
上面的變體中的變量僅作用于該if語句。
函數(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ù)可以有多個返回值,具體如下:
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ù):可以定義一個沒有名字的函數(shù),然后賦值給一個變量,或者直接運(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í)行完后,以出棧的方式來執(zhí)行。程序異常并不會影響defer運(yùn)行,因此可以使用defer做資源的釋放,或者是異常的捕獲。
//測試一下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...
類型斷言
類型斷言針對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{})來拋出異常,類似于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 //使用&來獲得v變量的指針
s:=*ptr //使用*來獲得指針?biāo)赶虻淖兞康闹?
&用于變量,來取得指針
*用于指針,來取得變量
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)
//使用*來獲得指針指向的內(nèi)存值
fmt.Println(*ptr)
}
也可以使用new關(guān)鍵字來創(chuàng)建一個指定類型的指針:
func Create(){
//使用new來創(chuàng)建指定類型的指針
ptr:=new(string)
fmt.Println(ptr)
*ptr = "range your dream"
fmt.Println(*ptr)
}
當(dāng)一個指針沒有指向任何內(nèi)存地址時,其值為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ù),定義在某一特定的類型上,通過這個類型的實(shí)例(接收者)來調(diào)用。
go中方法不像其他語言一樣,是定義在類中,而是通過接收者來關(guān)聯(lián)。
接收者必須顯式地?fù)碛幸粋€名字,在方法中必須被使用。
type Count int //go不允許為內(nèi)置的數(shù)據(jù)類型添加方法,這里自定義了一個類型
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中可以使用組合的方式來實(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對原方法進(jìn)行了重寫
}
接口
go中的接口定義了函數(shù)名,參數(shù)及返回類型,不進(jìn)行函數(shù)的實(shí)現(xiàn)。以java為例,類實(shí)現(xiàn)接口是通過implements關(guān)鍵字,并對接口中的函數(shù)進(jìn)行實(shí)現(xiàn)。在go中也是以結(jié)構(gòu)體來對接口進(jìn)行實(shí)現(xiàn)的,并不需要implements或者其他的關(guān)鍵字,而是只要實(shí)現(xiàn)了接口中定義的所有的函數(shù),就表示這個結(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接口
一個結(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用于對slice,map,channel分配內(nèi)存
并發(fā)編程
協(xié)程
提到高并發(fā)的解決方案便會想到多線程,多線程其實(shí)是以獲得cpu時間片的方式來解決單線程的阻塞等待的問題。顯而易見如果一個任務(wù)持續(xù)需要cpu的計算能力,那么多線程上下文的切換反而會讓效率變低。所以對于高密度計算的程序的線程數(shù)一般設(shè)置為cpu的核心數(shù)。而對于io密集型的程序,由于請求會阻塞到io,這時線程切換來處理其他請求,可以極大的提高系統(tǒng)的吞吐量。
但是系統(tǒng)內(nèi)存資源有限,目前操作系統(tǒng)支持的最大線程數(shù)為千量級,那么可以使用比線程更細(xì)粒度的協(xié)程
協(xié)程是在用戶態(tài)來實(shí)現(xiàn)的,所以和線程以及進(jìn)程是兩個層面的概念。操作系統(tǒng)無法感知協(xié)程,所以一個線程內(nèi)的協(xié)程在某一時刻只會有一個在運(yùn)行。但由于是在用戶態(tài),所以不再存在線程切換帶來的時間損失。golang的并發(fā)正是基于協(xié)程來實(shí)現(xiàn)的。
goroutine
Go 程序中使用 go 關(guān)鍵字為一個函數(shù)創(chuàng)建一個goroutine。一個函數(shù)可以被創(chuàng)建多個 goroutine,一個 goroutine 必定對應(yīng)一個函數(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
我們無法直接取得goroutine函數(shù)的結(jié)果,可以通過channel。
channel 是goroutine 之間互相通訊的東西。channel 是類型相關(guān)的,也就是說一個 channel 只能傳遞一種類型的值,這個類型需要在 channel 聲明時指定。
channel是一個FIFO的隊列,在任何時候,同時只能有一個 goroutine 訪問通道進(jìn)行發(fā)送和獲取數(shù)據(jù)
channel的一般聲明形式:var chanName chan ElementType
即在普通變量的基礎(chǔ)上增加了chan這個關(guān)鍵字
聲明的變量需要使用make分配內(nèi)存空間后才能使用:
通道實(shí)例 := make(chan 數(shù)據(jù)類型)
channel的讀寫操作
c <- x //向一個Channel發(fā)送一個值
<- c //從一個Channel中接收一個值
x = <- c //從Channel c接收一個值并將其存儲到x中
x, ok = <- c //從Channel接收一個值,如果channel關(guān)閉了或沒有數(shù)據(jù),那么ok將被置為false
默認(rèn)情況下,通信是同步且無緩沖的:在有接受者接收數(shù)據(jù)之前,發(fā)送不會結(jié)束。可以想象一個無緩沖的通道在沒有空間來保存數(shù)據(jù)的時候:必須要一個接收者準(zhǔn)備好接收通道的數(shù)據(jù)然后發(fā)送者可以直接把數(shù)據(jù)發(fā)送給接收者。所以通道的發(fā)送/接收操作在對方準(zhǔn)備好之前是阻塞的:
1)對于同一個通道,發(fā)送操作(協(xié)程或者函數(shù)中的),在接收者準(zhǔn)備好之前是阻塞的:如果ch中的數(shù)據(jù)無人接收,就無法再給通道傳入其他數(shù)據(jù):新的輸入無法在通道非空的情況下傳入。所以發(fā)送操作會等待 ch 再次變?yōu)榭捎脿顟B(tài):就是通道值被接收時(可以傳入變量)。
2)對于同一個通道,接收操作是阻塞的(協(xié)程或函數(shù)中的),直到發(fā)送者可用:如果通道中沒有數(shù)據(jù),接收者就阻塞了。
func main(){
m:=make(chan string)
//開啟一個routine做通道的發(fā)送方
go func() {
time.Sleep(time.Second*2)
m <- "hello"
}()
//main routine做通道的接收方,該語句會阻塞直到接收到數(shù)據(jù)
s:= <- m
fmt.Println(s)
}
循環(huán)接收
通道中的數(shù)據(jù)每次只能接收一個,但是通道是可以遍歷的,遍歷的結(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時這個錯誤并不陌生,下面通過一段程序來分析一下:
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í)行上面程序會出現(xiàn)deadlock的異常,我們分析在m<-0時,由于還沒有接收者,程序在此阻塞,導(dǎo)致無法執(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)建帶緩沖的通道呢?參見如下代碼:
通道實(shí)例 := make(chan 通道類型, 緩沖大小)
- 通道類型:和無緩沖通道用法一致,影響通道發(fā)送和接收的數(shù)據(jù)類型。
- 緩沖大?。簺Q定通道最多可以保存的元素數(shù)量。
- 通道實(shí)例:被創(chuàng)建出的通道實(shí)例。
func ChannelBuffer() {
//創(chuàng)建一個包含3個元素緩沖區(qū)的通道
c:=make(chan int,3)
//不會再有deadlock的異常
c <- 0
c <- 1
c <- 2
//查看當(dāng)前通道的大小
fmt.Println(len(c))
}
這就是我們常見的生產(chǎn)者/消費(fèi)者模式了
channel的關(guān)閉使用內(nèi)置的close(ch)函數(shù)
select
java nio中的核心實(shí)現(xiàn)在于Selector來實(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 ...")
}
}
}