main函数参数及可变参数列表解析

main函数的参数解析

平时写main函数大多都是int main(),然后误以为main函数是没有参数的,但在有些书上又看到main括号里有三个不怎么看的懂的参数,其实仔细研究就会发现,main函数也是有参数的,main函数的原型如下:
int main(int argc,char *argv[],char *envp[])
{
  program-statements
}

我们可以看到main函数里面是有三个参数的

第一个参数:**argc**是个整形变量,表示命令行参数的个数(含第一个参数)
第二个参数:**argv**是个字符指针的数组,每个元素是一个字符指针,指向一个字符串。这些字符串就是命令行中的每一个参数(字符串)
第三个参数:**envp**是字符指针的数组,数组的每一个元素是指向一个环境变量(字符串)的字符指针。

下面我们来看一段代码:

#include
int main(int argc,char *argv[],char *envp[])
{
    int i = 0;
    for (i = 0; i < argc; i++)
    {
        printf("%s\n", argv[i]);
    }
    return 0;
}

上面说了,argc表示命令行参数的个数,argv是命令行每一个参数,那这么看来,这段代码貌似是输出它的参数了,我们看一下运行结果:
main函数参数及可变参数列表解析_第1张图片
我们看到输出的是一个路径,那么这段路径是不是命令行的每一个参数呢,我们可以查看一下,找到命令行参数
main函数参数及可变参数列表解析_第2张图片
会发现此时命令参数后面是空的,所以上面一段路径并不是命令参数,那么我们给参数这里随便输入几个参数,运行结果会不会改变呢?
main函数参数及可变参数列表解析_第3张图片
再看运行结果:
main函数参数及可变参数列表解析_第4张图片
这时,在原来那一串路径下面又输出了我们刚输入的参数,那么我们可以图下这么解释他们:
main函数参数及可变参数列表解析_第5张图片
注:argv数组的最后一个元素存放了一个NULL的指针。
这样我们就解释了main函数里面的前两个参数,那么低三个参数有事什么意思呢?我们再来看一段代码:

#include
int main(int argc,char *argv[],char *envp[])
{
    int i = 0;
    while (envp[i] != NULL)
    {
        printf("%s\n", envp[i]);
        i++;
    }
    return 0;
}

这次我们输出envp,看看运行结果会是什么?
main函数参数及可变参数列表解析_第6张图片
哇!这满屏都是什么,从右边的滑动条可以看到,这只是截取了一小部分
main函数参数及可变参数列表解析_第7张图片
其实结果显示都是环境变量。
注:envp数组的最后一个元素也存放NULL指针。
到这里我们就把main函数的三个参数都解释完了。

为什么需要可变参数?

我们知道,函数的原型中,列出了函数期望接受的参数,但原型只能显示固定数目的参数,我们之前接触到的函数有没有可以不同时候接受不同数目参数的函数呢?其实这一类函数我们用的最多,只是我们没有注意而已,就是printf函数和scanf函数。那么这两种函数就用到了可变参数,如果不用可变参数将无法实现这两个函数。

可变参数列表解析

上面我们提到,函数在一般情况下是没有办法在不同时候接受不同的参数的,但是用过将函数实现为可变参数的形式,可以使得函数可以接受一个以上的任意多个参数(不固定)。
举个栗子:
实现一个函数可以求任意个参数的平均值。
#include
#include
int  average(int n, ...)
{
    va_list arg;
    int i = 0;
    int sum = 0;
    va_start(arg, n);
    for (i = 0; i < n; i++)
    {
        sum += va_arg(arg, int);
    }
    return sum / n;
    va_end(arg);
}
int main()
{
    int a = 1;
    int b = 2;
    int c = 3;
    int avg1 = average(2, a, c);
    int avg2 = average(3, a, b, c);
    printf("avg1=%d\n", avg1);
    printf("avg2=%d\n", avg2);
    return 0;
}

这段代码一眼望去我也不知道是怎么实现的,因为里面的va_list等之前是没有接触的,那么我们一步一步来看:

va_list arg是声明一个va_list类型的变量arg,到这里你可能会问va_list类型是个什么类型,我们在代码中转到它的定义可以来看一下:

main函数参数及可变参数列表解析_第8张图片

可以看到va_list其实是一个char *类型,所以va_list arg其实就是定义了一个char *类型的变量arg,它用于访问参数列表的未确定的部分。

那么va_start(arg,n)又是怎么一回事呢?我们继续转到它的定义看一下:
main函数参数及可变参数列表解析_第9张图片
我们看到的是宏,那么这个宏又是什么意思,经过几次转定义如下:
main函数参数及可变参数列表解析_第10张图片
我们再从后往回按照我们理解的意思带回去就明白了!

我们知道了,arg这个变量是调用va_start来初始化的。它的第一个参数是va_list的变量名,第二个参数是省略号前最后一个有名字的参数。初始化过程把arg变量设置为指向可变参数部分的第一个参数。

为了访问参数,需要使用va_arg
这里写图片描述

这个宏接受两个参数:va_list变量和参数列表中下一个参数类型。在这个例子中所有的可变参数都是整型。va_arg返回这个参数的值,**并使用va_arg指向下一个可变参数**。

最后当访问完最后一个可变参数之后,我们需要调用va_end

可变参数的限制

1.**可变参数必须从头到尾逐个访问**。如果你在访问了几个可变参数之后想半途而止,这是可以的,但是如果你想一开始就访问参数列表中间的参数,是无法做到的。
2.**参数列表中至少有一个命名参数**。如果连一个明明参数都没有,就无法使用va_start。
3.这些宏是无法直接判断实际存在参数的数量。
4,这些宏是无法判断每个参数类型的5.如果在va_arg中指定了错误的类型,那么其后果是不可预测的。

写的不到位的地方还请路过的大神指正!

你可能感兴趣的:(main函数参数及可变参数列表解析)