golang-基础-Go语言入门
本文最后更新于 2025-10-16,文章内容可能已经过时。
一、一个例子:Hello World!
package main
import "fmt"
func main() {
fmt.Println("Hello World,又是一只Gopher!")
}
- 包声明:package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
- 在完成包的 import 之后,开始对常量、变量和类型的定义或声明。
- 引入包:使用import圆括号进行引入包。
- 引入标准包
- 引入第三方包
二、Go语言关键字
关键字是一些特殊的用来帮助编译器理解和解析源代码的单词。Go语言中有25个关键字或保留字。关键字只能用在程序语法允许的地方,不能作为名称使用。
Go语言(Golang)共有 25 个关键字,以下是全部关键字的列表:
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
关键点说明:
-
关键字数量:
Go语言严格保留了 25 个关键字,这些关键字不能用作变量名、函数名或其他标识符。
-
分类与用途:
- 流程控制:
break、case、continue、default、defer、fallthrough、for、goto、if、else、select、switch。 - 数据类型与结构:
chan、map、struct、type。 - 函数与包:
func、package、import、return。 - 并发:
go(启动协程)、select(多通道操作)。 - 其他:
const(常量)、var(变量)、range(遍历)、interface(接口)等。
- 流程控制:
-
注意事项:
- 区分大小写:例如
break和Break是不同的标识符。 - 预定义标识符:如
append、make、len等不属于关键字,而是预定义的函数或变量。 - 特殊“伪关键字”:如
notwithstanding、thetruthofthematter等,这些是编译器忽略的“彩蛋”,不具有实际语法功能,不应算作关键字。
- 区分大小写:例如
三、预定义标识符
在 Go 语言中,预定义标识符(predeclared identifiers)是语言中预先定义的特殊标识符,它们具有特定的用途和含义,不能用作普通变量名、函数名等标识符(尽管技术上可以覆盖,但不建议)。以下是 Go 语言中预定义标识符的分类和详细说明:
1. 常量(Constants)
预定义的常量用于表示固定值:
true和false:布尔类型的真值和假值。iota:常量生成器,用于定义递增的整数序列。nil:表示空值,用于指针、切片、映射、通道、接口等类型的零值。
示例:
const (
a = iota // a = 0
b // b = 1
c // c = 2
)
2. 类型(Types)
预定义的类型标识符用于声明变量或数据结构:
- 基本类型:
bool(布尔类型)string(字符串类型)int、int8、int16、int32、int64(有符号整数)uint、uint8、uint16、uint32、uint64(无符号整数)float32、float64(浮点数)complex64、complex128(复数)byte(等同于uint8)rune(等同于int32,用于 Unicode 字符)
- 其他类型:
uintptr(无符号指针类型)error(错误类型,定义在fmt包中)
示例:
var age int = 25
var name string = "Alice"
3. 函数(Functions)
预定义的函数用于执行特定操作:
len:返回集合(字符串、数组、切片、映射、通道)的长度。cap:返回切片或通道的容量。make:创建切片、映射或通道。new:分配内存并返回指向该内存的指针。append:向切片追加元素。copy:复制切片元素。close:关闭通道。delete:删除映射中的键值对。panic和recover:用于异常处理。
示例:
slice := make([]int, 3) // 创建一个长度为3的切片
slice = append(slice, 4) // 追加元素
length := len(slice) // 返回4
capacity := cap(slice) // 返回切片的容量
4. 包(Packages)
预定义的包是 Go 标准库的一部分,提供了常用功能:
fmt:格式化输入输出(如Println、Printf)。os:操作系统相关功能(如文件操作、环境变量)。math:数学函数(如Sin、Sqrt)。strings:字符串处理(如Split、Trim)。time:时间和日期操作。reflect:反射操作(如获取变量类型)。
示例:
import "fmt"
func main() {
fmt.Println("Hello, World!") // 使用预定义的 fmt 包
}
5. 其他预定义标识符
defer:延迟执行函数(在函数返回前执行)。go:启动一个并发的 goroutine。package:定义包的名称。import:导入其他包。interface{}:空接口(可以表示任何类型)。
示例:
defer fmt.Println("This runs after the function exits") // defer 的使用
go func() { fmt.Println("Running in a goroutine") }() // 启动 goroutine
6. 特殊标识符
_(空标识符):用于忽略某些返回值或占位。nil:表示空值(如未初始化的指针、切片、映射等)。
示例:
_, err := os.Open("file.txt") // 忽略第一个返回值
if err != nil {
panic(err)
}
注意事项
- 不可重定义:虽然技术上可以覆盖预定义标识符(如
var nil = 10),但强烈不建议这样做,会导致代码难以理解和维护。 - 区分大小写:Go 是严格区分大小写的语言,预定义标识符的大小写必须正确。
- 导出规则:如果标识符首字母大写,则对外部包可见(导出);小写则仅在包内可见。
Go 语言的预定义标识符是语言核心的一部分,涵盖了类型、常量、函数、包等基础功能。掌握它们的用法是编写高效、安全 Go 代码的关键。建议遵循最佳实践,避免覆盖预定义标识符,并合理使用标准库中的功能。
四、可见性规则
Go语言的可见性规则是其设计中的核心特性之一,通过标识符的首字母大小写来控制代码的可见性,而非使用显式的访问修饰符(如public、private)。以下是详细的规则和设计哲学解析:
4.1、可见性规则的核心机制
-
标识符的可见性由首字母决定:
- 大写字母开头(如
PublicVar、PublicFunc()):公开(Exported),可被其他包访问。 - 小写字母开头(如
privateVar、privateFunc()):私有(Unexported),仅在当前包内可见。
// 包级别标识符 var PublicVar = "可被外部包访问" // 公开 var privateVar = "仅本包可见" // 私有 // 函数可见性 func PublicFunc() {} // 公开 func privateFunc() {} // 私有 // 类型可见性 type PublicStruct struct { // 公开类型 ExportedField string // 公开字段 hiddenField int // 私有字段 } type privateStruct struct { // 私有类型 Field string } - 大写字母开头(如
-
作用域规则:
- 包内可见性:同一包内的所有标识符(无论大小写)均可自由访问。
- 跨包可见性:只有大写字母开头的标识符可被外部包访问。
4.2、可见性作用域详解
1. 包内可见性
-
同一包内的所有文件共享可见性,无论标识符大小写。
-
示例:跨文件访问私有变量:
// file1.go package mypkg var internalCounter = 0 func Increment() { internalCounter++ // 同一包内可访问私有变量 } // file2.go package mypkg func GetCount() int { return internalCounter // 跨文件访问有效 }
2. 跨包可见性
-
外部包只能访问大写字母开头的标识符。
-
示例:外部包使用公开成员:
import "mypkg" func main() { mypkg.PublicVar // 有效 mypkg.PublicFunc() // 有效 mypkg.privateVar // 编译错误:不可见 }
4.3、反射与可见性规则
Go的反射机制(reflect包)受可见性规则限制:
-
未导出字段(小写开头):
-
反射可以遍历所有字段,但无法获取或修改未导出字段的值(除非在定义该结构体的包内)。
-
示例:
type User struct { name string // 未导出字段 Age int // 导出字段 } val := reflect.ValueOf(User{}) fieldVal := val.FieldByName("name") fmt.Println(fieldVal.CanSet()) // 输出 false
-
-
CanSet 方法:
- 仅当字段是导出的且值可寻址时,
CanSet()返回true。 - 未导出字段或不可寻址的值均无法修改。
- 仅当字段是导出的且值可寻址时,
-
处理未导出字段的推荐方式:
- 通过 Getter/Setter 方法:封装操作逻辑。
- 使用
unsafe包:高级场景下手动读写内存,但会破坏类型安全。
4.4、Internal 包机制
Go 通过 internal 目录实现更细粒度的访问控制:
-
规则:
- 将包放在
internal目录下,仅允许直接父级包访问。 - 上层包(祖先包)或外部包无法访问。
- 将包放在
-
示例:
project/ ├── main/ │ └── main.go └── internal/ └── utils/ └── helper.gomain包可以访问internal/utils。- 其他包(如
project/external)无法访问internal/utils。
4.5、设计哲学
Go 的可见性规则体现了其核心设计思想:
- 简单性:
- 无需复杂的修饰符,仅通过大小写即可明确标识符的可见性。
- 封装与信息隐藏:
- 限制外部访问内部实现细节,降低耦合性。
- 清晰性:
- 命名约定直接表达代码意图,提升可读性。
- 模块化:
- 通过包和
internal机制,实现代码的高内聚、低耦合。
- 通过包和
4.6、常见问题与注意事项
- 包名必须小写:
- 包名(
package)始终使用小写,与文件路径无关。 - 示例:
package fmt(标准库)。
- 包名(
- 导出标识符的命名规范:
- 推荐使用驼峰命名法(如
MyStruct、ExportedField)。
- 推荐使用驼峰命名法(如
- 反射绕过限制的条件:
- 仅在定义结构体的包内可通过反射访问未导出字段。
通过以上规则,Go 语言实现了简洁而强大的可见性控制,既保证了代码的安全性,又符合其“少即是多”的设计理念。
五、命名规范
以下是 Go 语言(Golang) 的命名规范总结,结合官方建议和社区最佳实践整理:
1. 包名(Package Name)
-
命名规则:
- 使用小写字母,单数形式,避免复数(如
users❌,应使用user✅)。 - 简短且具有描述性,避免冗余或模糊的名称(如
common、utils❌)。 - 包名与目录名保持一致(如包名
user,目录名也应为user)。 - 避免使用下划线(
_)或驼峰命名(MixedCase)。 - 标准库命名参考(如
time、http、fmt)。
- 使用小写字母,单数形式,避免复数(如
-
示例:
// 正确 package user package httpclient // 错误 package Users // 复数 ❌ package UserUtils // 驼峰 ❌
2. 函数名(Function Name)
-
命名规则:
- 使用 驼峰命名法(
CamelCase),首字母大写表示公开函数,小写表示私有函数。 - 函数名应简洁且明确,优先使用动词(如
CreateUser、CalculateSum)。 - 返回布尔值的函数以
Is、Has、Can开头(如IsEmpty()、HasPermission())。 - 避免冗余(如包名已隐含上下文时,可简化函数名)。
- 使用 驼峰命名法(
-
示例:
// 正确 func GetUserInfo(id int) (*User, error) func IsEmpty(str string) bool func ValidateInput(data interface{}) error // 错误 func GetUserByID(userID int) // 包名已为 "user" 时,"GetUser" 更简洁 ✅ func ValidateData(inputData interface{}) // 冗余 ❌
3. 变量名(Variable Name)
-
命名规则:
- 使用 小写字母和下划线(
snake_case)。 - 变量名应清晰描述用途,避免单字母(如
i作为循环索引除外)。 - 结构体字段和公开变量首字母大写,私有变量首字母小写。
- 使用 小写字母和下划线(
-
示例:
// 正确 var userCount int var startTime time.Time var userAge int // 错误 var UserCount int // 非结构体字段无需大写 ❌ var a int // 单字母 ❌
4. 接口名(Interface Name)
-
命名规则:
- 单方法接口以
er结尾(如Reader、Writer)。 - 多方法接口使用功能描述的名词,避免简单拼接
er(如UserService而非UserHandlerer)。 - 实现接口的结构体通常不加
Impl后缀(直接实现接口即可)。
- 单方法接口以
-
示例:
// 正确 type Reader interface { Read(p []byte) (int, error) } type UserService interface { Create(user *User) error Get(id int) (*User, error) } // 错误 type UserServiceImpl struct{} // 无需 Impl 后缀 ❌
5. 结构体名(Struct Name)
-
命名规则:
- 使用 驼峰命名法(
CamelCase),首字母大写。 - 名称应为名词或名词短语(如
User、UserProfile)。
- 使用 驼峰命名法(
-
示例:
// 正确 type User struct { ID int Name string } // 错误 type user struct{} // 私有结构体需首字母小写 ✅,但公开结构体需大写 ❌
6. 常量名(Constant Name)
-
命名规则:
- 使用 全大写字母和下划线(
UPPER_SNAKE_CASE)。 - 常量名应清晰描述其用途。
- 使用 全大写字母和下划线(
-
示例:
// 正确 const MaxRetries = 3 const DefaultTimeout = 5 * time.Second // 错误 const maxRetries = 3 // 非常量命名 ❌
7. 错误类型名(Error Name)
在 Go 语言中,错误类型(自定义错误类型)的命名规范与 错误变量(具体错误实例)的命名规范是不同的。以下是详细说明:
1. 错误类型(自定义错误类型)
-
命名规则:
自定义错误类型通常以
Error结尾(复数形式),表示这是一个错误类型。
示例:type InvalidInputError struct{} // 错误类型 type UserNotFoundError struct{} // 错误类型 -
原因:
- 这是 Go 社区的惯例,与标准库的错误类型命名保持一致(如
json.SyntaxError、os.PathError)。 - 类型名的后缀
Error明确表示这是一个错误类型,而非普通结构体。
- 这是 Go 社区的惯例,与标准库的错误类型命名保持一致(如
2. 错误变量(具体错误实例)
-
命名规则:
具体错误变量通常以
Err开头(单数形式),表示这是一个具体的错误值。
示例:var ErrInvalidInput = errors.New("invalid input") // 错误变量 var ErrUserNotFound = errors.New("user not found") // 错误变量 -
原因:
- 这是 Go 社区的惯例,与标准库的错误变量命名保持一致(如
os.ErrNotExist、json.ErrSyntax)。 - 变量名的前缀
Err明确表示这是一个错误值,而非普通变量。
- 这是 Go 社区的惯例,与标准库的错误变量命名保持一致(如
8. 测试文件与函数名
-
命名规则:
- 测试文件以
_test.go结尾(如user_test.go)。 - 测试函数以
Test开头,后接测试目标(如TestCreateUser_EmptyInput)。 - 示例命名:
TestFunctionName_TestCase。
- 测试文件以
-
示例:
// user_test.go func TestCreateUser_EmptyInput(t *testing.T) { // 测试逻辑 }
9. 其他规范
-
文件名:
- 使用小写字母和短横线(
kebab-case),避免驼峰(如http-client.go❌,应为http_client.go✅)。 - 文件名应简洁,通常包含功能描述(如
main.go、user_service.go)。
- 使用小写字母和短横线(
-
代码格式:
- 使用
gofmt工具自动格式化代码。 - 遵循官方代码风格指南(Effective Go)。
- 使用
| 元素 | 命名规则 |
|---|---|
| 包名 | 小写、单数、无下划线(如 user) |
| 函数名 | 驼峰、动词开头、布尔返回值以 Is/Has/Can 开头 |
| 变量名 | 小写、下划线、描述性(如 user_count) |
| 接口名 | 单方法以 er 结尾,多方法用功能描述(如 UserService) |
| 结构体名 | 驼峰、名词(如 UserProfile) |
| 常量名 | 全大写、下划线(如 MAX_RETRIES) |
| 错误名 | 以 Err 结尾(如 ErrInvalidInput) |
| 测试文件/函数 | 文件以 _test.go 结尾,函数以 Test 开头(如 TestCreateUser_EmptyInput) |
遵循这些规范可以提升代码的可读性、一致性和可维护性,同时符合 Go 社区的主流实践。
六、语法惯例
以下是 Go 语言(Golang) 的语法惯例总结,结合官方文档和社区最佳实践整理,涵盖代码风格、变量、函数、控制结构、并发编程等核心内容:
1. 代码格式与结构
- 自动格式化:
- 使用
gofmt工具自动格式化代码,确保团队协作时风格统一。 - 示例:
gofmt -w main.go。
- 使用
- 缩进与空格:
- 使用 Tab 缩进(而非空格)。
- 运算符两侧保留空格(如
a + b而非a+b)。 - 每行不超过 80 字符(可换行保持优雅)。
2. 变量与常量
-
变量声明:
- 显式声明:
var name string = "Alice"。 - 简短声明(仅限函数内部):
age := 25。 - 多变量声明:
x, y := 10, 20。
- 显式声明:
-
常量:
-
使用
const声明,常量名全大写 + 下划线(MAX_RETRY)。 -
示例:
const ( Pi = 3.14159 Limit = 100 )
-
3. 函数
-
函数定义:
-
使用
func关键字,参数和返回值类型在变量名后。 -
支持多返回值(常用于错误处理)。
-
示例:
func Add(a, b int) (int, error) { return a + b, nil }
-
-
命名返回值:
-
可为返回值命名,简化返回逻辑。
func Divide(a, b float64) (result float64, err error) { if b == 0 { err = errors.New("division by zero") return } result = a / b return }
-
4. 控制结构
-
if/else:
-
支持初始化语句,适合局部变量。
-
示例:
if x := compute(); x > 0 { fmt.Println("Positive") } else { fmt.Println("Non-positive") }
-
-
switch:
-
自动
break,无需显式fallthrough。 -
示例:
switch value { case 1: fmt.Println("One") case 2: fmt.Println("Two") default: fmt.Println("Unknown") }
-
-
for 循环:
-
Go 仅支持
for循环,通过条件模拟while。 -
示例:
// 计数循环 for i := 0; i < 5; i++ { fmt.Println(i) } // 条件循环 for count < 10 { count++ }
-
5. 错误处理
-
显式错误处理:
-
函数通常返回
(result, error),调用方需检查error。 -
示例:
file, err := os.Open("file.txt") if err != nil { log.Fatal(err) } defer file.Close()
-
-
panic/recover:
-
仅在不可恢复的异常时使用
panic,通过recover捕获。 -
示例:
func safeDivide(a, b float64) (float64, error) { defer func() { if r := recover(); r != nil { fmt.Println("Recovered from panic:", r) } }() if b == 0 { panic("division by zero") } return a / b, nil }
-
6. 包管理与导出
-
包名(Package Name):
- 小写字母,单数形式(如
user而非users)。 - 包名与目录名一致。
- 小写字母,单数形式(如
-
导出标识符:
-
首字母大写表示公开(导出),小写表示私有。
-
示例:
package user // 公开结构体 type User struct { ID int Name string } // 私有变量 var internalCounter int
-
7. 并发编程
-
Goroutine:
-
使用
go关键字启动并发任务。 -
示例:
go func() { fmt.Println("Running concurrently") }()
-
-
Channel:
-
用于 Goroutine 间通信,支持双向或单向。
-
示例:
ch := make(chan int) go func() { ch <- 42 // 发送数据 }() value := <-ch // 接收数据 fmt.Println(value)
-
8. 注释与文档
-
行注释:
-
使用
//,用于单行注释。 -
示例:
// 计算两个数的和 func Add(a, b int) int { return a + b }
-
-
块注释:
-
使用
/* */,用于多行注释或包文档。 -
示例:
/* * Package math 提供数学运算函数 */ package math
-
9. 数据结构
-
结构体(Struct):
-
定义自定义类型,字段首字母大写表示导出。
-
示例:
type Rectangle struct { Width float64 Height float64 }
-
-
切片(Slice)与映射(Map):
- 动态数组:
slice := []int{1, 2, 3}。 - 键值对:
m := map[string]int{"a": 1, "b": 2}。
- 动态数组:
10. 代码风格与最佳实践
- 命名规范:
- 变量:小写 + 下划线(
user_count)。 - 函数:驼峰(
CalculateSum),布尔函数以Is/Has/Can开头。 - 接口:单方法接口以
er结尾(如Reader)。
- 变量:小写 + 下划线(
- 避免冗余:
- 不使用
this或self,方法接收者直接用func (u User)。
- 不使用
- 错误优先:
- 错误处理放在前面(如
if err != nil)。
- 错误处理放在前面(如
11.总结
Go 语言的语法惯例强调简洁性、一致性与可读性,核心原则包括:
- 自动格式化(
gofmt)。 - 显式错误处理(避免异常)。
- 并发优先(Goroutine + Channel)。
- 命名清晰(遵循驼峰/蛇形规则)。
- 少即是多(避免复杂语法,如无继承、泛型)。
- 感谢你赐予我前进的力量

