Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции_СП_2004_1_00.doc
Скачиваний:
69
Добавлен:
04.11.2018
Размер:
882.69 Кб
Скачать

2. Использование указателей в качестве аргументов функций

Несмотря на то, что принятое в языке Си соглашение о передаче параметров по значению является достаточно естественным и обеспечивает полную независимость отдельных функций, в ряде практически важных случаев оно оказывается излишне ограничительным. Действительно, при таком решении вопроса аргументы всякой функции могут служить лишь носителями входной информации, в то время как часто оказывается необходимым придать им статус входно- выходных. С другой стороны, передача по значению структурированных данных, например массивов, оказывается мало эффективной из-за большой потери времени на копирование элементов передаваемой структуры и неоправданного перерасхода ресурса памяти на создание такой копии. Именно поэтому в подавляющем большинстве языков программирования механизм передачи агрегатов данных по значению никогда не реализуется. И, наконец, известный нам способ передачи параметров принципиально не позволяет использовать одни функции в качестве аргументов других функций. Возможность преодоления всех отмеченных выше трудностей открывается в связи с применением мощного аппарата указателей, являющихся полноправными переменными, значения которых могут передаваться из одной функции в другую. Такая передача значений указателей по существу реализует механизм доступа к переменным, определенным вне тела функции, по адресной ссылке, поскольку сами указатели являются носителями адресов этих переменных. Получив же необходимый адрес, функция, в свою очередь, может использовать его для доступа к соответствующему значению. Рассмотрение вопроса об использовании указателей в качестве аргументов функций мы начнем с простейшего случая передачи адресов скалярных переменных. Пусть, например, необходимо иметь функцию, заменяющую значение своего аргумента на его абсолютную величину. Чтобы вызывающая функция могла получить результат такой замены, она должна предоставить нашей функции адрес соответствующей переменной, т. е. указатель на ее размещение в памяти ЭВМ. Решение этой задачи может выглядеть следующим образом:

void abs(arg)

int *arg; /* Формальный параметр */

{ *arg = (*arg >= 0) ? (*arg) : -(*arg);

return;

}

Теперь в случае обращения

abs(&value);

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

scanf("%d", &alpha);

делает переменную alpha доступной в теле этой функции непосредственно через ее адрес, в результате чего введенное числовое значение будет известно и в точке вызова. Наиболее важную роль при разработке программ на языке Си играет возможность передачи между отдельными функциями массивов переменных и, в частности, символьных строк. Здесь вновь оказывается полезным аппарат указателей, ибо для обеспечения доступа к массиву в теле всякой функции ей достаточно передать адрес его нулевого элемента, причем носителем последнего является само имя этого массива. В следующем примере функция summa(), выполняющая суммирование элементов числового массива, получает от вызывающей ее функции main() адрес начала массива vector и общее количество его элементов:

main()

{ float s;

float vector[100]; /* Определение локального массива */

float summa(); /* Описание функции (см. $ 3) */

.......................

.......................

.......................

s = summa(vector, 100);

.......................

.......................

.......................

}

float summa(mas, n)

float mas[]; /* Описание формального массива */

int n;

{ int i;

float sum = 0;

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

sum += mas[i];

return (sum);

}

Обратим внимание на то, что при описании формального массива mas в заголовке функции summa() его размерность явно не указана. В этом, однако, и нет необходимости, поскольку фактическая работа будет выполняться здесь над массивом vector, адрес начала и длина которого передаются при обращении к этой функции. Более того, вместо описания массива неопределенной длины достаточно иметь эквивалентное ему описание

float *mas;

определяющее указатель на начало обрабатываемого массива. Использованное в этом примере предварительное описание функции summa() в теле функции main() будет рассмотрено в $ 3 настоящей лекции. Примером использования параметров для передачи символьных строк могут служить функции strcmp(), strcpy(), strlen() и другие, введенные в Лекции 3. Аргументами каждой из них являются массивы элементов типа char, идентифицируемые своими начальными адресами. В этом случае уже не нужно дополнительно передавать количество обрабатываемых символов, ибо конец всякой строки легко находится по завершающему ее нуль- символу. Рассмотрим наконец возможность использования указателей для передачи одних функций в другие, определив предварительно понятие указателя на функцию. Последнее, очевидно, должно связываться с адресом первого исполняемого оператора соответствующей функции, задающем ее входную точку. Указатель на функцию определяется в программе подобно тому, как и сами функции, с той лишь разницей, что в этом случае заголовок выглядит следующим образом:

<sc-specifier> <type-specifier> (*identifier) (<parameter-list>)

<parameter-declarations>

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

float (*calc)(alpha, beta)

float alpha, beta;

{ .........................

.........................

.........................

}

имя calc определено как указатель на функцию, возвращающую значение типа float. Обращение же к этой функции будут выглядеть так

(*calc)(x, y);

где переменные x и y имеют тип float и играют роль фактических параметров. Указатели на функции, в отличие от самих функций, могут не только выступать в роли самостоятельных единиц программы, но и быть формальными параметрами других функций. Именно эта их особенность позволяет осуществить передачу входных адресов функций через механизм параметров. Для иллюстрации такой возможности рассмотрим фрагмент программы, вычисляющей проекцию отрезка длины len, составляющего угол alpha с осью обсцисс, на одно из двух координатных направлений в зависимости от значения ключа direct:

double len; /* Длина отрезка */

double alpha; /* Угол с осью x */

main()

{ char direct; /* Ключ направления */

double px, py; /* Длины проекций */

double cos(double), sin(double); /* Описания вызываемых */

double proect(double (*) (double)); /* функций (см. $ 3) */

.................................

.................................

switch (direct)

{ case 'x':

px = proect(cos); /* Проекция на ось x */

break;

case 'y':

py = proect(sin); /* Проекция на ось y */

break;

}

.................................

.................................

}

double proect(func)

double (*func)(double); /* Указатель на функцию */

{ return (len*(*func)(alpha)); }

В этом примере формальным параметром функции proect является указатель func на функцию, возвращающую значение типа double. Фактическими же параметрами при ее вызове становятся имена функций cos() и sin(), задающие соответствующие входные точки.