第二章 函数与结构体 v1.0
第一部分 函数
一、基本形式
func 函数名(参数列表) 返回值类型{
函数体
return 返回值
}
func 函数名(参数列表) (返回值类型列表){
函数体
return 返回值列表
} //一般业务返回值有两个,第二个是错误信息
二、指针
指针在Go中可以用于修改传入的参数值
func 函数名(*参数名) 返回值类型{
函数体
return 返回值
} //用指针可修改传入参数值
三、函数处理
package main
import "fmt"
func add(nums ...int) int {
sum := 0
for _, num := range nums {
sum += num
}
return sum
}
func main() {
fmt.Println(add(1, 2)) // 输出: 3
fmt.Println(add(1, 2, 3, 4)) // 输出: 10
}
package main
import "fmt"
func add(a interface{}, b interface{}) interface{} {
switch a.(type) {
case int:
return a.(int) + b.(int)
case float64:
return a.(float64) + b.(float64)
default:
return nil
}
}
func main() {
fmt.Println(add(1, 2)) // 输出: 3
fmt.Println(add(1.1, 2.2)) // 输出: 3.3
}
四、接口
Go语言中,接口是一种抽象类型,定义一组方法,用于实现多态和解耦合。
1.定义
type 接口名 interface {
方法名1() 方法返回值1
方法名2() 方法返回值2
}
type Speaker interface {
Speak() string
}
2.实现接口
Go语言中没有显式的implements关键字,接口的实现是隐式的,任何类型只要实现了接口中定义的所有方法,就被认为实现了该接口
type Person struct {
Name string
}
func (p Person) Speak() string {
return "Hello, my name is " + p.Name
}
3.使用接口
接口类型变量可以存储任何实现了该接口的值,以此实现多态与解耦
func Greet(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
p := Person{Name: "Alice"}
Greet(p) // 输出: Hello, my name is Alice
}
4.空接口
Go语言中,空接口interface{}
是一个特殊的接口,它没有定义任何方法,因此所有类型都实现了空接口,常用于存储任意类型的值
func PrintAnything(v interface{}) { // 该函数接收任意类型变量并将变量值输出到标准输出
fmt.Println(v)
}
func main() {
PrintAnything(42) // 输出: 42
PrintAnything("Hello") // 输出: Hello
PrintAnything(3.14) // 输出: 3.14
}
5.类型断言
类型断言能够将接口类型的变量转换为具体类型
value, ok := 待转换变量.(待转换类型) // ok 为 boolean类型值,表示是否断言转换成功
func main() {
var i interface{} = "Hello"
s, ok := i.(string)
if ok {
fmt.Println(s) // 输出: Hello
} else {
fmt.Println("Type assertion failed")
}
}
6.类型选择
一种特殊的 switch 语句,用于根据接口变量的具体类型执行不同的分支
switch 变量标识 := 变量值.(type) {
case 变量类型1: // 变量值属于 变量类型1,执行操作1
操作1
...
default: // 不属于以上所有类型,执行操作n
操作n
}
func TypeSwitchDemo(i interface{}) {
switch v := i.(type) {
case int:
fmt.Println("Integer:", v)
case string:
fmt.Println("String:", v)
default:
fmt.Println("Unknown type")
}
}
func main() {
TypeSwitchDemo(42) // 输出: Integer: 42
TypeSwitchDemo("Hello") // 输出: String: Hello
TypeSwitchDemo(3.14) // 输出: Unknown type
}
第二部分 结构体
一、结构体的定义与使用
1.结构体定义
type 结构体名 struct{
成员变量名1 成员变量数据类型1 [`json:"json对应变量名"`] // 添加结构体标签,常用于json解析
成员变量名2 成员变量数据类型2
...
}
2.结构体使用
变量名 := 结构体名{成员变量名1:变量值1, 成员变量名2:变量值2, ...} //显式指定赋值
变量名 := 结构体名{变量值1, 变量值2, ...} //按照顺序依次赋值给各成员变量
变量名 := new(结构体名) // 不指定初始值,使用new关键字零值实例化
var 变量名 结构体名 // 不指定初始值,使用var关键字零值实例化
结构体变量名.成员变量名 // 访问结构体实例字段
结构体变量名.成员变量名 = 新值 // 修改结构体实例字段
结构体成员变量不能直接指定默认值,默认值是由Go语言的零值机制决定的。
变量没有被显式初始化时,Go语言会自动为其赋予一个默认值,例如:
- 数字类型(如int、float)的零值是0。
- 布尔类型的零值是false。
- 字符串类型的零值是空字符串""。
- 指针、切片、映射、通道、接口的零值是nil。
3.匿名字段
Go语言支持匿名字段(嵌入字段),允许将一个结构体嵌入到另一个结构体中
type Address struct {
City string
ZipCode string
}
type Person struct {
Name string
Age int
Address
}
func main() {
p := Person{
Name: "Alice",
Age: 30,
Address: Address{
City: "New York",
ZipCode: "10001",
},
}
fmt.Println(p.City) // 直接访问嵌入的字段
}
二、结构体方法
go语言中没有像类的概念,但是可以通过结构体struct实现面向对象编程。使用结构体方法使结构体可以做到类似于其他语言的类的形式,使结构体可以调用其成员方法。
1.结构体方法
func (结构体变量名 结构体名) 成员方法名(参数列表) 返回值类型{
函数体
}
type Config struct {
Host string
Port int
}
func (c *Config) SetDefaults() {
if c.Host == "" {
c.Host = "localhost"
}
if c.Port == 0 {
c.Port = 8080
}
}
2.构造方法
为结构体成员变量指定默认值,可以通过定义一个构造函数来实现
func 构造函数名(参数列表) *结构体类型名{
return &结构体类型名{
参数名: 初始值,
...
}
}
type Config struct {
Host string
Port int
}
func NewConfig(host string, port int) *Config {
return &Config{
Host: host,
Port: port,
}
}
func main() {
// 使用构造函数创建结构体实例
config := NewConfig("example.com", 9090)
fmt.Printf("Host: %s, Port: %d\n", config.Host, config.Port) # 输出:Host: example.com, Port: 9090
}
type Person struct {
Name string
Age int
}
type Option func(*Person) # 定义函数类型Option
func WithName(name string) Option {
return func(p *Person) {
p.Name = name
}
}
func WithAge(age int) Option {
return func(p *Person) {
p.Age = age
}
}
func NewPerson(opts ...Option) *Person {
person := &Person{
Name: "Unknown", // 默认值
Age: 0, // 默认值
}
for _, opt := range opts {
opt(person)
}
return person
}
func main() {
p1 := NewPerson(WithName("Alice"), WithAge(30))
p2 := NewPerson(WithName("Bob"))
p3 := NewPerson(WithAge(25))
fmt.Println(p1) // 输出: &{Alice 30}
fmt.Println(p2) // 输出: &{Bob 0}
fmt.Println(p3) // 输出: &{Unknown 25}
}
第三部分 包与依赖
一、包
包(Package)是Go代码组织单位。每个Go程序都是由包构成的。程序运行的入口是main包。
package main
import (
"fmt"
"mypackage"
)
func main() {
fmt.Println(mypackage.Hello()) # 输出 Hello
}
package mypackage
func Hello() string {
return "Hello!"
}
二、依赖管理的三种方式
1.Go Path
a.简介
Go Path是一个环境变量,结构一般为:
- bin:项目编译的二进制文件
- pkg:项目编译的中间产物,用于加速编译
- src:项目源码
b.原理
项目代码直接依赖于src下的代码,使用go get
下载下载最新版本的包至src目录下
c.弊端
无法实现package的多版本控制
2.Go Vendor
a.依赖管理原理
在项目目录下添加vendor文件,所有依赖包副本形式放在$ProjectRoot/vender
中
依赖寻址方式:vender=>GOPATH
b.优点
通过每个项目引入一份依赖的副本,解决了需要同个package依赖的冲突问题
c.弊端
无法控制依赖的版本,更新项目可能会出现依赖冲突,导致编译出错
3.Go Module
a.简介
通过go.mod文件管理依赖包版本,通过go get/go mod
指令工具管理依赖包
b.优点
可以定义版本规则和管理项目依赖关系
三、Go Module
1.依赖管理
- 配置文件,描述依赖:
go.mod
- 中心仓库管理依赖库:
Proxy
- 本地工具:
go get/mod
GOPROXY是Go语言中的一个环境变量,用于指定Go模块代理服务器。
GOPROXY="https://proxy1.cn,https://proxy1.cn,direct" //direct代表源站点
//查找顺序:proxy1->proxy2->Direct
go env -w GO111MODULE=on # 强制启用 Go Modules 功能
go env -w GOPROXY=https://goproxy.cn,direct # 修改 GOPROXY
2.依赖配置
module example/project/app //依赖管理基本单元
go 1.16 //原生库
require(
example/lib v1.0.0
... //标识依赖:[Module Path][Version/Pseudo-version]
)
- version
- 语义化版本:
${MAJOR}.${MINOR}.${PATCH}
- MAJOR版本:大版本,不同MAJOR代码之间可能不兼容
- MINOR版本:小版本,一般可能新增函数,需要做到至少向下兼容
- PATCH版本:BUG修复版本,一般做一些代码Bug修复
- 基于commit伪版本:
vX.0.0-yymmddhhmmss-哈希码 //前面类似于语义化版本
- 语义化版本:
- 定义关键字
- 间接依赖:在其后加
//indirect
作为关键字 - 对于主版本在2以上的模块会在模块路径后增加
/vN
后缀 - 对于没有go.mod文件并且主版本在2以上的的依赖,在后加
+incompatible
gorequire( example/lib4 v0.0.0-20180306012644-bacd9c7ef1dd //indirect example/lib5/v3 v3.0.2 example/lib6 v3.2.0+incompatible )
- 间接依赖:在其后加
3.go工具
a.go get
使用指令:go get example.org/pkg [指令]
指令 | 功能 |
---|---|
@update | 默认 |
@none | 删除依赖 |
@v1.0.0 | 下载指定tag版本,语义版本 |
@23dffdd5 | 下载特定的commit版本 |
@master | 下载分支的最新commit |
b.go mod
使用指令:go mod [指令]
指令 | 功能 |
---|---|
init | 初始化,创建go.mod文件 |
download | 下载模块到本地缓存 |
tidy | 增加需要的依赖,删除不需要的依赖 |
注:在实际开发过程中,尽量提交之前执行go tidy
,减少构建时无效依赖包的拉取。
go mod需要开启使用,go env中默认:GO111MODULE=off
,此时go命令行将不会支持module功能,将会沿用旧版本通过vendor目录或GOPATH模式来查找