04-编织灵魂旋律:Golang 函数的魔力绽放

在这里插入图片描述

📃个人主页:个人主页
🔥系列专栏:Golang基础
💬Go(又称Golang)是由Google开发的开源编程语言。它结合了静态类型的安全性和动态语言的灵活性,拥有高效的并发编程能力和简洁的语法。Go被设计用于构建可扩展、高性能的软件系统,具有优秀的内存管理和快速的编译速度,适用于Web开发、系统编程和云计算等领域。

文章目录

  • 函数的定义
  • 多个返回值
  • 变参
  • 传值与传指针
  • defer
  • 函数作为值、类型
  • Panic和Recover
  • main 函数和init 函数
  • import
    • 相对路径
    • 绝对路径
    • 特殊的import
      • 点操作
      • 别名操作
      • _操作

函数的定义

函数 是Go里面的核心设计,它通过关键字 func 来声明,它的格式如下:

func f(input1 type1, input2 type2) (output1 type1, output2 type2) {
	//这里是处理逻辑代码
	//返回多个值
	return value1, value2
}

上面的代码可以看出:

  • 关键字 func 用来声明一个函数 f
  • 函数可以有一个或者多个参数,每个参数后面带有类型,通过 , 分隔
  • 函数可以返回多个值
  • 上面返回值声明了两个变量 output1 和 output2 ,如果不想声明也可以,直接就两个类型
  • 如果只有一个返回值且不声明返回值变量,那么可以省略 包括返回值的括号
  • 如果没有返回值,那么就直接省略最后的返回信息
  • 如果有返回值, 那么必须在函数的外层添加return语句

下面来看一个实际应用函数的例子(用来计算Max值):

package main
import "fmt"
// 返回a和b中最大值.
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}
func main() {
	x := 3
	y := 4
	z := 5
	max_xy := max(x, y) //调用函数max(x, y)
	max_xz := max(x, z) //调用函数max(x, z)
	fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
	fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
	fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在这直接调用它
}

上面这个里面可以看到 max 函数有两个参数,它们的类型都是 int ,那么第一个变量的类型可以省略(即 a,b int,而非 a int, b int),默认为离它最近的类型,同理多于2个同类型的变量或者返回值。同时注意到它的返回值就是一个类型,这个就是省略写法。

多个返回值

Go语言比C更先进的特性,其中一点就是函数能够返回多个值。
直接看例子:

package main
import "fmt"
//返回 A+B 和 A*B
func f(A, B int) (int, int) {
	return A+B, A*B
}
func main() {
	x := 3
	y := 4
	res1, res2 := f(x, y)
	fmt.Printf("%d + %d = %d\n", x, y, res1)
	fmt.Printf("%d * %d = %d\n", x, y, res2)
}

上面的例子可以看到直接返回了两个参数,当然也可以命名返回参数的变量,这个例子里面只是用了两个类型,也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但如果函数是导出的(首字母大写),官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。

func SumAndProduct(A, B int) (res1 int, res2 int) {
	res1 = A+B
	res2 = A*B
	return
}

变参

Go函数支持变参。
接受变参的函数是有着不定数量的参数的。
为了做到这点,首先需要定义函数使其接受变参:

func f(arg ...int) {}

arg …int 告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是 int 。在函数体中,
变量 arg 是一个 intslice

for _, n := range arg {
	fmt.Printf("%d\n", n)
}

传值与传指针

传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。
为了验证上面的说法,来看一个例子:

package main
import "fmt"
//简单的一个函数,实现了参数+1的操作
func add1(a int) int {
	a = a+1 // 改变了a的值
	return a //返回一个新值
}
func main() {
	x := 3
	fmt.Println("x = ", x) // 应该输出 "x = 3"
	x1 := add1(x) //调用add1(x)
	fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4"
	fmt.Println("x = ", x) // 应该输出"x = 3"
}

虽然调用了 add1 函数,并且在 add1 中执行 a = a+1 操作,但是上面例子中 x 变量的值没有发生变化。
理由很简单:因为当调用 add1 的时候, add1 接收的参数其实是 x 的copy,而不是 x 本身。
如果真的需要传这个 x 本身,该怎么办呢?
这就牵扯到了所谓的指针。
变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。
只有 add1 函数知道 x 变量所在的地址,才能修改 x 变量的值。
所以需要将 x 所在地址 &x 传入函数,并将函数的参数的类型由 int 改为 *int ,即改为指针类型,才能在函数中修改 x 变量的值。此时参数仍然是按copy传递的,只是copy的是一个指针。
请看下面的例子:

package main
import "fmt"
//简单的一个函数,实现了参数+1的操作
func add1(a *int) int { // 请注意,
	*a = *a+1 // 修改了a的值
	return *a // 返回新值
}
func main() {
	x := 3
	fmt.Println("x = ", x) // 应该输出 "x = 3"
	x1 := add1(&x) // 调用 add1(&x) 传x的地址
	fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4"
	fmt.Println("x = ", x) // 应该输出 "x = 4"
}

这样,就达到了修改 x 的目的。
那么到底传指针有什么好处呢?
传指针使得多个函数能操作同一个对象。
传指针比较轻量级 (8bytes),只是传内存地址,可以用指针传递体积大的结构体。
如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当要传递大的结构体的时候,用指针是一个明智的选择。
Go语言中 channelslicemap 这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变 slice 的长度,则仍需要取地址传递指针)

defer

Go语言中有种不错的设计,即延迟(defer)语句,可以在函数中添加多个 defer 语句。
当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。
特别是当进行一些打开资源的操作时,遇到错误需要提前返回,在返回前需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,一般写打开一个资源是这样操作的:

func ReadWrite() bool {
	file.Open("file")
	// 做一些工作
	if failureX {
		file.Close()
		return false
	}
	if failureY {
		file.Close()
		return false
	}
	file.Close()
	return true
}

上面有很多重复的代码,Go的 defer 有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。
在 defer 后指定的函数会在函数退出前调用。

func ReadWrite() bool {
	file.Open("file")
	defer file.Close()
	if failureX {
		return false
	}
	if failureY {
		return false
	}
	return true
}

如果有很多调用 defer ,那么 defer 是采用后进先出模式,所以如下代码会输出:

 4 3 2 1 0
for i := 0; i < 5; i++ {
   defer fmt.Printf("%d ", i)
}

通常来说,defer 会用在释放数据库连接,关闭文件等需要在函数结束时处理的操作。

函数作为值、类型

在Go中函数也是一种变量,可以通过 type 来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型

type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1resultType1 [, ...])

函数作为类型到底有什么好处呢?
那就是可以把这个类型的函数当做值来传递,请看下面的例子:

package main
import "fmt"

type testInt func(int) bool // 声明了一个函数类型
func isOdd(integer int) bool {
	if integer%2 == 0 {
		return false
	}
	return true
}
func isEven(integer int) bool {
	if integer%2 == 0 {
		return true
	}
	return false
}
// 声明的函数类型在这个地方当做了一个参数
func filter(slice []int, f testInt) []int {
	var result []int
	for _, value := range slice {
		if f(value) {
			result = append(result, value)
		}
	}
	return result
}
func main(){
	slice := []int {1, 2, 3, 4, 5, 7}
	fmt.Println("slice = ", slice)
	odd := filter(slice, isOdd) // 函数当做值来传递了
	fmt.Println(odd)
	even := filter(slice, isEven) // 函数当做值来传递了
	fmt.Println(even)
}

函数当做值和类型在写一些通用接口的时候非常有用,通过上面例子看到 testInt 这个类型是一个函数类型,然后两个 filter 函数的参数和返回值与 testInt 类型是一样的,但是可以实现很多种的逻辑,这样使得程序变得非常的灵活。

Panic和Recover

Go没有像Java那样的异常机制,它不能抛出异常,而是使用了 panicrecover 机制。
一定要记住,应当把它作为最后的手段来使用,也就是说,代码中应当没有,或者很少有 panic 的东西。这是个强大的工具,请明智地使用它。
Panic 是一个内建函数,可以中断原有的控制流程,进入一个 panic 状态中。当函数 F 调用 panic ,函数F的执行被中断,但是 F 中的延迟函数会正常执行,然后F返回到调用它的地方。
在调用的地方, F 的行为就像调用了 panic
这一过程继续向上,直到发生 panicgoroutine 中所有调用的函数返回,此时程序退出。
panic 可以直接调用 panic 产生。
也可以由运行时错误产生,例如访问越界的数组。
Recover 是一个内建的函数,可以让进入 panic 状态的 goroutine 恢复过来。
recover 仅在延迟函数中有效。
在正常的执行过程中,调用 recover 会返回 nil ,并且没有其它任何效果。
如果当前的 goroutine 陷入 panic 状态,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
下面这个函数演示了如何在过程中使用 panic

var user = os.Getenv("USER")
func init() {
	if user == "" {
		panic("$USER is empty")
	}
}

下面这个函数检查作为其参数的函数在执行时是否会产生 panic

func throwsPanic(f func()) (b bool) {
	defer func() {
		if x := recover(); x != nil {
		b = true
	}
	}()
	f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
	return
}

注意:

  • defer 必须在 panic 语句之前
  • recover 必须配合 defer 使用

main 函数和init 函数

Go里面有两个保留的函数: init 函数(能够应用于所有的 package )和 main 函数(只能应用于 package main )。
这两个函数在定义时不能有任何的参数和返回值。
虽然一个 package 里面可以写任意多个 init 函数,但这无论是对于可读性还是以后的可维护性来说,强烈建议用户在一个 package 中每个文件只写一个 init 函数。
Go程序会自动调用 init()main() ,所以不需要在任何地方调用这两个函数。
每个 package 中的 init 函数都是可选的,但 package main 就必须包含一个 main 函数。
程序的初始化和执行都起始于 main 包。
如果 main 包还导入了其它的包,那么就会在编译时将它们依次导入。
有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt 包,但它只会被导入一次,因为没有必要导入多次)。
当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行 init 函数(如果有的话),依次类推。
等所有被导入的包都加载完毕了,就会开始对 main 包中的包级常量和变量进行初始化,然后执行 main 包中的 init 函数(如果存在的话),最后执行 main 函数。

import

相对路径

import "./model" //当前文件同一目录的model目录,但是不建议这种方式来import

绝对路径

import "shorturl/model" //加载gopath/src/shorturl/model模块

特殊的import

点操作

有时候会看到如下的方式导入包:

import(
	. "fmt"
)

这个点操作的含义就是这个包导入之后在调用这个包的函数时,可以省略前缀的包名,也就是前面调用的 fmt.Println("hello world") 可以省略的写成 Println("hello world")

别名操作

别名操作顾名思义可以把包命名成另一个用起来容易记忆的名字

import(
	f "fmt"
)

别名操作的话调用包函数时前缀变成了前缀,即 f.Println("hello world")

_操作

这个操作经常是让很多人费解的一个操作符,请看下面这个 import

import (
	"database/sql"
	_ "github.com/ziutek/mymysql/godrv"
)

_操作 其实是引入该包,不直接使用包里面的函数,而是调用了该包里面的 init 函数 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/30027.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

通过共享内存进行通信(嵌入式学习)

通过共享内存进行通信 概念特点函数示例代码 概念 在Linux中&#xff0c;共享内存是一种进程间通信&#xff08;IPC&#xff09;机制&#xff0c;允许多个进程共享同一块内存区域。这种通信方式可以提供高效的数据传输&#xff0c;特别适用于需要频繁交换数据的场景。 IO间进…

为CentOs配置静态IP

目录 第一步&#xff1a;查看物理机IP 第二步&#xff1a;虚拟机网络设置 点击虚拟机->编辑虚拟机设置 第三步&#xff1a;CentOS网络配置文件 第四步&#xff1a;重启网络 第五步&#xff1a;测试网络 为什么要设置静态IP 在安装好CentOS虚拟机以后&#xff0c;一般我…

程序替换原理

文章目录 一、程序替换 一、程序替换 程序替换用于将当前进程的用户空间的代码和数据全部替换为新程序的代码和数据&#xff0c;程序替换不会创建新进程&#xff0c;而是用当前进程执行新程序的代码&#xff0c;fork 创建子进程后&#xff0c;子进程默认执行的是父进程的代码&…

vue2和vue3的渲染过程简述版

文章目录 vue2渲染过程vue3渲染过程优化和扩充 vue2和vue3对比 vue2渲染过程 在Vue 2的渲染过程中&#xff0c;包括以下几个关键步骤&#xff1a; 解析模板&#xff1a;Vue 2使用基于HTML语法的模板&#xff0c;首先会将模板解析成抽象语法树&#xff08;AST&#xff09;&…

K8s 部署 Apache Kudu 集群

一、K8s 部署 Apache Kudu 集群 安装规划 组件replicaskudu-master3kudu-tserver3 1. 创建命名空间 vi kudu-ns.yamlapiVersion: v1 kind: Namespace metadata:name: apache-kudulabels:name: apache-kudukubectl apply -f kudu-ns.yaml查看命名空间&#xff1a; kubectl …

JUC高级-0614

5.LockSupport与线程中断 5.1 线程中断 蚂蚁金服面试题&#xff1a;如何中等一个线程&#xff0c;如何停止一个线程什么是中断机制 首先&#xff1a;一个线程不应该由其他线程来强制中断或停止&#xff0c;而是应该由线程自己自行停止。所以&#xff0c;Thread.stop, Thread.…

【spring源码系列-06】refresh中obtainFreshBeanFactory方法的执行流程

Spring源码系列整体栏目 内容链接地址【一】spring源码整体概述https://blog.csdn.net/zhenghuishengq/article/details/130940885【二】通过refresh方法剖析IOC的整体流程https://blog.csdn.net/zhenghuishengq/article/details/131003428【三】xml配置文件启动spring时refres…

十个实用MySQL函数

函数 0. 显示当前时间 命令&#xff1a;。 作用: 显示当前时间。 应用场景: 创建时间&#xff0c;修改时间等默认值。 例子&#xff1a; 1. 字符长度 命令&#xff1a;。 作用: 显示指定字符长度。 应用场景: 查看字符长度时。 例子&#xff1a; 2. 日期格式化 命令…

面试---简历

项目 1.1、商品管理 新增商品 同时插入商品对应的使用时间数据&#xff0c;需要操作两张表&#xff1a;product&#xff0c;product_usetime。在productService接口中定义save方法&#xff0c;该方法接受一张Dto对象&#xff0c;dto对象继承自product类&#xff0c;并将prod…

Stable Diffusion webui 基础参数学习

哈喽&#xff0c;各位小伙伴们大家好&#xff0c;最近一直再研究人工智能类的生产力&#xff0c;不得不说随着时代科技的进步让人工智能也得到了突破性的发展。而小编前段时间玩画画也是玩的不可自拔&#xff0c;你能想想得到&#xff0c;一个完全不会画画的有一天也能创作出绘…

【测试基础01】

软件测试 一、软件测试基本概念(1)、软件测试的定义(2)、软件错误的定义(3)、测试分类 二、需求文档的评审三、软件测试计划(1)、测试范围(2)、测试环境(3)、测试策略(4)、测试管理(5)、测试风险(6)、模板 一、软件测试基本概念 (1)、软件测试的定义 软件测试是从前期需求文档…

freeswitch 使用 silero-vad 静音拆分使用 fastasr 识别

silero-vad 在git 的评分挺高的测试好像比webrtc vad好下面测试下 silero-vad 支持c 和py 由于识别c的框架少下面使用py 以下基于python3.8torch1.12.0torchaudio 1.12.0 1.由于fastasr 需要16k 所以 将freeswitch的实时音频mediabug 8k转成16k 用socket传到py 模块代码…

ChatGLM-6B 在 ModelWhale和本地 平台的部署与微调教程

ChatGLM-6B 在 ModelWhale 平台的部署与微调教程 工作台 - Heywhale.com ChatGLM-6B 介绍 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费…

高压放大器在介电材料测试中的应用

介电材料测试是一项重要的材料性能测试&#xff0c;它涉及到物理学、化学、材料科学等多个学科领域。高压放大器是介电材料测试中的一种重要设备&#xff0c;它可以放大微弱的电信号&#xff0c;提高测试的准确性和精度。下面将详细介绍高压放大器在介电材料测试中的应用。 图&…

Web前端开发技术储久良第三版课后选择答案(1-10章)

P16-第1章 练习与实验答案 练习1 1.选择题 【1】Html是一种&#xff08;&#xff09;语言。 【A】编译型 【B】超文本标记 【C】高级程序设计 【D】面向对象编程【2】世界上第一个网页是()。 【A】http://www.w3c.org 【B】http:/info.cern.ch 【C】http://www.microsoft.com…

【论文阅读】(2023.06.09-2023.06.18)论文阅读简单记录和汇总

(2023.06.09-2023.06.12)论文阅读简单记录和汇总 2023/06/09&#xff1a;虽然下周是我做汇报&#xff0c;但是到了周末该打游戏还是得打的 2023/06/12&#xff1a;好累好困&#xff0c;现在好容易累。 目录 &#xff08;TCSVT 2023&#xff09;Facial Image Compression via …

2021电工杯数学建模B题解题思路

目录 一、前言 二、问题背景 三、具体问题 四、解题思路 &#xff08;一&#xff09;整体思路 &#xff08;二&#xff09;问题一 &#xff08;三&#xff09;问题二 &#xff08;四&#xff09;问题三 &#xff08;五&#xff09;问题四 &#xff08;六&#xff09;…

使用parcel搭建threejs开发环境

一、什么是parcel parcel官网&#xff1a;https://www.parceljs.cn/ Parcel是一个快速、零配置的Web应用打包器&#xff0c;可将JavaScript、CSS、HTML和图像等静态文件打包到一个捆绑文件中。它的主要目标是简化Web应用程序的打包过程&#xff0c;使开发人员可以更快速地创建…

【深度学习】1 感知机(人工神经元)

认识感知机 感知机接收多个输入信号&#xff0c;输出一个信号 感知机的信号只有“流/不流”(1/0)两种取值 0对应“不传递信号”&#xff0c;1对应“传递信号”。 输入信号被送往神经元时&#xff0c;会被分别乘以固定的权重。神经元会计算传送过来的信号的综合&#xff0c;只有…

怎么利用代理IP优化网络爬虫

网络爬虫会自动扫描互联网&#xff0c;搜集大量数据并将它们组织起来。但是&#xff0c;许多网站都采取了反爬虫策略&#xff0c;限制了网络爬虫的活动。这时候&#xff0c;代理IP就起到了关键作用。 一、代理ip在网络爬虫中的作用 代理ip爬虫中使用代理IP有很多好处。首先&…