主頁 > 後端開發 > go語言系列-從運算子到函式

go語言系列-從運算子到函式

2020-09-14 04:51:46 後端開發

編程兩大絕招

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

原碼、反碼、補碼

對于有符號的而言:

  1. 二進制的最高位是符號位:0表示整數,1表示負數

1 ==》[0000 0001] -1 ==》 [1000 0001]

  1. 正數的原碼、反碼、補碼都一樣

  2. 負數的反碼 = 它的原碼符號位不變,其它位取反(0 -> 1, 1 -> 0)

1 ==》原碼[0000 0001] 反碼[0000 0001] 補碼[0000 0001]

-1 ==》 原碼[1000 0001] 反碼[1111 1110]補碼[1111 1111]

  1. 負數的補碼 = 它的反碼 + 1

  2. 0的反碼,補碼都是0

  3. 在計算機運算的時候,都是以補碼的方式來運算的

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的比較

  1. 如果判斷的具體數值不多,而且符號整數、浮點數、字符、字串這幾種型別,建議使用switch陳述句,簡潔高效

  2. 對區間判斷和結果為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次

應用案例

  1. 統計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))
}
  1. 列印金字塔

使用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()
      }
}

  1. 列印九九乘法表
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

  1. Go語言的goto陳述句可以無條件地轉移到程式中指定的行

  2. 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)值型別默認是值傳遞:變數直接存盤值,記憶體通常在堆疊中分配

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

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

變數作用域

函式內部宣告/定義的變數叫區域變數,作用域僅限于函式內部

函式外部宣告/定義的變數叫全域變數,作用域在整個包都有效,如果其首字母為大寫,則作用域在整個程式有效

如果變數是在一個代碼塊,比如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))
}

函式使用的注意事項和細節

  1. 函式的形參串列可以是多個,回傳值串列也可以是多個

  2. 形參串列和回傳值串列的資料型別可以是值型別和參考型別

  3. 函式的命名遵循識別符號命名規范,首字符不能是數字,首字母大寫該函式可以被本包檔案和其它包檔案使用,類似public,首字母小寫,只能被本包檔案使用,其它包檔案不能使用,類似privat

4)函式中的變數是區域的,函式外不生效

5)基本資料型別和陣列默認都是值傳遞的,即進行值拷貝,在函式內修改,不會影響到原來的值

  1. 如果希望函式內的變數能修改函式外的變數(指的是默認以值傳遞的方式的資料型別),可以傳入變數的地址&,函式內以指標的方式操作變數,從效果上看類似參考,

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

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

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

  1. 為了簡化資料型別定義,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)
}
  1. 支持對函式回傳值命名

  2. 使用 _ 識別符號,忽略回傳值

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)
}
  1. 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函式的注意事項和細節

  1. 如果一個檔案同時包含全域變數定義,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

函式練習

  1. 函式可以沒有回傳值,撰寫一個函式,從終端輸入一個整數列印出對應的金字塔

? 思路分析:就是將原來寫的列印金字塔的案例,使用函式的方式封裝,在需要列印時,直接呼叫即可

//將列印金字塔的代碼封裝到函式中
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. 撰寫一個函式,從終端輸入一個整數(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)
}
  1. 撰寫函式,對給定的一個二維陣列(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

下一篇:golang與 postgresql簡單的增刪改查

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more