作為網絡安全初學者,會遇到採用Go語言開發的惡意樣本。因此從今天開始從零講解Golang程式語言,一方面是督促自己不斷前行且學習新知識;另一方面是分享與讀者,希望大家一起進步。前文介紹了Golang的順序控制語句和條件控制語句。這篇文章將詳細講解循環控制語句和流程控制,包括for、break、continue、goto及相關編程練習。
這系列文章入門部分將參考「尚矽谷」韓順平老師的視頻和書籍《GO高級編程》,詳見參考文獻,並結合作者多年的編程經驗進行學習和豐富,且看且珍惜吧!後續會結合網絡安全進行GO語言實戰深入,加油~
這些年我學過各種程式語言,從最早的C語言到C++,再到C#、PHP、JAVA,再到IOS開發、Python,到最新的GO語言,學得是真的雜。有時候覺得程式語言恰恰是最簡單的,而通過一門程式語言能夠解決實際問題或深入底層才是其價值所在,並且當我們學好一門程式語言後,其他程式語言都非常類似,殊途同歸,學起來也很迅速。
源碼下載:
前文參考:
文章目錄:一.for循環控制
1.基本語法
2.for-range循環
3.for編程經典案例
4.類似while和do-while循環
二.多重循環控制
案例1:循環計算平均成績
案例2:循環列印金字塔和倒三角
三.跳轉控制語句
1.break
2.continue
四.goto語句
五.跳轉控制語句return
六.Golang編程練習
一.for循環控制1.基本語法for循環的語法格式:
for 循環變量初始化; 循環條件; 循環變量迭代 {
循環操作語句
}
for循環主要有四要素,包括:
循環變量初始化
循環條件
循環操作(語句),也叫循環體
循環變量迭代
for循環語句基本流程如下:
for循環的執行順序說明如下:
下面舉個簡單的例子:
package main
import "fmt"
func main() {
//for循環
for n := 1; n <= 10; n++ {
fmt.Println("Eastmount", n)
}
}
輸出結果如下圖所示:
for的第二種語法格式:
for循環條件是返回一個布爾值的表達式,第二種方式是將變量初始化和變量迭代寫到其他位置。
for 循環判斷條件 {
//循環執行語句
}
案例如下所示:
for的第三種語法格式:
下面的寫法等價於 for; ; {} ,它是一個無限循環,通常需要配合break語句使用。
for {
//循環執行語句
}
案例如下所示:
2.for-range循環Go語言中range關鍵字用於for循環中迭代數組(array)、切片(slice)、通道(channel)或集合(map)的元素。在數組和切片中它返回元素的索引和索引對應的值,在集合中返回key-value對。數組後續文章介紹,這裡主要介紹遍歷字符串。
具體案例如下所示:
package main
import "fmt"
func main() {
//1.for-range遍歷字符串
str := "Eastmount CSDN"
for index, val := range str {
fmt.Printf("index=%d, value=%c \n", index, val)
}
//2.for-range遍歷數組
nums := []int{2, 3, 4}
for i, num := range nums {
fmt.Printf("index=%d, value=%d \n", i, num)
}
}
輸出結果如下圖所示:
如果我們的字符串含有中文,那麼傳統的遍歷字符串方式就會錯誤,會出現亂碼。原因是傳統對字符串的遍歷是按照字節來遍歷,而一個漢字在utf8編碼對應3個字節。如何解決呢?需要將str轉換成[]rune切片即可。
rune
golang中string底層是通過byte數組實現的。中文字符在unicode下佔2個字節,在utf-8編碼下佔3個字節,而golang默認編碼正好是utf-8。rune 等同於int32,常用來處理unicode或utf-8字符。
下面展示具體的代碼。
package main
import "fmt"
func main() {
//1.字符串遍歷-傳統方法
str := "CSDN!秀璋"
str2 := []rune(str) //將str轉換成[]rune
for i := 0; i < len(str2); i++ {
fmt.Printf("index=%d, value=%c \n", i, str2[i])
}
fmt.Println("")
//2.字符串遍歷-for range
for index, val := range str {
fmt.Printf("index=%d, value=%c \n", index, val)
}
fmt.Println("")
//3.如果不轉rune直接遍歷(亂碼)
for i := 0; i < len(str); i++ {
fmt.Printf("index=%d, value=%c \n", i, str[i])
}
}
輸出結果如下圖所示,前面兩種方式正確(按字節遍歷),第三種方式會出現亂碼。
3.for編程經典案例下面介紹各程式語言中循環都會出現的案例,計算1到100的個數及總和。
package main
import "fmt"
func main() {
var result = 0
var num = 0
for i := 1; i <= 100; i++ {
result += i
num++
}
fmt.Println("1+2+...+100 =", result)
fmt.Println("個數 =", num)
}
輸出結果如下圖所示:
流程圖如下圖所示:
4.類似while和do-while循環由於Golang只有for循環,沒有while關鍵字和do-while語法,所以只能通過for循環來模擬while和do-while循環,即使用for+break實現。
(1) while循環
for循環模擬while循環的核心代碼如下圖所示,需要注意:
for循環是一個無限循環
break語句是跳出for循環
類似於Java或C語言的while循環語句:
int i = 0;
while(i<10){ // notice there is only <
do something
}
案例如下,使用while輸出10句「hello world」。
package main
import "fmt"
func main() {
//類似while循環
var i int = 1
for {
//循環條件
if i > 10 {
break //結束循環
}
fmt.Println("hello world", i)
i++
}
}
輸出結果如下圖所示:
(2) do-while循環
由於do-while是先執行後判斷,所以for循環模擬do-while循環的核心代碼如下圖所示:
需要注意:
package main
import "fmt"
func main() {
//使用do-while實現輸出10句"hello world"
var j int = 1
for {
//先執行後判斷
fmt.Println("hello world", j)
j++
if j > 10 {
break //結束循環
}
}
}
具體含義為:
將一個循環放在另一個循環體內,就形成了嵌套循環。在外邊的for稱為外層循環,在裡面的for循環稱為內層循環。建議一般使用兩層,最多不要超過3層。
實質上,嵌套循環就是把內層循環當成外層循環的循環體,當只有內層循環的循環條件為false時,才會完全跳出內層循環,才可以結束外層的當次循環,開始下一次循環。
設外層循環次數為m次,內層為n次,則內層循環體實際上需要執行m*n次。
下面通過案例進行多重循環理解,這也是循環的一個難點和重點。正如韓老師所說,編程過程中遇到困難是很常見的,我們需要注意:
案例1:循環計算平均成績題目:統計3個班成績情況,每個班有4名同學,求出各個班的平均分和所有班級的平均分。學生的成績從鍵盤輸入。
package main
import "fmt"
func main() {
//求出各個班的平均分和所有班級的平均分(3個班 每個班有4名同學)
//解題:外層循環班級數 內存循環計算每個班級平均成績
var totalsum float64 = 0.0
for j := 1; j <= 3; j++ {
var sum float64 = 0.0
for i := 1; i <= 4; i++ {
var score float64
fmt.Printf("請輸入第%d班 第%d個學生的成績 \n", j, i)
fmt.Scanln(&score)
//累計總分
sum += score
}
fmt.Printf("第%d個班級的平均分是%v \n", j, sum / 4)
//計算各個班的總結成績
totalsum += sum
}
fmt.Printf("各個班級的總成績是%v 平均分是%v \n", totalsum, totalsum / (4* 3))
}
輸出結果如下圖所示:
同時,為了更靈活的編寫代碼,我們可以嘗試將循環控制常量3個班和4位同學修改為變量,具體如下:
var classNum int = 3
var stuNum int = 4
package main
import "fmt"
func main() {
//求出各個班的平均分和所有班級的平均分(3個班 每個班有4名同學)
//解題:外層循環班級數 內存循環計算每個班級平均成績
var classNum int = 3
var stuNum int = 4
var totalsum float64 = 0.0
for j := 1; j <= classNum; j++ {
var sum float64 = 0.0
for i := 1; i <= stuNum; i++ {
var score float64
fmt.Printf("請輸入第%d班 第%d個學生的成績 \n", j, i)
fmt.Scanln(&score)
//累計總分
sum += score
}
//注意golang數據類型需要轉換一致才能運算 int->float64
fmt.Printf("第%d個班級的平均分是%v \n", j, sum / float64(stuNum))
//計算各個班的總結成績
totalsum += sum
}
result := totalsum / float64(stuNum * classNum)
fmt.Printf("各個班級的總成績是%v 平均分是%v \n", totalsum, result)
}
如果需要繼續統計各班及格人數,怎麼實現呢?
案例2:循環列印金字塔和倒三角列印金字塔是經典的案例,在前面的第二篇文章也布置過。下面我們通過for循環列印各種金字塔,思路為:
首先介紹列印矩形和半個三角形金字塔代碼。
package main
import "fmt"
func main() {
var totalLevel int = 5
//1.列印矩形
for i := 1; i <= totalLevel; i++ { //i表示層數
for j := 1; j <= totalLevel; j++ { //每層輸出
fmt.Print("*")
}
fmt.Println() //換行
}
fmt.Println()
//2.列印正三角半個金字塔
for i := 1; i <= totalLevel; i++ { //i表示層數
for j := 1; j <= i; j++ { //每層輸出
fmt.Print("*")
}
fmt.Println() //換行
}
fmt.Println()
//3.列印倒三角半個金字塔
for i := 1; i <= totalLevel; i++ { //i表示層數
for j := (totalLevel - i); j >= 0; j-- { //每層輸出
fmt.Print("*")
}
fmt.Println() //換行
}
}
輸出結果如下圖所示:
理解上述代碼之後,我們再列印金字塔,其規律為:
空格 + * + 空格
* 1層 1個* 規律: 2*層數-1 空格 4個 規律:總層數-當前層數
*** 2層 3個* 規律: 2*層數-1 空格 3個 規律:總層數-當前層數
*****
*******
*********
完整代碼如下,包括實心金字塔和空心金字塔。
package main
import "fmt"
func main() {
/* 列印金字塔
* 1層 1個* 規律: 2*層數-1 空格 4個 規律:總層數-當前層數
*** 2層 3個* 規律: 2*層數-1 空格 3個 規律:總層數-當前層數
*****
*******
*********
*/
var totalLevel int = 5
//方法一 列印實心金字塔
for i := 1; i <= totalLevel; i++ { //i表示層數
//先列印空格
for j := (totalLevel - i); j >= 0; j-- {
fmt.Print(" ")
}
//列印星號
for k := (2 * i - 1); k > 0; k-- {
fmt.Print("*")
}
fmt.Println() //換行
}
fmt.Println()
//方法二 列印空心金字塔
for i := 1; i <= totalLevel; i++ { //i表示層數
//先列印空格
for j := 1; j <= (totalLevel - i); j++ {
fmt.Print(" ")
}
//列印星號
for k := 1; k <= (2 * i - 1); k++ {
//每層第一個和最後一個列印* 最底層全部列印*
if k == 1 || k == (2 * i - 1) || i == totalLevel {
fmt.Print("*")
} else {
fmt.Print(" ")
}
}
fmt.Println() //換行
}
}
輸出結果如下圖所示:
三.跳轉控制語句1.breakbreak語句用於終止某個語句塊的執行,用於中斷當前for循環或跳出switch語句。基本語法如下:
{
....
break
....
}
其示意圖如下圖所示(引用至韓老師)。
首先結合之前1+2+…+100介紹一個簡單案例,求當和第一次大於20的當前數字。
package main
import "fmt"
func main() {
//求出當和第一次大於20的當前數字
sum := 0
for i := 1; i <= 100; i++ {
sum += i //求和
if sum > 20 {
fmt.Println("sum大於20時當前數字是", i)
break //跳出循環
}
}
}
輸出結果如下圖所示:
break語句的注意事項:
通過標籤跳出for循環指定層的代碼如下所示:
package main
import "fmt"
func main() {
//通過標籤跳出for循環指定層
label1:
for i := 0; i < 4; i++ {
//label2:
for j := 0; j < 10; j++ {
if j ==2 {
//默認跳出最近的循環 這裡嘗試跳出外層循環
break label1
}
fmt.Println("j =", j, "i =", i)
}
}
}
運行結果如下,當j等於2時跳出所有循環,即調轉至label1。:
2.continuecontinue語句用於結束本次循環,它將繼續執行下一次循環。其基本語法如下:
{
...
continue
...
}
流程圖如下所示,它將結束本次循環,繼續迭代執行下一次循環。
同樣,continue語句出現在多層嵌套的循環語句體中時,可以通過標籤指明要跳過的是哪一層循環,這個和前面的break標籤使用規則一樣。
個人不太喜歡這個跳出指定循環層的功能,感覺代碼不是很好控制。尤其是大型項目時,更建議結合實際判斷條件進行跳出,當然作者理解能力尚淺,如果讀者有好的應用場景可以分享與我,謝謝。
舉個簡單案例:
package main
import "fmt"
func main() {
//continue跳出當前循環
for i := 0; i < 4; i++ {
for j := 0; j < 10; j++ {
if j ==2 {
continue
}
fmt.Println("j =", j, "i =", i)
}
}
}
輸出結果如下圖所示,每次都沒有輸出j=2。
如果我們需要使用continue列印100以內的奇數,則可以編寫如下代碼:
四.goto語句Golang中的goto語句可以無條件轉移到程序中指定的行,goto常與條件語句配合使用,用以實現條件轉移或跳出循環體。注意,在GO程序設計中一般不主張使用goto語句,以避免造成流程的混亂,使理解和調試程序都產生困難,同樣C語言也不主張使用。
goto label
....
label: statement
其執行流程如下圖所示:
下面介紹一個簡單的案例:
package main
import "fmt"
func main() {
//goto語句
fmt.Println("aaaaa")
var n int = 30
if n > 20 {
goto label
}
fmt.Println("bbbbb")
fmt.Println("ccccc")
label:
fmt.Println("ddddd")
fmt.Println("eeeee")
fmt.Println("fffff")
}
輸出結果如下圖所示:
五.跳轉控制語句returnreturn使用在方法或函數中,表示跳出所在的方法或函數。在分享函數的時候,我會詳細介紹。
return使用說明:
package main
import "fmt"
func main() {
//return語句
for i := 1; i<=10; i++ {
if i==3 {
return
}
fmt.Println("輸出結果", i)
}
fmt.Println("over")
}
輸出結果如下圖所示:
六.Golang編程練習1.題目(1) 列印1到100之間所有是9倍數的整數的個數及總和。
(2) 循環依次輸出「East 秀璋」字符串的所有字符。
(3) 列印9*9乘法表。
(4) 模擬網站登錄驗證機制.假設有3次機會,如果用戶名為「Eastmount」,密碼為「666666」提示登錄成功,否則提示剩餘機會;最終超過3次則提示「輸入錯誤次數過多,無法登錄」。
(5) 隨機生成1到100的一個數,如果生成99這個數就停止,計算一共使用多少次。
(6) 輸入如下4*5的矩陣。
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
(7) 利用循環求Fibonacci數列的前10個數。
(8) 求2到200間的全部素數。
2.解答(1) 列印1到100之間所有是9倍數的整數的個數及總和。
代碼如下:
package main
import "fmt"
func main() {
var max int = 100
var count int = 0
var sum int = 0
for i := 1; i <= max; i++ {
if i % 9 == 0 {
count++
sum += i
}
}
fmt.Printf("count=%v sum=%v \n", count, sum)
}
輸出結果如下圖所示:
(2) 循環依次輸出「East 秀璋」字符串的所有字符。
在數組上使用range將傳入index和值兩個變量。如果我們不需要使用該元素的序號,則使用空白符"_"省略。
package main
import "fmt"
func main() {
str := "East=秀璋"
//方法1
fmt.Printf("utf-8遍歷字符串\n")
for _, s := range str {
fmt.Printf("utf-8遍歷: %c %v \n", s, s)
}
fmt.Println("")
//方法2
str2 := []rune(str)
for i := 0; i < len(str2); i++ {
fmt.Printf("utf-8遍歷: index=%d, value=%c \n", i, str2[i])
}
fmt.Println("")
//方法3 亂碼
fmt.Printf("unicode遍歷字符串\n")
for i := 0; i < len(str); i++ {
ch := str[i]
fmt.Printf("unicode遍歷: %c %d\n", ch, ch)
}
}
輸出結果如下圖所示:
(3) 列印9*9乘法表。
代碼如下:
package main
import "fmt"
func main() {
//9*9乘法表
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()
}
}
輸出結果如下圖所示:
(4) 模擬網站登錄驗證機制.假設有3次機會,如果用戶名為「Eastmount」,密碼為「666666」提示登錄成功,否則提示剩餘機會;最終超過3次則提示「輸入錯誤次數過多,無法登錄」。
代碼如下:
package main
import "fmt"
func main() {
//模擬網站登錄
var name string
var pwd string
var loginChance = 3
for i := 1; i <= 3; i++ {
//輸入信息
fmt.Println("請輸入用戶名")
fmt.Scanln(&name)
fmt.Println("請輸入密碼")
fmt.Scanln(&pwd)
if name == "Eastmount" && pwd == "666666" {
fmt.Println("恭喜你登錄成功")
break
} else {
loginChance--
fmt.Printf("輸入錯誤,你還有%v次機會\n", loginChance)
}
}
if loginChance <= 0 {
fmt.Println("輸入錯誤次數過多,無法登錄")
}
}
輸出結果如下圖所示:
(5) 隨機生成1到100的一個數,如果生成99這個數就停止,計算一共使用多少次。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
//隨機生成1到100的一個數,如果生成99這個數就停止
var count int = 0
for {
//種子生成 使用系統時間的不確定性來進行初始化
rand.Seed(time.Now().UnixNano())
num := rand.Intn(100) + 1
fmt.Println("num =", num)
count++
if (num == 99) {
break
}
}
fmt.Println("生成隨機數99共使用次數 =", count)
}
輸出結果如下圖所示:
(6) 輸入如下4*5的矩陣。
代碼如下:
package main
import "fmt"
func main() {
var n int = 4
var m int = 5
var res int = 0
for i := 1; i <= n; i++ {
for j := 1; j <= m; j++ {
fmt.Printf("%v\t", i*j)
res++
}
fmt.Println("")
}
}
輸出結果如下圖所示:
(7) 利用循環求Fibonacci數列的前10個數。
解題思路對應的流程如下圖所示:
package main
import "fmt"
func main() {
//Fibonacci數列
var f1 int = 1
var f2 int = 1
var f3 int
fmt.Printf("%v\n%v\n", f1, f2)
for i := 1; i <= 10; i++ {
f3 = f1 + f2
fmt.Printf("%v\n", f3)
f1 = f2
f2 = f3
}
}
輸出結果如下圖所示:
(8) 求2到200間的全部素數。
代碼如下:
package main
import "fmt"
func main() {
//求2到200間的全部素數
var n int
var k int
var i int
var m int = 0
for n = 2; n <= 200; n++ {
k = n / 2
//如果n被i整除終止內循環
for i = 2; i <= k; i++ {
if n % i == 0 {
break
}
}
//如果i>k+1表示n未被整除
if i >= k + 1 {
fmt.Printf("%d\t", n)
//m控制換行 輸出10個素數換行
m = m + 1
if m % 10 == 0 {
fmt.Println()
}
}
}
}
運行結果如下圖所示:
七.總結寫到這裡,這篇基礎性Golang文章介紹完畢,希望您喜歡!祝大家新年快樂,牛氣沖天,也希望自己能分享更深入的文章。
一.for循環控制
1.基本語法
2.for-range循環
3.for編程經典案例
4.類似while和do-while循環
二.多重循環控制
案例1:循環計算平均成績
案例2:循環列印金字塔和倒三角
三.跳轉控制語句
1.break
2.continue
四.goto語句
五.跳轉控制語句return
六.Golang編程練習
1.題目
2.解答
Go基本運算和進位轉換了解後,後面的文章將詳細介紹Go語言的條件語句和循環語句知識,並結合案例進行普及。希望這篇基礎性文章對您有幫助,寫得不好的地方還請海涵。同時非常感謝參考文獻中的大佬們的文章分享,尤其是韓順平老師,深知自己很菜,得努力前行。也希望自己能深入下去,未來四年好好研究Go程式語言,做更多實際工程,寫更好的文章,共勉!
原始碼下載地址:
2020年在github的綠瓷磚終於貼完了第一年提交2100餘次,獲得1500多+stars,開源93個倉庫,300個粉絲。挺開心的,希望自己能堅持在github打卡五年,督促自己前行。
簡單總結下,最滿意的資源是YQ爆發時,去年2月分享的輿情和情感分析,用這系列有溫度的代碼為武漢加油;最高贊的是Python圖像識別系列,也獲得了第一位來自國外開發者的貢獻補充;最花時間的是Wannacry逆向系列,花了兩月逆向分析,幾乎成為了全網最詳細的該蠕蟲分析;還有AI系列、知識圖譜實戰、CVE復現、APT報告等。
當然也存在很多不足之處,希望來年分享更高質量的資源,也希望能將安全和AI頂會論文系列總結進來,真誠的希望它們能幫助到大家,感恩有你,一起加油~
2020年8月18新開的「娜璋AI安全之家」,主要圍繞Python大數據分析、網絡空間安全、人工智慧、Web滲透及攻防技術進行講解,同時分享論文的算法實現。娜璋之家會更加系統,並重構作者的所有文章,從零講解Python和安全,寫了近十年文章,真心想把自己所學所感所做分享出來,還請各位多多指教,真誠邀請您的關注!謝謝。
(By:娜璋AI之家 2021-02-20 星期六)
參考文獻:
Go官網:https://golang.org/
https://www.bilibili.com/video/BV1pt41127FZ
https://www.runoob.com/go/go-tutorial.html
https://studygolang.com/pkgdoc
《C程序設計》譚浩強老師
《GO高級編程》
https://www.runoob.com/go/go-range.html