java va start_函数变参的使用

介绍C/C++,java,和go语言对函数变参的使用。

1. C/C++语言的变参

1.1 变参函数声明

变参函数的声明

type func_name(const char * format, ...)

type func_name(int count, ...)

说明

变参是通过三个点号(...)来表示。

变参不能是函数第一个参数,否则编译器会报错,例如

t.c:4:12: error: ISO C requires a named argument before ‘...’

变参必须是函数的最后一个参数

va_start的第二个参数要求是最后一个命名参数,否则:

t.c:11:4: warning: second parameter of ‘va_start’ not last named argument [-Wvarargs]

1.2 如何调用

这个很简单啦

func_name("%s,%s,%d\n", "AA", "BB", 33); // 变参个数为 3

func_name("Hello\n"); // 变参个数为 0

func_name(1, 100) // 变参个数为 1

func_name(2, 100, 200) // 变参个数为 2

1.3 函数内如何分析变参

主要有三个相关的变量和宏,定义在 stdarg.h 头文件里。

// 定义va_list类型的变量

va_list valist;

// 初始化valist,第二个参数表示从这个参数之后的参数进入valist

// 这也正好符合前面的限制,变参不能是第一个参数。

va_start(valist, num);

// 依次访问所有的参数

// 其中第二个参数是类型,这依赖于应用程序自行保证类型的正确性。

for (i = 0; i < num; i++) {

sum += va_arg(valist, int);

}

// 清除valist

va_end(valist);

举个例子来说:

#include

#include

void showParam(char * types, ...) {

int i;

char c;

char * s;

va_list valist;

char * p = types;

va_start(valist, types);

for(;*p != '\0'; p++) {

switch(*p) {

case 'i':

i = va_arg(valist, int);

printf( "%d\n", i);

break;

case 'c':

c = va_arg(valist, int);

printf("%c\n", c);

break;

case 's':

s = va_arg(valist, char *);

printf("%s\n", s);

break;

default:

break;

}

}

va_end( valist );

}

int main(int argc, char * argv[]) {

showParam("ics", 123,'A',"abc");

return 0;

}

运行结果如下:

$ gcc main.c && ./a.out

123

A

abc

1.4 一个变参函数如何调用另一变参函数

例如

void Log(char * level, const char * format, ...) {

...

}

void Debug(const char * format, ...) {

...

}

Debug函数如何调用Log呢?

void Debug(const char * format, ...) {

Log("DEBUG", format, ...)

}

这样写行吗,好像语法不通啊。

实际上C/C++不支持一个变参直接调用另一个变参函数。

但是可以把被调用的函数调整一下即可,看例子:

#include

#include

void Debug(const char* format, ...)

{

char output[1024];

va_list valist;

va_start(valist, format);

vsprintf(output, format, valist);

va_end(valist);

printf("%s\n", output);

}

int main(int argc, char * argv[]) {

Debug("This is test log:%d=%s", 123, "ABCD");

return 0;

}

运行结果为:

$ gcc main.c && ./a.out

This is test log:123=ABCD

我们可以看到有一个函数vsprintf,看它的声明:

int vsprintf(char *str, const char *format, va_list ap);

这个函数里面,我们使用了va_list作为变参的类型,至此我们得出结论,如果一个变参函数需要调用另一个变参函数,那么被调用的变参函数需要进行改造,使用va_list定参来模拟变参。

这样前面的Debug/Log函数需要改造成如下格式:

#include

#include

void Log(char * level, const char * format, va_list valist) {

char output[1024];

vsprintf(output, format, valist);

printf("%s: %s\n", level, output);

}

void Debug(const char * format, ...) {

va_list valist;

va_start(valist, format);

Log("DEBUG", format, valist);

va_end(valist);

}

int main(int argc, char * argv[]) {

Debug("This is test log:%d=%s", 123, "ABCD");

return 0;

}

2. Java语言的变参

2.1 变参函数声明

type func_name(type ...params)

这个声明和C/C++的声明是一样的;不同点是

Java变参除了三个点(...)之外,还有名字和类型。

Java允许第一个参数就是变参。但同样不允许变参后面还有参数。

2.2 如何调用

调用方式和C/C++一样

public static void run(String... strings) {

for (String s: strings) {

System.out.println("run: " + s);

}

}

public static void main(String[] args) {

run(); // 变参个数为 0

run(""); // 变参个数为 1

run("AA","BB"); // 变参个数为 2

run("AA","BB", "CC"); // 变参个数为 3

}

运行结果

run:

run: AA

run: BB

run: AA

run: BB

run: CC

2.3 函数内如何分析变参

前面例子我们也看到了,很简单遍历参数就行了,不展开讲了。

2.4 一个变参函数如何调用另一变参函数

public static void run2(String ... strings) {

for (String s: strings) {

System.out.println("run2: " + s);

}

}

public static void run1(String... strings) {

run2(strings);

}

public static void main(String[] args) {

run1("AA","BB", "CC");

}

比起前面的C/C++,是不是感觉很幸福;太简单了,直接调用就行了。

到这里是不是可以完美的结束了呢,我问一个问题:

run1()和run2()都是变参函数,main传给run1()有三个参数("AA","BB","CC"),run1()传给run2()的是参数strings,我的问题是run2()收到的是一个参数(String...类型),还是三个参数(String)类型。

改造一下代码:

public static void run2(String ... strings) {

System.out.println("run2 param type=" + strings.getClass().getName());

System.out.println("run2 param size=" + strings.length);

}

public static void run1(String... strings) {

System.out.println("run1 param type=" + strings.getClass().getName());

System.out.println("run1 param size=" + strings.length);

run2(strings);

}

public static void main(String[] args) {

run1("AA","BB", "CC");

}

再运行

run1 param type=[Ljava.lang.String;

run1 param size=3

run2 param type=[Ljava.lang.String;

run2 param size=3

我们会发现,不管在run1()里面还是在run2()里面收到的参数strings实际上是一个string数组类型,是不是java使用数组来包装变参呢?

答案是肯定的。调用者可以使用数组来传递变参。

public static void main(String[] args) {

String params[] = {"AA","BB", "CC"};

run1(params);

// run1("AA","BB", "CC");

}

上述两个调用方法是一样的效果。

再看一个例子:

public static void run2(Object ... strings) {

System.out.println("run2 param type=" + strings.getClass().getName());

System.out.println("run2 param size=" + strings.length);

for (Object s: strings) {

System.out.println("run2 param=" + s);

}

}

public static void run1(Object... strings) {

System.out.println("run1 param type=" + strings.getClass().getName());

System.out.println("run1 param size=" + strings.length);

run2(strings);

}

public static void main(String[] args) {

String params[] = {"AA","BB", "CC"};

run1(params, "DD");

}

运行结果:

run1 param type=[Ljava.lang.Object;

run1 param size=2

run2 param type=[Ljava.lang.Object;

run2 param size=2

run2 param=[Ljava.lang.String;@15db9742

run2 param=DD

理解一下为什么是这样。

因为run1()收到的变参实际上是一个数组,包含两个元素,第一个元素是一个字符串数组("AA","BB", "CC"),第二个元素是一个字符串("DD")。

3. go语言的变参

3.1 变参函数的声明

func func_name(args ...interface{}) {

...

}

这种声明方式和java是一致的。

3.2 如何调用

func_name() # 变参个数为 0

func_name("") # 变参个数为 1

func_name("AA", "BB", "CC") # 变参个数为 3

3.3 参数分析

再看一个例子:

package main

import "fmt"

import "reflect"

func Log(args ...string) {

fmt.Println(reflect.TypeOf(args))

fmt.Printf("%d\n", len(args))

}

func main() {

Log()

Log("")

Log("INFO", "AAA", "This is a message")

}

运行结果:

$ go build && ./main

[]string

0

[]string

1

[]string

3

这下发现golang的变参和java的变参原理是一样的,都是以数组的方式(golang用slice)组织参数,那么就可以简单的把变参理解到slice就非常清楚了。

3.4 一个变参函数如何调用另一变参函数

package main

import "fmt"

import "reflect"

func Debug(args ...string) {

Log("DEBUG", args...)

}

func Log(level string, args ...string) {

fmt.Println(reflect.TypeOf(args))

fmt.Printf("%d\n", len(args))

}

func main() {

Debug()

Debug("")

Debug("AAA", "This is a message")

}

这个例子中main()=>Debug()=>Log(),Debug和Log都是变参函数;

注意在Debug里面调用Log的时候 需要显式的指定args是一个变参变量,这和java的使用方式有点差异。

正是这个差异,在golang里面变参和slice[]不是完全相等。虽然在java里面可以传一个数组给变参,但是在golang里面却不能传一个slice给变参。

package main

import "fmt"

import "reflect"

func Debug(args ...string) {

ss := []string{"aa", "bb"}

Log("DEBUG", ss)

}

func Log(level string, args ...string) {

fmt.Println(reflect.TypeOf(args))

fmt.Printf("%d\n", len(args))

}

func main() {

Debug()

Debug("")

Debug("AAA", "This is a message")

}

编译

./main.go:9: cannot use ss (type []string) as type string in argument to Log

可见在golang里面变参就是变参,不是slice对象;两者不能互通使用。

但是还是可以把slice标记为变参,通过在变量名后跟三个点(varname...)的格式,看下面例子代码:

package main

import "fmt"

import "reflect"

func Log(args ...string) {

fmt.Println(reflect.TypeOf(args))

fmt.Printf("%d\n", len(args))

}

func main() {

ss1 := []string{}

ss2 := []string{"aa", "bb"}

Log(ss1)

Log(ss2)

}

编译

$ go build && ./main

./main.go:15: cannot use ss1 (type []string) as type string in argument to Log

./main.go:16: cannot use ss2 (type []string) as type string in argument to Log

我们把他改成:

package main

import "fmt"

import "reflect"

func Log(args ...string) {

fmt.Println(reflect.TypeOf(args))

fmt.Printf("%d\n", len(args))

}

func main() {

ss1 := []string{}

ss2 := []string{"aa", "bb"}

//Log(ss1)

//Log(ss2)

Log(ss1...)

Log(ss2...)

}

再运行

$ go build && ./main

[]string

0

[]string

2

这样就运行的很好了。因为如果直接使用ss1那就是这是一个slice类型的参数,但是Log()函数并不支持slice类型参数,而如果换成ss1...表示这是多个字符串类型参数。

最后一个例子:

package main

import "fmt"

import "reflect"

func Log(args ...interface{}) {

fmt.Println(reflect.TypeOf(args))

fmt.Printf("%d\n", len(args))

}

func main() {

ss1 := []interface{}{}

ss2 := []interface{}{"aa", "bb"}

Log(ss1)

Log(ss2)

}

编译运行

$ go build && ./main

[]interface {}

1

[]interface {}

1

明白了把。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值