go语言之函数及闭包
一:函数
1 概述:
函数是 Go 程序源代码的基本构造单位,一个函数的定义包括如下几个部分,函数声明关键字 也町、 函数名、参数列表、返回列表和函数体。
函数名遵循标识符的命名规则, 首字母的大小写决定该函数在其他包的可见性:大写时其他包可见,小写时只有相同的包可以访问;
func 函数名 (参数列表) (返回值列表) {
函数体
}
2 特点
函数声明的格式
方法名首字母大写是public,方法名首字母小写private私有方法
1)函数类型
package main import (
"fmt"
)
//1 无参无返回值
func test(){
fmt.Println("三无产品")
}
//2 有参无返回值
func test1(v1 int,v2 int){
fmt.Println(v1,v2)
}
//3 有不定参数无返回值
func test2(args ...int){
for _,n :=range args{
fmt.Println(n)
}
} //调用以上函数
func main(){
test()
test1(,)
test2(,,,)
}
package main import "fmt" //无参有返回值
func test4(a int ,str string){
a = 666
str= "我是沙雕"
return
}
func test5() (int,string){
return 250,"字符串"
}
func main(){
a,str := test4() //test4没有返回值,运行会报错
fmt.Println(a,str)
_,s :=test5()
fmt.Println(s)
}
package main import (
"fmt"
)
//有参有返回值
//求两个数的最大值和最小值
func test1(num1 int,num2 int) (min int,max int){
if num1>num2{
min=num2
max=num1
}else {
max=num2
min=num1
}
return
}
func main(){
min,max := test1(,)
fmt.Println(min,max)
}
求1~100的和代码实现两种方法
package main import "fmt"
//循环实现1到100累加
func test01() int {
sum :=
for i :=;i<=;i++{
sum +=i
}
return sum
}
//递归实现1到100的累加
func test02(num int)int{
if num=={
return
}
return num +test02(num-)
} func main() {
fmt.Println(test01())
fmt.Println(test02())
}
2)注意:
() 有参无返回值 ,参数名就相当于函数体内层的局部变量,命名返回值变量会被初始化类型零值
()不支持默认值参数。
()不支持函数重载。
()不支持函数嵌套,严格地说是不支持命名函数的嵌套定义,但支持嵌套匿名函数
3 多值返回
func swap(a,b int) (int,int){
return b,a
}
4 实参到形参的传递
package main
import "fmt"
func chvalue(a int) int{
a=a+
return a
}
func chpointer(a *int){
*a = *a +
return
}
func main() {
a :=
chvalue(a) //实参传递给形参是值拷贝
fmt.Println(a) chpointer(&a) //实参传递给形参然仍是值拷贝,只不过复制的是a的地址值
fmt.Println(a)
}
5 不定参数
不定参数声明使用 (参数 .. . type) 的语法格式
特点:
() 所有的不定参数类型必须是相同的
()不定参数必须是函数的最后一个参数。
()不定参数名在函数体 内相当于切片,对切片的操作同样适合对不定参数的操作
package main
func sum(arr ...int)(sum int){
for _, v := range arr{ //arr相当于切片
sum += v
}
return
}
6 切片可以作为参数传递给不定参数,切片名后要加上 ”...“
package main
//import "go/types"
func sum(arr ...int)(sum int){
for _,v :=range arr{
sum += v
}
return
}
func main() {
slice := []int{,,,}
array := [...]int{,,,}
//数组不可以作为实参传递给不定参数的函数 所以sum(array...)会报错 cannot use array (type [4]int) as type []int in argument to sum
sum(slice...)
}
7 形参为不定参数的函数的函数和形参为切片的函数类型不相同
package main import (
"fmt"
) func suma(arr ...int)(sum int){
for v := range arr{
sum += v
}
return
} func sumb(arr []int) (sum int) {
for v := range arr{
sum += v
}
return
}
func main() {
//suma 和sumb的类型不一样
fmt.Printf("%T\n",suma)
fmt.Printf("%T\n",sumb)
}
8 函数签名
概述:
函数类型又 函数签名 个函 类型就是函数定义首行去掉函数名、参数名和{,可以
使用台nt.Printf 的”%T”格式化参数打印函数的类型。
package main
import "fmt"
func add(a,b int) int {
return a + b
}
func main() {
fmt.Printf("%T\n",add) //打印效果 func(int, int) int
}
两个函数类型相同的条件是:拥有相同的形参列表和返回值列表(列表元素的次序、个数和类型相同)形参名可以不同
func add(a,b int) int { return a+b)
func sub (x int, y int) (c int) { c=x- y ; return c )
可以使用 type 定义函数类型,函数类型变量可以作为函数的参数或返
package main
import "fmt"
func add(a,b int) int {
return a + b
}
func sub(a,b int) int {
return a - b
}
type Op func(int,int) int //定义一个函数类型,输入的是两个int类型
//返回值是一个int类型
func do(f Op,a,b int) int { //定义一个函数,第一个参数是函数类型Op
return f(a,b) //函数类型变量可以直接用来进行函数调用
}
func main(){
a := do(add,,) //函数名add可以当作相同函数类型的形参
fmt.Println(a) //
s := do(sub,,)
fmt.Println(s) //-1
}
总结:
实际函数类型变 和函数名都可以当作指针变量,该指针指向函数代码开始位置 通常说函数类型变量是一
种引用类型,未初始化的函数类型变量的默认值是nil
有名函数的函数名可以看作函数类型的常 ,可以
直接使用函数名调用函数,也可以直接赋值给函数类型变量,
package main
func sum(a,b int) int{
return a + b
}
func main(){
sum(,) //直接调用
f := sum //有名函数可以直接赋值给变脸
f(,)
}
9 匿名函数
Go 提供两种函数 有名函数和匿名函数。匿名函数可以看作函数字面量 所有直接使用函
数类型变量的地方都可以由匿名函数代替。医名函数可以直接赋值给函数变量,可以当作实参,
也可以作为返回值,还可以直接被调用
package main import "fmt" //匿名函数被直接赋值函数变量
var sum = func(a,b int) int {
return a + b
} func doinput(f func(int,int) int,a,b int) {
return
} //匿名函数作为返回值
func wrap(op string) func(int, int) int{
switch op {
case "add":
return func(a int, b int) int {
return a +b
}
case "sub":
return func(a int, b int) int {
return a + b
}
default:
return nil
}
}
func main() {
//匿名函数被直接调用
defer func() {
if err :=recover();err !=nil{
fmt.Println(err)
}
}()
sum(,)
//匿名函数作为实参
doinput(func(x , y int) int {
return x + y
},,)
opFunc :=wrap("add")
re := opFunc(,)
fmt.Printf("%d\n",re)
fmt.Println(f)
}
二 : defer关键字
使用
Go 函数里提供了 defe 关键字,可以注册多个延迟调用,这些调用以先进后出( FILO )的
顺序在函数返回前被执行
package main
import "fmt"
func test(x int){
fmt.Println(/x)
}
func main() {
//defer 是延迟操作
defer fmt.Println("aaa")
defer fmt.Println("bbb")
//报错并不影响程序的运行
defer test()
defer fmt.Println("ccc")
}
注意 :
defer 后面必须是函数或方法的调用,不能是语句,否则会报
express on in defer must be function call 错误。
defer 函数的实参在注册时通过值拷贝传递进去。
package main
func f() int{
a :=
defer func(i int) {
println("defer i=",i) //defer i= 0
}(a)
a++
return a
}
func main() {
f()
}
//注:实参a 的值在defer注册时通过值拷贝传递进去,后续语句a++不会影响defer语句最后输出结果
defer语句必须先注册后才能执行,如果defer位于return之后,则defer因为没有注册,不会执行
package main
func main(){
defer func() {
println("first")
}()
a := 0
println(a) //0
return
defer func() {
println("second") //first
}()
}
defer的好处是可以在一定程度上避免资源泄漏,特别是在有很多return语句,有多个资源需要关闭的场景,
很容易漏掉资源关闭操作
func CopyFile (dst , src string) (w int64 , err error) {
src , err := os.Open(src)
if err != nil {
return
}
dst, e rr := os . Create(dst)
if err != nil {
//src 很容易忘记关闭
src.Close ()
returη
}
w, err =工 Copy dst src )
dst.Close()
src.Close ()
return
}
在打开资源无报错后直接调用 defer 关闭资源
func CopyFile (dst , src string) (w nt64 err error) {
src , err := os.Open (src )
if err != nil {
return
}
defer src . Close()
dst , err := os . Create(dst)
if err != nil {
return
}
defer dst. Close ()
w, err =工 Copy(dst src)
return
}
//总结 1defer 语句的位置不当,有可能导致 panic 一般 def1 语句放在错误检查语句之后。
//2defer 也有明显的副作用: defer 会推迟资源的释放, defer 尽量不要放到循环语句里面,将大函数内部的defer语句单独拆分成
//一个小函数是一种很好的实践方式。另外, defer 相对于普通的函数调用需要间接的数据结构的支持,相对于普通函数调用有一定的性能损耗
//3 defer r 中最好不要对有名返回值参数进行操作
四 :闭包
概述:
闭包是由函数及其相关引用环境组合而成的实体,一般通过在匿
名函数中引用外部函数的局部变量或包全局变构成。
引用:闭包=函数+引用环境
详解:
闭包对闭包外的环境引入是直接引用,编译器检测到闭包,会将闭包引用的外部变量分配到堆上
如果函数返回的闭包引用了该函数的局部变量( 参数或函数内部变量〉
()多次调用该函数,返回的多个闭包所引用的外部变量是多个副本,原因是每次调用函
数都会为局部变量分配内存
()用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每一次调用该闭包对
该外部变量都有影响,因为闭包函数共享外部引用。
示例:
package main
func fa(a int) func(i int) int{
return func(i int) int {
println(&a,a)
a= a+i
return a
}
}
func main(){
f :=fa() //f 引用的外部的闭包环境包括本次函数调用的形参a的值1
g := fa() //g 引用的外部的闭包环境包括本次函数调用的形参a的值1
//此时f、g引用的闭包环境中的a的值并不是同一个,而是两次函数调用产生的副本
println(f()) //0xc00006c000 1 2
//多次调用f引用的同一个副本a
println(f()) //0xc00006c000 2 3
//g中a的值然仍是1
println(g()) //0xc00006c008 1 2
println(f()) //0xc00006c000 3 4
}
//f和g引用的是不同的a
(3)如果函数返回的闭包引用的是全局变量 ,则多次调用该函数返回的多个闭包引用的都是同一个a。
同理,调用 个闭包多次引用的也是同一个 。此时如果闭包中修改了a 值的逻辑,
每次闭包调用都会影响全局变量 的值。
示例:
package main var(
a=
) func fa() func(i int) int {
return func(i int) int {
println(&a,a)
a = a +i
return a
}
}
func main() {
f :=fa() //f 引用的外部的闭包环境包括全局交量a
g :=fa() //f 引用的外部的闭包环境包括全局变量a
///此时f、g 引用的闭包环境中的a 的值是同一个
println(f()) //0x4d68b8 0 1
println(g()) //0x4d68b8 0 2
println(g()) //0x4d68b8 0 3
println(g()) //0x4d68b8 0 4
}
(4)同一个函数返回的多个闭包共享该函数的局部
package main
func fa(base int) (func(int) int,func(int) int) {
print(&base,base) //0xc00006c00000xc00006c00800xc00006c000 1
add := func(i int) int {
base += i
println(&base,base)
return base
}
sub := func(i int) int{
base -= i
println(&base,base)
return base
}
return add,sub
}
func main() {
//f、g 闭包引用的 base 是同一个,是fa函数调用传递过来的实参值
f,g := fa()
//s、k 包引用的base是同一个是fa函数调用传递过来的实参值
s,k := fa()
//f、g和s、k 引用不同的闭包交量,这是由于fa每次调用都妥重新分配形参
println(f())
println(g())
println(s())
println(k())
}
go语言之函数及闭包的更多相关文章
- swift1.2语言函数和闭包函数介绍
swift1.2语言函数和闭包函数介绍 在编程中,随着处理问题的越来越复杂,代码量飞速增加.其中,大量的代码往往相互重复或者近似重复.如果不采有效方式加以解决,代码将很难维护. swift1.2语言函 ...
- 苹果新的编程语言 Swift 语言进阶(六)--函数和闭包
一 .函数 1.1. 函数的定义和调用 函数的定义以funckeyword作为前缀,接着是函数名字,接着跟着一个能够带有參数.也能够不带參数的圆括号.接着用-> 指示函数的返回类型. 函数运行体 ...
- Swift 1.1语言第7章 函数和闭包
Swift 1.1语言第7章 函数和闭包 在编程中,随着处理问题的越来越复杂.代码量飞速添加. 当中,大量的代码往往相互反复或者近似反复.假设不採有效方式加以解决.代码将非常难维护. 为了解决问题, ...
- 【Go语言学习】匿名函数与闭包
前言 入坑 Go 语言已经大半年了,却没有写过一篇像样的技术文章,每次写一半就搁笔,然后就烂尾了. 几经思考,痛定思痛,决定金盆洗手,重新做人,哦不,重新开始写技术博文. 这段时间在研究Go语言闭包的 ...
- 《JS语言精粹》学习笔记 函数部分の闭包
要理解闭包,首先要理解变量作用域,变量的作用域就两种,全局变量和局部变量,函数内部可以直接读取全局变量,函数外部无法读取函数内部的局部变量. 闭包定义:能读取函数内部局部变量的函数就是闭包,而只有函数 ...
- 如何设计一门语言(七)——闭包、lambda和interface
人们都很喜欢讨论闭包这个概念.其实这个概念对于写代码来讲一点用都没有,写代码只需要掌握好lambda表达式和class+interface的语义就行了.基本上只有在写编译器和虚拟机的时候才需要管什么是 ...
- 窥探Swift之函数与闭包的应用实例
今天的博客算是比较基础的,还是那句话,基础这东西在什么时候都是最重要的.说到函数,只要是写过程序就肯定知道函数是怎么回事,今天就来讨论一下Swift中的函数的特性以及Swift中的闭包.今天的一些小实 ...
- 窥探 Swift 之 函数与闭包的应用实例
今天的博客算是比较基础的,还是那句话,基础这东西在什么时候 都是最重要的.说到函数,只要是写过程序就肯定知道函数是怎么回事,今天就来讨论一下Swift中的函数的特性以及Swift中的闭包.今天的一些小 ...
- 函数:内嵌函数和闭包 - 零基础入门学习Python020
函数:内嵌函数和闭包 让编程改变世界 Change the world by program 内嵌函数和闭包 接下来这两节课我们谈的话题可能会"比较高级",所以如果是零基础的朋友, ...
随机推荐
- P5979 [PA2014]Druzyny dp 分治 线段树 分类讨论 启发式合并
LINK:Druzyny 这题研究了一下午 终于搞懂了. \(n^2\)的dp很容易得到. 考虑优化.又有大于的限制又有小于的限制这个非常难处理. 不过可以得到在限制人数上界的情况下能转移到的最远端点 ...
- 题解 [SHOI2002]滑雪
记忆化搜索$||dp||$剪枝 先讲方法,代码待会上 方法一:记忆化搜索 这个方法不怎么解释,就是每搜索完一个高度的最长路径记录一下,以后搜索其他的点时如果走到了这条路就直接用记录的值计算就是了 方法 ...
- heap相关算法的简单实现
// 12:06 PM/09/28/2017 #pragma once //向下调整算法 主要用来make_heap 以及pop_heap inline void adjustDown(int* he ...
- 使用selenium再次爬取疫情数据(链接数据库)
爬取网页地址: 丁香医生 数据库连接代码: def db_connect(): try: db=pymysql.connect('localhost','root','zzm666','payiqin ...
- 使用Vscode进行Python开发环境配置
Vscode是是一个强大的跨平台工具,我自己电脑是mac,公司电脑是win而且是内部环境,导致公司安装软件很费劲.好在vscode许多插件能直接离线安装,省去了很多麻烦. 很多人学习python,不知 ...
- CentOS yum 安装nginx
当使用以下命令安装Nginx时,发现无法安装成功 yum install -y nginx 需要做一点处理. 安装Nginx源 执行以下命令: rpm -ivh http://nginx.org/pa ...
- 如何利用NLog输出结构化日志,并在Kibana优雅分析日志?
上文我们演示了使用NLog向ElasticSearch写日志的基本过程(输出的是普通文本日志),今天我们来看下如何向ES输出结构化日志.并利用Kibana中分析日志. NLog输出结构化日志 Elas ...
- JS实例-01
输入成绩(0-100),不同的分数段奖励不同while(true){var a=prompt('请输入成绩');if (a>=0&&a<=100){ break;}}if ...
- 史上最全且最简洁易懂的Activity启动流程解析
Activity的启动流程是一个资深Android工程师必须掌握的内容,也是高职级面试中的高频面试知识点,无论是从事应用层开发,还是Framework开发,其重要性都无需我多言.而要真正理解它,就不可 ...
- WKWebView 网络请求Header 丢失
WKWebView 是苹果手机上主要的H5加载控件,它相比UIWebView 有诸多优势.在次不做比较,但是它的坑缺比较多.网上也有很多的例子但是做的比较好的真不多,我在这里推荐俩博客供大家参考.ht ...