第二章 函数与结构体 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}
}第三部分 泛型
Go 1.18 版本引入的重要特性,它允许开发者在定义函数、类型或方法时使用类型参数,而不必指定具体类型,从而实现更通用、可复用的代码
一、核心概念
1.类型参数 (Type Parameters)
类型参数是泛型定义中的占位符,用于表示未来将被具体类型替换的位置
泛型定义中用占位符表示 “待确定的类型”,例如T、K、V,使用时需指定具体类型。
// 格式
[占位符 约束]2.类型约束 (Type Constraints)
类型约束定义了类型参数可以接受的类型集合,确保泛型代码能够安全地操作传入的类型
any // 等价于 interface{}, 表示任意类型,是最宽松的约束
comparable // 表示支持 == 和 != 操作的可比较类型// 通过接口类型定义自定义约束
type Integer interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr
}
type Number interface {
~int | ~int64 | ~float64 // ~表示“底层类型为该类型”(如自定义type MyInt int也符合)
}3.实例化
使用泛型时,将类型参数替换为具体类型的过程,实例化时需要指定具体类型
// 实例化泛型类型
TypeName[InstanceType]二、泛型函数
泛型函数是可以接受一个或多个类型参数的函数,能够处理符合类型约束的多种数据类型
func FunctionName[T TypeConstraint](param T) T {
// 函数体
}// 定义一个泛型打印函数,可打印任意类型的切片
func PrintSlice[T any](s []T) {
for _, v := range s {
fmt.Printf("%v ", v)
}
fmt.Println()
}
// 定义一个泛型查找函数,查找元素在切片中的索引
func FindIndex[T comparable](s []T, target T) int {
for i, v := range s {
if v == target {
return i
}
}
return -1
}三、泛型类型与泛型方法
1.泛型类型
泛型类型是指在定义结构体、切片、映射、通道等类型时,引入类型参数,使该类型能适配多种数据类型
// 类型定义时使用泛型
type TypeName[T TypeConstraint] struct {
// 结构字段定义
}2.泛型方法
泛型方法,即带类型参数的方法,可具体分为两种情况:
泛型类型的关联方法
方法的接收者是泛型类型,方法的类型参数与接收者一致,无需额外声明
// 首先需要定义泛型结构体
type TypeName[T TypeConstraint] struct {
// 结构字段定义
}
// 再定义泛型方法
func (t *TypeName[T]) FunctionName(item T) {
// 方法实现
}// 定义泛型结构体(队列)
// T为类型参数,any表示任意类型均可作为队列元素
type Queue[T any] struct {
elements []T // 存储T类型的元素
}
// 入队
// 接收者为*Queue[T],与泛型结构体的类型参数一致
func (q *Queue[T]) Enqueue(item T) {
q.elements = append(q.elements, item)
}
// 出队
// 接收者为*Queue[T],返回队首元素和是否成功
func (q *Queue[T]) Dequeue() (T, bool) {
if len(q.elements) == 0 {
var zero T // 泛型类型的零值(如int为0,string为空串)
return zero, false
}
// 取出队首元素(切片第一个元素)
item := q.elements[0]
q.elements = q.elements[1:] // 移除队首元素
return item, true
}
// 获取队列长度
func (q *Queue[T]) Len() int {
return len(q.elements)
}独立类型参数的方法
方法自身声明独立的类型参数,接收者可以是普通类型(非泛型类型)
func (c *MyType) FunctionName[T any](t []T) {
// 方法实现
}四、泛型接口
泛型接口是指接口声明中包含类型参数,或接口的方法签名使用类型参数,使接口能适配不同类型的实现
// 接口定义时使用泛型
type interfaceName[T TypeConstraint] interface {
// 接口方法定义
}