前言:
本專題用于記錄自己(647)在Go語言方向的學(xué)習(xí)和積累刚陡。
系列內(nèi)容比較偏基礎(chǔ)啤斗,推薦給想要入門Go語言開發(fā)者們閱讀焚鲜。
目錄如下:
Go語言基礎(chǔ)(一)—— 簡介曲尸、環(huán)境配置、HelloWorld
Go語言基礎(chǔ)(二)—— 基本常用語法
Go語言基礎(chǔ)(三)—— 面向?qū)ο缶幊?/a>
Go語言基礎(chǔ)(四)—— 優(yōu)質(zhì)的容錯處理
Go語言基礎(chǔ)(五)—— 并發(fā)編程
Go語言基礎(chǔ)(六)—— 測試比肄、反射快耿、Unsafe
Go語言基礎(chǔ)(七)—— 架構(gòu) & 常見任務(wù)
Go語言基礎(chǔ)(八)—— 性能調(diào)優(yōu)
本篇將介紹如下內(nèi)容:
1.如何編寫一個Go測試程序?
2.變量薪前、常量的定義
3.基本數(shù)據(jù)類型
4.指針類型
5.運算符
6.條件與循環(huán)
7.數(shù)組與切片
8.Map
9.字符串
10.函數(shù)
(注:可根據(jù)數(shù)字快速定位本篇內(nèi)容)
為了接下來更方便的測試我們的Go代碼(Go服務(wù))润努,
首先关斜,介紹一下Go語言中如何測試我們的程序示括。
一、起步:如何編寫一個測試程序痢畜?
要求:
源碼文件以
_test
結(jié)尾:
xxx_test.go
測試方法名以
Test
開頭:
func TestXXX(t *testing.T){...}
實際步驟:
創(chuàng)建一個
first_test.go
文件垛膝。編寫如下代碼:
package try_test
import "testing"
func TestFirstTry(t *testing.T) {
t.Log("My first try!")
}
在終端下運行:
go test first_test.go -v
Demo:動手實現(xiàn)一個斐波那切(Fibonacci)數(shù)列
1,1丁稀,2吼拥,3,5线衫,8凿可,13,...
創(chuàng)建一個
fibonacci_test.go
文件授账。編寫如下代碼:
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
二枯跑、變量、常量的定義
1.變量
變量的定義白热,有3種方式:
- 第一種敛助,常規(guī)逐一聲明:
var a int = 1
var b int = 1
- 第二種:統(tǒng)一聲明:
var (
a int = 1
b int = 1
)
- 第三種:快速聲明,編譯器會根據(jù)所附的值推斷出該變量的類型屋确。
a := 1
b := 1
2.常量
- 常規(guī)賦值:
const abc = 2
- 快速設(shè)置連續(xù)常量值:
// 連續(xù)位常量賦值
const (
Monday = iota + 1
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
- 快速設(shè)置連續(xù)比特位常量值:
// 比特位常量賦值
const (
Readabele = 1 << iota
Writeable
Executable
)
三纳击、基本數(shù)據(jù)類型
類型 | 語法 |
---|---|
布爾型 | bool |
字符型 | string |
整型 | int、int8攻臀、int16焕数、int32、int64 |
無負(fù)號整型 | uint刨啸、uint8百匆、uint16、uint32呜投、uint64加匈、uintptr |
浮點型 | float32存璃、float64 |
字符型 | byte(相當(dāng)于uint8) |
Unicode或utf-8編碼 | rune(相當(dāng)于int32) |
復(fù)數(shù)型 | complex64、complex128 |
注意:Go語言不支持“隱式類型轉(zhuǎn)換”雕拼,只支持“顯式類型轉(zhuǎn)換”纵东。
(甚至連“別名類型”與“原有類型”之間也不支持隱式類型轉(zhuǎn)換。)
舉例:
var a int32 = 1
var b int64
b = a // 錯誤:隱式類型轉(zhuǎn)換啥寇,會報編譯錯誤錯誤偎球。
b = int64(a) // 正確:顯式類型轉(zhuǎn)換,成功辑甜。
常用典型的預(yù)定義值:
預(yù)定義 | 語法 |
---|---|
最大Int64 | math.MaxInt64 |
最小Int64 | math.MinInt64 |
最小Int32 | math.MaxInt32 |
最小Int32 | math.MinInt32 |
最大float64 | math.MaxFloat64 |
最大float32 | math.MaxFloat32 |
... | ... |
四衰絮、指針類型
與其他編程語言的差異:
不支持指針運算。
string
是值類型磷醋,其默認(rèn)的初始化值為空字符串猫牡,而不是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類型的初始化默認(rèn)是空字符串
t.Log("s并未初始化")
}
}
五、運算符
1.算數(shù)運算符:
運算符 | 描述 |
---|---|
+ | 加 |
- | 減 |
* | 乘 |
/ | 除 |
% | 取余 |
a++ | 后置自增 |
a-- | 后置自減 |
注:Go語言中沒有前置的自增(++)和自減(--)骇陈。
2.比較運算符:
運算符 | 描述 |
---|---|
== | 判斷值是否 “相等” |
!= | 判斷值是否 “不相等” |
> | 判斷是否 “大于” |
< | 判斷是否 “小于” |
>= | 判斷是否 “大于等于” |
<= | 判斷是否 “小于等于” |
注:兩個數(shù)組之間的 == 比較的條件震庭? (還是值比較,并非引用比較)
1.兩個數(shù)組擁有 “相同的元素個數(shù)” 你雌。
2.兩個數(shù)組中的 “每個元素的值都相等” 器联,才會返回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) // 編譯報錯拨拓,數(shù)組元素不一致
t.Log(d)
3.邏輯運算符:
和別的語言沒太大差別,簡單提一下逛球。
運算符 | 描述 |
---|---|
&& | AND(與)運算符千元,同true 為true ,否則false 颤绕。 |
|| | OR(或)運算符幸海,同false 為false ,否則true 奥务。 |
! | NOT(非)運算符物独,取反,true 為false 氯葬,false 為true 挡篓。 |
4.位運算符:(新增&^
按位清零運算符)
運算符 | 描述 |
---|---|
& | 二進(jìn)制“與”運算。 |
| | 二進(jìn)制“或”運算。 |
^ | 二進(jìn)制“異或”運算官研。 |
<< | 二進(jìn)制“左移”運算秽澳。 |
>> | 二進(jìn)制“右移”運算。 |
&^ | 將二進(jìn)制 “按位清零” 戏羽。將右邊所有為1的位數(shù)全置為0担神。(對左邊數(shù)進(jìn)行操作) |
注意:多了一個&^
,按位清零運算符始花。
舉個栗子:
// 連續(xù)位常量賦值
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
}
六妄讯、條件與循環(huán)
1.條件:if
與別的編程語言的區(qū)別:
- 條件必須是
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溜族!
}
2.條件:switch
switch與其他主流編程語言有些區(qū)別讹俊,主要是變得更方便垦沉、更快捷了煌抒。
- 默認(rèn)
break
,不需要主動寫break
厕倍。(如果想要貫穿寡壮,才用fallthrough
關(guān)鍵字,這點與swift很像讹弯。注意:但我測試結(jié)果走fallthrough
并不會去判斷下一個case
條件况既,而是直接執(zhí)行下一個case
里的代碼) - 條件表達(dá)式不限制“常量”或“整數(shù)”。
- 單個
case
支持多個結(jié)果選項组民,用逗號隔開棒仍。 - 可以不設(shè)定
switch
之后的條件表達(dá)式,這樣與多個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")
}
}
}
3.循環(huán):Go只支持for關(guān)鍵字。
舉個例子:
func TestWhileLoop(t *testing.T) {
/* 模仿while循環(huán) */
n := 0
for n < 5 {
t.Log(n)
n++
}
// for循環(huán)
for n := 0; n < 5; n++ {
t.Log(n)
}
/* 死循環(huán) */
for {
//...
}
}
七耸三、數(shù)組與切片
1.數(shù)組
- 數(shù)組的聲明:
var a [3]int // 聲明并初始化為默認(rèn)值0
a[0] = 1 // 簡單賦值
b := [3]int{1, 2, 3} // 聲明的同時進(jìn)行初始化
c := [2][2]int{{1, 2}, {3, 4}} // 聲明多維數(shù)組進(jìn)行初始化
- 數(shù)組的遍歷:
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)
}
}
- 數(shù)組的截嚷叶浮:
func TestArraySection(t *testing.T) {
arr := [...]int{1, 2, 3, 4, 5}
arr_sec := arr[1:2]
t.Log(arr_sec)
}
2.切片
切片是一種可變長的結(jié)構(gòu)體。(有點類似于iOS中的MutableArray
)
- 切片的聲明:
var s0 []int // 聲明
s0 = append(s0, 1) // 追加
s := []int{} // 聲明
s1 := []int{1, 2, 3} // 聲明并提供初始化值
s2 := make([]int, 2, 4) //聲明并提供初始化個數(shù):2仪壮,max最大元素個數(shù):4憨颠。
/*
語法:make([]type, len, cap)
其中l(wèi)en個元素會被初始化為默認(rèn)值0,未初始化的元素不可訪問积锅。
*/
1. 快速聲明語法:make([]type, len, cap)
其中爽彤,前len
個元素會被初始化為默認(rèn)值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最大個數(shù)為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])
}
- 切片的原理:切片的共享存儲結(jié)構(gòu)。
當(dāng)切片元素個數(shù)len
超過cap
時丹喻,會進(jìn)行2
倍擴(kuò)容薄货。
八、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不像數(shù)組有默認(rèn)值0税娜,所以len依然為0)
*/
m3 := make(map[string]int, 10)
t.Logf("len m3 = %d", len(m3))
- Map元素的訪問:
當(dāng)訪問的Map
不存在指定的Key
時,會默認(rèn)返回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?打印結(jié)果為0
m1[2] = 0 // 設(shè)置一個Key(2)和Value(0)业踏。
t.Log(m1[2]) // 打印一下還是0
if value, ok := m1[3]; ok { // var v = m1[3], ok是表達(dá)式的bool值
t.Logf("Key 3‘s value is %d", value)
} else {
t.Log("Key 3 is not existing.")
}
}
- Map的遍歷:
與遍歷數(shù)組for-range類似禽炬,但不同點在于 “數(shù)組返回的是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與工廠模式
- Map的
Value
可以是一個方法腹尖。 - 與Go的
Dock type
接口方式一起,可以方便的實現(xiàn)單一方法對象的工廠模式伐脖。
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語言中實現(xiàn)Set
Go沒有Set的實現(xiàn)热幔,但可以通過map[type]bool
來實現(xiàn)。
- 元素的唯一性
- 基本操作(添加元素晓殊、判斷元素是否存在断凶、刪除元素、元素個數(shù))
func TestMapForSet(t *testing.T) {
mySet := map[int]bool{} // 初始化map
mySet[1] = true // 設(shè)置key巫俺、value
n := 3
if mySet[n] {
t.Logf("%d is existing", n)
} else {
t.Logf("%d is not existing", n)
}
mySet[3] = true // 設(shè)置Key认烁、value
t.Log(len(mySet))
delete(mySet, 1) // 刪除Key為1的map
t.Log(len(mySet))
}
九、字符串
與其他編程語言的差異:
string
是 “值類型” ,而不是引用或指針類型却嗡。因此舶沛,默認(rèn)初始化時,會是一個""
空的字符串窗价。(而不是nil
)string
是只讀的byte slice
如庭,len
函數(shù)返回的是string
中的byte
數(shù)。string
類型的byte
數(shù)組可以存放任何數(shù)據(jù)撼港。
例如:
s = "\xE4\xB8\xA5" // 可以存儲任何二進(jìn)制數(shù)據(jù)
- 用
len
求出來的是byte
數(shù)坪它,并不是字符數(shù)。
問:Unicode 與 UTF8 的關(guān)系帝牡?
答:
Unicode
是一種字符集往毡。(code point
)
UTF-8
是Unicode
的存儲實現(xiàn)。(轉(zhuǎn)換為字節(jié)序列的規(guī)則)
舉個例子:
編碼 | 存儲 |
---|---|
字符 | "中" |
Unicode | 0x4E2D |
UTF-8 | 0xE4B8AD |
string/[]byte | [0xE4, 0xB8, 0xAD] |
- 使用Go語言支持的
strings
靶溜、strconv
庫:
首先开瞭,要導(dǎo)入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, "-")) // 字符串按"-"拼接
}
字符串轉(zhuǎn)型demo:
// 測試strconv庫
func TestConv(t *testing.T) {
s := strconv.Itoa(10) // Int轉(zhuǎn)string
t.Log("str" + s)
if value, err := strconv.Atoi("10"); err == nil {
t.Log(10 + value)
} else {
t.Log("轉(zhuǎn)換不成功!")
}
// t.Log(10 + strconv.Atoi("10")) // string轉(zhuǎn)Int罩息,編譯錯誤
}
十嗤详、函數(shù):是一等公民(可作為變量、參數(shù)瓷炮、返回值)
與其他編程語言的區(qū)別:
函數(shù)可以有 多個返回值 葱色。
所有參數(shù)都是 值傳遞 ,不是引用傳遞崭别。
slice
冬筒、map
恐锣、channel
會有傳引用的錯覺茅主。(slice
、map
土榴、channel
本身是一個結(jié)構(gòu)體诀姚,其中會包含下一塊slice
、map
玷禽、channel
的指針地址赫段。因此我們作為參數(shù)傳入函數(shù)的slice
、map
矢赁、channel
實際上被復(fù)制了一份糯笙,但本身操作的其他對象依然會是原slice
、map
和channel
的內(nèi)存地址撩银。)
這塊可能稍微有點繞给涕,可以看下slice的實現(xiàn)原理。(PS:可以參考“第七條”切片原理相關(guān)內(nèi)容)
函數(shù)可以作為“變量”的值。
函數(shù)可以作為“參數(shù)”和“返回值”够庙。
重點:函數(shù)式編程(Go)
因為在Go語言中恭应,函數(shù)可以作為變量、參數(shù)耘眨、返回值昼榛。
因此,在編程習(xí)慣上與別的編程語言有些差異剔难。
需要開發(fā)者去慢慢適應(yīng)胆屿。
實戰(zhàn)小技巧
- 支持函數(shù)可變長參數(shù)
語法: [參數(shù)名] ...[參數(shù)類型]
PS:不指定參數(shù)個數(shù),但需要指定參數(shù)類型偶宫。
舉個例子莺掠,我們想寫個求和函數(shù),就可以這么寫:
// 求和函數(shù)
func sum(ops ...int) int {
s := 0
for _, op := range ops {
s += op
}
return s
}
- 支持函數(shù)延時執(zhí)行读宙,多用于釋放資源(解鎖)
還是舉個例子:
// 模仿釋放資源的函數(shù)
func Clear() {
fmt.Println("Clear resources.")
}
// 測試函數(shù)延時執(zhí)行:多用于釋放資源
func TestDefer(t *testing.T) {
defer func() {
// 用于釋放一些資源(鎖)
Clear()
}()
fmt.Println("Started")
panic("Fatal error") // panic:程序異常中斷彻秆,defer仍會執(zhí)行
}
最后,本系列我是在蔡超老師的技術(shù)分享下總結(jié)结闸、實戰(zhàn)完成的唇兑,
感謝蔡超老師的技術(shù)分享。
PS:另附上桦锄,分享鏈接:《Go語言從入門到實戰(zhàn)》
祝大家學(xué)有所成扎附,工作順利。謝謝结耀!