当前位置: 首页 > news >正文

【Go】P3 Go语言程序结构

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函数之前自动执行
  • 执行顺序按照它们在源文件中出现的顺序

执行顺序

当程序运行时,执行顺序如下:

  1. 包级变量初始化:loadConfig()被调用,config变量被初始化
  2. 第一个init函数:输出"第一个init",执行setupLogging()
  3. 第二个init函数:输出"第二个init",执行connectDatabase()
  4. 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 语言数据结构。

http://www.dtcms.com/a/317808.html

相关文章:

  • 从零开始学AI——12.1
  • 数字驾驶舱是什么意思?如何搭建驾驶舱
  • Java 集合工具类
  • Solidity:接口与实现的“契约”关系研究,以Uniswap V3为例
  • 《算法导论》第 6 章 - 堆排序
  • MCP-PromptX AI小说创作使用教程
  • Linux(17)——Linux进程信号
  • C++ STL--> vector的模拟实现!
  • smart-water表设计方案
  • jdk-24的安装及环境变量配置
  • LazyLLM教程 | 第3讲:大模型怎么玩:用LazyLLM带你理解调用逻辑与Prompt魔法!
  • 【前端开发】四. JS内置函数
  • 芯片封装(DIP、SOP、QFP、QFN、BGA、LGA、PGA)
  • C++音视频流媒体开发面试题:音视频基础
  • OceanBase DBA实战营2期--自动分区分裂学习笔记
  • 机器翻译:语料库的定义与获取,及语料预处理
  • 安宝特方案丨工业AR+AI质检方案:致力于提升检测精度与流程效率
  • 无人机航拍数据集|第6期 无人机垃圾目标检测YOLO数据集772张yolov11/yolov8/yolov5可训练
  • LeetCode 分类刷题:611. 有效三角形的个数
  • 阿里云 Flink
  • 稀土新贵醋酸镥:高纯度材料的科技密码
  • 机器人定位装配的精度革命:迁移科技如何重塑工业生产价值
  • [特殊字符]企业游学 | 探秘字节,解锁AI科技新密码
  • 智慧养老破局:科技如何让“老有所养”变成“老有优养”?
  • 加载量化模型
  • 7.3 I/O方式 (答案见原书 P315)
  • HashMap 与 ConcurrentHashMap 深度解析
  • Java Stream (二)
  • 【模电笔记】—— 直流稳压电源——稳压电路
  • 从“T+1”到“T+0”:基于SQL构建MES到数据仓库的数据采集通道