編程兩大絕招
1.先易后難,即將一個復雜的問題分解成簡單的問題
2.先死后活
運算子
運算子是一種特殊的符號,用以表示資料的運算、賦值和比較等
運算子用于在程式運行時執行數學或邏輯運算
Go語言內置的運算子有:算術運算子、賦值運算子、邏輯運算子、關系運算子、位運算子、其他運算子
算術運算子
算術運算子是對數值型別的變數進行運算的,比如:加減乘除,在Go程式中使用的非常多

a % b = a - a / b * b
fmt.Println(“10%3=”,10%3) // =1
fmt.Println(“-10%3=”,-10%3) // =-10-(-10)/3*3 = -10-(-9)=-1
fmt.Println(“10%-3=”,10%-3) // =1
fmt.Println(“-10%-3=”,-10%-3) // =-1
Go的自增自減只能當作一個獨立語言使用,Go的++ 和 -- 只能寫在變數的后面,不能寫在變數的前面,即:只有a++ a-- 沒有 ++a --a
//1)假如還有97天放假,問:xx個星期零xx天
//2)定義一個變數保存華式溫度,華式溫度轉換攝氏溫度的公式為:5/9*(華式溫度-100)
// 請求出華式溫度對應的攝氏溫度
func main() {
week := 97 / 7
days := 97 % 7
fmt.Printf("還有%d個星期零%d天\n",week,days)
huashi := 134.2
sheshi := 5.0/9*(huashi-100)
fmt.Println("華式對應的攝氏",sheshi)
}
關系運算子(比較運算子)
關系運算子的結果都是bool型,也就是要么是true,要么是false
關系運算子組成的運算式,我們稱為關系運算式:a > b
關系運算式,經常用在if結構的條件中或回圈結構的條件中
| 運算子 | 描述 | 實體 A=10 B=20 |
|---|---|---|
| == | 檢查兩個值是否相等,如果相等回傳 true 否則回傳 false, | (A == B) 為 false |
| != | 檢查兩個值是否不相等,如果不相等回傳 true 否則回傳 false, | (A != B) 為 true |
| > | 檢查左邊值是否大于右邊值,如果是回傳 true 否則回傳 false, | (A > B) 為 false |
| < | 檢查左邊值是否小于右邊值,如果是回傳 true 否則回傳 false, | (A < B) 為 true |
| >= | 檢查左邊值是否大于等于右邊值,如果是回傳 true 否則回傳 false, | (A >= B) 為 false |
| <= | 檢查左邊值是否小于等于右邊值,如果是回傳 true 否則回傳 false, | (A <= B) 為 true |
邏輯運算子
用于連接多個條件(一般來講就是關系運算式),最終的結果是一個bool值

func main() {
//演示邏輯運算子的使用 &&
//&&也叫短路與:如果第一個條件為false,則第二個條件不會判斷,最終結果為false
var age int = 40
if age > 30 && age < 50 {
fmt.Println("ok1")
} else if age > 30 && age < 40 {
fmt.Println("ok2")
}
//演示邏輯運算子的使用 ||
//||也叫短路或:如果第一個條件為true,則第二個條件不會判斷,最終結果為true
if age > 30 || age < 50 {
fmt.Println("ok3")
} else if age > 30 || age < 40 {
fmt.Println("ok4")
}
//演示邏輯運算子的使用 !
if age < 30 {
fmt.Println("ok5")
} else if !(age< 30) {
fmt.Println("ok6")
}
}
//ok1
//ok3
//ok6
進制
進制也就是進位計數制,是人為定義的帶進位的計數方法(有不帶進位的計數方法,比如原始的結繩計數法,唱票時常用的“正”字計數法,以及類似的tally mark計數), 對于任何一種進制---X進制,就表示每一位置上的數運算時都是逢X進一位, 十進制是逢十進一,十六進制是逢十六進一,二進制就是逢二進一,以此類推,x進制就是逢x進位,—— 百度百科
現代的電子計算機技術全部采用的是二進制,因為它只使用0、1兩個數字符號,非常簡單方便,易于用電子方式實作,計算機內部處理的資訊,都是采用二進制數來表示的,二進制(Binary)數用0和1兩個數字及其組合來表示任何數,進位規則是“逢2進1”,數字1在同的位上表示不同的值,按從右至左的次序,這個值以二倍遞增,
在計算機的內部,運行各種運算時,都是以二進制的方式來運行的,
| 十進制 | 十六進制 | 八進制 | 二進制 |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 1 | 1 | 1 | 1 |
| 2 | 2 | 2 | 10 |
| 3 | 3 | 3 | 11 |
| 4 | 4 | 4 | 100 |
| 5 | 5 | 5 | 101 |
| 6 | 6 | 6 | 110 |
| 7 | 7 | 7 | 111 |
| 8 | 8 | 10 | 1000 |
| 9 | 9 | 11 | 1001 |
| 10 | A | 12 | 1010 |
| 11 | B | 13 | 1011 |
| 12 | C | 14 | 1100 |
| 13 | D | 15 | 1101 |
| 14 | E | 16 | 1110 |
| 15 | F | 17 | 1111 |
| 16 | 10 | 20 | 10000 |
| 17 | 11 | 21 | 10001 |
對于整數,常用的進制有二進制、八進制、十進制以及十六進制
1) 二進制: 0 - 1,滿2進1
在golang中,不能直接使用二進制來表示一個整數,它沿用了C的特點,但是可以以二進制的形式輸出它,
2) 八進制: 0 - 7,滿8進1,計算機內以0開頭表示
3) 十進制:0 - 9, 滿10進1
4) 十六進制: 0 - 9及A - F,滿16進1,計算機內以0x或者0X開頭表示,此處的A-F不區分大小寫,如0x21AF+1 = 0X21B0

進制轉換規矩
其它進制轉十進制
從最低位開始(右邊的),將每個位上的數提取出來,乘以X進制的(位數-1)次方,然后求和
0x1011 = 1 * 16^3 + 0 * 16^2 + 1 * 16^1 + 1 * 16^0 = 4113
十進制轉其它進制
將該數不斷除以X,直到商為0為止,然后將每步得到的余數倒過來,就是對應的X進制
156 【156 / 8 = 19(余4) 19 / 8 = 2(余2)】 == 0234
二進制轉其它進制
二進制轉八進制
規則:將二進制數每三位一組(從低位開始組合),轉成對應的八進制數即可,
11010101 => (11)(010)(101) => 0325
二進制轉十六進制
規則:將二進制數每四位一組(從低位開始組合),轉成對應的八進制數即可,
11010101 => (1101)(0101) =>0xD5
其它進制轉二進制
八進制轉二進制
將八進制的每1位,轉成對應的一個三位的二進制數即可
0237 => (010)(011)(111) => 10011111
十六進制轉二進制
將八進制的每1位,轉成對應的一個四位的二進制數即可
0x237 => (0010)(0011)(0111) => 1000110111
原碼、反碼、補碼
對于有符號的而言:
- 二進制的最高位是符號位:0表示整數,1表示負數
1 ==》[0000 0001] -1 ==》 [1000 0001]
-
正數的原碼、反碼、補碼都一樣
-
負數的反碼 = 它的原碼符號位不變,其它位取反(0 -> 1, 1 -> 0)
1 ==》原碼[0000 0001] 反碼[0000 0001] 補碼[0000 0001]
-1 ==》 原碼[1000 0001] 反碼[1111 1110]補碼[1111 1111]
-
負數的補碼 = 它的反碼 + 1
-
0的反碼,補碼都是0
-
在計算機運算的時候,都是以補碼的方式來運算的
1 + 1 1 - 1 = 1 + (-1)
位運算子
Go 語言支持的位運算子如下表所示,假定 A 為60,B 為13:
| 運算子 | 描述 | 實體 A =60 B =13 |
|---|---|---|
| & | 按位與運算子"&"是雙目運算子, 其功能是參與運算的兩數各對應的二進位相與,運算規則是:同時為1,結果為1,否則為0 | (A & B) 結果為 12, 二進制為 0000 1100 |
| | | 按位或運算子"|"是雙目運算子, 其功能是參與運算的兩數各對應的二進位相或運算規則是:有一個為1,結果為1,否則為0 | (A | B) 結果為 61, 二進制為 0011 1101 |
| ^ | 按位異或運算子"^"是雙目運算子, 其功能是參與運算的兩數各對應的二進位相異或運算規則是:當二進位不同時,結果為1,否則為0 | (A ^ B) 結果為 49, 二進制為 0011 0001 |
| << | 左移運算子"<<"是雙目運算子,左移n位就是乘以2的n次方, 其功能把"<<"左邊的運算元的各二進位全部左移若干位,由"<<"右邊的數指定移動的位數,高位丟棄,低位補0, | A << 2 結果為 240 ,二進制為 1111 0000 |
| >> | 右移運算子">>"是雙目運算子,右移n位就是除以2的n次方, 其功能是把">>"左邊的運算元的各二進位全部右移若干位,">>"右邊的數指定移動的位數,低位溢位,符號位不變,并用符號位補溢位的高位 | A >> 2 結果為 15 ,二進制為 0000 1111 |
| p | q | p & q | p | q | p ^ q |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 1 | 1 | 1 | 0 |
| 1 | 0 | 0 | 1 | 1 |
a := 1 >> 2 //0000 0001 ==> 0000 0000 ==> 0
c := 1 << 2 //0000 0001 ==> 0000 0100 ==> 4
假定 A = 60; B = 13; 其二進制數(求它們的補碼,實際上是對補碼進行運算)轉換為:
A = 0011 1100
B = 0000 1101
-----------------
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
計算機內部采用補碼進行運算
A+B
A的原碼為: 0011 1100
A的反碼為: 0011 1100
A的補碼為: 0011 1100
B的原碼為: 0000 1101
B的反碼為: 0000 1101
B的補碼為: 0000 1101
A + B
0011 1100
0000 1101
--------------
0100 1001
0100 1001轉換為10進制整數就是73
A-B = A + (-B)
A的原碼為: 0011 1100
A的反碼為: 0011 1100
A的補碼為: 0011 1100
-B的原碼為: 1000 1101
-B的反碼為: 1111 0010
-B的補碼為: 1111 0011
A - B =
0011 1100
1111 0011
--------------
0010 1111
0010 1111轉換為10進制整數就是47
需要注意的是如果得到的結果是正數,則補碼就是原碼,但是如果得到的結果是負數,則需要將補碼-1取反變成原碼后再轉換成整數
B - A
-A的原碼為: 1011 1100
-A的反碼為: 1100 0011
-A的補碼為: 1100 0100
B的原碼為: 0000 1101
B的反碼為: 0000 1101
B的補碼為: 0000 1101
B - A =
1100 0100
0000 1101
--------------
1101 0001
根據補碼符號位可以看出結果為負數,所以需要先-1轉換為反碼1101 0000,然后符號位不變,取反為原碼為1010 1111即為-47
賦值運算子
賦值運算子就是將某個運算后的值,賦給指定的變數
運算順序從右往左
賦值運算子的左邊只能是變數,右邊可以是變數、運算式、常量值
| 運算子 | 描述 | 實體 |
|---|---|---|
| = | 簡單的賦值運算子,將一個運算式的值賦給一個左值 | C = A + B 將 A + B 運算式結果賦值給 C |
| += | 相加后再賦值 | C += A 等于 C = C + A |
| -= | 相減后再賦值 | C -= A 等于 C = C - A |
| *= | 相乘后再賦值 | C = A 等于 C = C A |
| /= | 相除后再賦值 | C /= A 等于 C = C / A |
| %= | 求余后再賦值 | C %= A 等于 C = C % A |
| <<= | 左移后賦值 | C <<= 2 等于 C = C << 2 |
| >>= | 右移后賦值 | C >>= 2 等于 C = C >> 2 |
| &= | 按位與后賦值 | C &= 2 等于 C = C & 2 |
| ^= | 按位異或后賦值 | C ^= 2 等于 C = C ^ 2 |
| |= | 按位或后賦值 | C |= 2 等于 C = C | 2 |
其它運算
| 運算子 | 描述 | 實體 |
|---|---|---|
| & | 回傳變數存盤地址 | &a; 將給出變數的實際地址, |
| * | 指標變數, | *a; 是一個指標變數 |
go語言明確不支持三元運算子,在go中實作三元運算的效果
func main() {
var (
n int
i int = 10
j int = 12
)
//傳統的三元運算
//n = i > j ? i : j
if i > j {
n = i
} else {
n = j
}
fmt.Println("n = ",n)
}
運算子的優先級
| 分類 | 描述 | 關聯性 |
|---|---|---|
| 后綴 | () [] -> . ++ -- | 左到右 |
| 單目 | + - ! ~ (type) * & sizeof | *右到左* |
| 乘法 | * / % | 左到右 |
| 加法 | + - | 左到右 |
| 移位 | << >> | 左到右 |
| 關系 | < <= > >= | 左到右 |
| 相等 | == != | 左到右 |
| 按位AND | & | 左到右 |
| 按位XOR | ^ | 左到右 |
| 按位OR | | | 左到右 |
| 邏輯AND | && | 左到右 |
| 邏輯OR | || | 左到右 |
| 賦值運算子 | = += -= *= /= %= >>= <<= &= ^= |= | *右到左* |
| 逗號 | , | 左到右 |
鍵盤輸入陳述句
在編程中,需要接收用戶輸入的資料,就可以使用鍵盤輸入陳述句來獲取,InputDemo.go


func main() {
var (
name string
age byte
sal float32
isPass bool
)
//方式1:當程式執行到fmt.Scanln(&name),程式會停止在這里,等待用戶輸入,并回車
fmt.Println("請輸入姓名:")
fmt.Scanln(&name)
fmt.Println("請輸入年齡: ")
fmt.Scanln(&age)
fmt.Println("請輸入薪水: ")
fmt.Scanln(&sal)
fmt.Println("請輸入是否通過考試: ")
fmt.Scanln(&isPass)
fmt.Printf("名字是 %v \t 年齡是%v \t 薪水是 %v \t 是否通過考試%v \n ",name,age,sal,isPass)
//方式2:fmt.Scanf,可以按指定的格式輸入
fmt.Println("請輸入你的姓名,年齡,薪水,是否通過考試,使用空格隔開")
fmt.Scanf("%s %d %f %t",&name,&age,&sal,&isPass) //格式對應準確
fmt.Printf("名字是 %v \t 年齡是 %v \t 薪水是 %v \t 是否通過考試 %v \n ",name,age,sal,isPass)
}
程式流程控制
在程式中,程式運行的流程控制決定程式是如何執行的,是必須掌握的,主要有三大流程控制陳述句
? 1) 順序控制
? 2) 分支控制
? 3) 回圈控制
順序控制

分支控制
分支控制就是讓程式有選擇執行,有下面三種形式
? 1) 單分支
? 2) 雙分支
? 3) 多分支
單分支控制

func main() {
//當條件運算式為true時,就會執行{}的代碼,{}是必須有的,就算只寫一行代碼
if age := 20;age > 18 { //可以直接定義一個變數
fmt.Println("你要對自己的行為負責")
}
}
雙分支控制

func main() {
if age := 20;age < 18 { //當條件運算式成立,即執行代碼塊1,否則執行代碼塊2,{}也是必須有的
fmt.Println("你要對自己的行為負責")
} else { //else的位置需要注意
fmt.Println("你還小")
}
}
雙分支只會執行其中的一個分支
多分支控制

func main() {
//分析思路
//1. score 分數變數int
//2. 選擇多分支流程控制
//3. 成績從鍵盤輸入 fmt.Scanln
var score int
fmt.Println("請輸入成績:")
fmt.Scanln(&score)
//多分支判斷
if score == 100 {
fmt.Println("獎勵一輛BMW")
} else if score > 80 && score <= 99 {
fmt.Println("獎勵一臺P30pro")
} else if score >= 60 && score <= 80 {
fmt.Println("獎勵一個iPad")
} else {
fmt.Println("什么都不會獎勵")
}
}
多分支的判斷流程如下:
先判斷條件運算式1是否成立,如果為真,就執行代碼塊1
如果條件運算式1為假,就去判斷條件運算式2是否成立,如果條件運算式2為真,就執行代碼塊2
依次類推
如果所有的條件運算式不成立,則執行else的陳述句塊
else不是必須的
多分支只能有一個執行入口
嵌套分支
在一個分支結構中又完整的嵌套了另一個安整的分支結構,里面的分支的結構稱為內層分支外面的分支結構稱為外層分支,
func main() {
var second float64
fmt.Println("請輸入秒數")
fmt.Scanln(&second)
if second <= 8 { //嵌套分支不宜過多,建議控制在3層內,
//進入決賽
var gender string
fmt.Println("請輸入性別")
fmt.Scanln(&gender)
if gender == "男" {
fmt.Println("進入決賽的男子組")
} else {
fmt.Println("進入決賽的女子組")
}
} else {
fmt.Println("out ...")
}
}
switch 分支控制

switch的執行流程是,先執行運算式,得到值,然后和case的運算式進行比較,如果相等,就匹配到,然后執行對應的case陳述句塊,然后退出switch控制
如果switch的運算式的值沒有和任何的case的運算式匹配成功,則執行default的陳述句塊,執行后退出switch的控制
Go的case后的運算式可以有多個,使用逗號間隔
Go中的case陳述句塊不需要寫break,因為默認會有,即在默認情況下,當程式執行完case陳述句塊后,就直接退出該switch控制結構
func main() {
//思路分析
//1. 定義一個變數接收字符
//2. 使用switch完成
var key byte
fmt.Println("請輸入一個字符a,b,c")
fmt.Scanf("%c",&key)
//這里不能使用fmt.Scanln
//當且僅當前面的輸入型別不對,或者scan函式出錯情況下,才會出現你的這種情況,所以,我們要捕獲scan()系列方法的回傳值,如果回傳值不為nil,則不要再繼續往下執行scan了,如果還繼續scan,則會出現你的這種情況!
//至于為什么會出錯!因為scan沒有提供對byte型別的反射!也就是不能賦值給byte型別,可以賦值給string型別或者uint8型別,byte是uint8的型別的別名,也就是,比如,你要接收‘a’這個byte,但是你不能再控制臺輸入a,這樣的話就是字串了,你要輸入97,也就是輸入‘a’的ascii碼值!‘+’號同理!
//switch后可以不帶運算式,類似if -- else 分支來使用,
//switch后也可以直接宣告/定義一個變數,分號結束,不推薦
switch test(key) { //case/switch后是一個運算式(即:常量值、變數、一個有回傳值的函式都可以)
case 'a': //case后的各個運算式的值的資料型別,必須和switch的運算式資料型別一致
fmt.Println("周一,猴子穿新衣")
case 'b': //不需要大括號和break
fmt.Println("周二,猴子當小二")
fallthrough //默認只能穿透一層
case 'c': //case后面可以帶多個運算式,使用逗號間隔,case后面的運算式如果是常量值(字面量),則要求不能重復
fmt.Println("周三,猴子爬雪山")
default: //default陳述句不是必須的
fmt.Println("輸入有誤")
}
}
//請輸入一個字符a,b,c
//a
//周二,猴子當小二
//周三,猴子爬雪山
Type Switch:switch陳述句還可以被用于type-switch來判斷某個interface變數中實際指向的變數型別

switch和if的比較
-
如果判斷的具體數值不多,而且符號整數、浮點數、字符、字串這幾種型別,建議使用switch陳述句,簡潔高效
-
對區間判斷和結果為bool型別的判斷,使用if,if的使用范圍更廣
for回圈控制
讓一段代碼回圈的執行


func main() {
var str string = "hello, world! 紫色飛豬"
//如果我們的字串含有中文,那么傳統遍歷字串的方式就是錯誤的,會出現亂碼,原因是傳統的對字串的遍歷是按照位元組來遍歷的,而一個漢字再utf8編碼是對應3個位元組的
//方法1:[]rune()
////需要將str轉成[]rune切片
//str2 := []rune(str)
//for i := 0; i < len(str2) ; i++ {
//方法2: for range
//對應for-range 遍歷方式而言,是按照字符方式遍歷,因此如果字串有中文,也是可以的
for _, val := range str { //Go提供for-range的方式,可以方便遍歷字串和陣列
fmt.Printf("%c \t", val)
}
}
//h e l l o , w o r l d ! 紫 色 飛 豬



while和do ... while的實作
Go語言沒有while和do..while語法,這一點需要注意,如果我們需要使用類似其它語言(比如java/c 的while和do..while),可以通過for回圈來實作其使用效果,
while回圈的實作
回圈變數初始化
for {
if 回圈條件運算式 {
break //跳出for回圈..
}
回圈操作陳述句
回圈變數迭代
}
do..while的實作
回圈變數初始化
for {
回圈操作(陳述句)
回圈變數迭代
if回圈條件運算式 {
break //跳出for 回圈..
}
}
多重回圈控制
一個回圈放在另一個回圈體內,就形成了嵌套回圈,在外邊的for回圈稱為外層回圈在里面的for回圈稱為內層回圈,【建議一般使用兩層,最多不要超過3層】
實質上,嵌套回圈就是把內層回圈當作外層回圈的回圈體,當只有內層回圈的回圈條件為false時,才會完全跳出內層回圈,才可結束外層的當次回圈,開始下一次的回圈
外層回圈次數為m次,內層為n次,則內層回圈實際上需要執行m*n次
應用案例
- 統計3個班成績情況,每個班有5名同學,求出各個班的平均分和所有班級的平均分[學生的成績從鍵盤輸入]
func main() {
var classNum int
fmt.Println("請輸入有幾個班:")
fmt.Scanln(&classNum)
//fmt.Scanf("%d",&classNum)
var stuNum int
fmt.Println("請輸入每個班級有多少個人")
fmt.Scanln(&stuNum)
var totalSum float64 = 0.0
for j := 1; j <= classNum; j++ {
sum := 0.0
for i := 1; i <= stuNum; i++ {
var score float64
fmt.Printf("請輸入第%d班 第%d個學生的成績",j,i)
fmt.Scanln(&score)
//累計總分
sum += score
}
fmt.Printf("第%d個班級的平均分是%v \n",j, sum / float64(stuNum))
//將各個班的總成績累計到totalSum中
totalSum += sum
}
fmt.Printf("各個班級的總成績%v 所有班級的平均成績是%v ",totalSum,totalSum/float64(stuNum * classNum))
}
- 列印金字塔
使用for回圈撰寫一個程式,可以接收一個整數,表示層數,列印出金字塔
func main() {
//編程思路
//1. 列印一個矩形
//
// 1. 列印一個矩形
// ***
// ***
// ***
//2. 列印半個金字塔
//2. 列印半個金字塔
//* 1 個 *
//** 2 個 *
//*** 3 個 *
//3. 列印整個金字塔
// * 1層1個* 規律:2* 層數 - 1 空格 2 規律 總層數-當前層數
// *** 2層3個* 規律:2*層數 - 1 空格1 規律 總層數-當前層數
// ***** 3層5個* 規矩:2*層數 -1 空格0 規律 總層數-當前層數
//4. 將層數做成一個變數,先死后活
//var totalLevel int
//5. 列印空心金字塔
// *
// * *
// *****
//分析:在給每行列印*號時,需要考慮是列印*還是列印空格
//分析的結果是,每層的第一個和最后一個是列印*,其它就應該是空,即輸出空格
//分析到一個例外情況,最后層(底層)是全部打*
var totalLevel int
fmt.Println("請輸入列印的層數:")
fmt.Scanf("%d", &totalLevel)
//i 表示層數
for i := 1; i <= totalLevel; i++ {
//在列印*前先列印空格
for k := 1; k <= totalLevel-i; k++ {
fmt.Print(" ")
}
//j 表示每層列印多少*
for j := 1; j <= 2 * i -1; j++ {
if j == 1 || j== 2 * i - 1 || i == totalLevel {
fmt.Print("*")
} else {
fmt.Print(" ")
}
}
fmt.Println()
}
}

- 列印九九乘法表
func main() {
//列印出九九乘法表
//1. 正表
//var num int = 9
//for i := 1; i <= num ; i++ {
// for j := 1; j <= i; j++ {
// fmt.Printf("%v * %v = %v \t",j,i,j * i)
// }
// fmt.Println()
//}
//2. 倒表
var num int = 9
for i := num; i >= 1 ; i-- {
for j := 1; j <= i; j++ {
fmt.Printf("%v * %v = %v \t",j,i,j * i)
}
fmt.Println()
}
}
跳轉控制陳述句-break
break陳述句用于終止某個陳述句塊的執行,用于中斷當前for回圈或跳出switch陳述句,

func main(){
lable2:
//break陳述句出現在多層嵌套的陳述句塊中時,可以通過標簽指明要終止的是哪一層陳述句塊
for i := 0; i < 4 ; i++ {
for j := 0; j < 10;j++ {
if j == 2 {
//break //break 默認會跳出最近的for回圈
//break lable1
break lable2
}
fmt.Println("j = ",j)
}
}
}
跳轉控制陳述句-continue
continue陳述句用于結束本次回圈,繼續下一次回圈
continue陳述句出現在多層嵌套的回圈陳述句體中時,可以通過標簽指明要跳過的是哪一層回圈,這個和前面的break標簽的使用規則一樣

func main(){
lable2:
//break陳述句出現在多層嵌套的陳述句塊中時,可以通過標簽指明要終止的是哪一層陳述句塊
for i := 0; i < 4 ; i++ {
for j := 0; j < 10;j++ {
if j == 2 {
//break //break 默認會跳出最近的for回圈
//break lable1
continue lable2
}
fmt.Println("j = ",j)
}
}
}
//j = 0
//j = 1
//j = 0
//j = 1
//j = 0
//j = 1
//j = 0
//j = 1
跳轉控制陳述句- goto
-
Go語言的goto陳述句可以無條件地轉移到程式中指定的行
-
goto陳述句通常與條件陳述句配合使用,可用來實作條件轉移,跳出回圈體等功能
3)在Go程式設計中一般不主張使用goto陳述句,以免造成程式流程的混亂,使理解和除錯程式都產生困難

func main() {
var n int = 30
//演示goto的使用
fmt.Println("ok1")
if n > 20 {
goto lable1
}
fmt.Println("ok2")
fmt.Println("ok3")
fmt.Println("ok4")
lable1:
fmt.Println("ok5")
fmt.Println("ok6")
}
跳轉控制陳述句-reture
return使用在方法或者函式中,表示跳出所在的方法或函式
func main() {
for i := 1; i <= 10; i++ {
if i == 3 {
return
}
//如果return是在普通的函式,則表示跳出該函式,即不再執行函式中return后面的代碼,也可以理解成終止函式
//如果return是在main函式,表示終止main函式,也就是說終止程式
fmt.Println("zisefeizhu",i)
}
fmt.Println("hello world!")
}
//zisefeizhu 1
//zisefeizhu 2
Go函式支持回傳多個值,這一點是其它編程語法沒有的
func 函式名 (形參串列) (回傳值型別串列) {
陳述句...
return 回傳值串列
}
1)如果回傳多個值時,在接收時,希望忽略某個回傳值,則使用 _符號表示占位忽略
2)如果回傳值只有一個,(回傳值型別串列) 可以不寫()
package main
import "fmt"
//請撰寫要給函式,可以計算兩個數的和 和 差,并回傳結果
func getSumAndSub(n1 int, n2 int) (int, int) {
sum := n1 + n2
sub := n1 - n2
return sum, sub
}
func main() {
//呼叫getSumAndSub
//希望忽略某個回傳值,則使用 _符號表示占位忽略
_, res2 := getSumAndSub(1,2)
fmt.Printf("res2 = %v",res2)
}
生成亂數
在Go語言中生成亂數需要使用Seed(value)函式來提供偽亂數生成種子,一般情況下都會使用當前時間的納秒數字,如果不在生成亂數之前呼叫該函式,那么每次生成的亂數都是一樣的,函式rand.Float32和rand.Float64回傳介于[0.0, 1.0)之間的偽亂數,其中包括0.0但不包括1.0,函式rand.Intn(value)回傳介于[0,value)之間的偽亂數,
import (
"fmt"
"math/rand"
"time"
)
func main() {
nanotime := int64(time.Now().Nanosecond())
rand.Seed(nanotime)
for i := 0; i < 10; i++ {
fmt.Printf("%2.2f\t", 100 * rand.Float32())
}
}

隨機生成1-100的一個數,直到生成了99這個數,看看一共用了幾次?
分析:撰寫一個無限回圈的控制,然后不停的隨機生成數,當生成了99時,就退出這個無限回圈 ==》 break提示使用
隨機生成1 - 100整數
func main(){
//我們為了生成一個亂數,還需要read設定一個種子,
//time.Now().Unix:回傳一個從1970:01:01的0時0分0秒到現在的秒數
//rand.Seed(time.Now().Unix())
//如何隨機的生成1 - 100的整數
// n := rand.Intn(100) + 1 //[0 100)
//fmt.Println(n)
//隨機生成1 - 100的一個數,直到生成了99這個數,看看一共用了幾次
//撰寫一個無限回圈的控制,然后不停的隨機生成數,當生成了99時,就退出這個無限回圈 ==》 break提示使用
//隨機生成1 - 100整數
var count int = 0
for {
rand.Seed(time.Now().UnixNano()) //以當前系統時間作為種子引數,精確到納秒
n := rand.Intn(100) + 1
fmt.Println("n = ", n)
count++
if (n == 99) {
break //表示跳出for回圈
}
}
fmt.Println("生成 99 一共使用了",count)
}
函式、包和錯誤處理
為完成某一功能的程式指令(陳述句)的集合,稱為函式
在Go中,函式分為:自定義函式、系統函式(查看Go編程手冊)
函式的作用:減少代碼冗余、利于代碼維護
函式的基本語法
func 函式名 (形參串列) (回傳值串列) {
執行陳述句...
return 回傳值串列
}
形參串列:表示函式的輸入
函式中的陳述句:表示為了實作某一功能代碼塊
函式可以有回傳值,也可以沒有
入門案例:輸入兩個數,再輸入一個運算子(+,-,*,/),得到結果
func cal(n1,n2 float64, operator byte) float64 {
var res float64
switch operator {
case '+':
res = n1 + n2
case '-':
res = n1 - n2
case '*':
res = n1 * n2
case '/':
res = n1 / n2
default:
fmt.Println("運算子號錯誤...")
}
return res
}
func main() {
var n1 float64
var n2 float64
var operator byte
fmt.Println("請輸入n1的值:")
fmt.Scanln(&n1)
fmt.Println("請輸入n2的值:")
fmt.Scanln(&n2)
fmt.Println("請輸入一個符號:")
fmt.Scanf("%c",&operator)
result := cal(n1,n2,operator)
fmt.Println("result = ", result)
}
函式引數傳遞方式
值型別引數默認就是值傳遞,而參考型別引數默認就是參考傳遞
其實,不管是值傳遞還是參考傳遞,傳遞給函式的都是變數的副本,不同的是,值傳遞的是值的拷貝,參考傳遞的是地址的拷貝,一般來說,地址拷貝效率高,因為資料量小,而值拷貝決定拷貝的資料大小,資料越大,效率越低
值型別:基本資料型別 :int系列、float系列、bool、string、陣列和結構體struct
參考型別:指標、slice切片、map、管道chan、interface等都是參考型別
值傳遞和參考傳遞使用特點
1)值型別默認是值傳遞:變數直接存盤值,記憶體通常在堆疊中分配

-
參考型別默認是參考傳遞:變數存盤的是一個地址,這個地址對應的空間才真正存盤資料(值),記憶體通常在堆上分配,當沒有任何變數參考這個地址時,該地址對應的資料空間就成為一個垃圾,由GC來回收

-
如果希望函式內的變數能修改函式外的變數,可以傳入變數的地址&,函式內以指標的方式操作變數,從效果上看類似參考,這個案例在前面詳解函式使用注意事項時有寫

變數作用域
函式內部宣告/定義的變數叫區域變數,作用域僅限于函式內部
函式外部宣告/定義的變數叫全域變數,作用域在整個包都有效,如果其首字母為大寫,則作用域在整個程式有效
如果變數是在一個代碼塊,比如for/if中,那么這個變數的作用域就在該代碼塊

包
在實際的開發中,我們往往需要在不同的檔案中,去呼叫其它檔案的函式,比如main.go中,去使用utils.go檔案中的函式,如何實作? -》包
現在有兩個程式員共同開發一個Go專案,程式員A希望定義函式Cal,程式員B也想定義函式也叫Cal,兩個程式員為此吵了起來,怎么實作? -》包
包的本質實際上就是創建不同的檔案夾,來存程式檔案

Go的每一個檔案都是屬于一個包的,也就是說Go是以包的形式來管理檔案和專案目錄結構的
包的三大作用
? 1) 區分相同名字的函式、變數等識別符號
? 2) 當程式檔案很多時,可以很好的管理專案
? 3) 控制函式、變數等訪問范圍,即作用域
打包基本語法
package 包名
引入包的基本語法
import “包的路徑”
快速入門案例
包快速入門-Go相互呼叫函式,我們將func Cal定義到檔案utils.go,將utils.go放到一個包中,當其它檔案需要使用到utils.go的方法時,可以import 該包,就可以使用了,
PS:需要提前設定好GOPATH

utils.go
package utils //在給一個檔案打包時,該包對應一個檔案夾,比如這里的utils檔案夾對應的包名就是utils,檔案的包名通常和檔案所在的檔案夾名一致,一般為小寫字母
import "fmt"
//將計算的功能,放到一個函式中,然后在需要使用時,呼叫即可
//為了讓其它包的檔案使用Cal函式,需要將c大小類似其它語言的public
func Cal(n1 float64, n2 float64, operator byte) float64 {
//為了讓其它包的檔案,可以訪問到本包的函式,則該函式名的首字母需要大寫,類似其它語言的public,這樣才能挎包訪問
var res float64
switch operator {
case '+' :
res = n1 + n2
case '-' :
res = n1 - n2
case '*' :
res = n1 * n2
case '/' :
res = n1 / n2
default:
fmt.Println("運算子號錯誤...")
}
return res
}
main.go
package main //如果要編譯成一個可執行程式檔案,就需要將這個包宣告為main,即package main 這個就是一個語法規范,如果是寫一個庫,包名可以自定義
import (
"2020-04-02/utils" // 匯入包 (GOPATH提前設定好了) ////當一個檔案要使用其它包函式或變數時,需要先引入對應的包
//在import包時,路徑從 $GOPATH 的 src下開始,不用帶src,編譯器會自動從src下開始引入
"fmt"
)
func main() {
//分析思路...
var n1 float64 = 1.2
var n2 float64 = 2.3
var operator byte
fmt.Println("請輸入一個符號")
fmt.Scanf("%c",&operator)
result := utils.Cal(n1, n2, operator) //呼叫函式,包名.函式名()
//在訪問其它包函式,變數時,其語法是 包名.函式名
fmt.Println("result = ", result)
}

如果包名較長,Go支持給包取別名,注意細節:取別名后,原來的包名就不能使用了

函式的呼叫機制


函式的遞回呼叫【重】
一個函式在函式體內又呼叫了本身,稱為遞回呼叫


函式遞回呼叫需要遵守的重要原則
執行一個函式時,就創建一個新的受保護的獨立空間(新函式堆疊)
函式的區域變數是獨立的,不會相互影響
遞回必須向退出遞回的條件逼近,否則就是無限遞回
當一個函式執行完畢,或者遇到return,就會回傳,遵守誰呼叫,就將結果回傳給誰,同時當函式執行完畢或者回傳時,該函式本身也會被系統銷毀
題1:斐波那契數
? 請使用遞回的方式,求出斐波那契數 1,1,2,3,5,8,13...
? 給你一個整數n,求出它的斐波那契數是多少?
? 思路:
? 1) 當n == 1 || n == 2,回傳1
? 2) 當n >= 2,回傳 前面兩個數的和 f(n-1) + f(n-2)
//請使用遞回的方式,求出斐波那契數 1,1,2,3,5,8,13...
//給你一個整數n,求出它的斐波那契數是多少?
func fbn(n int) int {
if (n == 1 || n == 2) {
return 1
} else {
return fbn(n - 1) + fbn(n - 2)
}
}
func main() {
var n int
fmt.Println("請輸入n的值:")
fmt.Scanln(&n)
res := fbn(n)
fmt.Println("res = ",res)
}
題2:求函式值
? 已知f(1) = 3; f(n) = 2 * f(n-1) + 1;
? 請使用遞回的思想編程,求出f(n)的值?
? 思路
? 直接使用給出的運算式即可完成
func f(n int) int {
if n == 1 {
return 3
} else {
return 2 * f(n - 1) + 1
}
}
func main() {
var n int
fmt.Println("請輸入n的值:")
fmt.Scanln(&n)
fmt.Println("res = ",f(n))
}
題3:猴子吃桃子
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一個!以后每天猴子都吃其中的一半,然后再多吃一個,當到第十天時,想再吃時(還沒吃),發現只有一個桃子了,問:最初共多少個桃子?
思路
? 1) 第10天只有一個桃子
? 2) 第9天有幾個桃子 = (第10天桃子數量 + 1) * 2
? 3) 規矩:第n天的桃子資料 peach(n) = peach(n + 1) + 1) * 2
//?猴子吃桃子
//有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一個!以后每天猴子都吃其中的一半,然后再多吃一個,當到第十天時,想再吃時(還沒吃),發現只有一個桃子了,問:最初共多少個桃子?
//?思路
//1)第10天只有一個桃子
//2)第9天有幾個桃子 = (第10天桃子數量 + 1) * 2
//3)規矩:第n天的桃子資料 peach(n) = peach(n + 1) + 1) * 2
func peach(n int) int {
if n > 10 || n < 1 {
fmt.Println("輸入的天數不對")
return 0
} else if n == 10 {
return 1
} else {
return (peach(n + 1) + 1) * 2
}
}
func main() {
fmt.Println("第一天的桃子數量是 = ",peach(1))
}
函式使用的注意事項和細節
-
函式的形參串列可以是多個,回傳值串列也可以是多個
-
形參串列和回傳值串列的資料型別可以是值型別和參考型別
-
函式的命名遵循識別符號命名規范,首字符不能是數字,首字母大寫該函式可以被本包檔案和其它包檔案使用,類似public,首字母小寫,只能被本包檔案使用,其它包檔案不能使用,類似privat
4)函式中的變數是區域的,函式外不生效

5)基本資料型別和陣列默認都是值傳遞的,即進行值拷貝,在函式內修改,不會影響到原來的值
- 如果希望函式內的變數能修改函式外的變數(指的是默認以值傳遞的方式的資料型別),可以傳入變數的地址&,函式內以指標的方式操作變數,從效果上看類似參考,

7)Go函式不支持函式多載

- 在Go中,函式也是一種資料型別,可以賦值給一個變數,則該變數就是一個函式型別的變數了,通過該變數可以對函式呼叫

9)函式既然是一種資料型別,因此在Go中,函式可以作為形參,并且可以被呼叫

- 為了簡化資料型別定義,Go支持自定義資料型別
type 自定義資料型別名 資料型別 //相當于一個別名
type myInt int //這時 myInt 就等價int來使用了
type mySum func(int,int)int //這時mySum 就等價一個函式型別func(int,int)int
//這時 myFun 就是fun(int,int)int型別
//全域變數
type myFunType func(int,int) int
func getSum(n1 int, n2 int) int {
return n1 + n2
}
//函式既然是一種資料型別,因此在Go中,函式可以作為形參,并且呼叫
func myFun(funvar myFunType, num1 int,num2 int) int {
return funvar(num1, num2)
}
func main() {
a := getSum
fmt.Printf("a的型別%T,getSum型別是%T\n",a,getSum)
res := a(10,40) //等價 res := getSum(10,40)
fmt.Println("res = ",res)
b := myFun
fmt.Printf("b的型別%T\n",b)
res2 := myFun(getSum, 50, 60)
fmt.Println("res2 = ",res2)
//給int 取了別名,在go中myInt和int雖然都是int型別,但是go認為myInt和int兩個型別
type myInt int
var num1 myInt
var num2 int
num2 = int(num1) //注意:這里依然需要顯示轉換,Go認為myInt和int兩個型別
fmt.Println("num1 = ",num1,"num2 = ",num2)
}
-
支持對函式回傳值命名

-
使用 _ 識別符號,忽略回傳值
func cal(n1 int,n2 int)(sum int,sub int){
sum = n1 + n2
sub = n1 - n2
return
}
func main() {
res1, _ :=cal(10,20)
fmt.Printf("res1 = %d",res1)
}
- Go支持可變引數
//支持0到多個引數
func sum(args... int)sum int{
}
//支持1到多個引數
func sum(n1 int,args... int)sum int{
}
args是slice切片,通過args[index],可以訪問到各個值
如果一個函式的形參串列中有可變引數,則可變引數需要放在形參串列最后
撰寫一個函式sum,可以求出1到多個int的和
//可變引數的使用
func sum(n1 int,args... int) int {
sum := n1
//遍歷args
for i := 0; i < len(args); i++{
sum += args[i] //args[0]表示取出args切片的第一個元素值,其它依次類推
}
return sum
}
//測驗一下可變引數的使用
func main() {
res := sum(10,0,-1,90,10,100)
fmt.Println("res = ",res)
}
init函式
每一個源檔案都可以包含一個init函式,該函式會在main函式執行前,被Go運行框架呼叫,也就是說init函式會在main函式前被呼叫
init函式的注意事項和細節
- 如果一個檔案同時包含全域變數定義,init函式和main函式,則執行的流程:全域變數定義 --> init函式 --> main函式

2)init函式最主要的作用,就是完成一些初始化的作業,比如下面的案例
utils/utils.go
package utils
import "fmt"
var Age int
var Name string
//Age和Name全域變數,可以在main.go中使用
//但是需要初始化Age和Name
//init函式完成初始化作業
func init() {
fmt.Println("utils包的init()...")
Age = 100
Name = "zisefeizhu"
}
main/main.go
package main
import (
"2020-04-02/utils" // 匯入包 (GOPATH提前設定好了)
"fmt"
)
var age = test()
//為了看到全域變數是先被初始化的,先寫函式
func test() int {
fmt.Println("test()") //1
return 90
}
//init函式,通常可以在init函式中完成初始化作業
func init(){
fmt.Println("init()...") //2
}
func main() {
fmt.Println("main()... age=",age) //3
fmt.Println("Age = ",utils.Age,"Name = ",utils.Name)
}
//utils包的init()...
//test()
//init()...
//main()... age= 90
//Age = 100 Name = zisefeizhu
如果main.go和utils.go都含有變數定義,init函式時,執行的流程又是怎么樣的呢?

匿名函式
Go支持匿名函式,匿名函式就是沒有名字的函式,如果某個函式只是希望使用一次,可以考慮使用匿名函式,匿名函式也可以實作多次呼叫
匿名函式使用方式1
在定義匿名函式時就直接呼叫,這種方式匿名函式只能呼叫一次,
func main() {
//使用匿名函式完成求兩個數的和
res := func(n1 , n2 int) int {
return n1 + n2
}(10,20)
fmt.Println("res = ",res)
}
匿名函式使用方式2
將匿名函式賦給一個變數(函式變數),再通過該變數來呼叫匿名函式

全域匿名函式
如果將匿名函式賦給一個全域變數,那么這個匿名函式,就成為一個全域匿名函式,可以在程式有效
var (
//fun1就是一個全域匿名函式
Fun1 = func(n1 int, n2 int) int {
return n1 * n2
}
)
//全域匿名函式的使用
func main() {
res := Fun1(4,9)
fmt.Println("res = ",res)
}
閉包【重點】
閉包就是一個函式和與相關的參考環境組合的一個整體(物體)
//要搞清楚閉包的關鍵點就是要分析出回傳的函式和它使用(參考)到哪些變數,因為函式和它參考到的變數共同構成閉包
//累加器
func AddUpper() (func (int) int) { //AddUpper是一個函式,回傳的資料型別是fun(int)int
//可以這么理解:閉包是類,函式是操作,n是欄位,函式和它使用到n構成閉包
var n int = 10 //5-9 閉包的說明
return func(x int) int { //回傳的是一個匿名函式,但是這個匿名函式參考到函式外的n,因此這個匿名函式就和n形成一個整體,構成閉包
n = n + x
return n
}
}
func main() {
//使用前面的代碼
f := AddUpper()
fmt.Println(f(1)) // 11 //當反復的呼叫f函式時,因為n是初始化一次,因此每呼叫一次就進行累計
fmt.Println(f(2)) //13
fmt.Println(f(3)) //16
}

閉包的最佳實踐
撰寫一個程式,具體要求如下:
? 1) 撰寫一個函式makeSuffix(suffix string) 可以接收一個檔案后綴名(比如.jpg),并回傳一個閉包
? 2) 呼叫閉包,可以傳入一個檔案名,如果該檔案名沒有指定的后綴(比如.jpg),則回傳 檔案名.jpg,如果已經有.jpg后綴,則回傳原檔案名
? 3) 要求使用閉包的方式完成
? 4) strings.HasSuffix,該函式可以判斷某個字串是否有指定的后綴

函式的defer
在函式中,程式員經常需要創建資源(比如:資料庫連接、檔案句柄、鎖等),為了在函式執行完畢后,及時的釋放資源,Go的設計者提供defer(延時機制)
當Go執行到一個defer時,不會立即執行defer后的陳述句,而是將defer后的陳述句壓入到一個堆疊中【暫時稱該堆疊為defer堆疊】,然后繼續執行函式下一個陳述句
當函式執行完畢后,再從defer堆疊中,依次從堆疊頂取出陳述句執行(注:遵守堆疊 先入后出的機制),所以看到前面案例輸出的順序
在defer將陳述句放入到堆疊時,也會將相關的值拷貝同時入堆疊

defer的最佳實踐
defer最主要的價值是在,當函式執行完畢后,可以及時的釋放函式創建的資源,

在Go編程中的通常做法是,創建資源后,比如(打開了檔案,獲取了資料庫的鏈接,或者是鎖資源),可以執行defer file.Close() defer connect.Close()
在defer后,可以繼續使用創建資源
當函式完畢后,系統會依次從defer堆疊中,取出陳述句,關閉資源
這種機制,非常簡潔,程式員不用再為在什么時機關閉資源而煩心
時間和日期相關函式
在編程中,程式員會經常使用到日期相關的函式,比如:統計某段代碼指向性花費的時間等等
時間和日期相關函式,需要匯入time包

Time.Time 型別,用于表示時間
func main() {
//看看日期和時間相關函式和方法使用
//獲取當前時間
now := time.Now()
fmt.Printf("now = %v now type = %T",now, now)
//輸出:now = 2019-10-21 11:27:13.0130756 +0800 CST m=+0.005984201 now type = time.Time
}
如何獲取到其它的日期資訊
func main() {
//看看日期和時間相關函式和方法使用
//獲取當前時間
now := time.Now()
fmt.Printf("now = %v now type = %T \n",now, now)
//輸出:now = 2019-10-21 11:27:13.0130756 +0800 CST m=+0.005984201 now type = time.Time
fmt.Printf("年 = %v \n",now.Year())
fmt.Printf("月 = %v \n",now.Month())
fmt.Printf("月 = %v \n",int(now.Month()))
fmt.Printf("日 = %v \n",now.Day())
fmt.Printf("時 = %v \n",now.Hour())
fmt.Printf("分 = %v \n",now.Minute())
fmt.Printf("秒 = %v \n",now.Second())
//輸出:年 = 2019
//月 = October
//月 = 10
//日 = 21
//時 = 11
//分 = 31
//秒 = 38
格式化日期時間
方式1:就是使用Printf或者Sprintf
func main() {
//看看日期和時間相關函式和方法使用
//獲取當前時間
now := time.Now()
//格式化日期時間
fmt.Printf("當前年月日 %d-%d-%d %d:%d:%d \n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
dateStr := fmt.Sprintf("當前年月日 %d-%d-%d %d:%d:%d \n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
fmt.Printf("dateStr = %v\n",dateStr)
//輸出:當前年月日 2019-10-21 11:37:4
//dateStr = 當前年月日 2019-10-21 11:37:4
方式2:使用time.Format()方法完成
func main() {
//看看日期和時間相關函式和方法使用
//獲取當前時間
now := time.Now()
fmt.Printf("now = %v now type = %T \n",now, now)
//格式化日期時間的第二種方式
fmt.Printf(now.Format("2006-01-02 15:04:05"))
fmt.Println()
fmt.Printf(now.Format("2006-01-02"))
fmt.Println()
fmt.Printf(now.Format("15:04:05"))
fmt.Println()
//輸出:2019-10-21 11:40:37
//2019-10-21
//11:40:37
}
對上面代碼的說明
"2006-01-02 15:04:05" 這個字串各個數字可以自由自合,這樣可以按程式需求來回傳時間和日期
時間的常量
const (
Nanosecond Duration = 1 //納秒
Microsecond = 1000 * Nanosecond //微秒
Millisecond = 1000 * Microsecond //毫秒
Second = 1000 * Millisecond //秒
Minute = 60 * Second //分鐘
Hour = 60 * Minute //小時
)
常量的作用:在程式中可用于獲取指定時間單位的時間,比如想得到100毫秒
100 * time.Millisecond
結合Sleep來使用一下時間常量
func main() {
//每隔1秒列印一個數字,列印到100時就退出
i := 0
for {
i++
fmt.Println(i)
//休眠
//time.Sleep(time.Second)
time.Sleep(time.Millisecond * 100 )
if i == 100 {
break
}
}
}
time的Unix和UnixNano的方法

func main() {
//看看日期和時間相關函式和方法使用
//獲取當前時間
now := time.Now()
//Unix和UnixNano的使用
fmt.Printf("unix時間戳 = %v unixnano時間戳 = %v \n",now.Unix(),now.UnixNano())
//unix時間戳 = 1571641108 unixnano時間戳 = 1571641108537040600
時間和日期練習題
撰寫一段代碼來統計 函式test 執行的時間
方法1:在開發的程序中,我們常常需要知道執行某一塊代碼需要消耗的時間,這有利于我們知道代碼的執行效率一遍對其進行優化,我們一般就是在計算開始前設定一個起始時間,再在該塊代碼執行結束的地方設定一個結束時間,結束時間與開始時間的差值就是該快代碼執行所消耗的時間,在Go語言中可以使用time包中的Now()和Sub()函式:
func main() {
start := time.Now()
test()
end := time.Now()
result := end.Sub(start)
fmt.Printf("該函式執行完成耗時: %s\n", result)
}
func test() {
sum := 0
for i := 0; i < 100000000; i++ {
sum += i
}
}
方法2
func test() {
str := ""
for i := 0; i < 100000; i++ {
str += "hello" + strconv.Itoa(i)
}
}
func main() {
//在執行test前,先獲取到當前的unix時間戳
start := time.Now().Unix()
test()
end := time.Now().Unix()
fmt.Printf("執行test()耗費時間為%v秒\n",end - start)
}
內置函式
Go設計者為了編程方便,提供了一些函式,這些函式可以直接1使用,我們成為Go的內置函式,檔案:https://studygolang.com/pkgdoc -> builtin
len:用來求長度,比如string、array、slice、map、channel
new:用來分配記憶體,主要用來分配值型別,比如int、float32、struct...回傳的是指標
func main() {
num1 := 100
fmt.Printf("num1 的型別 %T , num1 的值=%v , num1 的地址%v \n",num1, num1, &num1)
num2 := new(int) //* int
//num2的型別%T = > *int
//num2的值 = 地址 0x04204c098 這個地址是系統分配
//num2的地址%v = 地址 0xc04206a020 這個地址是系統分配
*num2 = 100
fmt.Printf("num2 的型別%T ,num2的值 = %v ,num2的地址%v\n num2這個指標,指向的值=%v",num2,num2,&num2,*num2)
}

make:用來分配記憶體,主要用來分配參考型別,比如channel、map、slice 【放到后面再學習,畢竟需要管道、map、slice 的基礎】
append -- 用來追加元素到陣列、slice中,回傳修改后的陣列、slice
close -- 主要用來關閉channel
delete -- 從map中洗掉key對應的value
panic -- 停止常規的goroutine (panic和recover:用來做錯誤處理)
recover -- 允許程式定義goroutine的panic動作
imag -- 回傳complex的實部 (complex、real imag:用于創建和操作復數)
real -- 回傳complex的虛部
cap -- capacity是容量的意思,用于回傳某個型別的最大容量(只能用于切片和 map)
copy -- 用于復制和連接slice,回傳復制的數目
print、println -- 底層列印函式,在部署環境中建議使用 fmt 包
錯誤處理
在默認情況下,當發生錯誤后(panic),程式就會退出(崩潰)
如果希望:當發生錯誤后,可以捕獲到錯誤,并進行處理,保證程式可以繼續執行,還可以在捕獲到錯誤后,給管理員一個提示(郵件,短信 ...)
Go語言追求簡潔優雅,所以,Go語言不支持傳統的 try ... catch ... finally 這種處理
Go中引入的處理方式為:defer、panic、recover
這幾個例外的使用場景可以這么簡單描述:Go中可以拋出一個panic的例外,然后在defer中通過recover捕獲這個例外,然后正常處理
使用defer+recover來處理錯誤
func test() {
//使用defer + recover來捕獲處理例外
defer func() {
err := recover() //recover()內置函式,可以捕獲到例外
if err != nil { //說明捕獲到錯誤
fmt.Println("err = ",err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res = ", res)
}
func main() {
//測驗
test()
for {
fmt.Println("main()下面的代碼...")
time.Sleep(time.Second)
}
}
//輸出:err = runtime error: integer divide by zero
//main()下面的代碼...
//main()下面的代碼...
//...
錯誤處理的好處
進行錯誤處理后,程式不會輕易掛掉,如果加入預警代碼,就可以讓程式更加的健壯
func test() {
//使用defer + recover來捕獲處理例外
defer func() {
err := recover() //recover()內置函式,可以捕獲到例外
if err != nil { //說明捕獲到錯誤
fmt.Println("err = ",err)
//這里就可以將錯誤資訊發送給管理員...
fmt.Println("發送郵件給[email protected]")
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res = ", res)
}
func main() {
//測驗
test()
for {
fmt.Println("main()下面的代碼...")
time.Sleep(time.Second)
}
}
//輸出:err = runtime error: integer divide by zero
//發送郵件給[email protected]
//main()下面的代碼...
//main()下面的代碼...
//...
自定義錯誤
Go程式中,也支持自定義錯誤,使用errors.New 和 panic 內置函式
errors.New("錯誤說明"),會回傳一個error型別的值,表示一個錯誤
panic內置函式,接收一個interface{}型別的值(也就是任何值)作為引數,可以接收error型別的變數,輸出錯誤資訊,并退出程式
import (
"errors"
"fmt"
)
//函式去讀取以組態檔init.conf的資訊
//如果檔案名傳入不正確,就回傳一個自定義的錯誤
func readConf(name string) (err error) {
if name == "config.ini" {
//讀取...
return nil
} else {
//回傳一個自定義錯誤
return errors.New("讀取檔案錯誤...")
}
}
func test() {
err := readConf("config2.ini")
if err != nil {
//如果讀取檔案發送錯誤,就輸出這個錯誤,并終止程式
panic(err)
}
fmt.Println("test()繼續執行...")
}
func main() {
//測驗自定義錯誤的使用
test()
fmt.Println("main()下面的代碼...")
}
//輸出: panic: 讀取檔案錯誤...
//
//goroutine 1 [running]:
//main.test()
// E:/gostudent/src/2020-04-02/main/main.go:24 +0x61
//main.main()
// E:/gostudent/src/2020-04-02/main/main.go:31 +0x29
函式練習
- 函式可以沒有回傳值,撰寫一個函式,從終端輸入一個整數列印出對應的金字塔
? 思路分析:就是將原來寫的列印金字塔的案例,使用函式的方式封裝,在需要列印時,直接呼叫即可
//將列印金字塔的代碼封裝到函式中
func printPyramid(totoalLevel int) {
//i表示層數
for i := 1; i <= totoalLevel; i++ {
//在列印*前先列印空格
for k := 1; k <= totoalLevel-i; k++ {
fmt.Print(" ")
}
//j 表示每層列印多少*
for j := 1; j <= 2*i-1; j++ {
if j == 1 || j == 2*i-1 || i == totoalLevel {
fmt.Print("*")
} else {
fmt.Print(" ")
}
}
fmt.Println()
//for i := 1; i <= totoalLevel ; i++ {
// //在列印*前先列印空格
// for k := 1; k <= totoalLevel - i; k++ {
// fmt.Println(" ")
// }
// //j表示每層列印多少*
// for j := 1; j <= 2 * i -1 ; j++ {
// fmt.Println("*")
// }
// fmt.Println()
//}
}
}
func main(){
//呼叫printPyramid函式,就可以列印金字塔
//從終端輸入一個整數列印出對應的金字塔
var n int
fmt.Println("請輸入列印金字塔的層數")
fmt.Scanln(&n)
printPyramid(n)
}
- 撰寫一個函式,從終端輸入一個整數(1-9),列印出對應的乘法表
? 思路分析:就是將原來寫的呼叫九九乘法表的案例,使用函式的方式封裝,在需要列印時,直接呼叫即可
//撰寫一個函式呼叫九九乘法表
func printMulti(num int) {
//列印出九九乘法表
//i 表示層數
for i := 1; i <= num; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%v * %v = %v \t",j,i,j * i)
}
fmt.Println()
}
}
func main() {
//從終端輸入一個整數表示要列印的乘法表對應的數
var num int
fmt.Println("請輸入九九乘法表的對應數")
fmt.Scanln(&num)
printMulti(num)
}
-
撰寫函式,對給定的一個二維陣列(3 × 3)轉置,這個題講陣列的時候再完成

-
:
? 回圈列印輸入的月份的天數,【使用continue實作】
? 要有判斷輸入的月份是否錯誤的陳述句
? 類似列印:
? 請輸入年: 2019
? 請輸入月:-1
? 月份不足...
5):
? 撰寫一個函式;隨機猜數游戲;
? 有十次機會,如果第一次就猜中,提示:“你真是天才”
? 如果第2 -- 3次猜中,提示“你很聰明,趕上我了”
? 如果第4 -- 9次猜中,提示“一般般”
? 如果最后一次猜中,提示“可算猜對啦”
? 一次都沒猜對,提示“說你點啥好呢”
6):
? 撰寫一個函式:輸出100以內的所有素數(素數就只能被1和本身整除的數),每行顯示5個;并求和
7):
? 撰寫一個函式,判斷是打魚還是曬網
? 中國有句俗話叫“三天打魚兩天曬網”,如果從1990年1月1日起開始執行“三天打魚兩天曬網”,如果判斷在以后某一天是“打魚”還是“曬網”
8):
? 列印如下效果
? ----------小小計算器----------
? 1. 加法
? 2. 減法
? 3. 乘法
? 4. 除法
? 0. 退出
? 請選擇:1
? 10 + 5 = 15
----------小小計算器----------
? ......
9):
? 輸出小寫的a-z 以及大寫的Z-A
總結
這部分是學好Go語言的重中之重,基礎才是王道,很是疲勞的一天,
明天就是4月3號了,早清明,看天氣預報說有雨,路上行人欲斷魂,天上多了一顆星,有天我也會過去,不負此生是最堅強的倔強,路不止于此,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/30765.html
標籤:Go
上一篇:Go語言庫系列之dotsql
