Перейти к содержимому

Как передать указатель в функцию c

  • автор:

Указатели на функции

Чаще всего к ошибкам приводит использование указателей па функцию. Хотя функция — это не переменная, она по-прежнему имеет физическое положение в памяти, которое может быть присвоено указателю. Адрес, присвоенный указателю, является входной точкой в функцию. Указатель может использоваться вместо имени функции. Он также позволяет передавать функции как обычные аргументы в другие функции.

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

Адрес функции получается при использовании имени функции без каких-либо скобок или аргументов. (Очень похоже на массивы, где адрес получается с использованием имени массива без индексов.) Рассмотрим следующую программу, предназначенную для демонстрации нюансов объявления:

#include
#include
void check(char *a, char *b, int (*cmp) (const char *, const char *));
int main(void)
char s1 [80], s2[80];
int (*p) (const char*, const char*);
p = strcmp; /* получение адреса strcmp() */
gets(s1);
gets (s2);
check(s1, s2, p);
return 0;
>

void check (char *a, char *b, int (*cmp) (const char *, const char *))
printf(«Testing for equality.\n»);
if(!(*cmp) (a, b)) printf(«Equal»);
else printf(«Not equal»);
>

Когда вызывается check(), ей передаются два указателя на символы и один указатель на функцию. В функции check() аргументы объявляются как указатели на символы и указатель на функцию. Надо обратить внимание на способ объявления указателя на функцию. Следует использовать аналогичный метод при объявлении других указателей на функцию, за исключением тех случаев, когда отличается возвращаемый тип или передаваемые параметры. Скобки вокруг *cmp необходимы для правильной интерпретации компилятором данного выражения.

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

Рассмотрим работу функции strcmp() в функции check(). Оператор

if (!(*cmp) (a, b) ) printf(«Equal»);

осуществляет вызов функции, в данном случае strcmp(), с помощью cmp, который указывает на данную функцию. Вызов происходит с аргументами a и b. Данный оператор демонстрирует общий вид использования указателя на функцию для вызова функции, на которую он указывает. Круглые скобки вокруг *cmp необходимы вследствие наличия приоритетов. На самом деле можно напрямую использовать cmp, как показано ниже:

if (!cmp(a, b)) printf(«Equal»);

Данная версия также вызывает функцию, на которую указывает cmp, но она использует нормальный синтаксис. Использование ( cmp) помогает всем, читающим программу, понять, что указатель на функцию используется для вызова функции вместо вызова функции cmp. Возможно вызвать напрямую check(), используя strcmp, как показано ниже:

check(s1, s2, strcmp);

Данный оператор устраняет необходимость наличия дополнительной переменной-указателя.

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

#include
#include
#include
#include
void check(char *a, char *b, int (*cmp) (const char *, const char *));
int numcmp (const char *a, const char *b) ;
int main(void)
char s1[80], s2 [80];
gets (s1);
gets (s2);
if(isalpha(*s1))
check (s1, s2, strcmp);
else
check(s1, s2, numcmp);
return 0;
>

void check(char *a, char *b, int (*cmp) (const char *,const char *))
printf(«Testing for equality.\n»);
if(!(*cmp) (a, b)) printf («Equal»);
else printf(«Hot equal»);
>

int numcmp (const char *a, const char *b)
If(atoi(a)==atoi(b)) return 0;
else return 1;
>

Указатели на функции

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

#include #include int sum(int a, int b) < return a + b; >void main () < //Объявляем указатель на функцию int (*fptr)(int, int) = NULL; int result; //Присваиваем указателю значение - адрес функции //Это похоже на работу с массивом: операцию взятия адреса использовать не нужно fptr = sum; //Вызов осуществляется также, как и обычной функции result = fptr(10, 40); printf("%d", result); getch(); >
#include #include int dble(int a) < return 2*a; >int deleteEven(int a) < if (a % 2 == 0) < return 0; >else < return a; >> //Функция принимает массив, его размер и указатель на функцию, //которая далее применяется ко всем элементам массива void map(int *arr, unsigned size, int (*fun)(int)) < unsigned i; for (i = 0; i < size; i++) < arr[i] = fun(arr[i]); >> void main () < int a[] = ; unsigned i; map(a, 10, deleteEven); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >map(a, 10, dble); printf("\n"); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >getch(); >

В этом примере мы создали функцию отображения, которая применяет ко всем элементам массива функцию, которая передаётся ей в качестве аргумента. Когда мы вызываем функцию map, достаточно просто передавать имена функций (они подменяются указателями). Запишем теперь функцию map, которая получает в качестве аргумента массив типа void:

#include #include void dbleInt(void *a) < *((int*) a) *= 2; >void deleteEvenInt(void* a) < int tmp = *((int*) a); if (tmp % 2 == 0) < *((int*) a) = 0; >> void dbleDouble(void *a) < *((double*) a) *= 2.0; >void deleteEvenDouble(void* a) < int tmp = *((double*) a); if (tmp % 2 == 0) < *((double*) a) = 0; >> //Функция принимает массив, его размер, размер одного элемента и указатель на функцию, //которая далее применяется ко всем элементам массива void map(void *arr, unsigned num, size_t size, void (*fun)(void *)) < unsigned i; char *ptr = (char*) arr; for (i = 0; i < num; i++) < fun((void*) (ptr + i*size)); >> void main () < int a[] = ; double b[] = ; unsigned i; //Работаем с массивом типа int map(a, 10, sizeof(int), deleteEvenInt); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >map(a, 10, sizeof(int), dbleInt); printf("\n"); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >printf("\n"); //Работаем с массивом типа double map(b, 10, sizeof(double), deleteEvenDouble); for (i = 0; i < 10; i++) < printf("%.3f ", b[i]); >map(b, 10, sizeof(double), dbleDouble); printf("\n"); for (i = 0; i < 10; i++) < printf("%.3f ", b[i]); >getch(); >

Вот где нам понадобились указатели типа void. Так как map получает указатель на функцию, то все функции должны иметь одинаковые аргументы и возвращать один и тот же тип. Но аргументы функций должны быть разного типа, поэтому мы делаем их типа void. Функция map получает указатель типа void (*)(void*), поэтому ей теперь можно передавать любую из четырёх функций.
Пример другой функции: функция filter получает указатель на массив и возвращает размер нового массива, оставляя в нём только те элементы, для которых переданный предикат возвращает логическую истину (предикат – функция, которая возвращает истину или ложь). Сначала напишем для массива типа int:

#include #include #include int isOdd(int a) < return (a % 2 != 0); >int isGtThree(int a) < return a >3; > unsigned int filter(int *arr, unsigned size, int (*pred)(int), int** out) < unsigned i; unsigned j; //размер возвращаемого масива *out = (int*) malloc(sizeof(int)*size); for (i = 0, j = 0; i < size; i++) < if (pred(arr[i])) < (*out)[j] = arr[i]; j++; >> *out = (int*) realloc(*out, j*sizeof(int)); return j; > void main () < int a[] = ; int *aOdd = NULL; int *aGtThree = NULL; unsigned i; unsigned size; size = filter(a, 10, isOdd, &aOdd); for (i = 0; i < size; i++) < printf("%d ", aOdd[i]); >printf("\n"); size = filter(a, 10, isGtThree, &aGtThree); for (i = 0; i < size; i++) < printf("%d ", aGtThree[i]); >free(aOdd); free(aGtThree); getch(); >

Теперь для массива типа void

#include #include #include #include #include int isOddInt(void *a) < return (*((int*) a) % 2 != 0); >int isGtThreeInt(void* a) < return *((int*) a) >3; > int isOddDouble(void* a) < return (int)*((double*) a) / 2 != 0; >int isGtThreeDouble(void* a) < return *((double*) a) >3.0; > unsigned int filter(void *arr, unsigned num, size_t size, int (*pred)(void*), void** out) < unsigned i; unsigned j; //размер возвращаемого масива char* ptrIn = (char*) arr; char* ptrOut = NULL; *out = (void*) malloc(num*size); ptrOut = (char*) (*out); for (i = 0, j = 0; i < num; i++) < if (pred(ptrIn + i*size)) < memcpy(ptrOut + j*size, ptrIn + i*size, size); j++; >> *out = (void*) realloc(*out, j*size); return j; > void main () < int a[] = ; double b[] = ; int *aOdd = NULL; int *aGtThree = NULL; double *bOdd = NULL; double *bGtThree = NULL; unsigned i; unsigned size; size = filter(a, 10, sizeof(int), isOddInt, (void**)&aOdd); for (i = 0; i 7lt; size; i++) < printf("%d ", aOdd[i]); >printf("\n"); size = filter(a, 10, sizeof(int), isGtThreeInt, (void**)&aGtThree); for (i = 0; i < size; i++) < printf("%d ", aGtThree[i]); >printf("\n"); size = filter(b, 10, sizeof(double), isOddDouble, (void**)&bOdd); for (i = 0; i < size; i++) < printf("%.3f ", bOdd[i]); >printf("\n"); size = filter(b, 10, sizeof(double), isGtThreeDouble, (void**)&bGtThree); for (i = 0; i < size; i++) < printf("%.3f ", bGtThree[i]); >free(aOdd); free(bOdd); free(aGtThree); free(bGtThree); getch(); >

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

#include #include int sum(int a, int b) < return a + b; >int maxx(int a, int b) < return (a >b)? a: b; > int mul(int a, int b) < return a*b; >void fold(int *arr, unsigned size, int (*fun)(int, int), int *acc) < unsigned i; *acc = fun(arr[0], arr[1]); for (i = 2; i < size; i++) < *acc = fun(*acc, arr[i]); >> void main () < int a[] = ; int result; fold(a, 10, sum, &result); printf("%d\n", result); fold(a, 10, maxx, &result); printf("%d\n", result); fold(a, 10, mul, &result); printf("%d\n", result); getch(); >

Последний пример: функция сортировки вставками для массива типа void. Так как тип массива не известен, то необходимо передавать функцию сравнения.

#include #include #include #include int cmpIntDesc(void *a, void* b) < return *((int*) a) < *((int*) b); >int cmpIntAsc(void *a, void* b) < return *((int*) a) >*((int*) b); > int cmpDoubleAsc(void *a, void* b) < return *((double*) a) < *((double*) b); >int cmpDoubleDesc(void *a, void* b) < return *((double*) a) >*((double*) b); > void insertionSort(void* arr, unsigned num, size_t size, int (*cmp)(void *a, void *b)) < unsigned i, j; char *ptr = (char*) arr; char *tmp = (char*) malloc(size); for (i = 1; i < num; i++) < if (cmp(ptr + i*size, ptr + (i-1)*size)) < j = i; while (cmp(ptr + j*size, ptr + (j-1)*size) && j >0) < memcpy(tmp, ptr + j*size, size); memcpy(ptr + j*size, ptr + (j-1)*size, size); memcpy(ptr + (j-1)*size, tmp, size); j--; >> > > void main () < int a[] = ; double b[] = ; int i; insertionSort(a, 10, sizeof(int), cmpIntAsc); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >printf("\n"); insertionSort(a, 10, sizeof(int), cmpIntDesc); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >printf("\n"); insertionSort(b, 10, sizeof(double), cmpDoubleAsc); for (i = 0; i < 10; i++) < printf("%.3f ", b[i]); >printf("\n"); insertionSort(b, 10, sizeof(double), cmpDoubleDesc); for (i = 0; i < 10; i++) < printf("%.3f ", b[i]); >getch(); >

Массив указателей на функции

М ассив указателей на функции определяется точно также, как и обычный массив – с помощью квадратных скобок после имени:

#include #include #include #include #define ERROR_DIV_BY_ZERO -2 #define EPSILON 0.000001f float doSum(float a, float b) < return a + b; >float doSub(float a, float b) < return a - b; >float doMul(float a, float b) < return a * b; >float doDiv(float a, float b) < if (fabs(b) return a / b; > void main() < float (*menu[4])(float, float); int op; float a, b; menu[0] = doSum; menu[1] = doSub; menu[2] = doMul; menu[3] = doDiv; printf("enter a: "); scanf("%f", &a); printf("enter b: "); scanf("%f", &b); printf("enter operation [0 - add, 1 - sub, 2 - mul, 3 - div]"); scanf("%d", &op); if (op >= 0 && op < 4) < printf("%.6f", menu[op](a, b)); >getch(); >

Точно также можно было создать массив динамически

void main() < float (**menu)(float, float) = NULL; int op; float a, b; menu = (float(**)(float, float)) malloc(4*sizeof(float(*)(float, float))); menu[0] = doSum; menu[1] = doSub; menu[2] = doMul; menu[3] = doDiv; printf("enter a: "); scanf("%f", &a); printf("enter b: "); scanf("%f", &b); printf("enter operation [0 - add, 1 - sub, 2 - mul, 3 - div]"); scanf("%d", &op); if (op >= 0 && op < 4) < printf("%.6f", menu[op](a, b)); >free(menu); getch(); >

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

#include #include #include #include #define ERROR_DIV_BY_ZERO -2 #define EPSILON 0.000001f typedef float (*operation)(float, float); float doSum(float a, float b) < return a + b; >float doSub(float a, float b) < return a - b; >float doMul(float a, float b) < return a * b; >float doDiv(float a, float b) < if (fabs(b) return a / b; > void main() < operation *menu = NULL; int op; float a, b; menu = (operation*) malloc(4*sizeof(operation)); menu[0] = doSum; menu[1] = doSub; menu[2] = doMul; menu[3] = doDiv; printf("enter a: "); scanf("%f", &a); printf("enter b: "); scanf("%f", &b); printf("enter operation [0 - add, 1 - sub, 2 - mul, 3 - div]"); scanf("%d", &op); if (op >= 0 && op < 4) < printf("%.6f", menu[op](a, b)); >free(menu); getch(); >

Ещё один пример: функция any возвращает 1, если в переданном массиве содержится хотя бы один элемент, удовлетворяющий условию pred и 0 в противном случае.

#include #include typedef int (*Predicat)(void*); int isBetweenInt(void* a) < return *((int*) a) >10 && *((int*) a) < 12; >int isBetweenDouble(void* a) < return *((double*) a) >10.0 && *((double*) a) < 12.0; >int any(void* arr, unsigned num, size_t size, Predicat pred) < unsigned i; char* ptr = (char*) arr; for (i = 0; i < num; i++) < if (pred(ptr + i*size)) < return 1; >> return 0; > void main() < int a[10] = ; double b[10] = ; printf("has 'a' any value > 10 and < 12? %d\n", any(a, 10, sizeof(int), isBetweenInt)); printf("has 'b' any value >10 and

qsort и bsearch

В библиотеке stdlib си имеется несколько функций, которые получают в качестве аргументов указатели на функции. Функция qsort получает такие же аргументы, как и написанная нами функция insertionSort: массив типа void, размер массива, размер элемента и указатель на функцию сравнения. Давайте посмотрим простой пример — сортировка массива строк:

#include #include #include #include #define SIZE 10 //qsort передаёт функции сравнения указатели на элементы. //но наш массив - это массив указателей, так что qsort будет //передавать указатели на указатели int cmpString(const void* a, const void* b) < return strcmp(*(const char**) a, *(const char**) b); >void main() < char *words[SIZE]; char buffer[128]; int i, length; printf("Enter %d words:", SIZE); for (i = 0; i < SIZE; i++) < scanf("%127s", buffer); length = strlen(buffer); words[i] = (char*) malloc(length+1); strcpy(words[i], buffer); >printf("\n"); qsort(words, SIZE, sizeof(char*), cmpString); for (i = 0; i < SIZE; i++) < printf("%s\n", words[i]); free(words[i]); >getch(); >

Функция bsearch проводит бинарный поиск в отсортированном массиве и получает указатель на функцию сравнения, такую же, как и функция qsort. В случае, если элемент найден, то она возвращает указатель на этот элемент, если элемент не найден, то NULL.

#include #include int cmpInt(const void* a, const void* b) < return *(int*) a - *(int*) b; >void main() < int a[10] = ; int elm; int *index; printf("Enter number to search: "); scanf("%d", &elm); index = (int*) bsearch(&elm, a, 10, sizeof(int), cmpInt); if (index == NULL) < printf("element not found"); >else < printf("index = %d", (index - a)); >getch(); >

ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 sypachev_s_s@mail.ru Stepan Sypachev students

email

Всё ещё не понятно? – пиши вопросы на ящик

Передача параметров по указателю

Некоторые разработчики предпочитают наличие амперсанда в f(&obj) как подсказки о том, что значение obj может измениться внутри вызова. Правда, если obj уже и так указатель, то амперсанд не будет нужен.

Плюсы передачи по указателю

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

Это экономия одновременно и по памяти, и по скорости!

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

Можно использовать константный указатель: const T*

Минусы передачи по указателю

В Си существует неявное преобразование типа целого числа к типу указателя, о котором компилятор выдает лишь предупреждение, но не ошибку:

warning: passing argument 1 of ‘foo’ makes pointer from integer without a cast

Представьте себе, что foo довольно длинна, и везде, где употребляется x , нужна звёздочка, а вызывается foo 48 раз в разных местах программы — при этом иногда нужен амперсанд, иногда нет.

Итак, легко забыть звёздочку в теле функции foo или амперсанд — в её вызове, значит переданное число может случайно истолковаться как адрес числа. Обращение по непредсказуемому адресу приводит к ошибке Segmentation fault и завершению программы.

Поэтому передача аргументов по адресу опасна!

Как передать указатель в функцию c

При рассмотрении передачи параметров в функцию указывалось, что параметры передаются в функцию по значению. То есть функция не изменяет значения передаваемых аргументов. Однако, используя в качестве параметров указатели, мы можем получить доступ к значению аргумента и изменить его.

Например, пусть у нас будет простейшая функция, которая увеличивает число на единицу:

#include void increment(int x) < x = x + 1; printf("increment function: %d \n", x); >int main(void)

Здесь переменная n передается в качестве аргумента для параметра x. Передача происходит по значению, поэтому любое изменение параметра x в функции increment никак не скажется на значении переменной n. Что мы можем увидеть, запустим программу:

increment function: 11 main function: 10

Теперь изменим функцию increment, использовав в качестве параметра указатель:

#include void increment(int *x) < *x = *x + 1; printf("increment function: %d \n", *x); >int main(void)

Теперь в функции increment разыменовываем указатель, получаем его значение и увеличиваем его на единицу.

*x = *x + 1;

Это изменяет значение, которое находится по адресу, хранимому в указателе x.

Поскольку теперь функция в качестве параметра принимает указатель, то при ее вызове необходимо передать адрес переменной: increment(&n); .

В итоге изменение параметра x также повлияет на переменную n:

increment function: 11 main function: 11

Еще один показательный пример применения указателей в параметрах — функция обмена значений:

#include void swap(int *a, int *b) < int temp = *a; *a = *b; *b=temp; >int main(void)

Функция swap() в качестве параметров принимает два указателя. Посредством переменной temp происходит обмен значениями.

При вызове функции swap в нее передаются адреса переменных x и y, и в итоге их значения будут изменены.

Константые параметры

Если необходимо запретить изменять значение параметра-указателя внутри функции, то можно определить такой параметра как константный:

#include // константный параметр int twice(const int *x) < //*x = *x * *x; // так нельзя, так как x - константный параметр int y = *x + *x; return y; >int main(void) < int n = 10; int m = twice(&n); printf("n = %d \n", n); // n = 10 printf("m = %d \n", m); // m = 20 return 0; >

Фактически такой константный параметр будет работать как указатель на константу — мы не сможем изменить его значение внутри функции.

Массивы в параметрах

Если функция принимает в качестве параметра массив, то фактически в эту функцию передается только адрес начала массива. То есть как и в случае с указателями нам доступен адрес, по которому мы можем менять значения. В отличие от параметров примитивных типов, которые передаются по значению.

Например, определим функцию для увеличения элементов массива в два раза:

#include void twice(int n, int p[]) < for(int i = 0; i < n; i++) < p[i]= p[i] * 2; >> int main(void) < int nums[] = ; // получаем количество элементов массива int length = sizeof(nums)/sizeof(nums[0]); twice(length, nums); for(int i=0; i return 0; >

Функция twice в качестве параметров принимает массив и число его элементов и в цикле увеличивает их в два раза.

В функции main передаем массив в функцию twice и затем выводим его на консоль. В результате мы увидим, что массив nums был изменен:

2 4 6 8 10

Так как передача массива в функцию фактически представляет передачу адреса первого элемента, то массивы в параметрах мы можем заменить указателями:

#include void twice(int n, int *p) < for(int i=0; i> int main(void) < int nums[] = ; int length = sizeof(nums)/sizeof(nums[0]); twice(length, nums); for(int i=0; i return 0; >

В итоге в данном случае не будет большой разницы, какой тип имеет параметр — массив или указатель.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *