Go語言學習整理
本文基於菜鳥教程,對於本身不明白的點加了點我的註解,對於已明確的點作了刪除,可能結構不太清晰,看官們可移步Go語言教程
1 Go語言結構
當標識符(包括常量、變量、類型、函數名、結構字段等等)以一個大寫字母開頭,如:Group1,那麼使用這種形式的標識符的對象就能夠被外部包的代碼所使用(客戶端程序須要先導入這個包),這被稱爲導出(像面嚮對象語言中的 public);標識符若是以小寫字母開頭,則對包外是不可見的,可是他們在整個包的內部是可見而且可用的(像面嚮對象語言中的 protected )。
起始的{不能單獨成行(使用IDE爲JetBrain的GoLand,Goland我使用的破解方法都不太好使,也能夠直接在線編譯運行,網站自行百度)
package main
import "fmt"
func main()
{ // 錯誤,{ 不能在單獨的行上,應該寫成func main(){
fmt.Println("Hello, World!")
}
Go的編譯與運行
編譯生成二進制文件後再執行:
$ go build hello.go
$ ls
hello hello.go
$ ./hello
Hello, World!
直接go run運行:
$ go run hello.go
Hello, World!
在這裏的go run與go build的區別,go build會生成對應的可執行二進制文件,go run不會生成對應的二進制文件,會直接運行。
2 Go語言基礎語法與數據類型
2.1 基礎語法
在 Go 程序中,一行表明一個語句結束。每一個語句不須要像 C 家族中的其它語言同樣以分號 ; 結尾,由於這些工做都將由 Go 編譯器自動完成。若是你打算將多個語句寫在同一行,它們則必須使用 ; 人爲區分,但在實際開發中咱們並不鼓勵這種作法。如下爲兩個語句:
fmt.Println("Hello, World!")
fmt.Println("菜鳥教程:runoob.com")
標識符必須以字母或者下劃線開頭,不能以數字開頭。
2.2 數據類型
Go 編程語言中,數據類型用於聲明函數和變量。數據類型的出現是爲了把數據分紅所需內存大小不一樣的數據,編程的時候須要用大數據的時候才須要申請大內存,就能夠充分利用內存。Go 語言按類別有如下幾種數據類型:
1.bool型 var b bool = true
2.數字類型:int float32 float64 支持整型和浮點型 也支持複數,其中位運算採用補碼。
3.字符串
4.派生類型
指針、數組、struct、Channel、函數、切片、接口類型、Map(與Python中的dict相似)
其他類型;uint8 uint16 uint32 uint64 int8 int16 int32 int64 float32 float64
complex64:32位實數與虛數 complex128:64位實數與虛數
byte:相似unit8 rune:相似int32
uintptr:無符號整型 用於存放一個指針
3 Go變量常量運算符
3.1 Go變量
Go與c或者Java相比較而言,最大的特色定義順序基本是反的,定義變量時會將數據類型放在後面。
好比c或者Java中定義一個變量是int a,定義多個變量是int a, b, c;定義一個指向整型的指針是int* p。
而在Go中這些變量的定義基本上都反過來了,a int;a,b int;定義指針,var var_name *var-type, ip *int; fp *float。
數組定義:C++: int a[10];java: int[] arr = new int[10]; Go: a[10] int.
聲明變量:var identifier type
1.指定變量類型,聲明後若不賦值,則使用默認值
var v_name v_type
v_name = value
2.根據值自行判斷變量類型
Var v_name = value
3.省略var(這種方式在Go中出現次數最多),相似於octave的賦值方法 注意 :=左側(冒號後接等號)的變量不該該是已經聲明過的,不然會致使編譯錯誤。
v_name := value// 例如
var a int = 10
var b = 10
c := 10
3.1.1 多變量聲明
package main
var x, y int
var ( // 這種因式分解關鍵字的寫法通常用於聲明全局變量
a int
b bool
)
var c, d int = 1, 2
var e, f = 123, "hello"
//這種不帶聲明格式的只能在函數體中出現
//g, h := 123, "hello"
func main(){
g, h := 123, "hello"
println(x, y, a, b, c, d, e, f, g, h)
}
多變量同時賦值
a, b, c := 5, 7, "abc"
空白標識符 _ 也被用於拋棄值,如值 5 在:_, b = 5, 7 中被拋棄。_ 其實是一個只寫變量,你不能獲得它的值。這樣作是由於 Go 語言中你必須使用全部被聲明的變量,但有時你並不須要使用從一個函數獲得的全部返回值。並行賦值也被用於當一個函數返回多個返回值時,好比這裏的 val 和錯誤 err 是經過調用 Func1 函數同時獲得:val, err = Func1(var1)。
3.2 Go常量
const identifier [type] = value
能夠省略類型標識符[type],由於編譯器能夠根據變量的值來推斷其類型。
顯式類型定義: const b string = "abc"
隱式類型定義: const b = "abc"
iota:特殊常量,一個能夠被編譯器修改的常量。
iota 在 const關鍵字出現時將被重置爲 0(const 內部的第一行以前),const 中每新增一行常量聲明將使 iota 計數一次(iota 可理解爲 const 語句塊中的行索引)。
iota 能夠被用做枚舉值:
package main
import ("fmt")
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //獨立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢復計數
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
4 通道
通道(channel)是用來傳遞數據的一個數據結構,通道在Go中很重要。
goroutine 是 golang 中在語言級別實現的輕量級線程,僅僅利用 go 就能馬上起一個新線程。多線程會引入線程之間的同步問題,在 golang 中可使用 channel 做爲同步的工具。
經過 channel 能夠實現兩個 goroutine 之間的通訊。
通道可用於兩個 goroutine 之間經過傳遞一個指定類型的值來同步運行和通信。操做符 <- 用於指定通道的方向,發送或接收。若是未指定方向,則爲雙向通道。
ch <- v // 把 v 發送到通道 ch,向chan傳入數據
v := <-ch // 從 ch 接收數據
// 並把值賦給 v
聲明通道:
ch := make(chan int)//定義ch是一個接收int類型數據的channel
4.1 默認
默認狀況下,通道是不帶緩衝區的。發送端發送數據,同時必須又接收端相應的接收數據。
如下實例經過兩個 goroutine 來計算數字之和,在 goroutine 完成計算後,它會計算兩個結果的和:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 發送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 從通道 c 中接收
fmt.Println(x, y, x+y)
}
4.2 通道緩衝區
通道能夠設置緩衝區,經過 make 的第二個參數指定緩衝區大小:
ch := make(chan int, 100)
帶緩衝區的通道容許發送端的數據發送和接收端的數據獲取處於異步狀態,就是說發送端發送的數據能夠放在緩衝區裏面,能夠等待接收端去獲取數據,而不是馬上須要接收端去獲取數據。
不過因爲緩衝區的大小是有限的,因此仍是必須有接收端來接收數據的,不然緩衝區一滿,數據發送端就沒法再發送數據了。
package main
import "fmt"
func main() {
// 這裏咱們定義了一個能夠存儲整數類型的帶緩衝通道
// 緩衝區大小爲2
ch := make(chan int, 2)
// 由於 ch 是帶緩衝的通道,咱們能夠同時發送兩個數據
// 而不用馬上須要去同步讀取數據
ch <- 1
ch <- 2
// 獲取這兩個數據
fmt.Println(<-ch)
fmt.Println(<-ch)
}
4.3 Go遍歷通道與關閉通道
v, ok := <-ch
若是通道接收不到數據後 ok 就爲 false,這時通道就可使用 close() 函數來關閉。
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)//這裏必需要調用close,否則會報錯fatal error: all goroutines are asleep - deadlock!
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函數遍歷每一個從通道接收到的數據,由於 c 在發送完 10 個
// 數據以後就關閉了通道,因此這裏咱們 range 函數在接收到 10 個數據
// 以後就結束了。若是上面的 c 通道不關閉,那麼 range 函數就不
// 會結束,從而在接收第 11 個數據的時候就阻塞了。
for i := range c {
fmt.Print(i," ")
}
}
通道的特色:
經過信道發送和接收數據默認是阻塞的。這是什麼意思呢?當數據發送給信道後,程序流程在發送語句處阻塞,直到其餘協程從該信道中讀取數據。一樣地,當從信道讀取數據時,程序在讀取語句處阻塞,直到其餘協程發送數據給該信道(這個特色也很是重要,在後面涉及select和協程是會再次提到)。
5 Go條件與循環
5.1 條件
If else與switch case都與C++/Java基本一致,括號上也許存在細微的差異。
5.1.1 條件中的select語句
select是Go中的一個控制結構,相似於用於通訊的switch語句。每一個case必須是一個通訊操做,要麼是發送要麼是接收。
select隨機執行一個可運行的case。若是沒有case可運行,它將阻塞,直到有case可運行。一個默認的子句應該老是可運行的。
語法:
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你能夠定義任意數量的 case */
default : /* 可選 */
statement(s);
}
如下描述了 select 語句的語法:
每一個case都必須是一個通訊,即利用chan類型來讀寫數據,全部channel表達式都會被求值,全部被髮送的表達式都會被求值
1.若是任意某個通訊能夠進行,它就執行;其餘被忽略。
2.若是有多個case均可以運行,Select會隨機公平地選出一個執行。其餘不會執行。
不然:
若是有default子句,則執行該語句。
若是沒有default字句,select將阻塞,直到某個通訊能夠運行;Go不會從新對channel或值進行求值。
package main
import "fmt"
func main() {
var c1, c2, c3 chan int//定義了三個接收整型數據的channel
var i1, i2 int
select {
case i1 = <-c1:
fmt.Printf("received ", i1, " from c1\n")
case c2 <- i2:
fmt.Printf("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
fmt.Printf("received ", i3, " from c3\n")
} else {
fmt.Printf("c3 is closed\n")
}
default:
fmt.Printf("no communication\n")
}
}
這裏的c1,c2,c3就和咱們以前說的channel的特性串起來了,讀要等待寫,寫要等待讀,都被阻塞,因此三個case都沒有走,直接走進了default。疑問?這裏若是構建一個新的協程去解鎖信道的讀寫,最後仍是會走到default,爲何?
執行結果:
5.2 循環語句
5.2.1 無限循環
package main
import "fmt"
func main() {
for true {//不是while
fmt.Printf("這是無限循環。\n");
}
}
5.2.2 for循環
package main
import "fmt"
func main() {
numbers := [6]int{1, 2, 3, 5} /* for 循環 */
b :=0
for a := 0; a < len(numbers); a++ {//C++
fmt.Printf("a 的值爲: %d\n", numbers[a])
}
for b < len(numbers) {
fmt.Printf("b 的值爲: %d\n", numbers[b])
b++
}
}
//for 循環的 range 格式能夠對 slice、map、數組、字符串、Channel等進行迭代循環。格式以下:
//對於數組類型,這裏的i即爲數組下標,和Python中的enumerate相似。
for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
5.2.3 Goto
goto的用法與C++一致,在結構化程序設計中不主張使用goto,避免形成程序流程的混亂和理解調試沒必要要的困難。
語法格式:
goto label
..
...
label: statement
實例:
package main
import (
"fmt"
)
func main() {
var a int = 10
/* 循環 */
LOOP: for a < 20 {
if a == 15 {
/* 跳過迭代 */
a = a + 1
goto LOOP
}
fmt.Printf("a的值爲 : %d\n", a)
a++
}
}
a的值爲 : 10
a的值爲 : 11
a的值爲 : 12
a的值爲 : 13
a的值爲 : 14
a的值爲 : 16
a的值爲 : 17
a的值爲 : 18
a的值爲 : 19
Process finished with exit code 0
6 Go語言函數
函數是基本的代碼塊,用於執行一個任務。
Go 語言最少有個 main() 函數。
你能夠經過函數來劃分不一樣功能,邏輯上每一個函數執行的是指定的任務。
函數聲明告訴了編譯器函數的名稱,返回類型,和參數。
Go 語言標準庫提供了多種可動用的內置的函數。例如,len() 函數能夠接受不一樣類型參數並返回該類型的長度。若是咱們傳入的是字符串則返回字符串的長度,若是傳入的是數組,則返回數組中包含的元素個數。
func function_name( [parameter list] ) [return_types] {
函數體
}
實例
package main
import "fmt"
/* 函數返回兩個數的最大值 */
func max(num1, num2 int) int {
/* 聲明局部變量 */
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
//函數返回多個值
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Mahesh", "Kumar")
fmt.Println(a, b)
c := max(6,12)
fmt.Println("c: ",c)
}
6.1 函數參數
函數若是使用參數,該變量可稱爲函數的形參。
形參就像定義在函數體內的局部變量。
調用函數,能夠經過兩種方式來傳遞參數:
值傳遞和引用傳遞,引用傳遞是指在調用函數時,在函數體中對形參的修改,影響到了實參。
傳引用的方式,和C++的swap方式基本一致。
函數用法:函數做爲值,閉包,方法
值:return math.sqrt
6.2 閉包
Go語言支持匿名函數,可做爲閉包。匿名函數是一個"內聯"語句或表達式。匿名函數的優越性在於能夠直接使用函數內的變量,沒必要申明。
對於匿名函數,簡單記一下本身的認知:Python中也具備匿名函數。舉例:map(lambda x: x* x , [1,2,3]),這裏的特色是不須要顯式定義一個函數,簡潔。可是匿名函數還有另外一個特色是,匿名函數能夠做爲一個函數對象把它賦值給一個變量,再利用變量來調用該函數,即便得函數能夠像普通變量同樣被傳遞或者使用。
Python示例:
go實例中,咱們建立了函數 getSequence() ,返回另一個函數。該函數的目的是在閉包中遞增 i 變量,代碼以下:
package main
import "fmt"
func getSequence() func() int {
i := 0
return func() int {
i += 1
return i
}
}
func main() {
/* nextNumber 爲一個函數,函數 i 爲 0 */
nextNumber := getSequence() /* 調用 nextNumber 函數,i 變量自增 1 並返回 */
fmt.Print(nextNumber(), " ")
fmt.Print(nextNumber(), " ")
fmt.Print(nextNumber(), " ")
/* 建立新的函數 nextNumber1,並查看結果 */
nextNumber1 := getSequence()
fmt.Print(nextNumber1(), " ")
fmt.Print(nextNumber1(), " ")
}
代碼最後的執行結果
6.3 方法
Go 語言中同時有函數和方法。一個方法就是一個包含了接受者的函數,接受者能夠是命名類型/結構體類型的一個值或是一個指針。全部給定類型的方法屬於該類型的方法集,我的認爲這樣便可變相實現C++中的類內方法的類外定義。語法格式以下:
func (variable_name variable_data_type) function_name() [return_type]{
/* 函數體*/
}
實例
package main
import (
"fmt"
)
/* 定義結構體 */
type Circle struct {
radius float64
}
func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("圓的面積 = ", c1.getArea())
}
//該 method 屬於 Circle 類型對象中的方法
//特別注意下這裏和go中通常函數的定義是有區別的,func function_name( [parameter list] ) [return_types]是通常的函數聲明
func (c Circle) getArea() float64 {
//c.radius 即爲 Circle 類型對象中的屬性
return 3.14 * c.radius * c.radius
}
7 Go語言數組
7.1 初始化數組
初始化數組中 {} 中的元素個數不能大於 [] 中的數字。
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
不設置數組大小。Go本身判斷
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
7.2 多維數組
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
var a = [5][2]int{ {0,0}, {1,2}, {2,4}, {3,6},{4,8}}
8 Go指針
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮點型 */
指針變量指向一個值的內存地址。*來獲取指針所指向的內容,&取變量內存地址
nil與null None nil NULL同樣,都指代零值或者空值。
指針數組:var ptr [MAX]*int;
指向指針的指針:**ptr
傳指針做爲出參,swap函數的示例。
9 Go結構體、slice、range、集合
9.1 結構體
Go的結構體就是別的語言中的類,
type Books struct {
title string
author string
subject string
book_id int
}
以下方法相似構造函數:
結構體指針,即類指針
結構體對於類內函數的實現我的認爲能夠參看6.3,實現了類內函數的類外定義(C++也能夠作到,可是C++須要在類內聲明)。
9.2 Slice切片
切片:動態數組
最簡單的切片定義:即聲明一個未指定大小的數組來定義切片
arr []int
或者使用make函數來建立切片:var slice1 []type = make([]type, len) 也能夠簡寫爲slice1 := make([]type, len)
切片初始化: s :=[] int {1,2,3 }
s := arr[startIndex:endIndex] s := arr[startIndex:] s := arr[:endIndex],這幾種用法與Python的list的切片用法一致。
append()與copy() api
copy(numbers1,numbers) 拷貝numbers的內容到numbers1
9.3 Range
package main
import "fmt"
func main() {
//這是咱們使用range去求一個slice的和。使用數組跟這個很相似
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
//在數組上使用range將傳入index和值兩個變量。上面那個例子咱們不須要使用該元素的序號,因此咱們使用空白符"_"省略了。有時侯咱們確實須要知道它的索引。
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
//range也能夠用在map的鍵值對上。
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
//range也能夠用來枚舉Unicode字符串。第一個參數是字符的索引,第二個是字符(Unicode的值)自己。
for i, c := range "go" {
fmt.Println(i, c)
}
}
9.4 Map
Map 是一種無序的鍵值對的集合。Map 最重要的一點是經過 key 來快速檢索數據,key 相似於索引,指向數據的值。
Map 是一種集合,因此咱們能夠像迭代數組和切片那樣迭代它。不過,Map 是無序的,咱們沒法決定它的返回順序,這是由於 Map 是使用 hash 表來實現的。
/* 聲明變量,默認 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函數 */
map_variable := make(map[key_data_type]value_data_type)
map的初始化:
1.與Python字典相似
/* map插入key - value對,各個國家對應的首都 */
countryCapitalMap [ "France" ] = "Paris"
countryCapitalMap [ "Italy" ] = "羅馬"
2.表達式的方式來進行初始化
countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
delete()函數,參數爲map和其對應的mapkey
delete(countryCapitalMap, "France")
10 Go語言接口
Go 語言提供了另一種數據類型即接口,它把全部的具備共性的方法定義在一塊兒,並不須要像Java中那樣用implements來表示實現接口,任何其餘類型只要實現了這些方法就是實現了這個接口。
/* 定義接口 */
type interface_name interface {
method_name1 [return_type]//return_type表示爲可選參數
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}/* 定義結構體 */
type struct_name struct {
/* variables */
}/* 實現接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法實現 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法實現*/
}
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面向對象?:多態(接口),繼承,封裝(方法)。應該能夠說Go面向對象,可是Go實際借鑑的編程思想,不止面向對象一種。
---------------------
做者:暗焰之珩
來源:CSDN
原文:https://blog.csdn.net/weixin_42348333/article/details/86682676
版權聲明:本文爲博主原創文章,轉載請附上博文連接!java