Урок 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();	
}

 

рассказать друзьям и получить подарок

Оставить комментарий

Ваш email не будет опубликован. Обязательные поля отмечены *

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Translate Переводчик

Подписка на новости

SmartResponder.ru
Ваш e-mail: *
Ваше имя: *

Хостинг для Wordpress сайтов