go语言之函数

161 阅读7分钟

go语言之函数

函数的定义:

函数就是一段执行特殊任务的代码块

函数定义规范如下:

func 函数名(参数1 参数类型,参数2 参数类型)返回值类型{
    代码块1
    return xxx
}

和python类似的是,函数可以有一个或多个返回值。不同于Python,有多少个返回值就需要定义多少个变量来接收它们,而Python中返回多个返回值其实本质上是返回一个tuple(元组),只需要定义一个变量来接收。

//在Python中
def test1(name, age):
    name = '_'.join([name, 'test'])
    age += 1
    return name, age


if __name__ == '__main__':
    t1 = test1("surpass",18)
    print(type(t1))  //<class 'tuple'>

其实绝大数情况下,你是通过解压赋值的情况下来接收返回值,同时可以通过占位符来接收多余的变量。
name, _ = test1("surpass", 18)
print(name) // surpass_test
print(_)    // 19

//在go中
package main

import "fmt"

func testA(name string,age int)(string,int){
	names := []rune(name) // 通过name得到一个rune切片
	for _,value:=range "_test"{
		names = append(names,value)
	}
	age += 1
	return string(names),age
}

func main() {
	name,age := testA("surpass",18)
	fmt.Printf("name is %s\nage is %d\n",name,age)
}
/
name is surpass_test
age is 19
/

_空白符的作用

不同于Python,在go中使用_来忽略多余的变量。而在python 中是用占位符_接收多余的变量,它是可以打印出来的。

package main

import "fmt"

const pi = 3.1415926

func circle(radius float64)(float64,float64){
	area := pi * radius * radius
	perimeter := 2 * pi * radius
	return area,perimeter
}

func main() {
	var r float64 = 5
	area,_:=circle(r)
	fmt.Printf("半径r为%0.0f的圆:\n面积s为%0.1f",r,area)
}

不需要明确的返回值

函数的返回值可以在声明阶段指定返回值类型和变量,因此在return处不需要明确的返回值。

package main

import "fmt"

const pi = 3.1415926

func Calculations(radius float64)(area,perimeter float64){
	area = pi * radius * radius
	perimeter = 2 * pi * radius
	return
}

func main() {
	var radius float64 = 5
	area,perimeter := Calculations(radius)
	fmt.Printf("半径为%0.0f的圆,周长为%0.2f,面积为%0.2f",radius,perimeter,area)
}

在go语言中,函数是一等公民,它可以赋值给变量,可以作为数组,切片,字典的元素,可以作为函数的参数、返回值。

函数的返回值是一个函数

package main

import "fmt"

const pi = 3.1415926

func CalcA(radius float64)(area,perimeter float64,f func(area, perimeter float64)float64){
	area = pi * radius *radius
	perimeter = 2 * pi * radius
	f = func(area, perimeter float64) float64 {
		if area > perimeter{
			return area
		}else{
			return perimeter
		}
	}
	return
}

func main() {
	var radius float64 = 5
	area,perimeter,f := CalcA(radius)
	maxN := f(area,perimeter)
	fmt.Printf("半径为%0.0f的圆,周长和面积中最大的是%0.2f",radius,maxN)
}

函数参数为函数类型,返回值为带参数,带返回值的函数类型

package main

import (
	"fmt"
	"time"
)

func testc(name string)string{
	fmt.Printf("I am testc:%s\n",name)
	time.Sleep(time.Duration(4)*time.Second)
	names := []rune(name)
	for _,value:=range "_test"{
		names = append(names,value)
	}
	return string(names)
}

func decoratorA(f func(name string)string) func(name string)string{
	return func(name string) string{
		t1 := time.Now().Unix()
		newName := f(name)
		t2 := time.Now().Unix()
		t3 := int(t2-t1)
		fmt.Printf("程序运行一共花费了%d秒\n",t3)
		return newName
	}
}

func main() {
	testc := decoratorA(testc)
	name := testc("surpass")
	fmt.Printf("new name is %s\n",name)
}

匿名函数

匿名函数适用于临时使用的场景,在Python中使用lambda关键字来定义匿名函数

# 匿名函数适用于临时使用的场景,常配合Python中的内置函数一起使用,如sorted,filter等。

sum1 = (lambda x,y:x+y)(3,4) # 7
sorted(lst,key=lambda obj:obj.age,reverse=True)

在go语言中,匿名函数具有以下特性:

(1)定义在有名函数内部;
(2)不能是有名函数;

示范例子:

package main

import "fmt"

func testB(name string)func()string{
	return func()string {
		names := []rune(name)
		for _,value := range "_look"{
			names = append(names,value)
		}
		return string(names)
	}
}

func main() {
	f := testB("dolphin")
	name2 := f()
	fmt.Printf("new name is %s\n",name2)
}

闭包函数

闭包函数的定义:内嵌函数(在go中,是匿名函数)对外部作用域有引用。闭包函数提供一种给函数传参的方式。

闭包函数的经典应用就是装饰器,装饰器是一种设计模式,它的本质是:

在不修改被装饰对象源代码和调用方式的本质下,给被装饰对象添加额外的功能。

在Python中,通过语法糖@来实现装饰器,如一个简单的程序运行统计时间装饰器如下:

import time


def decorator(func):
    def wrapper(*args, **kwargs):
        time1 = time.time()
        func(*args, **kwargs)
        time2 = time.time()
        print('运行程序一共经过了%d秒\n' % (int(time2 - time1)))

    return wrapper


@decorator
def test1(name):
    print('I am test1:%(name)s' % {'name': name})
    time.sleep(3)


if __name__ == '__main__':
    test1("kevin")

得益于Python的启示,go语言中的装饰器可以这样来实现

package main

import (
	"fmt"
	"time"
)

//装饰器的实现

func testC(name string)string{
	names := []rune(name)
	for _,value := range "_perfect"{
		names = append(names,value)
	}
	time.Sleep(time.Duration(3)*time.Second)
	return string(names)
}

func decoratorN(f func(name string)string)(res func(name string)string){
	res = func(name string) string {
		t1 := time.Now().Second()
		newName := f(name)
		t2 := int(time.Now().Second()-t1)
		fmt.Printf("程序运行一共经历了%d秒\n",t2)
		return newName
	}
	return
}

func main() {
	testC := decoratorN(testC)
	fmt.Printf("new name is %s\n",testC("kevin"))
}

/*
装饰器的精髓在两个函数:
(1)将被装饰函数作为参数传入;
(2)将装饰过的函数作为返回值返回。
*/

如果我们希望这个装饰器可以装饰不同类型的函数了,即被装饰器对象可以是任意函数,应该怎么做了?

答:采用空的interface,空interface是泛型的基础。

package main

import (
	"fmt"
	"reflect"
	"strconv"
	"time"
)

func testD(name string)string{
	names := []rune(name)
	for _,value:=range "_perfect"{
		names = append(names,value)
	}
	time.Sleep(time.Duration(3)*time.Second)
	return string(names)
}

func testE(name string,age int)(info map[string]string){
	info = map[string]string{
		"name":name,
		"age":strconv.Itoa(age),
	}
	time.Sleep(time.Duration(4)*time.Second)
	return 
}

func decoratedA(decoF,Fn interface{}){
	var decoratorFunc,targetFunc reflect.Value
	decoratorFunc = reflect.ValueOf(decoF).Elem() //作为输出装饰完毕之后的函数,这里的函数是通过反射动态生成的
    targetFunc = reflect.ValueOf(Fn) //动态的获取被装饰的函数(也即目标函数)
	v := reflect.MakeFunc(targetFunc.Type(), func(args []reflect.Value) (results []reflect.Value) {
		t1 := time.Now().Unix()
		results = targetFunc.Call(args)
		t2 := int(time.Now().Unix()-t1)
		fmt.Printf("程序运行一共经历了%d秒\n",t2)
		return
	}) //通过反射生成一个新的函数
	decoratorFunc.Set(v) //将装饰过的函数存储到输出函数中
	return
}

func main() {
	testD := testD
	testE := testE
	decoratedA(&testD,testD) //需要输出的函数需要以指针的方式进行传入
	decoratedA(&testE,testE)
	newName := testD("dolphin")
	fmt.Printf("the new name is %s\n",newName)
	info := testE("surpass",18)
	for key,value:=range info{
		fmt.Printf("%s[%s]\n",key,value)
	}
}

参考链接:

www.cnblogs.com/lxsky/p/120…

变量作用域以及范围

我们知道,在同一包下,函数名不能重名。定义在函数内部的变量是局部变量,它的作用域是函数内部。定义在全局的变量,全局有效,只要修改了,会影响全局。

package main

import "fmt"

var a int  //在全局声明一个变量a,未对其初始化,自动初始化其类型的默认值0


func testX(){
    fmt.Printf("a的值为%d\n",a) //先从testX()内部找,没有再去全局找
}

func main() {
	var a int
	fmt.Printf("a的值为%d\n",a) // 0
	a = 19
	testX() // 0
	fmt.Printf("a的值为%d\n",a) // 19
}


/*
a的值为0
a的值为0
a的值为19
*/

go语言之包管理

go语言的包,就是类似Python的模块。

到目前为止,我们看到的 Go 程序都只有一个文件,文件里包含一个 main 函数和几个其他的函数。在实际中,这种把所有源代码编写在一个文件的方法并不好用。以这种方式编写,代码的重用和维护都会很困难。而包(Package)解决了这样的问题。 

包的作用:

包的作用是用于组织go的源代码,提高了代码的可读性和复用性

自定义包:

-go语言的代码必须放在gopath的src路径之下
-包的导入是从gopath的src路径之下开始检索的
-除了main包之外,建议包名就叫文件夹名,一个文件夹下的包名必须一致
-同一包下,变量、函数只能定义一次
-同一包下的变量和函数,可以直接使用
-包内的函数或变量,想让外部包使用的话,必须首字母大写
-以后下载的第三方包都是放在gopath的src路径下

特殊函数init函数

init函数不需要调用就会执行,可以定义多个init函数

包导入的几种方式

(1)直接导入
import "dolphin/surpass"
(2)给包重命名
import surpass "dolphin/surpass"
名字,变量/函数
(3)包只导入,不使用
import _ "dolphin/surpass"

go语言没有一个统一包管理地址,大家都放到github之上

go mode模式

go mode有两种创建方式

命令行方式和go modules

命令行方式

在命令行下输入:
go mod init 项目名
在当前路径下创建出go.mod(该项目依赖go的版本,第三方包版本)
	-项目路径的cmd窗口,go get第三方包,就会在go.mod中加入依赖
	-以后把项目copy给别人,直接go install
	-自定义的包,存放在项目的路径下
	-使用代理方式:手动写或者在goland中配置

go modules

goland中创建项目的时候,可以直接使用go modules,配置环境变量(加代理)