本文就小编曾经遇到的函数及其参数问题,整理一篇博客在此。涉及到默认参数、函数重载、可变参数等方面。

默认参数

在函数声明中可以设置若干个默认参数,这些参数在函数调用时可以省略。例如:

void print(int a = 3, int b=4){
    cout<<a<<" "<<b<<endl;
}
print();  // 3 4
print(4); // 4 4
print(,3); // compile error

默认参数提供了更灵活的函数声明。简化函数逻辑的同时,也提供了一种扩展既有函数的方式。

在很多情况下,函数的声明与定义是分开的。语法上讲我们可以在声明时给出默认参数, 也可以在定义时给出默认参数。然而在函数调用处,编译器会进行语法检查, 被调用的“函数签名”是否存在、是否有歧义,只取决于调用处可见的那个函数(声明或定义)。

函数重载

C++中可以通过不同的参数个数来进行函数重载。唯一需要注意的是提供默认参数时,重载函数的调用会有二义性。例如:

void print(int a = 1){}
void print(int a = 1, int b = 2){}
print();

同时定义上述两个print函数没有问题,因为它们的函数签名是不同的。但print()的调用存在歧义,此处会发生编译错误。 因此,使用默认参数时,要避免函数重载的二义性

除了虚函数外,函数调用的函数地址是在编译期决定的。

可变参数

可变参数是指一个函数可以接受可变数目的参数,在C语言中最常见的便是printfscanf。 它们是如何实现的呢?

当然这不是函数重载,我们只定义一个函数,它可以接受任意个参数。

我们知道函数调用前需要将参数压栈,默认情况下C++会将参数从右向左顺序压栈。 栈空间是从高地址向低地址生长的,故第一个参数拥有最低的地址。 获取第一个参数后,我们只要知道后续参数的类型和个数,就可以逐个按照地址取出来。

在DLL生成和跨语言的过程调用时,常常遇到压栈顺序和由谁清空参数栈的问题,它们可以在函数声明中加入类似_stdcall_cdecl等关键字来指定。

如何定义一个可变参数的函数?C++提供了stdarg.h头文件,它定义了若干宏来方便上述的操作。 首先使用va_list来获取参数栈,va_start来指定第一个参数的地址。 此后便可以使用va_arg()来获取后续的参数了,这时需要指定类型(因为需要确定空间大小)。 最后用va_end来释放资源。一个简单的可变参数的函数像这样:

#include <stdarg.h>
void my_print(int count, ...){
    va_list ap;
    va_start(ap, count);
    for(int j=0; j<count; j++)
        cout<<va_arg(ap, double)<<endl;
    va_end(ap);
}

第一个参数是必不可少的,我们需要用它来确定参数的个数。

本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2015/07/07/cpp-functions-and-arguments.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。