文章目录
函数
声明和定义:
go
//声明
var sum func(int,int) int
//定义并使用
func main() {
sum = func(a int, b int) int {
return a + b
}
fmt.Println(sum(1, 2))
}
//声明并定义
func main() {
fmt.Println(sum(1, 2))
}
func sum (a int,b int) int {
return a + b
}
参数
相同类型且相连的形参可以共用同一个类型
go
func main() {
fmt.Println(sum(1, 2, "1", "2"))
}
func sum(a, b int, c, d string) int {
return a + b
}
和java一样又一个变长参数
go
func main() {
fmt.Println(sumAll(1, 2, 1, 2))
}
func sumAll(nums ...int) int {
sum := 0
for i := range nums {
sum += i
}
return sum
}
返回值
在Go中,不支持函数重载。
普通返回值及返回多个:类型相同且相邻的参数而言,可以只声明一次类型
go
返回一个
func returnCal(a,b int) int {
return a + b
}
返回2个
func returnCal(a, b int) (int,int) {
return a + b,a-b
}
返回三个
func returnCal(a, b int) (int, int, int) {
return a + b, a - b, a / b
}
具名返回值
go
func returnCal(a, b int) (res int) {
res = a + b
return
}
相当于,就是把返回值的声明放到了函数声明里面
func returnCal(a, b int) (int) {
var res int;
res = a+b
return res
}
也可以声明多个,像下面这样
func returnCal(a, b int) (c, d int) {
c = a + b
b = a - b
return
}
Go中的函数参数是传值传递,即在传递参数时会拷贝实参的值
这里笔者花了很长时间才理解,总结如下:
Go 中的 slice、map、chan、string 本质上都是 带指针的结构体(header)
函数传参时 Go 总是 传值:它会把这个结构体整体 拷贝一份,但结构体内部的指针字段的值保持不变
因此 两个变量虽然是不同的结构体副本,却都指向同一块底层数据。
所以它们表现得像"引用传递",但本质仍然是"值传递,只是值里包含指针"。
类比java中就是,java的所有引用类型都是一个类(相当于go中的结构体),但是类就是对象,只需要拷贝其在栈上的引用即可,但是go会拷贝除结构体中引用部分的整个结构体。

defer
在函数返回之前执行,类似于java中的finally
go
func test13() any {
defer fmt.Println(2)
return 1
}
但是:
defer 后面的函数参数会立即计算,不是等到执行时才计算。
注意:2两种情况
第一种:具名返回值
go
func test13() (x int) {
x = 10
defer func() {
x++ // 能改返回值
}()
return
}
//结果是11
第二种情况:匿名返回值
go
func test13() (int) {
x := 10
defer func() {
x++ // 能改返回值
}()
return x
}
// 结果是10
为什么呢,笔者这里详细解释下:
具名返回值 = 函数返回值本身是一个变量 → defer 能改它
匿名返回值 = return 的结果是一个值副本 → defer 改不到
方法
方法这个定义在go里面对应有java开发经验的人来说很不好理解:
笔者这里简单通俗易懂的解释下:
在java中方法是属于类的,只有类对象可以调用方法,如:obj.func()...,但是在go中是没有类的,但是我需要让我的自定义的类型拥有自定义的方法(这里可以理解为自定义的java类中自定义一些方法)
那怎么做呢?
那就先定义一个自定义的类型,再定义自定义的方法
再标识这些自定义的方法属于自定义的类型
这要就可以通过自定义的类型调用这些独属于自己自定义的方法了
例:
go
// 先定义一个自定义类型:
type NameSlice []string
// 定义一个i 是NameSlice 类型的变量理解为java中的this,标识这个方法属于i
func (i NameSlice) getByIndex(index int) string {
return i[index]
}
// 定义一个i 是NameSlice 类型的变量理解为java中的this,标识这个方法属于i
func (i NameSlice) setByIndex(index int, value string) {
i[index] = value
}
func main() {
nameSlice := NameSlice{"zs", "ls", "ww"}
nameSlice.setByIndex(0, "abc")
fmt.Println(nameSlice.getByIndex(0))
}
//输出abc
接口
其实这个接口和java中的接口差不多,也是一个interface,里面只写一个方法声明,留给其他结构体去继承
但是注意:go中的实现接口是隐式的。不会有特殊的关键字说明某个结构体实现了某个接口
所以判断结构体实现某个接口的方法:一个结构体(或任意类型)必须实现接口里的"全部方法",一个都不能少,否则就不算实现接口
例:
go
func main() {
dog := Animal(Dog{name: "xww", legCount: 4})
dog.sayName()
fmt.Println(dog.getLegCount())
}
// 动物接口
type Animal interface {
sayName()
getLegCount() int
}
// 狗 结构体
type Dog struct {
name string
legCount int
}
//实现 动物sayName方法
func (dog Dog) sayName() {
fmt.Println(dog.name)
}
//实现 动物getLegCount方法
func (dog Dog) getLegCount() int {
return dog.legCount
}
空接口:
空接口是一个特殊的接口:空接口中没有任何方法
go
type Any interface{}
一个类型只要实现了接口的所有方法,才算实现接口,所以空接口没有方法 → 所有类型都满足要求
看看下面这个
go
func main() {
x := any(1)
y := interface{}(1)
fmt.Println(x, y)
}
// 这2种写法是等价的,在go内部会声明type any = interface{}
接口内部可以看成是一个由(val,type)组成的元组,type是具体类型,在调用方法时会去调用具体类型的具体值。
泛型
Java中的泛型就是任意类型T,可以接受任意类型的变量,但是go中的泛型实际上是限制变量的类型范围
比如:
1.函数形参泛型
go
func sum[T int | float64](a, b T) T {
return a + b
}
类似于java的
java
public static <T extends Number> T sum(T a, T b)
2.结构体泛型
go
func main() {
student := Student[int]{
Name: "jack",
Id: 1024,
}
fmt.Println(student)
}
type Student[T int | string] struct {
Name string
Id T
}
3.泛型接口
go
func main() {
child1 := Child[string]{
Msg: "呱呱呱",
}
fmt.Println(child1)
child2 := Child[int]{
Msg: 111122223333,
}
fmt.Println(child2)
}
type Say[T int | string] interface {
say() T
}
type Child[T int | string] struct {
Msg T
}
func (c Child[T]) say() T {
return c.Msg
}
异常
再java中异常处理就是throw或 try catch,再go中,异常不需要捕获,而是作为返回值传递给了调用方
go
file, err := os.Open("README.txt")
if err != nil {
fmt.Println("文件打不开:", err)
return
}
fmt.Println(file.Name())
手动创建异常:
go
err := errors.New("这是一个错误")
//得到一个格式化参数的error
err = fmt.Errorf("这是%d个格式化参数的的错误", 1)
fmt.Println(err)
打印异常:
go
// 没有包装异常,直接输出错误信息,把错误变成普通字符串
fmt.Printf("%v", errors.New("a"))
// 创建异常链,能追踪到原始错误
fmt.Printf("%w", errors.New("a"))
一般上面的输入都打印不了异常栈
使用"github.com/pkg/errors"打印异常栈:
go
import (
"fmt"
"strings"
"github.com/pkg/errors"
)
func main() {
fmt.Printf("%+v", getErr())
}
func getErr() error {
return errors.New("aabbcc")
}urn errors.New("aabbcc")
}
/**
输出
aabbcc
main.getErr
C:/dev/com/goTest/demoProject1/main.go:15
main.main
C:/dev/com/goTest/demoProject1/main.go:11
runtime.main
C:/dev/devtool/go/src/runtime/proc.go:285
runtime.goexit
C:/dev/devtool/go/src/runtime/asm_amd64.s:1693
*/
1.error:这是最标准的 Go 错误方式,用 errors.New 或 fmt.Errorf 创建,通过返回值往上层传递,完全等价于 Java 中 返回错误对象,而不是抛异常
2.panic(严重错误/不可恢复):用于 崩溃程序 的情况,例如:访问越界空指针(nil dereference)程序自身无法继续运行,对比 Java类似 try { ... } catch(Exception e) {},但只能在 defer 中使用
常用于:防止 goroutine 崩掉整个程序
3.recover(从 panic 恢复):用于捕获 panic 让程序继续运行,只能在 defer 中使用。
如:
go
func run() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获 panic:", r)
}
}()
panic("fatal")
}