golang-基础-Go语言入门
一、一个例子: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.go
main
包可以访问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)。
- 命名清晰(遵循驼峰/蛇形规则)。
- 少即是多(避免复杂语法,如无继承、泛型)。
- 感谢你赐予我前进的力量