1. 程式人生 > 其它 >Go語言基礎知識總結(持續中)

Go語言基礎知識總結(持續中)

Go基礎知識總結

變數宣告

Go語言中的變數需要宣告以後才可以使用(需要提前定義變數)並且聲明後必須使用(不適用會報錯)

標準宣告

var 變數名 變數型別

example:

var name string
var id int
var isOk bool

多變數宣告可以整合在一起

var (
	name string
	id int
	isOk bool

)

變數初始化

Go語言在宣告變數的時候,會自動對變數對應的記憶體區進行初始化操作。

var 變數名 變數型別 = 表示式

example:

var name string = "A2rcher"
var id int = 123

型別推斷

Go語言提供了一種簡易的方式,不需要提供變數型別,可以根據後面的初始化操作後自動推斷其變數型別

var name = "A2rcher"
var id = 123

短變數宣告

在之後學習到的函式中,還可以用更簡單的方式來宣告一個變數(也是最常用的宣告方法),使用:=宣告並初始化變數

package main
import (
	"fmt"
)
var m = 100 //全域性變數

func main(){
	n:=100
	m:=300//宣告並初始化區域性變數
	fmt.Println(m, n)
}

匿名變數

所謂匿名,通俗的講就是沒有名字的變數,用下劃線_表示,匿名變數不佔用名稱空間(Go語言中變數聲明後必須使用,匿名變數除外),不會分配記憶體,所以匿名變數之間不存在重複宣告。

package main

import "fmt"

func foo() (string, int) {

	return "A2rcher", 20
}

func main() {
	x, _ := foo()
	_, y := foo()

	fmt.Println("x=", x, "y=", y)

}

常量

變數中還有一中是常量,它相對於變數來講是永恆不變的值,對於變數來說一開始可以不做賦值直接定義型別,但是對於常量來講他需要在定義的時候就要賦值,用const

const p = 3.1415
const e = 2.7182

多常量宣告可以整合在一起

const (
	p = 3.1415
	e = 2.7182
)

iota常量計算器

iota是Go語言中的常量計算器,在常量表達式中使用。

package main

import "fmt"

const (
	n1 = iota
	n2
	n3
	n4
)

func main() {

	fmt.Println(n1)//0
	fmt.Println(n2)//1
	fmt.Println(n3)//2
	fmt.Println(n4)//3
}

iota在const關鍵字出現時將被重置為0。const中每新增一行常量宣告將使iota計數一次(iota可理解為const語句塊中的行索引)。 使用iota能簡化定義,在定義列舉時很有用。

使用下劃線_可以跳過其中一個

package main

import "fmt"

const (
	n1 = iota//0
	_		 //1
	n2		 //2
	n3		 //3
	n4		 //4
)

func main() {

	fmt.Println(n1)//0
	fmt.Println(n2)//2
	fmt.Println(n3)//3
	fmt.Println(n4)//4
}

資料型別

整型

型別 描述
uint8 無符號 8位整型 (0 到 255)
uint16 無符號 16位整型 (0 到 65535)
uint32 無符號 32位整型 (0 到 4294967295)
uint64 無符號 64位整型 (0 到 18446744073709551615)
int8 有符號 8位整型 (-128 到 127)
int16 有符號 16位整型 (-32768 到 32767)
int32 有符號 32位整型 (-2147483648 到 2147483647)
int64 有符號 64位整型 (-9223372036854775808 到 9223372036854775807)

特殊整型

型別 描述
uint 32位作業系統上就是uint32,64位作業系統上就是uint64
int 32位作業系統上就是int32,64位作業系統上就是int64
uintptr 無符號整型,用於存放一個指標

浮點型

Go語言支援兩種浮點型數:float32float64。這兩種浮點型資料格式遵循IEEE 754標準: float32 的浮點數的最大範圍約為 3.4e38,可以使用常量定義:math.MaxFloat32float64 的浮點數的最大範圍約為 1.8e308,可以使用一個常量定義:math.MaxFloat64

列印浮點數時,可以使用fmt包配合動詞%f,程式碼如下:

package main
import (
        "fmt"
        "math"
)
func main() {
        fmt.Printf("%f\n", math.Pi)
        fmt.Printf("%.2f\n", math.Pi)
}

複數

complex64和complex128

var c1 complex64
c1 = 1 + 2i
var c2 complex128
c2 = 2 + 3i
fmt.Println(c1)
fmt.Println(c2)

複數有實部和虛部,complex64的實部和虛部為32位,complex128的實部和虛部為64位。

布林

字串

byte和rune

運算子

運算子 描述
+ 相加
- 相減
* 相乘
/ 相除
% 求餘
運算子 描述
== 檢查兩個值是否相等,如果相等返回 True 否則返回 False。
!= 檢查兩個值是否不相等,如果不相等返回 True 否則返回 False。
> 檢查左邊值是否大於右邊值,如果是返回 True 否則返回 False。
>= 檢查左邊值是否大於等於右邊值,如果是返回 True 否則返回 False。
< 檢查左邊值是否小於右邊值,如果是返回 True 否則返回 False。
<= 檢查左邊值是否小於等於右邊值,如果是返回 True 否則返回 False。
運算子 描述
&& 邏輯 AND 運算子。 如果兩邊的運算元都是 True,則為 True,否則為 False。
|| 邏輯 OR 運算子。 如果兩邊的運算元有一個 True,則為 True,否則為 False。
! 邏輯 NOT 運算子。 如果條件為 True,則為 False,否則為 True。
運算子 描述
& 參與運算的兩數各對應的二進位相與。 (兩位均為1才為1)
| 參與運算的兩數各對應的二進位相或。 (兩位有一個為1就為1)
^ 參與運算的兩數各對應的二進位相異或,當兩對應的二進位相異時,結果為1。 (兩位不一樣則為1)
<< 左移n位就是乘以2的n次方。 “a<<b”是把a的各二進位全部左移b位,高位丟棄,低位補0。
>> 右移n位就是除以2的n次方。 “a>>b”是把a的各二進位全部右移b位。
運算子 描述
= 簡單的賦值運算子,將一個表示式的值賦給一個左值
+= 相加後再賦值
-= 相減後再賦值
*= 相乘後再賦值
/= 相除後再賦值
%= 求餘後再賦值
<<= 左移後賦值
>>= 右移後賦值
&= 按位與後賦值
|= 按位或後賦值
^= 按位異或後賦值

流程控制

if else

for 初始語句;條件表示式;結束語句{
	迴圈語句
}

example:

package main

import "fmt"

func main() {
	for i := 0; i < 10; i++ {
		fmt.Println(i)
	}
}

for迴圈的初始語句可以被忽略,但是用作與隔開的分號;還是要有的。

package main

import "fmt"

var i = 0

func main() {
	for ; i < 10; i++ {
		fmt.Println(i)
	}
}

for迴圈的結束語句也是可以省略掉的

package main

import "fmt"

var i = 0

func main() {
	for i < 10 {
		fmt.Println(i)
		i++
	}
}

for range(鍵值迴圈)

鍵值迴圈可以用作與遍歷,切片,map還有channel。遵循一下規律

  1. 陣列、切片、字串返回索引和值。
  2. map返回鍵和值。
  3. 通道(channel)只返回通道內的值。

switch

goto(跳轉指定標籤)

goto的使用可以這麼理解,在一個迴圈中當執行到某一個位置時我不想讓他繼續執行下去而是跳到其他的地方,這個時候就可以使用goto,用來簡化一些程式碼的實現過程。

func gotoDemo1() {
	var breakFlag bool
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				// 設定退出標籤
				breakFlag = true
				break
			}
			fmt.Printf("%v-%v\n", i, j)
		}
		// 外層for迴圈判斷
		if breakFlag {
			break
		}
	}
}

使用goto語句能簡化程式碼:

func gotoDemo2() {
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				// 設定退出標籤
				goto breakTag
			}
			fmt.Printf("%v-%v\n", i, j)
		}
	}
	return
	// 標籤
breakTag:
	fmt.Println("結束for迴圈")
}

break

跳出迴圈

continue

繼續下次迴圈

陣列

陣列定義

var 變數名 [元素數量]T
var a [3]int
var b [4]string

陣列初始化

package main

import "fmt"

func main() {

	var testArray [3]int
	var numArray = [3]int{1, 2, 3}
	var strArray = [3]string{"a", "b", "c"}
	fmt.Println(testArray)
	fmt.Println(numArray)
	fmt.Println(strArray)
}

使用[...]可以根據初始值自動判斷元素有多少

package main

import "fmt"

func main() {

	var testArray [3]int
	var numArray = [...]int{1, 2, 3,4,5}
	var strArray = [...]string{"a", "b", "c", "d","f","v"}
	fmt.Println(testArray)
	fmt.Println(numArray)
	fmt.Println(strArray)
}

陣列遍歷

for迴圈遍歷

package main

import "fmt"

func main() {

	var testArray [3]int
	var numArray = [...]int{1, 2, 3, 4, 5}
	var strArray = [...]string{"a", "b", "c", "d", "f", "v"}
	fmt.Println(testArray)
	fmt.Println(numArray)
	fmt.Println(strArray)
	for i := 0; i < len(numArray); i++ {
		fmt.Println(i)
	}
	for v := 0; v < len(strArray); v++ {
		fmt.Println(strArray[v])

	}
}

for range遍歷

package main

import "fmt"

func main() {

	var testArray [3]int
	var numArray = [...]int{1, 2, 3, 4, 5}
	var strArray = [...]string{"a", "b", "c", "d", "f", "v"}
	fmt.Println(testArray)
	fmt.Println(numArray)
	fmt.Println(strArray)
	for i, v := range numArray {
		fmt.Println(i, v)
	}
	fmt.Println("----------------------------------------------------")
	for m, n := range strArray {
		fmt.Println(m, string(n))
	}
}

執行結果可以明顯看出他兩個是有區別的。

for迴圈遍歷的結果並沒有把角標打印出來,而是直接出的結果。

for range迴圈遍歷的結果時把對應的角標也列印了出來,所以我們可以改善一下,使用匿名變數可以讓for range的結果for迴圈的結果一樣。

package main

import "fmt"

func main() {

	var testArray [3]int
	var numArray = [...]int{1, 2, 3, 4, 5}
	var strArray = [...]string{"a", "b", "c", "d", "f", "v"}
	fmt.Println(testArray)
	fmt.Println(numArray)
	fmt.Println(strArray)
	for _, v := range numArray {
		fmt.Println(v)
	}
	fmt.Println("----------------------------------------------------")
	for _, n := range strArray {
		fmt.Println(string(n))
	}
}

同樣的原理,可以對多為陣列進行遍歷。

切片

切片跟陣列很像,但是切片相當於在陣列型別的基礎上做了一層封裝,相較於陣列可以更快的操作一塊資料集合。

切片定義

var 變數名 [元素個數]T

可以看到他的定義方式跟陣列一模一樣,所以他的初始化也是一樣的。

切片長度和容量

用內建函式len(),cap()可以求出切片的長度容量

package main

import "fmt"

func main() {

	var test1Slice [3]int
	fmt.Println(len(test1Slice))
	fmt.Println(cap(test1Slice))
	var test2Slice = [...]string{"a", "b", "c", "d", "e", "f"}
	fmt.Println(len(test2Slice))
	fmt.Println(cap(test2Slice))
}

表示式

切片表示式中有low和high兩個定義來表示切片中的界限值

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s := a[1:3]  // s := a[low:high]
	fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
}
a[2:]  // 等同於 a[2:len(a)]
a[:3]  // 等同於 a[0:3]
a[:]   // 等同於 a[0:len(a)]

make()函式構造切片

make([]T,元素數量,切片容量)

example:

func main(){
	a := make([]int,2,10)
}

參考

判斷切片是否為空

一定要使用len(s)==0來判斷,不能使用nil比較來判斷。

append()方法新增元素

append()方法為切片同臺新增元素,可以一次新增一個或者多個,還可以新增另一個其他切片中的元素。

package main

import "fmt"

func main() {
	var s []int
	s = append(s, 1)
	s = append(s, 2, 3, 4)
	s2 := []int{2, 3, 4}
	s = append(s, s2...)
	fmt.Println(s)
}

map使用

map:是一種無序的基於key-value的資料結構,必須初始化才可以使用。

map定義

map[keyType]valueType
  • KeyType:表示鍵的型別。
  • ValueType:表示鍵對應的值的型別。

map初始化

map預設初始值為nil,使用make()函式分配記憶體初始化:

make(map[keyType]valueType,[cap])

其中cap表示map的容量,該引數雖然不是必須的,但是我們應該在初始化map的時候就為其指定一個合適的容量。

map使用

package main

import "fmt"

func main() {

	sourceMap := make(map[string]int, 20)
	sourceMap["A2rcher"] = 20
	sourceMap["emo"] = 30
	fmt.Println(sourceMap)
	fmt.Printf("type  %T\n", sourceMap)

}

map可以在宣告的時候就填充元素:

package main

import "fmt"

func main() {

	sourceMap := make(map[string]int, 20)
	sourceMap["A2rcher"] = 20
	sourceMap["emo"] = 30
	fmt.Println(sourceMap)
	fmt.Printf("type  %T\n", sourceMap)
	fmt.Println("***************")
	sourceTest := map[string]int{
		"lucher": 20,
		"xs":10,
	}
	fmt.Println(sourceTest)


}

判斷鍵值對是否存在

package main

import "fmt"

func main() {

	sourceMap := make(map[string]int, 20)
	sourceMap["A2rcher"] = 20
	sourceMap["emo"] = 30
	fmt.Println(sourceMap)
	fmt.Printf("type  %T\n", sourceMap)
	fmt.Println("***************")
	sourceTest := map[string]int{
		"lucher": 20,
		"xs":     10,
	}
	fmt.Println(sourceTest)
	fmt.Printf("type %T\n", sourceTest)

	fmt.Println("______________________________")
	v, ok := sourceMap["emo"]
	if !ok {
		fmt.Println("查無此人")
	} else {
		fmt.Println(v)
	}

}

map遍歷

for range遍歷

package main

import "fmt"

func main() {

	sourceMap := make(map[string]int, 20)
	sourceMap["a"] = 90
	sourceMap["b"] = 100
	sourceMap["c"] = 60

	for k, v := range sourceMap {
		fmt.Println(k, v)
	}
}

如果只想遍歷前面的key時,可以把v省略。但要是想遍歷value時,就需要匿名變數的幫助了。

package main

import "fmt"

func main() {

	sourceMap := make(map[string]int, 20)
	sourceMap["趙"] = 90
	sourceMap["錢"] = 100
	sourceMap["孫"] = 60

	for k, v := range sourceMap {
		fmt.Println(k, v)
	}
	for k := range sourceMap {
		fmt.Println(k)
	}
	for _, m := range sourceMap {
		fmt.Println(m)

	}
}

delete()函式刪除鍵值對

元素為map型別的切片

這個可以理解為在切片裡面,各個元素得型別是map。

例如:

var a = make([]map[string]int,3)

值為切片型別的map

這個可以理解為在map函式裡面key值是切片。

例如:

var a1 = make(map[string][]int,3)

函式

函式.最重要的一部分

函式定義

func 函式名(引數)(返回值){
	函式體
}

example:

func TestFunction(x int,y,int)int{
	return x + y
}

對於引數部分如果兩個引數型別是一樣的可以簡化

func TestFunction(x,y int)int{
	return x + y
}

可變引數

可變引數就是引數數量不固定,參考陣列...

返回值

Go語言中函式支援多返回值,函式如果有多個返回值時必須用()將所有返回值包裹起來。

func calc(x, y int) (int, int) {
	sum := x + y
	sub := x - y
	return sum, sub
}

返回值命名

函式定義時可以給返回值命名,並在函式體中直接使用這些變數,最後通過return關鍵字返回。

例如:

func calc(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}

返回值補充

當我們的一個函式返回值型別為slice時,nil可以看做是一個有效的slice,沒必要顯示返回一個長度為0的切片。

func someFunc(x string) []int {
	if x == "" {
		return nil // 沒必要返回[]int{}
	}
	...
}

全域性變數

在函式外定義的變數,函式內可以訪問到全域性變數

區域性變數

在函式內定義的變數,只能在函式內訪問得到。

注意:

如果區域性變數和全域性變數成名,有限訪問區域性變數。

定義函式型別

type 函式型別 func(int ,int) int

函式作為引數

package main

import "fmt"

func add(x, y int) int {
	return x + y

}

func calc(x, y int, op func(int, int) int) int {
	return op(x, y)
}
func main() {
	ret2 := calc(10, 20, add)
	fmt.Println(ret2)

}

函式作為返回值

func do(s string) (func(int, int) int, error) {
	switch s {
	case "+":
		return add, nil
	case "-":
		return sub, nil
	default:
		err := errors.New("無法識別的操作符")
		return nil, err
	}
}

匿名函式

匿名函式就是沒有名字的函式,可以把函式賦值給變數,也可以把函式作為返回值。

func(引數)(返回值){
	函式體

}

匿名函式沒有辦法像普通函式那樣子呼叫,他需要儲存在某一個變數中(就是賦值),然後在執行。

package main

import "fmt"

func test() {
	func () {
		fmt.Println("匿 名 函 數")
	}()
}
func main() {
	test()
}


匿名函式執行:

func test() {
	func () {
		fmt.Println("匿 名 函 數")
	}()//在後面加上括號就相當於執行
}

或者賦值:

package main

import "fmt"

func test() func() {
	return func() {
		fmt.Println("匿 名 函 數")
	}
}
func main() {
	r := test()
	r()

}

閉包

閉包指的是一個函式和與其相關的引用環境組合而成的實體。簡單來說,閉包=函式+引用環境

package main

import "fmt"

//閉包簡單示例  //閉包概念 閉包=函式+外層變數的引用

func test() func() {
	name := "A2rcher"
	return func() {
		fmt.Println("匿 名 函 數",name) // 如果在匿名函式中找不到呼叫的變數,他就會向外層去找
	}									//這個外層變數並不是全域性變數,而是函式test()中的區域性變數
}
func main() {
	r := test() // r引用了函式test()中的變數還有匿名函式 ,可以說r此時就是一個閉包
	r()

}

閉包還可以這麼寫(比較典型的一個例子)

package main

import "fmt"

//閉包簡單示例  //閉包概念 閉包=函式+外層變數的引用

func test(name string) func() {

	return func() {
		fmt.Println("匿 名 函 數", name) 
	}
}
func main() {
	r := test("A2rcher") 
	r()

}

example:

兩個很經典的例子 [參考部落格](Go語言基礎之函式 | 李文周的部落格 (liwenzhou.com))

panic & recover & defer

panic/recover:可以理解為異常處理模式(但是Go語言中並沒有異常處理機制,只是這樣方便理解),

package main

import "fmt"

//panic and recover

func a() {
	fmt.Println("func is a")
}

//recover必須配合defer使用,而且defer一定要在panic前定義。
func b() {
	defer func() {
		err := recover()
		if err != nil { //如果err不等於nil,說明這個程式出錯
			fmt.Println("func b is err ")
		}
	}()
	panic("func is b")
}

func c() {
	fmt.Println("func is c")
}

func main() {
	a()
	b()
	c()

}

程式執行期間funcB中引發了panic導致程式崩潰,異常退出了。這個時候我們就可以通過recover將程式恢復回來,繼續往後執行。

內建函式

內建函式 介紹
close 主要用來關閉channel
len 用來求長度,比如string、array、slice、map、channel
new 用來分配記憶體,主要用來分配值型別,比如int、struct。返回的是指標
make 用來分配記憶體,主要用來分配引用型別,比如chan、map、slice
append 用來追加元素到陣列、slice中
panic和recover 用來做錯誤處理

指標

Go語言中指標沒有偏移跟運算,只需要搞清楚三個概念和記住兩個符號:指標地址,指標型別,指標取值,&(取地址)和*(根據地址取值)。。

貼個連結吧,大佬還是大佬

結構體

類型別名和自定義型別

type定義關鍵字型別

type myInt int
//將myInt定義成int型別

結構體定義

type 型別名 struct{
	欄位名 欄位型別
	欄位名 欄位型別
	...
}

由於Go語言不是面向物件程式語言,是一個面向介面的變成語言,所以他本身不想java那樣有多型繼承等關係,在Go語言中通過struct來實現面向物件的。(struct YYDS!)

  • 型別名:標識自定義結構體的名稱,在同一個包內不能重複。
  • 欄位名:表示結構體欄位名。結構體中的欄位名必須唯一。
  • 欄位型別:表示結構體欄位的具體型別。

很簡單的例子:

type Person struct{
	name string
	address string
	age int
	sex string
}

自定義一個Person型別,他有name,address,age,sex四個欄位分別代表個自內容。如果我要使用Person中某一個欄位,我可以直接呼叫Person就行了。

結構體例項化

結構體例項化後才可以分配記憶體使用(例項化後才可以使用自定義型別)

由於結構體本身就是一共型別,所以在宣告的時候可以像宣告變數一樣宣告結構體

var 結構體例項 結構體型別

example:

package main

import "fmt"

type Person struct {
	name string
	age  int
}

func main() {

	var p1 Person
    //通過.來訪問結構體的欄位(成員變數),例如p1.name和p1.age等。
	p1.name = "A2rcher"
	p1.age = 20

	fmt.Println(p1)
}

匿名結構體

匿名結構體是用來處理一些臨時的資料,比如說我現在A,B兩個函式,但是我臨時需要使用c資料,這個時候可以用到匿名結構體。

package main

import "fmt"

type Person struct {
	name string
	age  int
}

func main() {

	var p1 Person
	p1.name = "A2rcher"
	p1.age = 20

	var user struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}
	user.Age = 30
	user.Name = "emo"

	fmt.Println(p1)
	fmt.Println(user)

}

指標型別結構體

指標型別結構體是絕大部分情況下用到的,可以通過new關鍵字對結構體進行例項化得到結構體的地址。(new跟make的區別以及使用的場景參考函式中的內建函式以及指標部分)

方法和接收者

介面

併發