go进阶语法

文章目录

函数

声明和定义:

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")
}
相关推荐
666HZ6661 小时前
C语言——黑店
c语言·开发语言
Gomiko1 小时前
JavaScript基础(八):函数
开发语言·javascript·ecmascript
xkroy1 小时前
Spring Boot日志
java·spring boot·后端
n***F8751 小时前
【Spring Boot】SpringBoot自动装配-Import
java·spring boot·后端
〝七夜5691 小时前
JVM内存结构
java·开发语言·jvm
初级炼丹师(爱说实话版)1 小时前
JAVA泛型作用域与静态方法泛型使用笔记
java·开发语言·笔记
盖世英雄酱581361 小时前
Java.lang.Runtime 深度解析
java·后端
码事漫谈1 小时前
C++智能指针避坑指南:90%人会犯的3个致命错误
后端
码事漫谈1 小时前
不止于代码:一位开发者在2025开放原子大会的见闻与破圈思考
后端