Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
25
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

В теле функции geoMean() определены: указатель point типа va_list для "перебора" аргументов; переменная product для "накапливания" произведения значений аргументов; переменная count – счетчик аргументов и вспомогательная переменная temp. Переменной temp присваивается явно указанное в заголовке значение параметра real. Затем выполняется цикл, в котором переменная temp с помощью макроса va_arg() последовательно получает значения аргументов. Цикл завершается, когда из списка аргументов выбрано нулевое значение. После этого макрос va_end() завершает обработку списка аргументов. Если счетчик (переменная counter) аргументов отличен от нулевого значения, то с помощью библиотечной функции pow() из произведения значений аргументов (переменная product) извлекается корень степени counter. Результат служит возвращаемым значением функции geoMean().

Функция pow() не может выполняться при отрицательном значении первого аргумента, если второй аргумент не целый. Поэтому выполняется проверка условия (counter>0 && product>0.0). В случае его ложности выдается сообщение об ошибке, и выполнение программы завершает библиотечная функция exit().

В основной программе обращение к функции geoMean() используется в качестве аргумента макроса PRINTF(). Остальное очевидно и иллюстрируется результатами.

ЗАДАЧА 08-23. Напишите функцию для вычисления значения

многочлена Pn(x)=an·xn+an-1·xn-1+...+a1·x1+a0. Коэффициенты an, an-1,...,a0 и аргумент x – действительные числа. Степень (значе-

ние n), аргумент x и коэффициенты передавайте в функцию с помощью списка параметров изменяемой длины. В основной программе вычислите и напечатайте значения нескольких полиномов.

Для вычисления полиномов традиционно используют схему Горнера:

Pn(x)=(...(an·x+an-1) x+an-2) x+...+a1) x+a0 .

Прототип требуемой функции с переменным числом параметров:

double polynom(double x,int power,...);

321

В прототипе первый параметр представляет аргумент полинома. Целочисленный параметр power степень полинома (n). Вслед за ним должны размещаться вещественные (типа double) значения коэффициентов an, an-1,...,a0. В следующей программе приведено решение задачи с функцией polynom().

/* 08_23.c - вычисление полинома заранее нефиксированной степени */

#include <stdio.h> #include <stdarg.h>

#define PRINTF(EXPRESSION) \ printf(#EXPRESSION"=%f\n",EXPRESSION)

double polynom(double x,int power,...)

{

va_list gear; double result=0.0; int i;

va_start(gear,power);

result = va_arg(gear,double); for (i=power; i>0; i--)

result = result*x + va_arg(gear,double); va_end(gear);

return result;

}

int main()

{

double x=156, y=24; PRINTF(polynom(x,1,1.0,0.0)); PRINTF(polynom(1.55,4,-22.0,y,-1.4,7.3)); PRINTF(polynom(1.0,4,4.0,3.0,2.0,1.0,0.0)); PRINTF(polynom(2.0,4,4.0,3.0,2.0,1.0,0.0)); return 0;

}

Результаты выполнения программы:

polynom(x,1,1.0,0.0)=156.000000 polynom(1.55,4,-22.0,y,-1.4,7.3)=-29.659638 polynom(1.0,4,4.0,3.0,2.0,1.0,0.0)=10.000000 polynom(2.0,4,4.0,3.0,2.0,1.0,0.0)=98.000000

В теле функции polynom() определен указатель va_list gear, обеспечивающий перебор аргументов. Он "настраивается" на "окон-

322

чание" параметра power с помощью макроса va_start(). Затем из списка аргументов макросом va_arg() выбирается значение коэффициента an. Далее в цикле реализована схема Горнера и в переменной result формируется результат. Остальное очевидно.

Обратите внимание на незащищенность функции polynom() от ошибок в задании аргументов. Стоит задать меньшее количество коэффициентов, чем определено параметром power, и получишь ошибки на этапе исполнения готовой программы.

ЗАДАЧА 08-24. Напишите функцию, которая возвращает адрес минимальной из вещественных переменных, адресованных ее аргументами, количество которых не фиксировано.

В точке вызова список аргументов функции должен быть всегда фиксирован. В теле функции необходимо иметь сведения о конкретной длине использованного списка аргументов. Так как параметрами являются адреса, то удобно при обращении к функции завершать список аргументов значением NULL. Решение задачи обеспечивает следующая программа:

/* 08_24.c - адрес максимального из нескольких объектов */

#include <stdio.h> #include <stdarg.h>

#define PRINTF(EXPRESSION) \ printf(#EXPRESSION"=%f\n",EXPRESSION)

double * maxParam(double * real,...)

{

va_list pointer;

double * pMax, * pClaiment; if (real == NULL)

{ printf("\nNo arguments!"); exit(1);

}

pMax = real; va_start(pointer,real);

pClaiment = va_arg(pointer,double *); while (pClaiment != NULL)

{

if (*pMax < *pClaiment) pMax=pClaiment;

323

pClaiment = va_arg(pointer,double *);

}

va_end(pointer); return pMax;

}

int main()

{

double x=156, y=24, z=92;

PRINTF(* maxParam(&x,&y,&z,NULL));

*maxParam(&x,&y,&z,NULL) =

-(* maxParam(&x,&y,&z,NULL)); PRINTF(* maxParam(&x,&y,&z,NULL)); PRINTF(* maxParam(NULL));

return 0;

}

Результаты выполнения программы:

*maxParam(&x,&y,&z,NULL)=156.000000

*maxParam(&x,&y,&z,NULL)=92.000000

No arguments!

В функции maxParam() определен указатель pointer типа va_list для "перебора" списка аргументов, каждый из которых типа double *. Первый из них обязательный (real). Если он отличен от NULL, то его значение присваивается вспомогательному указателю pMax. Макрос va_start() "настраивает" указатель pointer на начало первого необязательного параметра. Его значение с помощью обращения к va_arg() присваивается указателю pClaiment типа double *. Затем в цикле (пока pClaiment != NULL) перебираются значения всех аргументов, и в качестве значения указателя pMax сохраняется адрес объекта с максимальным значением. После окончания цикла обращение к макросу va_end() завершает обработку списка аргументов неопределенной длины, и функция возвращает найденный адрес объекта с максимальным значением.

В основной программе определены три переменных (x, y, z). Функция maxParam() определяет адрес максимальной из них. Его разыменование в вызове макроса PRINTF() соответствует значению переменной x. Следующее обращение к maxParam() изменяет знак этой переменной, после чего переменной с максимальным значением

324

становится z. Последнее обращение maxParam(NULL) завершает исполнение функции и программы в целом.

ЗАДАЧА 08-25. Напишите функцию для нормирования значений вещественных переменных, адресованных ее аргументами. Список аргументов завершите значением NULL. Нормирование состоит в делении каждой из переменных на их среднее арифметическое.

Задача похожа на задачу 08-24 – поиск адреса объекта с заданным свойством, но имеется существенное отличие – придется дважды перебрать список аргументов. Вначале для вычисления количества и суммы значений переменных, затем для деления значений объектов на их среднее арифметическое. В обоих случаях схема обработки одна и та же. Указатель va_list pointer последовательно адресует параметры. Вспомогательный указатель double * pCur принимает значения аргументов. Его разыменование позволяет получить доступ к внешним переменным, адресованным аргументами. Если pCur==NULL, перебор аргументов завершается, цикл закончен. После первого просмотра аргументов вычисляется их среднее арифметическое, затем указатели pCur и pointer вновь настраиваются на начало списка параметров. Следующий за этим цикл нормирует значения переменных, адресованных аргументами.

Решение задачи:

/* 08_25.c - нормирование значений нескольких объектов */

#include <stdio.h> #include <stdarg.h> #include <math.h> #include <float.h>

#define PRINTF(EXPRESSION) \ printf(#EXPRESSION"=%f\n",EXPRESSION)

void blending(double * real,...)

{

va_list pointer; double * pCur = real; double sum = 0.0;

int number=0; va_start(pointer,real); while (pCur != NULL)

325

{number++;

sum += *pCur;

pCur = va_arg(pointer,double *);

}

if (number <= 1 || fabs(sum) < FLT_MIN ) return; sum /= number;

pCur = real; va_start(pointer,real); do

{*pCur /= sum;

pCur = va_arg(pointer,double *);

}

while (pCur != NULL); va_end(point);

}

int main()

{

double w=10.0, x=30.0, y=2.0, z=1.0; blending(&w, &x, NULL); puts("Normalization results: "); PRINTF(w);

PRINTF(x);

blending(&w, &x, &y, &z, NULL); puts("Normalization results: "); PRINTF(w);

PRINTF(x);

PRINTF(y);

PRINTF(z); return 0;

}

Результаты выполнения программы:

Normalization results: w=0.500000

x=1.500000 Normalization results: w=0.400000

x=1.200000

y=1.600000

z=0.800000

326

В теле функции blending() дважды использован макрос va_start(). В обоих случаях он устанавливает указатель pointer на начало первого необязательного параметра, т.е. вслед за обязательным параметром double * real. При этом указателю pCur присваивается значение real и начинаются исполнения циклов.

В основной программе определены четыре переменных типа double (w, x, y, z). При первом обращении к функции blending() нормируются значения w и x. При втором обращении изменяются значения всех четырех переменных.

ЗАДАЧА 08-26. Упорядочите по возрастанию целочисленные значения переменных, адресуемых параметрами функции. Количество переменных заранее не известно, а определяется списком аргументов функции. (Массивы не использовать. Список аргументов завершить значением NULL.)

У предлагаемой функции сортировки обычно не будет большого количества аргументов. Вряд ли целесообразно упорядочивать большое количество переменных, не используя массивов, а явно употребляя уникальные имена. Следовательно, о выборе наиболее эффективного алгоритма можно не беспокоиться. Наша цель в данной задаче – показать, как многократно перебирать параметры функции, число которых заранее (при написании функции) не задано. Используем алгоритм с многократным прямым перебором сортируемых объектов. В нем будет два вложенных цикла. Во внешнем цикле очередной аргумент сравнивается со всеми аргументами, размещенными в списке правее него. Запоминается значение аргумента, адресующего минимальную из переменных. По завершении внутреннего цикла меняются значения очередной и найденной минимальной переменной. Затем выполняется переход к следующему аргументу списка и, если он отличен от NULL, вновь выполняется внутренний цикл.

Программное решение задачи:

/* 08_26.c - упорядочить объекты, адресованные аргументами */

#include <stdio.h> #include <stdarg.h>

#define PRINTD(EXPRESSION) \ printf(#EXPRESSION"=%d\n",EXPRESSION)

void sort(int * begin,...)

327

{

va_list outward; va_list inward;

int * pCur, * pMin, * pNext; int temp; va_start(outward,begin); pMin = pNext = begin; while(pNext != NULL)

{inward = outward;

pCur = va_arg(inward, int *); while(pCur != NULL)

{if (*pMin > *pCur) pMin = pCur;

pCur = va_arg(inward, int *);

}

if ( pMin != pNext)

{temp = * pMin;

*pMin = * pNext;

*pNext = temp;

} /* end of while "pCur"*/

pNext = pMin = va_arg(outward, int *); }/* end of while "pNext"*/

va_end(outward);

}

int main()

{

int x=156, y=24, z=92; sort(&x,&y,&z,NULL); PRINTD(x);

PRINTD(y);

PRINTD(z); return 0;

}

Результаты выполнения программы:

x=24

y=92

z=156

В теле функции sort() определены два указателя типа va_list. Первый из них outward последовательно адресует аргументы спи-

328

ска. Во внутреннем цикле изменяются значения указателя inward. Для доступа к значениям переменных, адресованных аргументами, используются три указателя типа int * pNext – адресует очередную переменную (вначале адресованную самым левым аргументом функции). Именно эта переменная должна стать минимальной после первой итерации внешнего цикла. pCur во внутреннем цикле последовательно адресует аргументы слева от значения pNext. pMin сохраняет адрес самой меньшей из переменных, просмотренных во внутреннем цикле.

Внутренний цикл завершается при условии pCur==NULL. Если адрес (pMin) найденной минимальной переменной отличается от адреса pNext, то по обычной схеме меняются значения соответствующих переменных. Затем из списка аргументов с помощью указателя outward и макроса va_arg() выбирается следующий адрес и присваивается указателям pNext и pMin. Если не достигнут конец списка (pNext != NULL), выполняется очередная итерация внешнего цикла. По его завершении макрос va_end() завершает обработку переменного списка параметров.

В основной программе определены и инициализированы три переменные (x, y, z). С помощью обращения к функции sort() их значения упорядочены.

Коротко о важном

По материалам темы сформулированы следующие утверждения. В конце большинства из них приведены номера программ, подтверждающих справедливость утверждений.

!При отсутствии в тексте программы до обращения к функции ее определения (или прототипа) компилятор считает, что функция возвращает значение типа int (08_01_1.c).

!Прототип функции в спецификации параметров может не содержать их имен (08_01_2.c).

!Прототип функции не может размещаться среди исполнимых операторов программы (08_01_3.с).

!Вызов функции, возвращающей значение конкретного типа, можно использовать в качестве фактического параметра другой или той же функции (задание к 08_01.с, 08_02_2.с).

329

!Интерфейс функции определяется ее прототипом, а также совокупностью препроцессорных средств и глобальных объектов, ис-

пользуемых в ее теле (08_03.с, 08_03_1.с, 08_03_2.с).

!Обращение к функции, не возвращающей типизированного значения (указан тип void), используется только в виде отдельного оператора (08_04.с).

!В функции, не возвращающей типизированного значения, для выхода из функции при завершении исполнения ее тела оператор return не обязателен (08_04.с).

!В теле функции, не возвращающей типизированного значения, оператор return используется без выражения, вычисляющего возвращаемое значение (08_04_1.с).

!При каждом обращении к функции ее параметрам выделяются участки памяти, полностью независимые и отличные от участков памяти, выделенных для аргументов, использованных в вызове функции (08_05.с, 08_05_1.с).

!Изменение параметров в теле функции не влияют на значения соответствующих аргументов (08_05.с).

!При последовательном обращении к функции (или к разным функциям) их параметру может выделяться один и тот же участок памяти, освобождаемый при каждом выходе из функции

(08_05_1.с).

!Аргументы-указатели не могут изменяться за счет исполнения операторов тела функции. (Могут изменяться только адресуемые ими объекты вызывающей программы. См. программы 08_06.с

08_10.с).

!В теле функции можно получить доступ к внешнему по отношению к функции объекту с помощью его адреса, использованного в качестве аргумента (08_06.с, 08_07.с).

!К вызову функции, возвращающей адрес объекта, можно применять операцию разыменования и использовать в левой части при-

сваивания (08_08.с, 08_14.с).

!Разность указателей, адресующих элементы массива, равна разности индексов этих элементов (08_09.с).

!Если адреса двух объектов приведены к арифметическому типу, то результат их вычитания выражается в байтах (08_09_1.с).

!Вместо нетипизированного указателя (void *) можно использовать указатель любого типа, но функция с нетипизированными

330