Урок 9.8. Функции с переменным числом параметров
9.8. Функции с переменным числом параметров
В С++ можно создавать функции с переменным числом параметров. Параметры, их количество и типы, становятся известны только во время вызова функции. Формат описания функций с переменным числом параметров:
<тип> <имя> (<список явных параметров>, …) { <тело функции> } |
Здесь:
<тип> – тип возвращаемого значения,
<список параметров> – список параметров известных до вызова функции.
После списка явных параметров следует необязательная запятая и троеточие, которое сообщает компилятору, что контроль типов и количества параметров при вызове функции не следует.
При создании функций с переменным числом параметров необходимо предусмотреть способ определения количества параметров и их типов. Используется для этих целей два способа:
- один из параметров определяет число параметров функции;
- в списке явных параметров задается параметр, указывающий на конец списка параметров.
Если не один из этих способов не используется, тогда можно использовать специальный набор макроопределений.
Параметры функции помещаются в стек, при этом первый параметр оказывается в вершине стека. Переход от одного параметра к другому осуществляется с помощью указателей.
Пример 1. Задание числа дополнительных параметров с помощью первого параметра. Функция вычисляет сумму значений дополнительных параметров. Список явных параметров состоит из одного параметра, который задает число дополнительных параметров.
#include <stdio.h> #include <conio.h> #include <iostream> void main() { int sum(int n,...); //прототип функции с переменным //числом параметров cout << "\n 4+6=" << sum(2,4, 6); cout << "\n 1+2+3+4+5+6=" << sum(6,1,2,3,4,5,6); cout << "\n Parametrov net.Summa ravna:" << sum(0); getch(); } int sum(int n,...)//n-число суммируемых параметров { int s=0; int *p = &n; //получение адреса параметров в стеке for(int i=1; i<=n; i++) s+=*++p; //суммируем числа return s; } |
Для доступа к параметрам, которые один за другим попали в стек, используется адрес первого параметра (*p = &n;), таким образом, указатель устанавливается на начало списка параметров в стеке (в памяти). Затем в цикле при помощи указателя p перемещаемся по параметрам и суммируем их, извлекая (*p) из памяти. Все параметры должны иметь одинаковый тип.
Проверка соответствия типов для дополнительных параметров не выполняется, поскольку компилятор не имеет информации, необходимой для проведения проверки. При вызове функции дополнительные параметры типа char и short передаются как int, а float — как double.
Пример 2. Определение конца списка параметров с помощью параметра индикатора. Функция product() вычисляет произведение дополнительных параметров. Как только встречается нулевой параметр, вычисление произведения прекращается. Указатель p указывает на начало списка параметров (double*p=&a;).
#include <iostream> double product(double a, ...) { double b; if (a)//проверяем на отсутствие дополнительных параметров { b = 1.0; //b - произведение //цикл до тех пор, пока p не укажет на 0 for (double* p=&a; *p; p++) b *= *p; } else b = 0.0; return b; } void main() { double product(double a, ...);//прототип функции с // переменным числом параметров cout << "\n product (6e0, 5e0, 3e0, 0e0) = " << product (6e0, 5e0, 3e0, 0e0); cout << "\n product (1.5f, 2.0f, 0.0f, 3.0f, 0.0f) = " << product (1.5f, 2.0f, 0.0f, 3.0f, 0.0f); cout << "\n product (0.0) = " << product (0.0); cout << "\n product (1.0e-28,0.0) = " << product (1.0e-28,0.0); getch(); } |
Напомним, что при вызове функции дополнительные параметры типа float передаются как double, так что для параметров типа float надо использовать функцию с параметрами типа double.
Для использования в программе функций с переменным числом параметров, при вызове которых можно использовать параметры разных типов, удобно использовать специальный набор макроопределений (заголовочный файл stdarg.h). Макрокоманды для доступа к списку фактических параметров переменной длины, имеют следующий формат:
//связывание переменной param с первым параметром
void va_start(va_list param,<последний явный параметр>);
//получение значения очередного параметра типа type
type va_arg(va_list param, type);
//организации корректного выхода из функции
void va_end(va_list param);
Кроме перечисленных макросов, в файле stdarg.h определен специальный тип данных va_list, который является указателем. Именно такого типа должны быть первые операнды, используемые при обращении к макрокомандам va_start, va_arg, va_end.
Определим порядок использования макросов. В теле функции обязательно определяется переменная типа va_list: va_list f. С помощью макроса va_start переменная f связывается с первым необязательным параметром, т.е. с началом списка неизвестной длины:
va_start(f,<последний явный параметр>)
Теперь с помощью указателя f можно получить значение фактического параметра, задав его тип. Макрос
va_arg(f, type);
позволяет получить значение очередного параметра типа type. Кроме того, макрос меняет значение указателя f на адрес следующего фактического параметра в списке.
Макрокоманда va_end предназначена для организации корректного выхода из функции с переменным числом параметров: va_end(f).
Приведем пример использования макросов в функции конкатенации любого числа строк. Строки передаются функции с помощью списка указателей. В конце списка помещается нулевой указатель NULL.
#include <stdio.h> #include <conio.h> #include <stdarg.h> //для макросов переменного списка параметров #include <iostream> using namespace std; // функция конкатенации неопределенного числа строк char *concat(char *s1, ...) { va_list par; //указатель на параметры списка char *cp; int len = strlen(s1); //длина 1-го параметра va_start(par,s1); //начало переменного списка //цикл для определения общей длины параметров строк while(cp = va_arg(par, char *)) len += strlen(cp); //выделение памяти для результата char *stroka = new char[len + 1]; strcpy(stroka, s1); va_start(par, s1);//начало переменного списка //цикл конкатенации параметров строк while(cp = va_arg(par, char *)) strcat(stroka, cp); va_end(par); return stroka; } void main() { setlocale(LC_CTYPE, "Russian"); //прототип функции с переменным числом параметров char *concat(char *s1, ...); char *s; //указатель для результата s = concat("\nVerba"," volant, ","scripta ","manent\n", "(Слова исчезают, написанное остается.)", NULL); cout << s; _getch(); } |
В приведенной функции concat() тип параметров заранее известен и фиксирован (указатель на символы). В некоторых случаях параметры могут изменяться как по числу, так и по типу. В этих случаях необходимо, каким-то образом сообщать функции типы параметров для правильного их извлечения из стека. Например, в функциях printf() и scanf() в качестве первого операнда задается строка форматов, которая и содержит информацию о вводимых или выводимых данных.
Следующий пример, функция miniprintf(), показывает, как это можно сделать. В этой функции допускаются только два вида форматов %d и %f, которые позволяют макросу va_arg() правильно извлекать значения. Окончание вывода определяется завершением перебора форматов.
#include <conio.h> #include <stdarg.h> //для макросов переменного списка параметров #include <iostream> using namespace std; void miniprintf(char *format, ...) { va_list ap; //указатель на необязательный параметр char *p; //для просмотра строки format int ii; //целые параметры double dd; //параметры типа double va_start(ap, format); //настроились на первый параметр for(p = format; *p; p++) { if(*p != '%') {cout << *p; continue; } switch (*++p) { case 'd': ii = va_arg(ap, int); cout << ii; break; case 'f': dd = va_arg(ap, double); cout << dd; break; default: cout << *p; } }//конец цикла просмотра строки форматов va_end(ap); //подготовка к завершению функции } void main() { void miniprintf(char *format, ...); int k = 123; double d = 2.718282; miniprintf("\n Integer k = %d \n Double d = %f", k, d); _getch(); } |