go实例—函数或方法的可变长参数
支持可变长参数列表的函数可以支持任意个传入参数,比如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\n",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\n",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\n",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\n",s)
fmt.Println(s)
} func main() {
arr := [3]string{"Hello","World","Go"}
change(arr[:]...)
fmt.Println(arr)
}
输出结果跟上面的例子是一样的
给大家列几种比较常见的写法:
func change(s ...string) {
fmt.Printf("%T\n",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\n", arg)
fmt.Println(arg)
} func main() {
nums := []interface{}{1, 2, 3, 4}
fmt.Printf("TestArgs addr of slice %p\n", 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
go实例—函数或方法的可变长参数的更多相关文章
- Python中函数的参数传递与可变长参数
转自旭东的博客原文 Python中函数的参数传递与可变长参数 Python中传递参数有以下几种类型: (1)像C++一样的默认缺省函数 (2)根据参数名传参数 (3)可变长度参数 示例如下: (1)默 ...
- 方法的可变长参数 传入参数个数不确定可用(Type ... values)
/** * 可变长的参数. * 有时候,我们传入到方法的参数的个数是不固定的,为了解决这个问题,我们一般采用下面的方法: * 1. 重载,多重载几个方法,尽可能的满足参数的个数.显然这不是什么好办法. ...
- CSIC_716_20191109【函数的语法,以及函数的分类,可变长参数*args】
函数 定义.作用.及使用方式 函数是一种工具,可以被重复调用. 使用函数可精简重复代码,减少冗余,增加代码的可读性. 函数要先构造函数,然后调用函数. 构造及调用函数的语法结构 关键字def 函数名 ...
- 0521Day03命名规范 Data函数 可变长参数 枚举类型
[重点] 命名规范 枚举类型 Date函数 可变长参数 pirnt,println 命名规范 1. 驼峰命名法:main,username,setUsername 用于变量.方法的命名 2. Pasc ...
- Noah的学习笔记之Python篇:函数“可变长参数”
Noah的学习笔记之Python篇: 1.装饰器 2.函数“可变长参数” 3.命令行解析 注:本文全原创,作者:Noah Zhang (http://www.cnblogs.com/noahzn/) ...
- java中可变长参数的定义及使用方法
JAVA中可以为方法定义可变长参数( Varargs)来匹配不确定数量的多个参数,其定义用“...”表示.其实,这类似于为方法传了一个数组,且在使用方法上也和数组相同,如下: public void ...
- C语言开发具有可变长参数的函数的方法
学习交流可加 微信读者交流①群 (添加微信:coderAllen) 程序员技术QQ交流①群:736386324 --- 前提:ANSI C 为了提高可移植性, 通过头文件stdarg.h提供了一组方便 ...
- python基础语法5 函数定义,可变长参数
函数 1.什么是函数 函数就是一种工具. 可以重复调用 2.为什么要用函数 1.防止代码冗(rong)余 2.代码的可读性差 3.怎么用函数 1.定义函数-->制造工具 2.调用函数--> ...
- python 函数可变长参数
python中的可变长参数有两种: 一种是非关键字参数(*元组),另一种是关键字参数(**字典) 非关键字可变长参数: """ 非关键字可变参数,一个星号作为元组传入函数 ...
随机推荐
- vue proxyTable 跨域问题。
- huawei
线程堆栈(Thread Stack)和托管堆(Managed Heap) 每个正在运行的程序都对应着一个进程 (process),在一个进程内部,可以有一个或多个线程(thread),每个线程都拥有一 ...
- [LeetCode] questions conlusion_InOrder, PreOrder, PostOrder traversal
Pre: node 先, Inorder: node in, Postorder: node 最后 PreOrder Inorde ...
- AFNetworking 源码解析
3.0 之后,就取消了NSOperation的控制. 因为根据Apple Developer Document的文档 https://developer.apple.com/documentation ...
- 基于jquery ajax的多文件上传进度条
效果图 前端代码,基于jquery <!DOCTYPE html> <html> <head> <title>主页</title> < ...
- the import XXXX cannot be resolved 解决方法
明明XXX类完全没问题 突然就报错了 解决方法: 原因一:一个项目引用了兄弟项目的类,报错The import XXX cannot be resolved 解决办法:需要在引用的兄弟项目右键选择Ma ...
- MyBatis基础入门《五》核心配置文件
MyBatis基础入门<五>核心配置文件 描述: 在前面的章节中,简单的学习使用了一下mybatis,对于配置文件没有过多详细说明. 这里先描述项目中的一个核心配置文件:mybatis-c ...
- 笔记 : 将本地项目上传到GitHub
一.准备工作 1. 注册github账号https://github.com, 安装git工具 https://git-for-windows.github.io/ 2. 创建SSH KEY(由于本地 ...
- 第一章 JS基础
1.JavaScript的作用:表单验证,减轻服务器压力动态效果动态改变页面内容 2.JavaScript的组成ECMAScript语法规定BOM对象模型(浏览器对象模型)DOM对象模型(文档对象模型 ...
- python读取大文件
最近在学习python的过程中接触到了python对文件的读取.python读取文件一般情况是利用open()函数以及read()函数来完成: f = open(filename,'r') f.rea ...