PHP转Go之玩明白Go里的函数

Go 里面存在方法(Methods)函数(Functions)两个类型,两者区别就是前者会跟某个结构体关联;你可以理解方法为 PHP 中类的方法,而函数则是独立的函数,可以直接调用

本文概要

  • 这算是构造函数么?Go 中的 init 函数
  • 一个新概念:延迟调用 defer
  • 不定参数、默认值与多返回值
  • 值传递和引用传递
  • 方法定义时是绑定结构体还是绑定结构体指针?

这算是构造函数么?Go 中的 init 函数

Golang 中提供了一个 init 函数,这个函数很特殊:

  1. init 函数不能接受任何参数,它们也不能返回任何值;
  2. 它不能被显式的调用,而是在包被导入式自动调用,这个调用甚至要早于 main 函数(main 函数是 Go 语言的执行入口函数)

综上,init 函数的确是有一些构造函数的能力,但是又不完全等同于 PHP 的构造函数

go 复制代码
package main

import "fmt"

func init() {
	fmt.Println("这里是 init 函数")
}

func main() {
	fmt.Println("这里是 main 函数")
}

//Output,这里可以看到 init 是先于 main 执行的
这里是 init 函数
这里是 main 函数

一个新概念:延迟调用 defer

在函数中可以使用 defer 声明一行代码,在函数退出时执行这部分代码

Golang 复制代码
package main
import "fmt"
func init() {
	defer fmt.Println("这里是 init defer 函数")
	defer fmt.Println("这里是 init defer2 函数")
	fmt.Println("这里是 init 函数")
}

func main() {
	fmt.Println("这里是 main 函数")
}
// Output: main中语句是最后输出
这里是 init 函数
这里是 init defer2 函数
这里是 init defer 函数
这里是 main 函数

defer 使用时的需要特别注意以下几点:

  • defer的执行顺序是,后进先出
  • defer不能改变返回参数的值,但是很多时候函数返回值是指针,所以可以通过修改目标结构体的值达到修改返回值的目的
  • 即使panic,也不影响已经注册的defer语句的执行

defer 最常用的场景就以下两个,其他场景都比较小众:

1、资源清理:比如关闭socket,关闭已打开的文件句柄,释放锁等

2、异常处理:配合recover捕获panic

不定参数、默认值与多返回值

  • 不定参数

不定参数实际上任何语言都支持,PHP和c/c++等都支持,golang中的不定参数有个特殊点,就是这个不定参数一定是函数是函数的最后一个参数,而且一个函数最多只能有一个可变参数

golang 复制代码
func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
            total += num
    }
    return total
}
  • 默认值

PHP的函数支持默认值,但是Golang中函数必须要显式的传递也就是不支持默认值

  • 返回值

Golang支持一个函数返回多个返回值,通常返回错误值都是最后一个参数,当然并不建议返回过多值

值传递和引用传递

我想如果你只写过PHP,这个值传递和引用传递可能会对你造成很大的困惑,PHP中虽然也支持引用传递,但是极少这样去用,这里在解释下值传递和引用传递的区别:

  1. 所谓值传递,就是将数据完全复制一份,所以在函数中操纵传进去的变量都不会影响外面的值
  2. 引用传递,核心就是将数据地址传进函数,函数操纵数据跟外面的数据地址是一样的,所以函数内改了数据会影响到外面

这里最需要注意的就是 Slice和Map都是引用传递,所以函数内使用数据时要特别注意,在这篇文章中 juejin.cn/post/727904... 我们讲解Slice时把其底层实现简单做了介绍,无论是Slice还是Map实际上底层结构体内存的都是指向真实数据的指针,所以即使把Slice这个结构体复制了,指向的数据还是同一份数据;

数组就是例外,数组的传递时是值传递。

方法定义时是绑定结构体还是绑定结构体指针?

我们在某个结构体上绑定方法时,可以使用指针也可以不用指针,例如:

Golang 复制代码
type J2do struct {
	Name string `json:name`
}
func (j J2do) SetName(name string) {
	j.Name = name  // 这个修改后不会
}
func (j *J2do) SetName2(name string) {
	j.Name = name
}
j := J2do{Name: "小能"}
j.SetName("非指针")  // 这个修改不会影响变量j的值,因为SetName函数调用时,复制了该结构体
fmt.Println(j)
j.SetName2("指针")   // 使用指针,则操纵了同一份数据,所以改变了j中Name的值
fmt.Println(j)

推荐大家在非特殊场景下,直接使用带指针的方式定义方法

下一章:准备讲下结构体的 tag

相关推荐
uzong2 小时前
技术故障复盘模版
后端
GetcharZp3 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程3 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研3 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi4 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国5 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy5 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack5 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9656 小时前
pip install 已经不再安全
后端
寻月隐君6 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github