前言:
本專題用於記錄本身(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語言中如何測試咱們的程序。數組
源碼文件以_test
結尾: xxx_test.go
架構
測試方法名以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
複製代碼
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
複製代碼
變量的定義,有3種方式:
var a int = 1
var b int = 1
複製代碼
var (
a int = 1
b int = 1
)
複製代碼
a := 1
b := 1
複製代碼
const abc = 2
複製代碼
// 連續位常量賦值
const (
Monday = iota + 1
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
複製代碼
// 比特位常量賦值
const (
Readable = 1 << iota
Writeable
Executable
)
複製代碼
類型 | 語法 |
---|---|
布爾型 | 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 |
... | ... |
與其餘編程語言的差別:
不支持指針運算。
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並未初始化")
}
}
複製代碼
運算符 | 描述 |
---|---|
+ | 加 |
- | 減 |
* | 乘 |
/ | 除 |
% | 取餘 |
a++ | 後置自增 |
a-- | 後置自減 |
注:Go語言中沒有前置的自增(++)和自減(--)。
運算符 | 描述 |
---|---|
== | 判斷值是否 「相等」 |
!= | 判斷值是否 「不相等」 |
> | 判斷是否 「大於」 |
< | 判斷是否 「小於」 |
>= | 判斷是否 「大於等於」 |
<= | 判斷是否 「小於等於」 |
注:兩個數組之間的 == 比較的條件? (仍是值比較,並不是引用比較) 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)
複製代碼
和別的語言沒太大差異,簡單提一下。
運算符 | 描述 |
---|---|
&& | AND(與)運算符,同true 爲true ,不然false 。 |
\ | | |
! | NOT(非)運算符,取反,true 爲false ,false 爲true 。 |
&^
按位清零運算符)運算符 | 描述 |
---|---|
& | 二進制「與」運算。 |
\ | |
^ | 二進制「異或」運算。 |
<< | 二進制「左移」運算。 |
>> | 二進制「右移」運算。 |
&^ | 將二進制 「按位清零」 。將右邊全部爲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
}
複製代碼
與別的編程語言的區別:
bool
類型相似這樣:
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!
}
複製代碼
switch與其餘主流編程語言有些區別,主要是變得更方便、更快捷了。
break
,不須要主動寫break
。(若是想要貫穿,才用fallthrough
關鍵字,這點與swift很像。注意:但我測試結果走fallthrough
並不會去判斷下一個case
條件,而是直接執行下一個case
裏的代碼)case
支持多個結果選項,用逗號隔開。switch
以後的條件表達式,這樣與多個if
、else
邏輯相同。舉個例子:
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")
}
}
}
複製代碼
舉個例子:
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 {
//...
}
}
複製代碼
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)
}
複製代碼
切片是一種可變長的結構體。(有點相似於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
表明追加的元素。
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
倍擴容。/* 第一種初始化方式:有初始值 */
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
不存在指定的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.")
}
}
複製代碼
與遍歷數組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)
}
}
複製代碼
Value
能夠是一個方法。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))
}
複製代碼
Go沒有Set的實現,但能夠經過map[type]bool
來實現。
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))
}
複製代碼
與其餘編程語言的差別:
string
是 「值類型」 ,而不是引用或指針類型。所以,默認初始化時,會是一個""
空的字符串。(而不是nil
)
string
是隻讀的byte slice
,len
函數返回的是string
中的byte
數。
string
類型的byte
數組能夠存聽任何數據。
例如:
s = "\xE4\xB8\xA5" // 能夠存儲任何二進制數據
複製代碼
len
求出來的是byte
數,並非字符數。問:Unicode 與 UTF8 的關係?
答:
Unicode
是一種字符集。(code point
)
UTF-8
是Unicode
的存儲實現。(轉換爲字節序列的規則)
舉個例子:
編碼 | 存儲 |
---|---|
字符 | "中" |
Unicode | 0x4E2D |
UTF-8 | 0xE4B8AD |
string/[]byte | [0xE4, 0xB8, 0xAD] |
strings
、strconv
庫:首先,要導入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,編譯錯誤
}
複製代碼
與其餘編程語言的區別:
函數能夠有 多個返回值 。
全部參數都是 值傳遞 ,不是引用傳遞。
slice
、map
、channel
會有傳引用的錯覺。(slice
、map
、channel
自己是一個結構體,其中會包含下一塊slice
、map
、channel
的指針地址。所以咱們做爲參數傳入函數的slice
、map
、channel
實際上被複制了一份,但自己操做的其餘對象依然會是原slice
、map
和channel
的內存地址。)
這塊可能稍微有點繞,能夠看下slice的實現原理。(PS:能夠參考「第七條」切片原理相關內容)
函數能夠做爲 「變量」 的值。
函數能夠做爲 「參數」 和 「返回值」 。
由於在Go語言中,函數能夠做爲變量、參數、返回值。 所以,在編程習慣上與別的編程語言有些差別。 須要開發者去慢慢適應。
語法: [參數名] ...[參數類型]
PS:不指定參數個數,但須要指定參數類型。
舉個例子,咱們想寫個求和函數,就能夠這麼寫:
// 求和函數
func sum(ops ...int) int {
s := 0
for _, op := range ops {
s += op
}
return s
}
複製代碼
仍是舉個例子:
// 模仿釋放資源的函數
func Clear() {
fmt.Println("Clear resources.")
}
// 測試函數延時執行:多用於釋放資源
func TestDefer(t *testing.T) {
defer func() {
// 用於釋放一些資源(鎖)
Clear()
}()
fmt.Println("Started")
panic("Fatal error") // panic:程序異常中斷,defer仍會執行
}
複製代碼
PS:另附上,分享連接:《Go語言從入門到實戰》 祝你們學有所成,工做順利。謝謝!