zoukankan      html  css  js  c++  java
  • 函数或方法的可变长参数 —— 专题

    支持可变长参数列表的函数可以支持任意个传入参数,比如fmt.Println函数就是一个支持可变长参数列表的函数。

    需要注意的是,可变长参数应该是函数定义的最右边的参数,即最后一个参数

    Go 中的一些内置函数都是可变参数函数,例如:append() 函数:

    func append(slice []Type, elems ...Type) []Type
    

    如何工作

    可变参数函数的工作原理就是把可变参数转化成切片
    调用函数时,不传可变参数,在 Go 语言里也是合法的,这种情况下,s 是一个长度和容量都是 0 的 nil 切片

    func change(str1 string, s ...string) {
        fmt.Println(str1)
        fmt.Printf("%T
    ",s)     // %T 输出变量类型
        fmt.Println(s)
    }
    
    func main() {
        blog := "seekload.net"
        change(blog,"Hello","World","Go")
    }
    

      输出:

    seekload.net
    []string
    [Hello World Go]
    

      从输出结果看出,change() 函数中,参数 s 是 []string 类型的切片。
    可变参数函数的工作原理就是把可变参数转化成切片。调用 change() 函数时可变参数是 Hello、World、Go,这三个参数被编译器转化成切片
    []string{"Hello","World","Go"},然后被传入change()函数。
    另外,调用 change() 函数时候,可以不传可变参数,在 Go 语言里也是合法的,这种情况下,s 是一个长度和容量都是 0 的 nil 切片。

    将切片传递给可变参数函数

    func change(str1 string, s ...string) {
        fmt.Println(str1)
        fmt.Printf("%T
    ",s)
        fmt.Println(s)
    }
    
    func main() {
        slice := []string{"Hello","World","Go"}
        blog := "seekload.net"
        change(blog,slice)
    }
    

      上述代码中,将切片 slice 传给可变参数函数 change(),结果编译出错:cannot use slice (type []string) as type string in argument to change 。
    原因很简单,由可变参数函数的定义可知,s …string 意味它可以接受 string 类型的可变参数。代码第 10 行,slice 作为可变参数传入 change() 函数。前面我们知道,可变参数会被转换为 string 类型切片然后再传入 change() 函数中。但 slice 是一个 string 类型的切片,编译器试图通过下面这种方式在 slice 基础上再创建一个切片:

    change("seekload.net", []string{s})
    

      之所以会失败,因为 slice 是 []string 类型,而不是 string 类型。
    庆幸的是,Go 提供了将切片传入可变参数函数的语法糖:直接在切片后加上 … 后缀。这样,切片将直接传入函数,不会再创建新的切片。
    修改上面的代码:

    change(blog,slice...)
    

      输出:

    seekload.net
    []string
    [Hello World Go]
    

      前面提到一点,通过 Go 提供的语法糖将可变参数函数传入切片,不会创建新的切片。如果在函数中改变切片的值会发生什么呢?

    func change(s ...string) {
        s[0] = "seekload.net"
        fmt.Printf("%T
    ",s)
        fmt.Println(s)
    }
    
    func main() {
        slice := []string{"Hello","World","Go"}
        change(slice...)
        fmt.Println(slice)
    }
    

      输出:

    []string
    [seekload.net World Go]
    [seekload.net World Go]
    

      

    从结果可以看出,main() 函数的切片已经改变了。
    为什么会有这样的输出呢?代码第 9 行,使用了语法糖...并且将切片作为可变参数传入 change() 函数。如上面讨论的,如果使用了...,切片本身会作为参数直接传入,不会再创建一个新的切片。所以在 change() 函数中,第一个元素被替换成 seekload.net,会影响到 main() 函数的切片。

    另外,我们也可以通过将数组转化成切片传递给可变参数函数

    func change(s ...string) {
        s[0] = "seekload.net"
        fmt.Printf("%T
    ",s)
        fmt.Println(s)
    }
    
    func main() {
        arr := [3]string{"Hello","World","Go"}
        change(arr[:]...)
        fmt.Println(arr)
    }
    

      输出结果跟上面的例子是一样的

    给大家列几种比较常见的写法:

    func change(s ...string) {
        fmt.Printf("%T
    ",s)
        fmt.Println(s)
    }
    func main() {
        slice1 := []string{"Hello","World","Go"}
        slice2 := []string{"Aa","Bb"}
        // 1、
        change(append(slice1,"Again")...)
        // 2、
        change(append(slice1,slice2...)...)
    }
    

      输出:

    []string
    [Hello World Go Again]
    []string
    [Hello World Go Aa Bb]
    

      

    1. 变长参数:非切片类型 和 切片类型

    package main
    import "fmt"
    // 这个函数可以传入任意数量的整型参数
    func sum(nums ...int) {
    	fmt.Println(nums)
    	total := 0
    	for i, num := range nums {
    		fmt.Println(i)
    		total += num
    	}
    	fmt.Println(total)
    }
    func main() {
    	// 支持可变长参数的函数调用方法和普通函数一样
    	// 也支持只有一个参数的情况
    	sum(1)
    	sum(1, 2)
    	sum(1, 2, 3)
    	// 如果你需要传入的参数在一个切片中,像下面一样
    	// "func(slice...)"把切片打散传入
    	nums := []int{1, 2, 3, 4}
    	sum(nums...)
    }
    

      注意可变参数的传入方式

      输出结果:

    [1]
    0
    1
    [1 2]
    0
    1
    3
    [1 2 3]
    0
    1
    2
    6
    [1 2 3 4]
    0
    1
    2
    3
    10

    2. 任意类型的变长参数

    /*
    任意类型的不定参数,用interface{}表示
    */
    func MyPrintf(args ...interface{}) {
        for _, arg := range args { //迭代不定参数
            switch arg.(type) {
            case int:
                fmt.Println(arg, "is int")
            case string:
                fmt.Println(arg, "is string")
            case float64:
                fmt.Println(arg, "is float64")
            case bool:
                fmt.Println(arg, " is bool")
            default:
                fmt.Println("未知的类型")
            }
        }
    }
    
    func main() {
        /*输出结果:
                    12 is int
                    haha is string
                    12.5 is float64
                    false  is bool
                    -12.5 is float64
        */
        MyPrintf(12, "haha", 12.5, false, -12.5)
    }
    

      

     =============== 坑 =====================

    package main
    import(
    	"fmt"
    )
    func TestArgs(first int,arg ...interface{}){
    	fmt.Println(first,arg)
    }
    
    func main(){
    	nums:=[]int64{1,2,3,4}
    	TestArgs(1,nums...)
    }
    

    想要实现的代码逻辑很明了

    1. TestArgs 接受一个 int 参数,可变长的参数,并且类型为 interface{}

    2. nums 做为 slice,使用 ... 语法打散后传入 TestArgs

    看上去逻辑没问题,但编译报错

    [root@YY yang]# go build tt.go
    # command-line-arguments
    ./tt.go:13:11: cannot use nums (type []int64) as type []interface {} in argument to TestArgs
    

    居然报类型不匹配,写 Go 也一年了,这块的认知太不到位,一直认为会将 nums 打散,再以 interface{} 这个通用类型组成 interface{} slice 传到 TestArgs

    那么,我们看看到底 nums 传进去后是什么

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func TestArgs(first int, arg ...interface{}) {
    	fmt.Println(first, reflect.TypeOf(arg))
    }
    
    func main() {
    	nums := []interface{}{1, 2, 3, 4}
    	TestArgs(1, nums...)
    }
    

      执行结果

    [root@YY yang]# ./tt
    1 []interface {}
    

      那么确认是把可变参数当做 slice 传给函数,这点和 python 很像。那我们再看看 slice 是不是同一个

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func TestArgs(first int, arg ...interface{}) {
    	fmt.Println(first, reflect.TypeOf(arg))
    	fmt.Printf("TestArgs addr of slice %p
    ", arg)
    	fmt.Println(arg)
    }
    
    func main() {
    	nums := []interface{}{1, 2, 3, 4}
    	fmt.Printf("TestArgs addr of slice %p
    ", nums)
    	TestArgs(1, nums...)
    	TestArgs(2, "abc", "def", "ghlfk")
    }
    

      执行后输出如下

    [root@YY yang]# ./tt
    TestArgs addr of slice 0xc42004a040
    1 []interface {}
    TestArgs addr of slice 0xc42004a040
    [1 2 3 4]
    2 []interface {}
    TestArgs addr of slice 0xc420058150
    [abc def ghlfk]
    

      nums和arg地址相同!原来,如果传入的可变参数本身就是由 slice 以 ... 形式传入的,那么就直接使用这个 slice,不会新建 slice。那么我理解的 Go 的可变参数执行方式:

    对于 func(first int, arg ...T)
    
    1. 当不传可变参数时,对应的 arg 就是 nil
    2. 传入单个可变参数时,实际上执行 [] T{arg1,arg2,arg3}
    3. 传入...语法的 slice时,直接使用这个 slice
    
    由此我们就很好理解开篇的小例子为什么执行不了,[]int64 和 []interface{} 是两个类型的 slice 。
    

      转自:https://www.jianshu.com/p/94710d8ab691

  • 相关阅读:
    linux 命令 # tar zcvf Work.tar.gz Work
    ODBC
    vmware 与机器共享
    关机!!!
    reador哦
    asp.net的三层架构图
    十大著名黑客——阿德里安拉莫
    十大著名黑客——查德斯德尔曼
    十大著名黑客——埃里克雷蒙德
    十大著名黑客——George Hotz
  • 原文地址:https://www.cnblogs.com/yorkyang/p/8144434.html
Copyright © 2011-2022 走看看