Go語言基礎(二)—— 基本經常使用語法

前言:
本專題用於記錄本身(647)在Go語言方向的學習和積累。 系列內容比較偏基礎,推薦給想要入門Go語言開發者們閱讀。

目錄以下:
Go語言基礎(一)—— 簡介、環境配置、HelloWorld
Go語言基礎(二)—— 基本經常使用語法
Go語言基礎(三)—— 面向對象編程
Go語言基礎(四)—— 優質的容錯處理
Go語言基礎(五)—— 併發編程
Go語言基礎(六)—— 測試、反射、Unsafe
Go語言基礎(七)—— 架構 & 常見任務
Go語言基礎(八)—— 性能調優編程


本篇將介紹以下內容:
1.如何編寫一個Go測試程序?
2.變量、常量的定義
3.基本數據類型
4.指針類型
5.運算符
6.條件與循環
7.數組與切片
8.Map
9.字符串
10.函數
(注:可根據數字快速定位本篇內容)swift


爲了接下來更方便的測試咱們的Go代碼(Go服務), 首先,介紹一下Go語言中如何測試咱們的程序。數組

1、起步:如何編寫一個測試程序?

要求:

  1. 源碼文件以_test結尾: xxx_test.go架構

  2. 測試方法名以Test開頭: func TestXXX(t *testing.T){...}併發

實際步驟:

  • 建立一個first_test.go文件。app

  • 編寫以下代碼:編程語言

package try_test

import "testing"

func TestFirstTry(t *testing.T) {
	t.Log("My first try!")
}
複製代碼

在終端下運行:函數式編程

go test first_test.go -v
複製代碼

Demo:動手實現一個斐波那切(Fibonacci)數列

1,1,2,3,5,8,13,...函數

  • 建立一個fibonacci_test.go文件。oop

  • 編寫以下代碼:

package fibonacci

import (
	"testing"
)

func TestFibList(t *testing.T) {
	// var a int = 1
	// var b int = 1

	// var (
	// a int = 1
	// b int = 1
	// )

	a := 1
	b := 1

	for i := 0; i < 5; i++ {
		t.Log(" ", b)
		tmp := a
		a = b
		b = tmp + a
	}
}
複製代碼
  • 在終端下運行:
go test fibonacci_test.go -v
複製代碼

2、變量、常量的定義

1.變量

變量的定義,有3種方式:

  • 第一種,常規逐一聲明:
var a int = 1
	var b int = 1
複製代碼
  • 第二種:統一聲明:
var (
	  a int = 1
	  b int = 1
	)
複製代碼
  • 第三種:快速聲明,編譯器會根據所附的值推斷出該變量的類型。
a := 1
	b := 1
複製代碼

2.常量

  • 常規賦值:
const abc = 2
複製代碼
  • 快速設置連續常量值:
// 連續位常量賦值
const (
	Monday = iota + 1
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
	Sunday
)
複製代碼
  • 快速設置連續比特位常量值:
// 比特位常量賦值
const (
	Readable = 1 << iota
	Writeable
	Executable
)
複製代碼

3、基本數據類型

類型 語法
布爾型 bool
字符型 string
整型 int、int八、int1六、int3二、int64
無負號整型 uint、uint八、uint1六、uint3二、uint6四、uintptr
浮點型 float3二、float64
字符型 byte(至關於uint8)
Unicode或utf-8編碼 rune(至關於int32)
複數型 complex6四、complex128

注意:Go語言不支持 「隱式類型轉換」 ,只支持 「顯式類型轉換」 。 (甚至連 「別名類型」「原有類型」 之間也不支持隱式類型轉換。)

舉例:

var a int32 = 1
	var b int64

	b = a        // 錯誤:隱式類型轉換,會報編譯錯誤錯誤。
	b = int64(a) // 正確:顯式類型轉換,成功。
複製代碼

經常使用典型的預約義值:

預約義 語法
最大Int64 math.MaxInt64
最小Int64 math.MinInt64
最小Int32 math.MaxInt32
最小Int32 math.MinInt32
最大float64 math.MaxFloat64
最大float32 math.MaxFloat32
... ...

4、指針類型

與其餘編程語言的差別:

  • 不支持指針運算。

  • string是值類型,其默認的初始化值爲 空字符串 ,而不是nil

舉例:

func TestPoint(t *testing.T) {
	a := 1
	aPoint := &a
	// aPoint = aPoint + 1 // 錯誤:Go不支持指針運算。
	t.Log(a, aPoint)
	t.Logf("%T %T", a, aPoint)
}

func TestString(t *testing.T) {
	var s string
	t.Log("字符串:" + s + "?")
	t.Log(len(s))

	if s == "" { // 判斷字符串有無初始化不能判nil,由於string類型的初始化默認是空字符串
		t.Log("s並未初始化")
	}
}
複製代碼

5、運算符

1.算數運算符:

運算符 描述
+
-
*
/
% 取餘
a++ 後置自增
a-- 後置自減

注:Go語言中沒有前置的自增(++)和自減(--)

2.比較運算符:

運算符 描述
== 判斷值是否 「相等」
!= 判斷值是否 「不相等」
> 判斷是否 「大於」
< 判斷是否 「小於」
>= 判斷是否 「大於等於」
<= 判斷是否 「小於等於」

注:兩個數組之間的 == 比較的條件? (仍是值比較,並不是引用比較) 1.兩個數組擁有 「相同的元素個數」 。 2.兩個數組中的 「每一個元素的值都相等」 ,纔會返回true

咱們寫個小demo,測試一下:

a := [...]int{1, 2, 3, 4}
	b := [...]int{1, 3, 4, 5}
	c := [...]int{1, 2, 3, 4}
	d := [...]int{1, 2, 3, 4, 5}

	t.Log(a == b) // false
	t.Log(a == c) // true

	// t.Log(a == d) // 編譯報錯,數組元素不一致
	t.Log(d)
複製代碼

3.邏輯運算符:

和別的語言沒太大差異,簡單提一下。

運算符 描述
&& AND(與)運算符,同truetrue,不然false
\ |
! NOT(非)運算符,取反,truefalsefalsetrue

4.位運算符:(新增&^按位清零運算符)

運算符 描述
& 二進制「與」運算。
\
^ 二進制「異或」運算。
<< 二進制「左移」運算。
>> 二進制「右移」運算。
&^ 將二進制 「按位清零」 。將右邊全部爲1的位數全置爲0。(對左邊數進行操做)

注意:多了一個&^,按位清零運算符。

舉個栗子:

// 連續位常量賦值
const (
	Monday = iota + 1
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
	Sunday
)

// 測試按位清零
func TestBitClear(t *testing.T) {
	a := 5 // 0101
	t.Log("按位清零前:", a) // 0101

	a = a &^ Wednesday // 5 &^ 3
	t.Log("按位清零後:", a) // 0100
}
複製代碼

6、條件與循環

1.條件:if

與別的編程語言的區別:

  1. 條件必須是bool類型
  2. 支持變量賦值

相似這樣:

if var declaration; condition {
		//...
	}
複製代碼

舉個簡單例子:

if a := 647; a == 647 {
		t.Log(a)
	}
複製代碼

若是寫了個請求,相似就成了這樣:

if v, err := someFunc(); err == nil {
		// 無error,成功!do something!
	} else {
		// 有error,失敗!do something!
	}
複製代碼

2.條件:switch

switch與其餘主流編程語言有些區別,主要是變得更方便、更快捷了。

  1. 默認break,不須要主動寫break。(若是想要貫穿,才用fallthrough關鍵字,這點與swift很像。注意:但我測試結果走fallthrough並不會去判斷下一個case條件,而是直接執行下一個case裏的代碼)
  2. 條件表達式不限制「常量」或「整數」。
  3. 單個case支持多個結果選項,用逗號隔開。
  4. 能夠不設定switch以後的條件表達式,這樣與多個ifelse邏輯相同。

舉個例子:

func TestSwitchCondition(t *testing.T) {
	for i := 0; i < 5; i++ {
		switch i {
		case 0, 2:
			t.Log("Even")
		case 1, 3:
			t.Log("Odd")
		default:
			t.Log("it is not 0-3")
		}
	}
}

func TestSwitchCaseCondition(t *testing.T) {
	for i := 0; i < 5; i++ {
		switch {
		case i%2 == 0:
			t.Log(i)
			// fallthrough
		case i%2 == 1:
			t.Log(i)
			// fallthrough
		default:
			t.Log("unknown")
		}
	}
}
複製代碼

3.循環:Go只支持for關鍵字。

舉個例子:

func TestWhileLoop(t *testing.T) {
	/* 模仿while循環 */
	n := 0
	for n < 5 {
		t.Log(n)
		n++
	}

	// for循環
	for n := 0; n < 5; n++ {
		t.Log(n)
	}

	/* 死循環 */
	for {
      //...
	}
}
複製代碼

7、數組與切片

1.數組

  • 數組的聲明:
var a [3]int // 聲明並初始化爲默認值0
	a[0] = 1     // 簡單賦值

	b := [3]int{1, 2, 3}           // 聲明的同時進行初始化
	c := [2][2]int{{1, 2}, {3, 4}} // 聲明多維數組進行初始化
複製代碼
  • 數組的遍歷:
func TestArrayTravel(t *testing.T) {
	arr := [...]int{1, 3, 4, 5}

	for i := 0; i < len(arr); i++ {
		t.Log(arr[i])
	}

	for _, element := range arr {
		t.Log(element)
	}

	for index, element := range arr {
		t.Log(index, element)
	}
}
複製代碼
  • 數組的截取:
func TestArraySection(t *testing.T) {
	arr := [...]int{1, 2, 3, 4, 5}
	arr_sec := arr[1:2]
	t.Log(arr_sec)
}
複製代碼

2.切片

切片是一種可變長的結構體。(有點相似於iOS中的MutableArray

  • 切片的聲明:
var s0 []int       // 聲明
	s0 = append(s0, 1) // 追加

	s := []int{}            // 聲明
	s1 := []int{1, 2, 3}    // 聲明並提供初始化值
	s2 := make([]int, 2, 4) //聲明並提供初始化個數:2,max最大元素個數:4。
	/* 語法:make([]type, len, cap) 其中len個元素會被初始化爲默認值0,未初始化的元素不可訪問。 */
複製代碼

1. 快速聲明語法:make([]type, len, cap) 其中,前len個元素會被初始化爲默認值0,未初始化的元素不可訪問。(這是個容易形成crash的點,不能越界操做。)

2. 追加元素語法:s = append(s , element) 其中s表明切片,element表明追加的元素。

  • 切片的簡單使用: 直接上demo,
func TestSliceInit(t *testing.T) {
	var s0 []int
	t.Log(len(s0), cap(s0))

	s0 = append(s0, 1)
	t.Log(len(s0), cap(s0))

	s1 := []int{1, 2, 3, 4} // 快速初始化切片
	t.Log(len(s1), cap(s1))

	s2 := make([]int, 3, 5) // 初始化3個,max最大個數爲5
	t.Log(len(s2), cap(s2))

	t.Log(s2[0], s2[1], s2[2])

	s2 = append(s2, 1) // 添加元素
	t.Log(s2[0], s2[1], s2[2], s2[3])
	// t.Log(s2[0], s2[1], s2[2], s2[3], s2[4])
}
複製代碼
  • 切片的原理:切片的共享存儲結構。 當切片元素個數len超過cap時,會進行2倍擴容。

8、Map(鍵值對Key: Value)

1.Map基本操做:

  • Map的三種初始化方式:
/* 第一種初始化方式:有初始值 */
	m1 := map[string]int{"Key1": 1, "Key2": 2, "Key3": 3}
	t.Log(m1)
	t.Logf("len m1 = %d", len(m1))

	/* 第二種初始化方式:無初始值 */
	m2 := map[string]int{}
	m2["Key"] = 16
	t.Logf("len m2 = %d", len(m2))

	/* 第三種初始化方式:初始化cap大小(最大容量),但len依然爲0。(理由以下,map不像數組有默認值0,因此len依然爲0) */
	m3 := make(map[string]int, 10)
	t.Logf("len m3 = %d", len(m3))
複製代碼
  • Map元素的訪問:

當訪問的Map不存在指定的Key時,會默認返回0值。 所以,Go語言中,不能經過Value是否爲空來判斷Key是否存在。

若是想要判斷Value是否存在,可用以下方式:

if value, ok := map[key]; ok {
  // 有值
} else {
  // 無值
}
複製代碼

Demo:

func TestAccessNotExistingKey(t *testing.T) {
	m1 := map[int]int{} // 初始化一個空map
	t.Log(m1[1])        // 隨便訪問一個Key?打印結果爲0

	m1[2] = 0    // 設置一個Key(2)和Value(0)。
	t.Log(m1[2]) // 打印一下仍是0

	if value, ok := m1[3]; ok { // var v = m1[3], ok是表達式的bool值
		t.Logf("Key 3‘s value is %d", value)
	} else {
		t.Log("Key 3 is not existing.")
	}
}
複製代碼
  • Map的遍歷:

與遍歷數組for-range相似,但不一樣點在於 「數組返回的是index,Map返回的是Key」

func TestTravelMap(t *testing.T) {
	map1 := map[string]int{"Key1": 1, "Key2": 2, "Key3": 3}
	for key, value := range map1 {
		t.Log(key, value)
	}
}
複製代碼

2.Map與工廠模式

  1. Map的 Value 能夠是一個方法。
  2. 與Go的 Dock type 接口方式一塊兒,能夠方便的實現單一方法對象的工廠模式。
func TestMapWithFunValue(t *testing.T) {
	m := map[int]func(op int) int{}
	m[1] = func(op int) int { return op }
	m[2] = func(op int) int { return op * op }
	m[3] = func(op int) int { return op * op * op }
	t.Log(m[1](2), m[2](2), m[3](3))
}
複製代碼

3.在Go語言中實現Set

Go沒有Set的實現,但能夠經過map[type]bool來實現。

  1. 元素的惟一性
  2. 基本操做(添加元素、判斷元素是否存在、刪除元素、元素個數)
func TestMapForSet(t *testing.T) {
	mySet := map[int]bool{} // 初始化map
	mySet[1] = true         // 設置key、value
	n := 3
	if mySet[n] {
		t.Logf("%d is existing", n)
	} else {
		t.Logf("%d is not existing", n)
	}

	mySet[3] = true // 設置Key、value
	t.Log(len(mySet))

	delete(mySet, 1) // 刪除Key爲1的map
	t.Log(len(mySet))
}
複製代碼

9、字符串

與其餘編程語言的差別:

  • string「值類型」 ,而不是引用或指針類型。所以,默認初始化時,會是一個""空的字符串。(而不是nil

  • string是隻讀的byte slicelen函數返回的是string中的byte數。

  • string類型的byte數組能夠存聽任何數據。

例如:

s = "\xE4\xB8\xA5" // 能夠存儲任何二進制數據
複製代碼
  • len求出來的是byte數,並非字符數。

問:Unicode 與 UTF8 的關係?
答:
Unicode是一種字符集。(code point
UTF-8Unicode的存儲實現。(轉換爲字節序列的規則)

舉個例子:

編碼 存儲
字符 "中"
Unicode 0x4E2D
UTF-8 0xE4B8AD
string/[]byte [0xE4, 0xB8, 0xAD]
  • 使用Go語言支持的stringsstrconv庫:

首先,要導入strings和strconv庫:

import (
	"strconv"
	"strings"
)
複製代碼

字符串的分割與拼接的demo:

// 測試strings庫
func TestStringFunc(t *testing.T) {
	s := "A,B,C"
	parts := strings.Split(s, ",") // 字符串按","分割
	for _, part := range parts {
		t.Log(part)
	}
	t.Log(strings.Join(parts, "-")) // 字符串按"-"拼接
}
複製代碼

字符串轉型demo:

// 測試strconv庫
func TestConv(t *testing.T) {
	s := strconv.Itoa(10) // Int轉string
	t.Log("str" + s)

	if value, err := strconv.Atoi("10"); err == nil {
		t.Log(10 + value)
	} else {
		t.Log("轉換不成功!")
	}
	// t.Log(10 + strconv.Atoi("10")) // string轉Int,編譯錯誤
}
複製代碼

10、函數:是一等公民(可做爲變量、參數、返回值)

與其餘編程語言的區別:

  • 函數能夠有 多個返回值

  • 全部參數都是 值傳遞 ,不是引用傳遞。
    slicemapchannel會有傳引用的錯覺。(slicemapchannel自己是一個結構體,其中會包含下一塊slicemapchannel的指針地址。所以咱們做爲參數傳入函數的slicemapchannel實際上被複制了一份,但自己操做的其餘對象依然會是原slicemapchannel的內存地址。)

這塊可能稍微有點繞,能夠看下slice的實現原理。(PS:能夠參考「第七條」切片原理相關內容)

  • 函數能夠做爲 「變量」 的值。

  • 函數能夠做爲 「參數」「返回值」

重點:函數式編程(Go)

由於在Go語言中,函數能夠做爲變量、參數、返回值。 所以,在編程習慣上與別的編程語言有些差別。 須要開發者去慢慢適應。

實戰小技巧

  1. 支持函數可變長參數

語法: [參數名] ...[參數類型]

PS:不指定參數個數,但須要指定參數類型。

舉個例子,咱們想寫個求和函數,就能夠這麼寫:

// 求和函數
func sum(ops ...int) int {
	s := 0
	for _, op := range ops {
		s += op
	}
	return s
}
複製代碼
  1. 支持函數延時執行,多用於釋放資源(解鎖)

仍是舉個例子:

// 模仿釋放資源的函數
func Clear() {
	fmt.Println("Clear resources.")
}

// 測試函數延時執行:多用於釋放資源
func TestDefer(t *testing.T) {
	defer func() {
		// 用於釋放一些資源(鎖)
		Clear()
	}()
	fmt.Println("Started")
	panic("Fatal error") // panic:程序異常中斷,defer仍會執行
}
複製代碼

最後,本系列我是在蔡超老師的技術分享下總結、實戰完成的, 感謝蔡超老師的技術分享

PS:另附上,分享連接:《Go語言從入門到實戰》 祝你們學有所成,工做順利。謝謝!

相關文章
相關標籤/搜索