Go-函数的那些事儿
定义
函数是结构化编程的最小模块单元。它将复杂的算法过程分解为若干较小任务,隐藏相关细节,使得程序结构更加清晰,易于维护。
函数被设计成相对独立,通过接收输入参数完成一段算法指令,输出或存储相关结果。因此,函数还是代码复用和测试的基本单元。
Go函数借鉴了动态语言
无须前置声明。
不支持命名嵌套定义(nested)。
不支持同名函数重载(overload)。
不支持默认参数。
支持不定长变参。
支持多返回值。
支持命名返回值。
支持匿名函数和闭包。
第一类对象(first-class object)指可在运行期创建,可用作函数参数或返回值,可存入变量的实体。最常见的用法就是匿名函数。
package main
func helloworld(){
println("hello world ")
}
func exec(f func()){
f()
}
func main(){
exec(helloworld)
}
返回指针
func test() *int{
a := 0x100
return &a
}
func main(){
var a *int = test()
println(a,*a)
}
go build -gcflags="-l -m" main.go # 分析堆渣
go tool objdump -s "main/.main" test # 反编译
命名规则
通常是动词和介词加上名词,例如writeHello。
避免不必要的缩写,printError要比printErr更好一些。
避免使用类型关键字,比如buildUserStruct看上去会很别扭。
避免歧义,不能有多种用途的解释造成误解。
避免只能通过大小写区分的同名函数。
避免与内置函数同名,这会导致误用。
避免使用数字,除非是特定专有名词,例如UTF8。
避免添加作用域提示前缀。
使用相同术语,保持一致性。
使用习惯用语,比如init表示初始化,is/has返回布尔值结果。
使用反义词组命名行为相反的函数,比如get/set、min/max等。
参数
不管是指针、引用类型,还是其他类型参数,都是值拷贝传递(pass-by-value)。区别无非是拷贝目标对象,还是拷贝指针而已。在函数调用前,会为形参和返回值分配内存空间,并将实参拷贝到形参内存。
package main
import "fmt"
func test(x *int){
fmt.println("pointer:%p,target:%v",&x,x)
}
func main(){
a := 0x100
p := &a
fmt.Printf("point:%p,target:%v",&a,p)
}
尽管实参和形参都指向同一目标,但传递指针时依然被复制。开辟新的内存去存储指针值
二级指针
//不用返回参数,可使用二级指针传出值
func test(p **int){
x := 100
*p = &x
}
func main(){
var p *int
test(&p)
println(*p)
}
复合参数
type serverOption struct{
address string
port int
path string
timeout time.Duration
log *log.Logger
}
func newOption() *serverOption{
return &serverOption{
address: "127.0.0.1",
port: 80,
path: "/",
timeout: time.Second * 5,
log: nil
}
}
func server(option *serverOption){
fmt.Printf("%#v",*option)
}
func main(){
opt1 := newOption()
opt1.port=8080
server(opt1)
}
变参
//变参就是一次性接受多个参数
func test(s string,a ...int){
fmt.Printf("%T,%v\n",a,a)
}
func main(){
test("string",1,2,3)
}
//当切片与数组成为参数时,需要进行展开操作
func test(s string,a ...int){
fmt.Printf("%T,%v\n",a,a)
}
func main(){
v1 := [3]int{1,2,3}
test("ss",v1[:]...) //...放在后面意为展开的意思,如果是数组会转成切片再传去
}
//切片是引用类型,可以直接修改值
func test(s string,a ...int){
for i := range a {
a[i] +=100
}
}
func main(){
v1 := [3]int{1,2,3}
test("ss",v1[:]...) //...放在后面意为展开的意思,如果是数组会转成切片再传去
fmt.println(v1)
}
需要有return
func test(a,b int) (int,error){
if b == 0 {
return 0,errors.new("b not's zero")
}
return a/b,nil
}
func main(){
var a,b int = 2,2
result,err := test(a,b)
fmt.Println(result,err)
}
func test(a,b int) (int,error){
if b == 0 {
return 0,errors.new("b not's zero")
}
return a/b,nil
}
func main(){
var a,b int = 2,0
result,err := test(a,b)
fmt.Println(result,err)
}
匿名函数
匿名函数是指没有定义名字符号的函数。
除没有名字外,匿名函数和普通函数完全相同。最大区别是,我们可在函数内部定义匿名函数,形成类似嵌套效果。匿名函数可直接调用,保存到变量,作为参数或返回值。直接执行:
func main(){
v1 := func(a,b int) int {
return a + b
}
v1(1,2)
}
可以通过通道进行传输
func testStruct(){
type cale type {
mul func(x,y int) int
}
x := cale{
mul: func(x,y int) int{
return x + y
},
}
println(x.mul(2,3))
}
func testChannel(){
c := make(chan func(int,int)int,2)
c <- func(x,y int) int {
return x+y
}
println((<-c)(1,2)) //如果(<-c)后面不接参数,可以不要()
}
func main(){
testChanel()
testStruct()
}
闭包
闭包(closure)是在其词法上下文中引用了自由变量的函数,或者说是函数和其引用的环境的组合体。这种说明太学术范儿了,很难理解,我们先看一个例子。
func test(x int) func(){
return func(){
println(x)
}
}
func main(){
f := test(123)
f()
}
///
func test(x int) func() {
return func() {
println(x)
}
}
func main() {
var f func() = test(123)
f()
}
那具体什么叫闭包呢?
func test(x int) func() {
println(&x)
return func() {
println(&x, x)
}
}
func main() {
v1 := 10
f := test(v1)
f()
}
通过输出指针,我们注意到闭包直接引用了原环境变量。分析汇编代码,你会看到返回的不仅仅是匿名函数,还包括所引用的环境变量指针。所以说,闭包是函数和引用环境的组合体更加确切。
defer
语句defer向当前函数注册稍后执行的函数调用。这些调用被称作延迟调用,因为它们直到当前函数执行结束前才被执行,常用于资源释放、解除锁定,以及错误处理等操作。
func main() {
file, err := os.Open("./text.go")
if err != nil {
println(errors.New("this is error").Error())
}
defer file.Close()
}
defer顺序是倒排的
func main() {
defer println(1)
defer println(2)
defer println(3)
defer println(4)
defer println(5)
}
在函数中既有return 又有 defer
func test()(z int){
defer func(){
println("defer:",z)
z+=100
}()
return z+=100
}
func main(){
println(test())
}
对defer的性能进行分析
相比直接用CALL汇编指令调用函数,延迟调用则须花费更大代价。这其中包括注册、调用等操作,还有额外的缓存开销。
以最常用的mutex为例,我们简单对比一下两者的性能差异。
Main_test.go
var m sync.Mutex
func call(){
m.Lock()
m.Unlock
}
func defercall(){
m.Lock()
defer m.Unlock
}
func BenchmarkCall(b *testing.B){
for i:=0;i<=b.N;i++{
call()
}
}
func BenchmarkDefercall(b *testing.B){
for i:=;i<=b.N;i++{
defercall()
}
错误处理
标准库将error定义为借口类型
type error interfance{
Error() string
}
当我们自定义错误时,我们自定义的值返回给error时,error本身是interface类型,可以接受任意值,我们需要书写自定义类型的Error犯法,从而复合要求,不然程序会报错
练习
type divError struct{
x,y int
}
func(divError) Error(){
return "division by zero"
}
func div(x,y) (int,error){
if y==0{
return 0,divError{x,y}
}
return x/y,nil
}
func main(){
z,err := div(5,0)
if err != nil{
switch e:=err.(type){
case divError:
fmt.Println(e.x,e.y)
default:
fmt.Println(e)
}
log.Fatalln(err)
}
println(z)
}
我们需要自定义错误类型,以容纳更多上下文状态信息。这样的话,还可基于类型做出判断。
在正式代码中,我们不能忽略error返回值,应严格检查,否则可能会导致错误的逻辑状态。调用多返回值函数时,除error外,其他返回值同样需要关注。
以os.File.Read方法为例,它会同时返回剩余内容和EOF。
大量函数和方法返回error,使得调用代码变得很难看,一堆堆的检查语句充斥在代码行间。解决思路有:
使用专门的检查函数处理错误逻辑(比如记录日志),简化检查代码。
在不影响逻辑的情况下,使用defer延后处理错误状态(err退化赋值)。
在不中断逻辑的情况下,将错误作为内部状态保存,等最终“提交”时再处理。
panic与recover
与error相比,panic/recover在使用方法上更接近try/catch结构化异常。
func panic(v interfance{})
func recover() interfance{}
它们是内置函数而非语句。panic会立即中断当前函数流程,执行延迟调用。而在延迟调用函数中,recover可捕获并返回panic提交的错误对象。
//panic and recover
func main() {
defer func() {
if err := recover(); err != nil {
log.Fatalln(err)
}
}()
panic(" this is errors")
println("exit")
}
因为panic参数是空接口类型,因此可使用任何对象作为错误状态。而recover返回结果同样要做转型才能获得具体信息。
无论是否执行recover,所有延迟调用都会被执行。但中断性错误会沿调用堆栈向外传递,要么被外层捕获,要么导致进程崩溃。
//连续调用panic,只有最后一个panic会被执行
func main() {
defer func() {
for {
if err := recover(); err != nil {
log.Println(err)
} else {
log.Fatalln(err)
}
}
}()
defer func() {
panic("this is defer func error")
}()
panic("this is main error")
}
通过recover去保护代码
func test(x, y int) {
z := 0
func() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
z = 0
}
}()
z = x / y //保护此行代码
}()
println("x / y =", z)
}
func main() {
test(5, 0)
}
除非是不可恢复性、导致系统无法正常工作的错误,否则不建议使用panic。(例如:文件系统没有操作权限,服务端口被占用,数据库未启动等情况。)
package main
import "runtime/debug"
func test() {
panic("this test error")
}
func main() {
defer func() {
if err := recover(); err != nil {
debug.PrintStack()
}
}()
test()
}
标签:函数,err,int,test,func,Go,println,main,事儿
From: https://blog.csdn.net/l47ronin/article/details/141873769