歡迎來到Golang的世界!在當今快節奏的軟件開發領域,選擇一種高效、簡潔的編程語言至關重要。而在這方面,Golang(又稱Go)無疑是一個備受矚目的選擇。在本文中,帶領您探索Golang的世界,一步步地了解這門語言的基礎知識和實用技巧。
目錄
初識函數
引入包
init函數
匿名函數
閉包
defer關鍵字
系統函數
錯誤處理
初識函數
為完成某一功能的程序指令(語句)的集合稱為函數,函數的作用就是提高代碼的復用性和可維護性,減少代碼的冗余。接下來我們舉一個簡單的例子,看一看函數如何進行處理和調用:
package main
import "fmt"
/* 函數基本格式func 函數名(參數列表) (返回值列表) {函數體return 返回值列表}
*/
// 求和函數
func cal(a, b int) int {return a + b
}
func main() {sum := cal(1, 2)fmt.Println(sum) // 3
}
函數有一些區別于其他語言的一些細節所在,接下來本文將對這些細節及其所起作用一一講解:
函數名:
1)遵循標識符命名規范:見名知意addNum,駝峰命名addNum
2)首字母不能是數字
3)首字母大寫該函數可以被本包文件和其它包文件使用(類似public)
4)首學母小寫只能被本包文件使用,其它包文件不能使用(類似private)
形參列表:
1)形參列表:個數:可以是一個參數,可以是n個參數,可以是0個參數
2)形式參數列表:作用:接收外來的數據
3)實際參數:實際傳入的數據
返回值類型列表:
可以是多個,根據具體進行進行返回。
package main
import "fmt"
// 求和函數
func cal(a, b int) (int, int) {result1 := a + bresult2 := a - breturn result1, result2
}
func main() {sum1, sum2 := cal(1, 2)// 如果不想接收第二個值,可以使用下劃線占位進行忽略//sum1, _ := cal(1, 2)fmt.Println(sum1, sum2) // 3 -1
}
函數不支持重載:
在C++、Java(從C++11開始支持方法重載)等語言中。它允許程序員在相同的作用域內定義多個具有相同名稱但參數列表(參數類型、參數個數或參數順序)不同的函數。
// Go語言中不支持函數重載,但可以使用不同的函數名
func printInt(x int) { fmt.Println("Printing an integer:", x)
}
func printFloat(x float64) { fmt.Println("Printing a float:", x)
} func main() { printInt(10) // 調用printInt printFloat(10.5) // 調用printFloat
}
支持可變參數:
golang中支持可變參數(如果你希望函數帶有可變數量的參數)
package main
import "fmt"
// 定義一個函數,函數參數為:可變參數 ... 參數的數量可變
// args...int 可以傳入任意多個數量的int類型的數據,傳入0個,1個...n個
func test(args ...int) {// 函數內部處理可變參數的時候,可將可變參數當作切片來處理// 遍歷可變參數for i := 1; i < len(args); i++ {fmt.Println(i, args[i])}
}func main() {test(1, 2) // 1 2fmt.Println("-----------")test() // 空
}
值拷貝:
基本數據類型和數組默認都是值傳遞的即進行值拷貝。在函數內修改不會影響到原來的值。
package main
import "fmt"
func test(num int) {num = 30fmt.Println(num)
}func main() {var num int = 10test(num) // 30fmt.Println(num) // 10
}
引用傳遞:
以值傳遞的方式的數據類型,如果希望在函數內的變量能夠修改函數外的變量,可以傳入變量的地址&,函數內以指針的方式操作變量,從效果來看類似引用傳遞。
package main
import "fmt"
func test(num *int) {*num = 30fmt.Println(num)
}func main() {var num int = 10fmt.Println(&num) // 0xc00000a0c8test(&num) // 調用test函數,將num的地址作為參數傳遞給test函數,test函數中修改num的值,結果為0xc00000a0c8fmt.Println(num) // 30
}
賦值變量:
在Go中,函數世是一種數據類型,可以賦值給一個變量,則該變量就是一個函數類型的變量了。通過該變量可以對函數調用。.
package main
import "fmt"
// 定義函數
func test(num int) {fmt.Println(num)
}func main() {// 函數也是一種數據類型,可以賦值給變量a := testfmt.Printf("a的類型是: %T, test函數的類型是:%T \n", a, test) // a的類型是: func(int), test函數的類型是:func(int)// 通過賦值變量,調用函數a(10) // 10
}
形參調用:
函數既然是一種數據類型,因此在Go中,函數可以作為形參,并且調用(把函數本身當做一種數據類型)
package main
import "fmt"
// 定義函數
func test(num int) {fmt.Println(num)
}
// 定義函數,把函數作為參數傳遞給另一個函數
func test2(num int, testfunc func(int)) {fmt.Println("---")testfunc(num)
}func main() {// 函數也是一種數據類型,可以賦值給變量a := testfmt.Printf("a的類型是: %T, test函數的類型是:%T \n", a, test) // a的類型是: func(int), test函數的類型是:func(int)// 通過賦值變量,調用函數a(10) // 10// 函數作為參數傳遞給另一個函數test2(10, test) // --- 10
}
自定義數據類型:
為了簡化數據類型定義,Go支持自定義數據類型基本語法,type自定義數據類型名數據類型
可以理解為:相當于起了一個別名例如:typemylnt int -----》這時mylnt就等價int來使用了。
// 自定義數據類型(相當于起別名):給int類型起了別名叫myInt類型
type myInt int
var num1 myInt = 10
fmt.Println(num1) // 10//var num2 int = 10
//num2 = num1 // 雖然是別名,但是在go語言中,別名是不能進行隱式轉換的,還是認為myInt與int類型不同
package main
import "fmt"
// 定義函數
func test(num int) {fmt.Println(num)
}
// 定義函數,把函數作為參數傳遞給另一個函數
// 自定義數據類型
type myfunc func(int)
func test2(num int, testfunc myfunc) {fmt.Println("---")testfunc(num)
}func main() {// 函數也是一種數據類型,可以賦值給變量a := testfmt.Printf("a的類型是: %T, test函數的類型是:%T \n", a, test) // a的類型是: func(int), test函數的類型是:func(int)// 通過賦值變量,調用函數a(10) // 10// 函數作為參數傳遞給另一個函數test2(10, test) // --- 10
}
當然這里還支持函數返回值命名,里面順序無所謂,具體如下代碼示例:
package main
import "fmt"
func test(num1 int, num2 int) (sum int, sub int) {sum = num1 + num2sub = num1 - num2return
}func main() {sum, sub := test(10, 2)fmt.Println(sum, sub) // 12 8
}
引入包
在Go語言中,包(package)是一個非常重要的概念,它用于組織和管理代碼。
包的概念:
1)組織代碼:包是Go語言中代碼的基本組織單元。它將相關的函數、類型、變量和常量等組合在一起,形成一個邏輯上的整體。
2)命名空間:每個包都有其自己的命名空間,包內的標識符(如函數名、類型名、變量名等)在該包內是唯一的,但在不同的包中可以重名。通過包名可以區分和引用不同包中的標識符。
3)封裝性:包可以隱藏其內部實現細節,只對外暴露必要的接口。這有助于保護代碼的封裝性,減少不同模塊之間的耦合度。
使用意義:
1)代碼復用:通過將代碼組織成包,可以方便地在不同的項目或模塊中復用這些代碼。只需要將包導入(import)到需要使用的地方,就可以使用其中的函數、類型和變量等。
2)模塊化管理:Go語言的包機制支持模塊化管理,可以將大型項目拆分成多個小模塊(即包),每個模塊負責實現特定的功能。這樣可以提高代碼的可讀性和可維護性,降低項目的復雜度。
3)類型安全:由于每個包都有自己的命名空間,因此可以避免不同包中的標識符重名導致的類型沖突問題。這有助于提高代碼的類型安全性。
4)隱藏實現細節:通過將實現細節封裝在包內部,只對外暴露必要的接口,可以隱藏實現細節,降低外部對內部實現的依賴和耦合度。這有助于提高代碼的可維護性和可擴展性。
5)遵循最佳實踐:Go語言的包機制鼓勵開發者遵循最佳實踐,如將相關的函數和類型組織在同一個包中,使用統一的命名規范等。這有助于提高代碼的可讀性和可維護性。
以下是導包后調用的基礎案例:
我們在導包的時候,也是需要關注一些細節方面的內容,方便我們快速導包使用,細節如下:
1)package進行包的聲明,建議:包的聲明這個包和所在的文件夾同名
2)main包是程序的入口包,一般main函數會放在這個包下
3)打包語法:package 包名
4)引l入包的語法:import"包的路徑”包名是從$GOPATH/src/后開始計算的,使用/進行路徑分隔。
5)如果有多個包,建議一次性導入,格式如下:
????????import(
????????????????"fmt"
????????????????"dbutils"
????????)
6)在函數調用的時候前面要定位到所在的包
7)首字母大寫,函數可以被其它包訪問
8)一個目錄下不能有重復的函數
9)包名和文件夾的名字,可以不一樣
10)一個目錄下的同級文件歸屬一個包
11)在程序層面,所有使用相同package包名的源文件組成的代碼模塊,在源文件層面就是一個文件夾
當然也可以給包起別名,起完別名之后原來的包名就不能再使用了:
init函數
初始化函數,可以用來進行一些初始化的操作每一個源文件都可以包含一個init函數,該函數會在main函數執行前,被Go運行框架調用。示例代碼如下所示:
全局遍歷、init函數、main函數的執行順序流程如下:
多個init函數情況,先執行外包中的init函數,最后執行main函數中的init函數,示例如下:
匿名函數
go語言支持匿名函數,如果我們某個函數只是希望使用一次,可以考慮使用匿名函數,以下是使用匿名函數的使用方式:
定義匿名函數時就直接調用,這種方式匿名函數只能調用一次:
package main
import "fmt"
func main() {// 定義匿名函數result := func(num1 int, num2 int) int {return num1 + num2}(10, 3)fmt.Println(result) // 13
}
將匿名函數賦給一個變量(該變量就是函數變量了),再通過該變量來調用匿名函數:
package main
import "fmt"
func main() {// 將匿名函數賦值給變量,然后通過變量調用匿名函數sub := func(a, b int) int {return a - b}// 調用匿名函數result := sub(10, 5)fmt.Println(result) // 5
}
將匿名函數作為全局變量使用:
package main
import "fmt"
// 設置全局匿名函數
var sub = func(a, b int) int {return a - b
}func main() {// 調用匿名函數result := sub(10, 5)fmt.Println(result) // 5
}
閉包
閉包就是一個函數和與其相關的引|用環境組合的一個整體。
閉包的本質:是一個匿名函數,只是這個函數引入外界的變量 / 參數匿名函數 + 引用的變量 / 參數 = 閉包,示例代碼如下:
package main
import "fmt"
// 函數求和,函數的名字是getSum,參數為空
// getSum返回一個函數,這個函數是int類型的參數,返回值也是int類型
func getSum() func(int) int {var sum int = 0return func(num int) int {sum += numreturn sum}
}func main() {f := getSum()// 可以看到匿名函數中引用的那邊變量會一直保存再內存中,可以一直使用fmt.Println(f(1)) // 1fmt.Println(f(2)) // 3fmt.Println(f(3)) // 6
}
閉包的特點:返回的是一個匿名函數,但是這個匿名函數引用到函數外的變量/參數,因此這個匿名函數就和變量/參數形成一個整體,構成閉包。包中使用的變量/參數會一直保存在內存中,所以會一直使用,這就意味著閉包不可濫用。
defer關鍵字
在函數中,程序員經常需要創建資源,為了在函數執行完畢后,及時的釋放資源,Go的設計者提供defer關鍵字,接下來通過如下代碼進行講解:
遇到defer關鍵字會將后面的代碼語句壓入棧中,也會將相關的值同時拷貝入棧中,不會隨著函數后面的變化而變化。代碼如下:
package main
import "fmt"
func add(num1, num2 int) int {// 在go中,程序遇到defer關鍵字,不會立即執行defer后面的語句,而是將defer后面的語句放入到棧中,然后執行函數后面的語句。defer fmt.Println("num1 = ", num1)defer fmt.Println("num2 = ", num2)// 棧的特點是先進后出,所以defer后面的語句會先執行// 在函數執行完畢后,從棧中取出語句開始執行,按照先進后出的順序執行sum := num1 + num2fmt.Println("sum = ", sum)return sum
}
func main() {fmt.Println(add(1, 2))
}
最終得到的結果如下所示:
defer應用場景:比如你想關閉某個使用的資源,在使用的時候直接隨手defer,因為defer有延遲執行機制(函數執行完畢再執行defer壓入棧的語句),所以你用完隨手寫了關閉,比較省心,省事!
系統函數
系統函數是一個相對模糊的概念,它可以指代任何與底層系統交互或執行特定系統級別任務的函數。在Go語言中,這些函數可能來自標準庫、第三方庫、通過系統調用實現的功能或者內置的函數,接下來我們就講解一下go語言中常見的內置函數,使用內置函數也不用導包的,直接用就行:
len(str):統計字符串的長度,按字節進行統計
func main() {// 統計字符串的長度,按字節進行統計str := "hello world"fmt.Println(len(str)) // 11
}
r:=[]rune(str):字符串遍歷
func main() {str := "hello world"// 對字符串進行變量// 方式1:利用鍵值循環:for - rangefor i, value := range str {fmt.Printf("索引為:%d, 具體的值為:%c \n", i, value)}fmt.Println("---------------------------------")// 方式2:利用 r:=[]rune(str)r := []rune(str)for i := 0; i < len(r); i++ {fmt.Printf("索引為:%d, 具體的值為:%c \n", i, r[i])}
}
最終得到的結果如下所示:
n, err := strconv.Atoi("66"):字符串轉整數;str = strconv.ltoa(6887):整數轉字符串
func main() {// 字符串轉整數num1, _ := strconv.Atoi("123")fmt.Println(num1) // 123// 整數轉字符串num2 := strconv.Itoa(123)fmt.Println(num2) // "123"
}
strings.Contains("javaandgolang", "go"):查找子串是否在指定的字符串中
func main() {// 查找子串是否在指定的字符串中result := strings.Contains("javaandgolang", "go")fmt.Println(result) // true
}
strings.Count("javaandgolang","a"):統計一個字符串有幾個指定的子串
func main() {// 查找子串是否在指定的字符串中result := strings.Count("javaandgolang", "a")fmt.Println(result) // 4
}
fmt.Println(strings.EqualFold("go", "Go")):不區分大小寫的字符串比較
func main() {fmt.Println(strings.EqualFold("go", "Go")) // true
}
strings.Index("javaandgolang", "a"):返回子串在字符串第一次出現的索引值,如果沒有返回-1
func main() {result := strings.Index("javaandgolang", "a")fmt.Println(result) // 1
}
當然下面還有一些函數,這里就不再一一演示了:
strings.Replace("goandjavagogo", "go","golang", n):字符串的替換,n表示替換幾個
strings.Split("go-python-java", "-"):按照指定的某個字符,為分割標識,將一個學符串拆分成字符串數組
strings.ToLower("Go") // go;strings.ToUpper"go") //Go:字符串的字母進行大小寫的轉換
strings.TrimSpace("?? go and java??? "):將字符串左右兩邊的空格去掉
strings.Trim("~golang~ ", " ~"):將字符串左右兩邊指定的字符去掉
strings.TrimLeft("~golang~ ", " ~");strings.TrimRight("~golang~ ", " ~"):指定字符兩邊去掉
strings.HasPrefix("http://java.sun.com/jsp/jstl/fmt", "http"):判斷字符串是否以指定的字符串開頭
當然go語言還內置了時間函數,這里進行一個簡單的演示:
package main
import ("fmt""time"
)
func main() {// 日期時間函數now := time.Now()// Now() 是一個結構體,類型是time.Time, 返回的是當前時間// 2024-05-26 16:05:18.4238005 +0800 CST m=+0.000660501 ~~~ 對應的類型為: time.Timefmt.Printf("%v ~~~ 對應的類型為: %T\n", now, now)// 獲取年月日fmt.Printf("年: %d\n", now.Year()) // 2024fmt.Printf("月: %d\n", now.Month()) // 5fmt.Printf("日: %d\n", now.Day()) // 26// 獲取當前年月日時分秒的字符串 Format()函數是結構體的方法,作用是格式化時間fmt.Printf("年月日時分秒: %s\n", now.Format("2006-01-02 15:04:05")) // 2024-05-26 16:05:18
}
最終達到的效果如下所示:
錯誤處理
當函數出現運行錯誤的時候,會導致程序被中斷,是無法繼續執行后續代碼的,所以我們需要對錯誤進行一個處理,以下是程序出現錯誤時的情況:
在Go語言中,defer和recover是兩個與函數延遲執行和異常處理相關的關鍵字。它們主要用于錯誤處理和資源清理等場景。
package main
import "fmt"
func test() {// 利用defer+recover機制,捕獲異常,defer后加上匿名函數的調用defer func() {// 調用recover()函數,捕獲異常err := recover()if err != nil {// 捕獲異常后,執行相應的處理邏輯fmt.Println("錯誤已經捕獲")fmt.Println("捕獲異常:", err)}}()num1 := 10num2 := 0result := num1 / num2fmt.Println(result)
}func main() {test()fmt.Println("上述邏輯正常,開始執行下面的代碼~")fmt.Println("main函數執行完畢")
}
最終實現的效果如下所示:
如果想自定義錯誤的話,可以采用下面的方式進行:
package main
import ("errors""fmt"
)
func test() (err error) {num1 := 10num2 := 0// 自定義錯誤if num2 == 0 {// 拋出自定義錯誤return errors.New("除數不能為0")} else {result := num1 / num2fmt.Println(result)// 如果沒有錯誤,返回零值return nil}
}func main() {err := test()if err != nil {fmt.Println("自定義錯誤", err)}fmt.Println("上述邏輯正常,開始執行下面的代碼~")fmt.Println("main函數執行完畢")
}
最終實現的結果如下所示:
有一種情況:程序出現錯誤以后,后續代碼就沒有必要執行,想讓程序中斷,退出程序:借助:builtin包下內置函數:panic