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 语言数据结构。