Главная страница

Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник


Скачать 1.57 Mb.
НазваниеВ., Фомин С. С. Курс программирования на языке Си Учебник
АнкорКурс на Си
Дата18.02.2023
Размер1.57 Mb.
Формат файлаdocx
Имя файлаПодбельский. Курс программирования на Си.docx
ТипУчебник
#943863
страница17 из 42
1   ...   13   14   15   16   17   18   19   20   ...   42

int (* menu_items[ ]) ( ) = {f1, f2};

Такое определение массива указателей на функции по меньшей мере не очень наглядно. Упростить подобные определения можно с помощью вспомогательных обозначений (имен), вводимых специ­фикатором typedef. Например, то же самое определение массива указателей можно ввести так:

typedef int (*Menu_action) (void);

Menu_action menu_items [ ] = {f1, f2};

Здесь typedef вводит обозначение Menu_action для типа «ука­затель на функции с пустым списком параметров, возвращающие значения типа int».

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

  • qsort ( ) - функция быстрой сортировки массива;

  • search ( ) - функция поиска в упорядоченном массиве элемен­та с заданными свойствами.

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

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

void qsort(void * base, size_t nelem, size_t width, int (*fcmp)(const void * p1, const void * p2));

Это прототип функции быстрой сортировки, входящей в стан­дартную библиотеку функций. Прототип находится в заголовочном файле stdlib.h. Функция qsort( ) сортирует содержимое таблицы (массива) однотипных элементов, неоднократно вызывая функцию сравнения, подготовленную пользователем. Для вызова функции сравнения ее адрес должен заместить указатель fcmp, специфици­рованный как параметр. При использовании qsort( ) программист должен подготовить таблицу сортируемых элементов в виде одно­мерного массива фиксированной длины и написать функцию, по­зволяющую сравнивать два любых элемента сортируемой таблицы. Остановимся на параметрах функции qsort( ):

  • base - указатель на начало таблицы (массива) сортируемых элементов (адрес нулевого элемента массива);

  • nelem - количество сортируемых элементов в таблице (целая величина, не большая размера массива) - сортируются первые nelem элементов от начала массива;

  • width - размер элемента таблицы (целая величина, опреде­ляющая в байтах размер одного элемента массива);

  • fcmp - указатель на функцию сравнения, получающую в ка­честве параметров два указателя p1, p2 на элементы таблицы и возвращающую в зависимости от результата сравнения целое число:

  • если 7 8p1 < *p2, функция fcmp ( ) возвращает отрицательное целое < 0;

  • если *p1 == *p2, fcmp ( ) возвращает 0;

  • если *p1 > *p2, fcmp ( ) возвращает положительное целое > 0.

При сравнении символ «меньше, чем» (<) означает, что после сор­тировки левый элемент отношения *p1 должен оказаться в таблице перед правым элементом *p2, то есть значение *p1 должно иметь меньшее значение индекса в массиве, чем *p2. Аналогично (но об­ратно) определяется расположение элементов при выполнении со­отношения «больше, чем» (>).

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

"Six - 6",

"Seven - 7",

"Eight - 8" };

/* Размер таблицы: */

int n = sizeof(pc)/sizeof(pc[0]);

int i;

printf("\n До сортировки:");

for (i = 0; i < n; i++)

printf("\npc [%d] = %p -> %s", i,pc[i],pc[i]);

/* Вызов функции упорядочения: */ qsort((void *)

pc,/* Адрес начала сортируемой таблицы */

n,/* Число элементов сортируемой таблицы */ sizeof(pc[0]), /* Размер одного элемента */ compare /* Имя функции сравнения (указатель) */ );

printf("\n\n После сортировки:");

for (i = 0; i < n; i++)

printf("\npc [%d] = %p -> %s", i,pc[i],pc[i]);

}

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

  • до сортировки:

    pc

    [0]

    = 00B8

    ->

    One - 1

    pc

    [1]

    = 00C0

    ->

    Two - 2

    pc

    [2]

    = 00C8

    ->

    Three - 3

    pc

    [3]

    = 00D2

    ->

    Four - 4

    pc

    [4]

    = 00DC

    ->

    Five - 5

    pc

    [5]

    = 00E5

    ->

    Six - 6

    pc

    [6]

    = 00ED

    ->

    Seven - 7

    pc

    [7]

    = 00F7

    ->

    Eight - 8

  • после сортировки:

pc

[0]

= 00F7

->

Eight - 8

pc

[1]

= 00DC

->

Five - 5

pc

[2]

= 00D2

->

Four - 4

pc

[3]

= 00B8

->

One - 1

pc

[4]

= 00ED

->

Seven - 7

pc

[5]

= 00E5

->

Six - 6

pc

[6]

= 00C8

->

Three - 3

pc

[7]

= 00C0

->

Two - 2

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

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

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

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

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

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

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

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

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

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

    1. Функции с переменным

количеством аргументов

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

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

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

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

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

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

#include

/*Функция суммирует значения своих аргументов типа int */ long summa(int k,...)

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

{

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

for(; k; k--)

total += *(++piok);

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,1,2,3,4,5,6));

}

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

summa(2, 6, 4) = 10

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

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

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

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

#include

/* Функция вычисляет произведение аргументов:*/

double prod(double arg, ...) {

double aa = 1.0; /* Формируемое произведение*/

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, 0e0) = %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(0e0) = %e",prod(0e0));

}

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

prod(2e0, 4e0, 3e0, 0e0) = 2.400000е+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(0e0) = 0.000000е+00

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

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

#include

void main()

{ /* Прототип функции: */

long minimum(char, int , ...);

printf("\n\tminimum('l',3,10L,20L,30L) = %ld", minimum('l',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; /* Настроились на первый необязательный параметр */

int min = *pi; /* Значение первого необязательного параметра */

for(; k; k--, pi++)

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

return (long)min;

}

if (z == 'l')

{

long *pl = (long*)(&k+1);

long min = *pl; /* Значение первого необязательного параметра */

for(; k; k--, pl++)

min = min > *pl ? *pl : min;

return (long)min;

}

printf("\nOiBu6Ka! Неверно задан 1-й параметр:");

return 2222L;

}

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

minimum('l', 3, 10L, 20L, 30L) = 10

minimum('i', 4, 11, 2, 3, 4) = 2

Ошибка! Неверно задан 1-й параметр:

minimum('k',2,0,64)=2222

В приведенных примерах функций с изменяемыми списками ар­гументов перебор параметров выполнялся с использованием адрес­ной арифметики и явным применением указателей нужных типов. К проиллюстрированному способу перехода от одного параметра к другому нужно относиться с осторожностью. Дело в том, что по­рядок размещения параметров в памяти ЭВМ зависит от реализа­ции компилятора. В компиляторах имеются опции, позволяющие изменять последовательность размещения параметров. Стандартная для языка Си на современном ПК с процессором фирмы Intel после­довательность: меньшее значение адреса у первого параметра функ­ции, а остальные размещены подряд в соответствии с увеличением адресов. Противоположный порядок обработки и размещения будет у функций, определенных и описанных с модификатором pascal. Этот модификатор и его антипод - модификатор cdecl являются до­полнительными ключевыми словами, определенными для ряда ком­пиляторов. Не останавливаясь подробно на возможностях, предо­ставляемых модификатором pascal, отметим два факта. Во-первых, применение модификатора pascal необходимо в тех случаях, когда функция, написанная на языке Си, будет вызываться из программы, подготовленной на Паскале. Во-вторых, функция с модификатором pascal не может иметь переменного списка аргументов, то есть в ее определении и в ее прототипе нельзя использовать многоточие.
1   ...   13   14   15   16   17   18   19   20   ...   42


написать администратору сайта