Язык программирования Си допускает использование функций, которые имеют нефиксированное количество параметров. Более того может быть неизвестным не только количество, но и типы параметров. То есть точное определение параметров становится известным только во время вызова функции.
Для определения параметров неопределенной длины в таких функциях используется многоточие:
тип имя_функции(обязательные параметры, ...)
При этом надо учитывать, что функция должна иметь как минимум один обязательный параметр. Например:
int sum(int n, ...) { //.......... }
Для упрощения работы с переменным количеством параметров неопределенных типов в языке Си в стандартом заголовочном файле stdarg.h определены специальные макрокоманды:
va_start(); va_arg(); va_end();
Все эти макросы используют специальный тип данных va_list, который также определен в stdarg.h и который позволяет обрабатывать нефиксированный набор параметров.
Макрос va_start имеет следующее определение:
void va_start(va_list param, последний_явный_параметр);
Первый параметр макроса - param
связывает объект va_list с первым необязательным параметром. Для его определения в качестве второго параметра в макрос передается последний обязательный параметр функции. Таким образом, используя
последний обязательный параметр, мы можем нацелить объект va_list на адрес первого необязательного параметра. То есть фактически va_list выступает в данной роли как указатель.
Макрос va_arg имеет следующее определение:
type va_arg(va_list param, type);
Этот макрос позволяет получить значение параметра типа type
, а также переместить указатель va_list на следующий необязательный параметр.
Макрос va_end позволяет выйти из функции с переменным списком параметров. Она имеет следующее определение:
void va_end(va_list param);
В качестве параметра va_end принимает указатель va_start, который ранее был задействован в макросах va_start и va_arg.
Например, определим функцию, которая вычисляет сумму чисел, причем количество чисел нефиксировано::
#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() то же имеют неопределенное число параметров, но их типы также неопределены:
int printf(const char* format, ...); int scanf(const char* format, ...);
Для идентификации типов аргументов параметр format использует спецификаторы %d, %c и так далее. Например, определим собстенную функцию, которая будет выводит текст на экран, принимая параметры разных типов:
#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("Count: %d \tPrice: %f", 24, 68.4); return 0; }
Для упрощения примера здесь взяты только два спецификатора: d (для типа int) и f (для типа double). В самой функции display с помощью
указателя char *c
пробегаемся по всем символам переданной строки format, пока этот указатель не станет указывать на нулевой символ (*c!='\0'
). Если символ не равен знаку %, то выводим этот символ. Иначе смотрим, какой символ идет после
знака % - d или f. В зависимости от этого получаем либо объект int, либо объект double.