Функции с переменным количеством параметров
Язык программирования Си допускает использование функций, которые имеют нефиксированное количество параметров. Более того может быть неизвестным не только количество, но и типы параметров. То есть точное определение параметров становится известным только во время вызова функции.
Для определения параметров неопределенной длины в таких функциях используется многоточие:
1
тип имя_функции(обязательные параметры, ...)
При этом надо учитывать, что функция должна иметь как минимум один обязательный параметр.
Например, определим функцию, которая вычисляет сумму чисел, количество чисел нефиксировано:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
int sum(int n, ...)
{
int result = 0;
// получаем указатель на параметр n
for(int *ptr = &n; n>0; n--)
{
result+= *(++ptr);
}
return result;
}
int main(void)
{
printf("%d \n", sum(4, 1, 2, 3, 4));
printf("%d \n", sum(5, 12, 21, 13, 4, 5));
return 0;
}
При работе с параметрами следует учитывать, что для языка Си, как правило, меньшее значение адреса у первого параметра, а остальные размещаются дальше подряд (Но это характерно НЕ для всех компиляторов). Поэтому мы можем получить адрес первого параметра и указателем пробежаться по адресам, которые идут после адреса первого параметра.
Первый и обязательный параметр функции sum - n - указывает на количество необязательных параметров. В цикле устанавливаем указатель ptr на адрес параметра n и последовательно перемещаем его. С помощью операции разыменования *ptr после перемещения указателя на один элемент вперед получаем значение и выполняем сложение с переменной result.
В то же время нельзя не отметить недостаток данного решения: все параметры представляют один и тот же тип. Кроме того, мы можем при наборе кода ошибиться со значением первого параметра, тогда результаты функции будут непредсказуемыми. Третий недостаток - компилятор может вносить свои коррективы в размещение параметров, соответственно результат опять может быть непредсказуемым.
И для решения этих проблем и упрощения работы с переменным количеством параметров неопределенных типов в языке Си в стандартом заголовочном файле stdarg.h определены специальные макрокоманды:
1
2
3
va_start();
va_arg();
va_end();
Все эти макросы используют специальный тип данных va_list, который также определен в stdarg.h и который позволяет обрабатывать списки параметров с нефиксированным количеством.
Макрос va_start имеет следующее определение:
1
void va_start(va_list param, последний_явный_параметр);
Первый параметр макроса - param связывает объект va_list с первым необязательным параметром. Для его определения в качестве второго параметра в макрос передается последний обязательный параметр функции. Таким образом, используя последний обязательный параметр, мы можем нацелить объект va_list на адрес первого необязательного параметра. То есть фактически va_list выступает в данной роли как указатель.
Макрос va_arg имеет следующее определение:
1
type va_arg(va_list param, type);
Этот макрос позволяет получить значение параметра типа type, а также переместить указатель va_list на следующий необязательный параметр.
Макрос позволяет выйти из функции с переменным списком параметров. Она имеет следующее определение:
1
void va_end(va_list param);
В качестве параметра она принимает указатель va_start, который ранее был задействован в макросах va_start и va_arg.
Перепишем предыдущий пример с использованием этих макрокоманд:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdarg.h>
int sum(int n, ...)
{
int result = 0;
va_list factor; //указатель va_list
va_start(factor, n); // устанавливаем указатель
for(int i=0;i<n; i++)
{
result += va_arg(factor, int); // получаем значение текущего параметра типа int
}
va_end(factor); // завершаем обработку параметров
return result;
}
int main(void)
{
printf("%d \n", sum(4, 1, 2, 3, 4));
printf("%d \n", sum(5, 12, 21, 13, 4, 5));
return 0;
}
В функции sum() вначале определяется указатель va_list factor;.
Далее связываем этот указатель с первым необязательным параметром: va_start(factor, n);.
В цикле пробегаемся по всем необязательным параметрам и их значение прибавляем к переменной result: result += va_arg(factor, int);
В конце завершаем обработку параметров: va_end(factor);.
Результат этой программы будет тот же, что и в предыдущем случае. Но здесь опять же нам надо передавать количество необязательных параметров в качестве первого параметра функции sum. И, кроме того, мы точно знаем, что необязательные параметры имеют тип int.
Но стоит отметить, что используемые нами функции ввода-вывода printf() и scanf() то же имеют неопределенное число параметров, но их типы также неопределены:
1
2
int printf(const char* format, ...);
int scanf(const char* format, ...);
Для идентификации типов аргументов параметр format использует спецификаторы %d, %c и так далее. Например, определим собстенную функцию, которая будет выводит текст на экран, принимая параметры разных типов:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <stdarg.h>
void display(char* format, ...)
{
int d;
double f;
va_list factor; // указатель на необязательный параметр
va_start(factor, format); // устанавливаем указатель
for(char *c = format;*c; c++)
{
if(*c!='%')
{
printf("%c", *c);
continue;
}
switch(*++c)
{
case 'd':
d = va_arg(factor, int);
printf("%d", d);
break;
case 'f':
f = va_arg(factor, double);
printf("%.2lf", f);
break;
default:
printf("%c", *c);
}
}
va_end(factor);
}
int main(void)
{
display("Age:%d \t Weight:%f", 24, 68.4);
return 0;
}
Для упрощения примера здесь взяты только два спецификатора: d (для типа int) и f (для типа double). В самой функции display с помощью указателя char *c пробегаемся по всем символам переданной строки format, пока этот указатель не станет указывать на нулевой символ (*c!='\0'). Если символ не равен знаку %, то выводим этот символ. Иначе смотрим, какой символ идет после знака % - d или f. В зависимости от этого получаем либо объект int, либо объект double.