Указатели и функции в языке программирования С
Теоретическая часть
В предыдущей лабораторной работе рассматривались примеры функций, аргументами которых выступали указатели. В данной лабораторной работе подробнее будут рассмотрены вопросы, касающиеся указателей и функций.
Ранее было отмечено, что в языке С аргументы передаются в функции по значению и не существует прямого способа изменить переменную вызывающей функции, действуя внутри вызываемой функции. Благодаря аргументам-указателям функция может обращаться к объектам в вызвавшей ее функции, в том числе модифицировать их [11.1]. В качестве примера рассмотрим функцию swap(), в задачу которой входит обмен элементов местами. Для решения такой задачи необходимо передать из вызывающей программы (например, из главной функции main() ) в функцию указатели на переменные, которые нужно изменить. Программный код решения примера:
#include <stdio.h>
#include <conio.h>
// Прототип функции
void swap(int*, int*);
int main (void) {
int a = 10,
b = -20;
// Вывод на консоль исходных значений переменных
printf("\n Initial values:\n a = %d, b = %d\n", a, b);
// Вызов функции swap() с фактическими параметрами
swap(&a, &b);
// Результат после обращения функции swap()
printf("\n New values:\n a = %d, b = %d\n", a, b);
printf("\n ... Press any key: ");
_getch();
return 0;
}
// Определение функции
void swap(int *pa, int *pb)
{
int temp;
temp = *pa;
*pa = *pb;
*pb = temp;
}В программе в качестве фактических параметров функции swap() выступают адреса заданных переменных. Можно было в главной функции определить указатели и инициализировать их адресами заданных переменных, а потом передать эти указатели в функцию swap.
Результат выполнения программы показан нa рис. 11.1.
Указатели, передаваемые в функцию, могут быть указателями на указатели. Указатели могут указывать на начало какого-либо массива и т. д. Указатели могут использоваться для защиты массивов, над которыми необходимо произвести некоторые вычисления или преобразования.
Особым свойством указателей можно считать возможность использовать их в качестве возвращаемых значений функций. Поскольку функции возвращают только одно значение, то несколько значений одного типа можно поместить в массив, а затем указатель на этот массив использовать в качестве возвращаемого значения.
Общая форма определения функции, которая возвращает указатель, следующая:
тип *имя_функции ( аргументы функции )
{
// тело функции
тип *имя_указателя;
?
return имя_указателя;
}Рассмотрим пример, в котором осуществляется сложение двух одномерных массивов и результат возвращается через указатель.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
int *out2(int A[], int B[], int);
int main (void) {
int i, n;
int A[] = {1,2,3,4,5};
int B[] = {2,2,2,2,2};
int *ptrAB = NULL;
n = (sizeof(A)/sizeof(A[0]));
puts("\n The initial arrays: ");
for (i = 0; i < n; i++)
printf(" %d", A[i]);
puts("");
for (i = 0; i < n; i++)
printf(" %d", B[i]);
ptrAB = out2(A, B, n);
puts("\n\n Result from function: ");
for (i = 0; i < n; i++)
printf(" %d", ptrAB[i]);
puts("\n\n Control of the arrays: ");
for (i = 0; i < n; i++)
printf(" %d", A[i]);
puts("");
for (i = 0; i < n; i++)
printf(" %d", B[i]);
free(ptrAB); // освобождение выделенной памяти
printf("\n\n ... Press any key: ");
_getch();
return 0;
}
int *out2(int A[], int B[], int n)
{
int i;
int *ptr = (int *)calloc(n, sizeof(int)); //выделение памяти
for (i = 0; i < n; i++)
ptr[i] = A[i] + B[i];
return ptr;
}Программа не требует особых пояснений.
Следует отметить, что никогда не следует возвращать адрес переменной, определенной в теле функции, так как переменные функции являются локальными, и они существуют только во время работы функции.
Указатели возвращаются подобно значениям любых других типов данных. Чтобы вернуть указатель, функция должна объявить его тип в качестве типа возвращаемого значения. Таким образом, если функция возвращает указатель, то значение, используемое в ее инструкции return, также должно быть указателем. В частности, многие библиотечные функции, предназначенные для обработки строк, возвращают указатели на символы.
В языке С существует такой механизм как указатель на функцию. Допустим, существует несколько функций для различных операций с данными. В этом случае оказывается удобным определить указатель на функцию, и использовать его там, где требуется производить расчет для различных функций.
Указатель на функцию – это переменная, содержащая адрес в памяти, по которому расположена функция [11.2]. Имя функции – это адрес начала программного кода функции. Указатели на функции могут быть переданы функциям в качестве аргументов, могут возвращаться функциями, сохраняться в массивах и присваиваться другим указателям на функции [11.2].
Типичное определение указателя на функцию следующее:
тип_возвращаемый_функцией(*имя_указателя_на_функцию)(аргументы);
В приведенном объявлении используются круглые скобки, в которых собственно и определяется указатель на функцию, которая возвращает тот или иной тип – тип_возвращаемый_функцией. Хотя знак * обозначает префиксную операцию, он имеет более низкий приоритет, чем функциональные круглые функции, поэтому для правильного комбинирования частей объявления необходимы еще и дополнительные скобки [11.1]. При этом аргументы – это аргументы той или иной функции с заданным типом возвращаемого значения, и на которую ссылается указатель *имя_указателя_на_функцию. Очевидно, что возможны сложные объявлений функций.
Указатели на функции часто используются в системах, управляемых меню [11.2]. Пользователь выбирает команду меню (одну из нескольких). Каждая команда обслуживается своей функцией. Указатели на каждую функцию находятся в массиве указателей. Выбор пользователя служит индексом, по которому из массива выбирается указатель на нужную функцию.
Другим типичным применением указателей на функции являются реализация обобщенных алгоритмов, например, алгоритмов сортировки и поиска. В этом случае критерии сортировки и поиска реализуются в виде отдельных функций и передаются при помощи указателей на функции в качестве параметра реализации основного алгоритма.
