Go語言程序結構
- Go語言程序結構
- 命名規則與編程慣例
- 核心規則
- 四種聲明語句詳解
- var聲明:變量聲明
- const聲明:常量聲明
- type聲明:類型定義
- func聲明:函數聲明
- 簡短變量聲明(:=)
- 使用規則和限制
- 指針:安全的內存地址操作
- 基本概念和操作
- 結構體指針的自動解引用
- new函數與內存分配
- new vs make的區別
- 變量生命周期與內存管理
- 生命周期規則
- 賦值操作與元組賦值
- 包管理與文件組織
- 包聲明與導入
- 包的初始化與init函數
- 執行順序
- 作用域規則詳解
- 作用域層級
- 變量遮蔽示例
Go語言程序結構
命名規則與編程慣例
Go語言通過簡潔的命名規則實現了代碼的清晰性和可維護性。
核心規則
導出機制:首字母大小寫決定標識符的可見性
- 首字母大寫:導出的(公開),包外可訪問
- 首字母小寫:未導出的(私有),僅包內可訪問
- 例如:
fmt
包的Printf
函數就是導出的,可以在fmt包外部訪問
命名風格:采用駝峰命名法,避免下劃線
package main// 導出的變量和函數(首字母大寫)
var PublicVar int = 100
func PublicFunction() {fmt.Println("可以被其他包調用")
}// 未導出的變量和函數(首字母小寫)
var privateVar string = "private"
func privateFunction() {fmt.Println("僅本包內可用")
}// 良好的命名示例
var userName string // 駝峰命名
var HTTPClient *http.Client // 縮寫詞保持大寫
const MaxConnections = 100 // 常量
實踐要點:
- 包名使用小寫單詞,簡潔明了
- 常量名要有意義,不基于數值命名
- 縮寫詞保持一致的大小寫(URL、HTTP、JSON)
四種聲明語句詳解
Go語言提供四種聲明語句,每種都有特定的用途和語法規則。
var聲明:變量聲明
// 基本語法
var age int // 聲明,使用零值
var name string = "Go" // 聲明并初始化
var score = 95.5 // 類型推斷// 批量聲明
var (width int = 100height int = 200title string = "Go編程"
)// 多變量聲明
var x, y int = 10, 20
零值機制:未初始化變量自動設置為類型對應的零值
- 數值類型:0
- 布爾類型:false
- 字符串:“”
- 指針、切片、映射:nil
const聲明:常量聲明
// 基本常量
const Pi = 3.14159
const AppName string = "MyApp"// 常量組
const (StatusOK = 200StatusError = 500StatusNotFound = 404
)// 無類型常量的威力
const (Big = 1 << 100 // 1 << 100 表示將數字 1 向左位移 100 位,相當于計算 2^100Small = Big >> 99 // Big >> 99 表示將 Big 向右位移 99 位,右移 99 位后得到 2^100 / 2^99 = 2^1 = 2
)
type聲明:類型定義
// 定義新類型(具有新的方法集)
type Celsius float64
type UserID int// 為新類型添加方法
func (c Celsius) String() string {return fmt.Sprintf("%.1f°C", c)
}// 類型別名(Go 1.9+)
type StringSlice = []string // 完全等價于[]string// 復雜類型定義
type Person struct {Name stringAge int
}type Handler func(http.ResponseWriter, *http.Request)
func聲明:函數聲明
// 基本函數
func add(a, b int) int {return a + b
}// 多返回值
func divmod(dividend, divisor int) (quotient, remainder int) {quotient = dividend / divisorremainder = dividend % divisorreturn // 命名返回值可省略return后的變量名
}// 錯誤處理模式
func divide(a, b float64) (float64, error) {if b == 0 {return 0, errors.New("除數不能為零")}return a / b, nil
}// 變參函數
func sum(numbers ...int) int { //numbers 是一個可變參數,意味著這個函數可以接受任意數量的 int 類型參數total := 0for _, num := range numbers {total += num}return total
}
簡短變量聲明(:=)
:=
是Go語言的語法糖,讓變量聲明更加簡潔。
使用規則和限制
func example() {// 基本使用name := "Go語言" // 等價于 var name = "Go語言"count := 42 // 類型推斷為int// 多變量聲明x, y := 10, 20// 處理函數返回值result, err := strconv.Atoi("123") // strconv.Atoi() 是Go標準庫中的函數,用于將字符串轉換為整數(ASCII to Integer的縮寫)if err != nil {log.Fatal(err)}// 重新聲明(至少一個新變量)result, status := calculate(), true // result被重新聲明
}
關鍵限制:
- 只能在函數內部使用
- 至少要聲明一個新變量
- 會產生變量遮蔽問題
指針:安全的內存地址操作
Go語言的指針比C語言更安全,不支持指針運算。
基本概念和操作
func pointerExample() {// 基本指針操作x := 42p := &x // p是指向x的指針fmt.Println("x的值:", x) // 42fmt.Println("x的地址:", p) // 0x... 內存地址fmt.Println("指針指向的值:", *p) // 42// 通過指針修改值*p = 100fmt.Println("修改后x的值:", x) // 100// 指針的零值var ptr *intfmt.Println("指針零值:", ptr) // <nil>fmt.Println("是否為nil:", ptr == nil) // true
}// 指針作為函數參數實現引用傳遞
func swap(x, y *int) {*x, *y = *y, *x
}func main() {a, b := 10, 20fmt.Printf("交換前: a=%d, b=%d\n", a, b)swap(&a, &b)fmt.Printf("交換后: a=%d, b=%d\n", a, b)
}
結構體指針的自動解引用
type Person struct {Name stringAge int
}func structPointerExample() {p := &Person{"Alice", 30}// 以下兩種寫法等價(自動解引用)p.Age = 31 // 簡潔寫法(*p).Name = "Bob" // 顯式解引用
}
Person{"Alice", 30}
:創建一個Person實例,Name為"Alice",Age為30。&
:取地址操作符,獲取該實例的內存地址p
:是一個指向Person的指針變量
new函數與內存分配
new函數用于分配內存并返回指向零值的指針。
new vs make的區別
// new: 分配零值內存,返回指針
func newExample() {p := new(int) // 分配一塊內存來存儲 int 類型的值fmt.Println(*p) // 0 (int的零值)*p = 42// 等價寫法var x intp2 := &x
}// make: 用于slice、map、channel的初始化
func makeExample() {// slices := make([]int, 5) // 長度為5的slices2 := make([]int, 5, 10) // 長度5,容量10// mapm := make(map[string]int)m["key"] = 42// channelc := make(chan int) // 無緩沖channelc2 := make(chan int, 5) // 緩沖區大小為5
}
-
p := new(int)
等價于var x int; p2 := &x
-
make
slice 中長度與容量的定義:s := make([]int, 3, 8) // 底層數組: [0, 0, 0, _, _, _, _, _] // |<-長度3->|<--容量8-->| // 可訪問部分 總共可用空間
-
緩沖區是channel內部用來臨時存儲數據的空間
ch := make(chan int, 3) // 可以存儲3個值的緩沖區 ch <- 1 // 不阻塞 ch <- 2 // 不阻塞 ch <- 3 // 不阻塞 ch <- 4 // 這里會阻塞,因為緩沖區已滿
無緩沖channel:適合需要嚴格同步的場景,如等待goroutine完成
帶緩沖channel:適合生產者-消費者模式,可以提高程序性能和解耦
緩沖區本質上就是一個先進先出(FIFO)的隊列,用來在發送方和接收方之間臨時存儲數據。
變量生命周期與內存管理
Go的垃圾回收器自動管理內存,但理解變量生命周期有助于寫出更高效的代碼。
生命周期規則
var globalVar = "全局變量" // 程序整個生命周期func lifeCycleExample() {localVar := "局部變量" // 函數執行期間// 變量逃逸:局部變量返回后仍被引用p := &localVarreturn p // localVar逃逸到堆上
}func memoryExample() {// 棧分配:函數內局部變量x := 42// 堆分配:new創建或變量逃逸p := new(int)// slice在堆上分配底層數組s := make([]int, 1000)// 當這些變量不再被引用時,GC會回收內存
}
賦值操作與元組賦值
Go支持簡潔的多重賦值語法。
func assignmentExample() {// 基本賦值x := 10x = 20// 元組賦值:變量交換a, b := 1, 2a, b = b, a // 一行完成交換// 函數多返回值賦值quotient, remainder := divmod(17, 5) //divmod 除法// 使用空白標識符忽略不需要的值// strconv.Atoi() 函數將字符串轉換為整數,返回兩個值:轉換后的整數和可能的錯誤_, err := strconv.Atoi("123") // 忽略轉換結果value, _ := strconv.Atoi("456") // 忽略錯誤// 結構體字段賦值type Point struct { X, Y int }var p Pointp.X, p.Y = 10, 20
}
包管理與文件組織
Go程序由包組成,包是代碼組織和復用的基本單元。
包聲明與導入
// main.go - 主程序包
package mainimport ("fmt" // 標準庫"net/http" // 標準庫子包"github.com/gin-gonic/gin" // 第三方包// 導入別名f "fmt"h "net/http"// 匿名導入(僅執行init函數)_ "github.com/lib/pq"
)func main() {f.Println("使用別名導入")
}
// utils/helper.go - 工具包
package utilsimport "strings"// 導出函數(首字母大寫)
func FormatName(name string) string {return strings.Title(strings.ToLower(name))
}// 未導出函數(首字母小寫)
func internalHelper() {// 僅包內使用
}
包的初始化與init函數
package mainimport "fmt"// 包級變量初始化(按依賴順序)
var config = loadConfig()// init函數:在main前執行
func init() {fmt.Println("第一個init")setupLogging()
}func init() {fmt.Println("第二個init")connectDatabase()
}func main() {fmt.Println("main函數執行")
}
- Go允許在同一個包中定義多個init函數
- 這些函數會在main函數之前自動執行
- 執行順序按照它們在源文件中出現的順序
執行順序
當程序運行時,執行順序如下:
- 包級變量初始化:loadConfig()被調用,config變量被初始化
- 第一個init函數:輸出"第一個init",執行setupLogging()
- 第二個init函數:輸出"第二個init",執行connectDatabase()
- main函數:最后執行,輸出"main函數執行"
作用域規則詳解
Go語言有清晰的作用域層次結構。
作用域層級
package main // 包作用域開始import "fmt" // fmt在文件作用域var globalVar = "包級變量" // 包作用域func scopeExample() { // 函數作用域開始var functionVar = "函數變量"if true { // 塊作用域開始var blockVar = "塊變量"fmt.Println(globalVar, functionVar, blockVar)// 變量遮蔽globalVar := "局部變量遮蔽全局變量"fmt.Println(globalVar) // 打印局部變量} // 塊作用域結束// fmt.Println(blockVar) // 錯誤:blockVar超出作用域fmt.Println(globalVar) // 訪問包級變量
}func anotherFunction() {fmt.Println(globalVar) // 可以訪問包級變量// fmt.Println(functionVar) // 錯誤:無法訪問其他函數的變量
}
變量遮蔽示例
var message = "全局消息"func shadowExample() {fmt.Println(message) // "全局消息"message := "函數消息" // 遮蔽全局變量fmt.Println(message) // "函數消息"{message := "塊消息" // 遮蔽函數變量fmt.Println(message) // "塊消息"}fmt.Println(message) // "函數消息"
}
下一篇博文,將以結構化分享 go 語言數據結構。