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

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

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

242

 

 

Программирование на языке Си

рс

[3] = 00В8

-> One

- 1

рс

[4] = 00ED

-> Seven - 7

рс

[5] = 00Е5

-> Six

- 6

рс

[6] = 00С8

-> Three - 3

рс

[7] = 00С0

-> Two

- 2

Вывод адресов, т.е. значений указателей pc[i], выполняется в шестнадцатеричном виде с помощью спецификации преобразо­ вания %р.

Обратите внимание на значения указателей pc[i]. До сорти­ ровки разность между рс[1] и рс[0] равна длине строки "One - 1" и т.д. После упорядочения рс[0] получит значение, равное исходному значению рс[7], и т.д.

Для выполнения сравнения строк (а не элементов массива рс[ ]) в функции com pare() использована библиотечная функ­ ция strcm p(), прототип которой в заголовочном файле string.h имеет вид:

int strcmp(const char *sl, const char *s2);

Функция strcm p() сравнивает строки, связанные с указате­ лями si и s2. Сравнение выполняется посимвольно, начиная с начальных символов строк и до тех пор, пока не встретятся не­ совпадающие символы либо не закончится одна из строк.

Прототип функции strcm p() требует, чтобы параметры име­ ли тип (const char *). Входные параметры функции сошраге() имеют тип (const void *), как предусматривает определение функции qsort(). Необходимые преобразования для наглядно­ сти выполнены в два этапа. В теле функции сошраге() опреде­ лены два вспомогательных указателя типа (unsigned long *), которым присваиваются значения адресов элементов*, сортируе­ мой таблицы (элементов массива рс[ ]) указателей. В своку оче­ редь, функция strcm p() получает разыменования этих ука­ зателей, т.е. адреса символьных строк. Таким образом, выпол­ няется сравнение не элементов массива char * рс[ ], а тех строк, адреса которых являются значениями pc[i]. Однако функция qsort() работает с массивом рс[ ] и меняет местами только зна­

Глава 5. Функции

243

чения его элементов. Последовательный перебор массива рс[ | позволяет в дальнейшем получить строки в алфавитном поряд­ ке, что иллюстрирует результат выполнения программы. Так как pc[i] - указатель на некоторую строку, то по спецификации преобразования %s выводится сама строка.

Если не использовать вспомогательных указателей pa, pb, то функцию сравнения строк можно вызвать из тела функции compare( ):таким оператором:

return strcmp((char *)(*(unsigned long *)a), (char *)(*(unsigned long *)b));

где каждый указатель (void *) вначале преобразуется к типу (unsigned long *).

Последующее разыменование "достает" из нескольких смеж­ ных байтов значение соответствующего указателя pc[i], затем преобразование (char *) формирует такой указатель на строку, который нужен функции strcm p().

5.5. Ф ункции с перем енны м количеством парам етров

В языке Си допустимы функции, количество параметров у которых при компиляции функции не фиксировано. Кроме того, могут быть неизвестными и типы параметров. Количество и ти­ пы параметров становятся известными только в момент вызова функции, когда явно задан список фактических параметров. При определении и описании таких функций, имеющих списки па­ раметров неопределенной длины, спецификация формальных параметров заканчивается запятой и многоточием. Формат про­ тотипа функции с переменным списком параметров:

тип имя (спецификация явных параметров,...);

где тип - тип возвращаемого функцией значения; имя - имя функции;

16

244

Программирование на языке Си'

спецификация явныхпараметров - список специфика­ ций параметров, количество и типы которых фиксированы и известны в момент компиляции. Эти параметры можно назвать обязательными.

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

Каждая функция с переменным списком параметров должна иметь механизм определения их количества и их типов. Прин­ ципиально различных подходов к созданию этого механизма два. Первый подход предполагает добавление в конец списка реально использованных (необязательных) фактических пара­ метров специального параметра-индикатора с уникальным зна­ чением, которое будет сигнализировать об окончании списка. При таком подходе в теле функции параметры последовательно перебираются, и их значения сравниваются с заранее известным концевым признаком. Второй подход предусматривает передачу в функцию сведений о реальном количестве фактических пара­ метров. Эти сведения о реальном количестве используемых фактических параметров можно передавать в функцию с помо­ щью одного из явно задаваемых (обязательных) параметров. В обоих подходах - и при задании концевого признака, и при ука­ зании числа реально используемых фактических параметров - переход от одного фактического параметра к другому выполня­ ется с помощью указателей, т.е. с использованием адресной арифметики. Проиллюстрируем сказанное примерами.

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

Глава 5. Функции

245

#±nclude <stdio.h>

/*Функция суммирует значения своих параметров типа int */

long svmma(int k,...)

/* k - число суммируемых параметров */

{

int *pick = &k; /* Настроили указатель на параметр к*/

long total(= 0; for(; к; к— )

total += * (++pick); return total;

}

void main()

{

printf("\n summa(2, 6, 4) <* %d",summa(2,6,4)); printf("\n summa(6, 1, 2, 3, 4, 5, 6) = %d",

summa(6,l,2,3,4,5,6));

}

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

summa(2, 6, 4) = 1 0

summa(6,. 1, 2, 3, 4, 5, 6) = 21

Для доступа к списку параметров используется указатель pick типа int *. Вначале ему присваивается адрес явно заданного параметра к, т.е. он устанавливается на начало списка парамет­ ров в памяти. Затем в цикле указатель pick перемещается по ад­ ресам следующих фактических параметров, соответствующих неявным формальным параметрам. С помощью разыменования *(++pick) выполняется выборка их значений. Параметром цикла суммирования служит аргумент к, значение которого уменьша­ ется на 1 после каждой итерации и, наконец, становится нуле­ вым. Особенность функции - возможность работы только с целочисленными фактическими параметрами, так как указатель pick после обработки значения очередного параметра "перемещается вперед" на величину sizeof(int) и должен быть всегда установлен на начало следующего параметра.

Недостаток функции - возможность ошибочного задания не­ верного количества реально используемых параметров.

246

Программирование на языке Си

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

#include <stdio.h>

/* Функция вычисляет произведение параметров:*/ double prod(double arg, ...)

{

double aa = 1.0; /* Формируемое произведение111/ double *prt = &arg; /* Настроили указатель

на параметр arg */

if (*prt == 0.0) return 0.0;

for (; *prt; prt++) aa *= *prt;

return aa;

}

void main()

{

double prod(double,...);/* Прототип функции */ printf("\n prod(2e0, 4e0, 3e0, OeO) = %e",

prod(2e0,4e0,3e0,0e0)); printf("\n prod(1.5, 2.0, 3.0, 0.0) %f",

prod(1.5,2.0,3.0,0.0)); printf("\n prod(1.4, 3.0, 0.0, 16.0, 84.3,

0.0)=%f",

prod(1.4,3.0,0.0,16.0,84.3,0.0)); printf( "\n prod(OeO) = %e",prod(OeO));

}

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

prod(2e0, 4e0, ЗеО, 0e0)'= 2.400Q00e+01 prod(1.5, 2.0, 3.0, 0.0) = 9.000000

prod(1.4, 3.0, 0.0, 16.0, 84.3, 0.0) = 4.200000 prod(OeO) = 0.000000e+00

В функции prod() перемещение указателя prt по списку фак­ тических параметров выполняется всегда за счетщзменения prt на величину sizeof(double). Поэтому все фактические парамет­ ры при обращении к функции prod() должны иметь тип double.

Глава 5. Функции

247

В вызовах функции проиллюстрированы некоторые варианты задания параметров. Обратите внимание на вариант с нулевым значением параметра в середине списка. Параметры вслед за этим значением игнорируются. Недостаток функции - неопре­ деленность действий при отсутствии в списке фактических па­ раметров параметра с нулевым значением.

Чтобы функция с переменным количеством параметров мог­ ла воспринимать параметры различных типов, необходимо в качестве исходных данных каким-то образом передавать ей ин­ формацию о типах параметров. Для однотипных параметров возможно, например, такое решение - передавать с помощью дополнительного обязательного параметра признак типа пара­ метров. Определим функцию, выбирающую минимальное из значений параметров, которые могут быть двух типов: или только long, или только int. Признак типа параметра будем пе­ редавать как значение первого обязательного параметра. Второй обязательный параметр указывает количество параметров, из значений которых выбирается минимальное. В следующей про­ грамме предложен один из вариантов решения сформулирован­ ной задачи:

#include <stdio.h> void main()

{ /* Прототип функции: */ long minimum(char, int ,...);

printf("\n\tminimum('13,10L,20L,30L) = %ld", minimum('1',3,10L,20L,30L));

printf("\n\tminimum('i',4,11, 2, 3, 4) = %ld", minimum('i',4,11,2,3,4));

printf( "\n\tminimum(' k', 2, 0, 64) = %ld", minimum('k',2,0,64));

}

/* Определение функции о переменным списком параметров */

long minimum(char z, int k,...)

{

if (z = 'i ')

{

int *pi = &k + 1; /* Настроились на первый необязательный параметр */

248

 

 

Программирование на языке Си

int min = *pi; /* Значение первого

*/

 

 

необязательного параметра

for (; к; к— , pi++)

 

 

 

 

min = min > *pi ? *pi : min;

 

return

(long)min;

 

 

 

 

}

'1')

 

 

 

 

 

if (z ==

 

 

 

 

 

{

*pl =

(long*) (&k+l);

 

 

long

 

 

long min =

*pl; /* Значение первого

*/

 

 

необязательного параметра

for(; к; к— , pl++)

 

 

 

min = min > *pl

? *pl : min;

 

return (long)min;

 

 

 

 

}

 

 

 

 

 

 

printf("\пОшибка! Неверно задан 1-й пара­

 

метр :") ;

 

 

 

 

 

 

return 2222L;

 

 

 

 

 

}

 

 

 

 

 

 

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

 

 

 

minimum('1', 3,

10L,

20L,

30L) = 1 0

 

minimum('i'f 4,

11,

2, 3,

4) = 2

 

Ошибка? Неверно задан 1-й параметр: minimum('к ',2,0,64)=2222

В приведенных примерах функций с изменяемыми списками параметров перебор параметров выполнялся с использованием адресной арифметики и явным применением указателей нужных типов. К проиллюстрированному способу перехода от одного параметра к другому нужно относиться с осторожностью. Дело в том, что порядок размещения параметров в памяти ЭВМ зави­ сит от реализации компилятора. В компиляторах имеются оп­ ции, позволяющие изменять последовательность размещения параметров. Стандартная для языка Си на IBM PC последова­ тельность: меньшее значение адреса у первого параметра функ­ ции, а остальные размещены подряд в соответствии с уве­ личением адресов. Противоположный порядок обработки и раз­ мещения будет у функций, определенных и описанных с моди­ фикатором pascal. Этот модификатор и его антипод -

Глава 5. Функции

249

модификатор cdecl являются дополнительными ключевыми словами, определенными для ряда компиляторов. Не останавли­ ваясь подробно на возможностях, предоставляемых модифика­ тором pascal, отметим два факта. Во-первых, применение модификатора pascal необходимо в тех случаях, когда функция, написанная на языке Си, будет вызываться из программы, под­ готовленной на Паскале. Во-вторых, функция с модификатором pascal не может иметь переменного списка параметров, т.е. в ее определении и в^е прототипе нельзя использовать многоточие.

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

Для обеспечения мобильности программ с функциями, имеющими изменяемые списки параметров, в каждый компиля­ тор стандарт языка предлагает включать специальный набор макроопределений, которые становятся доступными при вклю­ чении в текст программы заголовочного файла stdarg.h. Макро­ команды, обеспечивающие простой и стандартный (не зависящий от реализации) способ доступа к конкретным спи­ скам фактических параметров переменной длины, имеют сле­ дующий формат:

void va_start(va_list рагат, последний явный параметр)-, type va_arg(va_list рагат, type)-,

void va_end(va_list рагат)’,

Кроме перечисленных макросов, в файле stdarg.h определен специальный тип данных va_list, соответствующий потребно­ стям обработки переменных списков параметров. Именно тако­ го типа должны быть первые фактические параметры, используемые при обращении к макрокомандам va_start(), va_arg(), va_end(). Кроме того, для обращения к макросу va_arg() необходимо в качестве второго параметра использо-

250

Программирование на языке Си

вать обозначение типа {type) очередного параметра, к которому выполняется доступ. Объясним порядок использования пере­ численных макроопределений в теле функции с переменным количеством параметров (рис. 5.2). Напомним, что каждая из функций с переменным количеством параметров должна иметь хотя бы один явно специфицированный формальный параметр, за которым после запятой стоит многоточие. В теле функции обязательно определяется объект типа va_list. Например, так:

va_list factor;

Определенный таким образом объект factor обладает свойства­ ми указателя. С помощью макроса va_start() объект factor свя­ зывается с первым необязательным параметром, т.е. с началом

.списка неизвестной длины. Для этого в качестве второго аргу­ мента при обращении к макросу va_start() используется по­ следний из явно специфицированных параметров функции (предшествующий многоточию):

va_start (factor, последнийявныйпараметр);

Рассмотрев выше способы перемещения по списку парамет­ ров с помощью адресной арифметики, мы понимаем, что указа­ тель factor сначала "нацеливается" на адрес последнего явно специфицированного параметра, а затем перемещается на его длину и тем самым устанавливается на начало переменного списка параметров. Именно поэтому функция с переменным списком параметров должна иметь хотя бы один явно специфи­ цированный параметр.

Теперь с помощью разыменования указателя factor мы мо­ жем получить значение первого фактического параметра из пе­ ременного списка. Однако нам не известен тип этого факти­ ческого параметра. Как и без использования макросов, тип па­ раметра нужно каким-то образом передать в функцию. Если это сделано, т.е. определен тип type очередного параметра, то об­ ращение к макросу

va_arg (factor, type)

Глава 5. Функции

 

 

 

 

 

 

251

 

 

Реальные фактические параметры

 

 

 

обязательные

 

 

необязательные

 

int

int

float

int

 

char

int| |

long double

N

К

sum

Z

 

cc

ro

 

smell

va_list factor; /* Определили указатель * /

 

 

 

va_start (factor, sum); /* Настройка указателя (см. ниже) */

 

N

К

sum

|

Z

 

cc

ro

smell

 

 

 

 

k

 

 

 

 

factor-------------------------------

int ix;

ix=va_arg (factor, int); /* Считали в ix значение Z типа int, одновременно сместили указатель на длину переменной типа int */

N

К

sum | Z

cc

ro

smell

 

 

 

4 k

 

 

factor---------------------:-----------------------

char сх;

cx=va_arg (factor, char); /* Считали в сх значение сс типа char и

 

 

сместили указатель на длину объекта типа char */

N

К

sum |

Z

cc

ro

smell

 

 

 

 

 

ik

 

int * pr;

pr=va_arg(factor, int); /* Считали в pr адрес го типа int* массива типа int [ ] и сместили указатель factor на размер указателя int* */

N К sum | Z cc ro smell

4 k

factor

long double рр;

pp=va_arg (factor, long double); /* Считали значение smell */

Рис. 5.2. Схема обработки переменного списка параметров с помощью макросов стандартной библиотеки

Соседние файлы в папке книги