• 【Hello Go】Go语言函数


    定义格式

    函数是构成代码执行的逻辑结构

    在Go语言中 函数的基本组成为

    • func关键字
    • 函数名
    • 参数列表
    • 返回值
    • 函数体
    • 返回语句

    基本代码格式如下

    func // 函数名(// 参数) (// 返回值) {                                                                                  
        // 函数语句
        
        // 可以return多个返回值
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    函数定义说明:

    • func关键字 : Go语言声明函数必须要使用func关键字
    • 函数名称 : Go语言的函数名称默认规则为 开头为小写字母即为私有 否则即为公有
    • 参数列表: 支持多个参数 多个参数之间用逗号分隔开 不支持默认参数
    • 返回类型: 我们可以有多个返回类型
    • 返回值: 如果有返回值 我们必须要添加return语句

    自定义函数

    无参数无返回值

    下面是无参数无返回值函数的定义和调用

    // 无参数无返回值函数的定义
    func test()  {
    	fmt.Println("hello world")
    }
    
    func main()  {
    	// 调用
    	test()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    有参数无返回值

    // 有参数无返回值函数的定义 
    func test2(a , b int) {
    
    }
    
    func test3(a int , b int)  {
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里的定义方式有两种 一种是每个标识符后面都添加数据类型 一种是将数据类型添加在最后(如果都一样的话)

    func main()  {
    	// 调用
    	test2(10 , 10)
    }
    
    • 1
    • 2
    • 3
    • 4

    不定参数列表

    不定参数是指函数传入的参数不确定 为了做到这点 我们首先要将函数定义为接受不定参数类型

    定义代码如下

    func test4(args ...int)  { 
    	for _, v := range args {
    		fmt.Println(v)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    形如 ... type 格式类型只能作为函数的参数类型存在 并且只能作为最后一个参数

    	// 函数调用 可以传递0~多个数据
    	test4()
    	test4(1)
    	test4(1 , 2, 3, 4)
    
    • 1
    • 2
    • 3
    • 4

    不定参数的传递

    不定参数也会遇到需要继续往下传递参数的情况 下面是不定参数传递的两段代码

    func test4(args ...int)  { 
    	for _, v := range args {
    		fmt.Println(v)
    	}
    }
    
    
    func test5(args ...int)  { 
    	for _, v := range args {
    		fmt.Println(v)
    	}
    }
    
    func test6(args ...int){
    	test4(args ...)      // 传递方式一 直接将所有参数全部传入 格式如图
    	test5(args[1:]...)   // 传递方式二 将参数列表中从1开始(包括1位置)全部传入
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在我们的test6函数中演示了 用不定参数传参数的两种方式

    方式二中我们使用了切片 不了解的同学可以暂时放放 下面几篇博客中会进行讲解

    这里我们只需要记住传参的格式是 args ...

    有返回值

    我们的返回值定义在参数后面

    虽然说我们在定义返回值的时候可以省略标识符 直接使用类型 但是官方文档确不推荐我们这么做 因为这样子做会导致我们程序的可读性变差

    我们推荐下面这种定义方式

    func test7(key int) (value int)  {
    	return 1
    }
    
    • 1
    • 2
    • 3

    如果一个函数有返回值 那么我们在函数的最后就必须要返回一个值 否则会编译不通过

    有多个返回值

    在函数有多个返回值的情况下 我们有两种返回方式

    方式一 : 给各个返回值标识符命名 之后return

    
    func test9(key int) (a1 int , a2 int)  {
    	a1 = 1 
    	a2 = 2
    	return 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    方式二: 直接return多个返回值

    func test8(key int) (a1 int , a2 int)  {
    	return 1 , 2
    }
    
    • 1
    • 2
    • 3

    函数类型

    在Go语言中 函数也是一种数据类型

    我们可以通过type来定义它 它的类型就是拥有相同参数 相同返回值的类型

    我们可以用它来做到一些好玩的事情 比如说函数套函数

    我们下面定义了一个函数类型 给他取别名为 FuncType

    type FuncType func(int , int) (int)  // 声明了一个函数类型 注意 func后面没有函数名 
    
    • 1

    那么我们就可以在下面的函数中 使用这个FuncType作为参数

    type FuncType func(int , int) (int)  // 声明了一个函数类型 注意 func后面没有函数名 
    
    func Clac(a int , b int , f FuncType)(result int){
    	result = f(a  , b)
    	return result
    }
    
    func Add(a int , b int) (result int){
    	result = a + b 
    	return result
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    之后我们就可以这么调用这个函数

    res := Clac(10 , 8 , Add)
    
    • 1

    匿名函数和闭包

    这里先给大家解释下闭包的概念

    闭包就是一个函数 捕获 了和他在同一作用域的其他变量和常量

    这也就意味着 当一个函数闭包了 不管这个函数在任何地方被调用 它都能使用这些变量和常量 不管它们有没有出作用域

    所以说 只要闭包还在使用 这些变量就会一直存在 不会销毁


    在Go语言中 所有的匿名函数都是闭包的

    下面简单介绍几种匿名函数的定义方式 解释就直接放在注释里面了

    	// 这两个参数会被我们下面的匿名函数捕获
    	var a int = 10
    	var str string = "abcde"
    
    	// 方式一 使用:= 来让一个变量接收func类型
    	f1 := func() { // 这里的func()是一个匿名函数 无参数无返回值
    		fmt.Println(a, "  ", str)
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    	// 方式二 在你们函数的末尾直接调用
    	func(a int , b int)(result int){
    		result = a + b
    		fmt.Println(result)
    		return result
    	}(1 , 1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    匿名函数的闭包是引用

    如果我们在匿名函数内部 修改了闭包的变量 那么外部的值也会改变

    	var a int = 10 
    	var str string = "abcde"
    
    	f1 := func(){
    		a = 20
    		str = "go"
    	}
    
    	f1()
    
    	// 此时打印a 和 str 我们会发现它们的值改变了 
    	fmt.Println(a , str)	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    返回值为匿名函数

    我们都知道 局部变量在出了作用域之后就会被销毁 但是我们如果使用匿名对象作为返回值 就能够让该变量存在的时间延长 比如说下面的代码

    func squares() (func() int) {
    	var x int 
    
    	return func() (ans int){
    		x++
    		return x * x
    	}
    }
    
    func main()  {
    	f := squares()
    
    	fmt.Println(f())
    	fmt.Println(f())
    	fmt.Println(f())
    	fmt.Println(f())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    解释下 我们 squares 函数的返回值是一个匿名对象

    该匿名对象将局部变量 x 捕捉了 并且在内部++ 之后平方

    因此 该局部变量的生命周期被延长了 所以说我们最后得到的结果会是

    1 4 9 16 … …

    延迟调用defer

    关键字defer用于延时一个函数或者是方法的调用

    需要注意的是 defer方法只能出现在函数的内部

    func test() {
    	defer fmt.Println("no.1")
    	fmt.Println("no.2")
    }
    
    func main() {
    	test()
    	defer fmt.Println("no.3")
    	fmt.Println("no.4")
    
    	// 输出结果为  2  1  4  3
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面的两个函数演示了defer关键字的作用

    在一个作用域内 如果我们使用了defer关键字 那么语句就会在作用域即将被销毁的时候执行

    如果说有多个defer语句的话 遵循栈的原则 也就是后进先出

    这里需要注意的一点是

    如果有某个函数或者某个延时调用发生错误 整个栈也会被清空 也就是所有延时调用语句都会执行

    func test() {
    	defer fmt.Println("no.1")
    	fmt.Println("no.2")
    }
    
    func test1(x int) {
    	v1 := 100 / x
    	_ = v1 
    }
    
    func main() {
    	test()
    	defer fmt.Println("no.3")
    	fmt.Println("no.4")
    	defer test1(0) // 会发生错误
    	// 输出结果为  2  1  4  3
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    就比如说上面的代码 我们故意写了除0错误 但是它的执行结果却是所有语句都运行完毕之后再报错

    defer和匿名函数结合使用

    func main() {
    	a , b := 10 , 20 
    
    	defer func(x int) {
    		fmt.Println(x)
    	} (a) // 将a以传值传递的方式传递给匿名函数func
    
    	a += 10 
    	b += 100
    
    	fmt.Println(a)
    	fmt.Println(b)
    
    	// 输出为 20  120  10
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    获取命令行参数

    在Go语言中 如果我们要获取命令行参数的话 需要使用到os包

    代码演示如下

    func main()  {
    	args := os.Args // 获取用户的所有参数
    
    	// 如果获取失败 或者是参数不足则报错 否则打印出前两个参数
    	if args == nil || len(args) < 2{
    		fmt.Println("err!!")
    		return
    	}
    
    	ip := args[1]
    	port := args[2]
    
    	fmt.Println(ip)
    	fmt.Println(port)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    React Native 0.70 版本发布,Hermes 成为默认引擎
    天选之子Linux是如何发展起来的?为何对全球IT行业的影响如此之大?
    git入门
    django 自动化脚本的开发 - 脚本仅启动运行一次 / 执行定时任务 - 测试过程
    风口下的低代码,怎么样了?
    MERGE Into 的用法在ORACLE和高斯数据库上的差异
    Modbus转Profinet网关在大型自动化仓储项目应用案例
    小程序只用云开发,如何发送公众号模板消息?
    Linux ——repo下载与使用方法详解
    详细介绍mysql表格id清零的方法
  • 原文地址:https://blog.csdn.net/meihaoshy/article/details/134425499