Язык С: можно ли вернуть функцию из другой функции?
В С++ этот вопрос решается возвращением функционального объекта с перегруженным оператором (). Можно ли в чистом си сделать так, чтобы функция в виде своего результата возвращала другую функцию, что-то на подобии этого:
void (*message) (void) function(int args)
Или еще проще:
message function(int args)
- c
- функции
- функциональное-программирование
Отслеживать
задан 13 апр 2020 в 23:06
430 2 2 серебряных знака 8 8 бронзовых знаков
Конечно, можно. См., например, man dlsym
14 апр 2020 в 9:03
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
Можно вернуть указатель на функцию (в С++ это тоже работает):
void a() <> // Вариант 1 void (*b(int args))() < (void)args; return a; >// Вариант 2 typedef void func_t(); func_t *c(int args) < (void)args; return a; >// Вариант 3 typedef void (*func_t2)(); func_t2 d(int args)
Отслеживать
ответ дан 13 апр 2020 в 23:18
HolyBlackCat HolyBlackCat
26.9k 3 3 золотых знака 27 27 серебряных знаков 40 40 бронзовых знаков
Интересно, а каррирование автору надо или нет?)
13 апр 2020 в 23:26
@Qwertiy про карирование я узнал только сейчас. Про него так же хотелось бы спросить.
13 апр 2020 в 23:37
@zurn, у меня нет ни малейшей идеи, как это можно было бы сделать в си. За счёт времени жизни переменной получается, что аргумент должен быть частью возвращаемого значения. В плюсах — это поле класса, потому и работает. А вот в си уже вряд ли получится. Ну или надо возвращать структуру с метолдом call, но это уже немного не то, чего хотелось бы? Или пойдёт?
Как в функции вызвать другую функцию c
Указатель на функцию может передаваться в другую функцию в качестве параметра. Например:
#include int add(int x, int y) < return x+y; >int subtract(int x, int y) < return x-y; >int operation(int (*op)(int, int), int a, int b) < return op(a, b); >int main(void)
Здесь в функции operation первый параметр — указатель int (*op)(int, int) представляет функцию, которая возвращает значение типа int и принимает два параметра типа int . Результатом функции является вызов той функции, на которую указывает указатель.
Определению указателя соответствуют две функции: add и subtract, поэтому их адрес можно передать в вызов функции operation: operation(add, a, b); .
Другой пример — функция, которая может принимать в качестве параметра некоторое условие:
#include int isEven(int x) < return x%2==0; >int isPositive(int x) < return x>0; > void action(int (*condition)(int), int numbers[], int n) < for(int i=0; i> > int main(void) < int nums[] = ; int n = sizeof(nums)/sizeof(nums[0]); printf("Even numbers: "); action(isEven, nums, n); printf("\nPositive numbers: "); action(isPositive, nums, n); return 0; >
Первый параметр функции action — указатель int (*condition)(int) представляет функцию, которая принимает целое число и в зависимости от того, соответствует оно условию или нет, возвращает 1 (если соответствует) или 0. На момент определения функции action точное условие может быть неизвестно.
В текущей программе условия представлены двумя функциями. Функция isEven() возвращает 1, если число четное, и 0, если число нечетное. А функция isPositive() возвращает 1, если число положительное, и 0, если отрицательное.
При вызове функции action() в нее можно передать нужное условие: action(isEven, nums, n); . В итоге программа выведет на экран числа из массива nums, которые соответствуют переданному условию:
Even numbers: -4 -2 0 2 4 Positive numbers: 1 2 3 4 5
Указатель на функцию как возвращаемое значение
Функция может возвращать указатель на другую функцию. Это может быть актуально, если имеется ограниченное количество вариантов — выполняемых функций, и надо выбрать одну из них. Но при этом набор вариантов и выбор из них определяется в промежуточной функции.
Например, нам надо выбрать и выполнить одну из трех арифметических операций
#include int add(int x, int y) < return x + y; >int subtract(int x, int y) < return x - y; >int multiply(int x, int y) < return x * y; >// int choice - выбранный пункт int (*select(int choice))(int, int) < // возвращаем нужную функцию switch (choice) < case 2: return subtract; case 3: return multiply; default: return add; >> int main(void) < int (*operation)(int, int); // указатель на выбранную функцию operation = select(1); // получаем указатель на функцию add - сложение int result = operation(6, 4); // выполняем функцию printf("result: %d \n", result); // result: 10 return 0; >
В данной программе мы предполагаем, что пользователь должен выбрать для выполнения одну из трех функций: add, subtract, multiply, каждая из которых представляет определенное действие.
Все выбираемые функции имеют один и тот же прототип вида:
int action(int, int);
То есть возвращают значение типа int и принимают два параметра типа int .
Сам выбор происходит в функции select() . Она возвращает указатель на функцию — по сути выбранную функцию. Посмотрим на ее заголовок:
int (*select(int choice))(int, int)
Если рассматривать по компонентам, то заголовок выглядит следующим образом:
тип_указателя_на_функцию (*select(параметры_функции))(параметры_указателя_на_функцию)
То есть сначала идет возвращаемый тип функции, на который возвращается указатель (в данном случае int )
Далее в скобках идет название самой функции и ее параметры — (*select(int choice)) . То есть функция select , которая возвращает указатель на функцию, принимает один параметр — choice — условный номер арифметической функции.
Зтаем идут типы параметров функции, указатель на которую возвращается: (int, int)
То есть в итоге функция select() имеет один параметр choice , который представляет тип int , и возвращает указатель на функцию, которая имеет прототип int action(int, int) .
В самой функции select() в зависимости от значения параметра choice возвращаем определенную функцию:
switch (choice)
В функции main сначала определяем указатель, который соответствует прототипу арифметических функций add, subtract, multiply:
int (*operation)(int, int);
Далее в этот указатель получаем результат из функции select , передав в нее номер функции:
operation = select(1);
По номеру 1 функция select возвращает функцию add . Соответственно переменная-указатель operation будет хранить адрес функции add.
Далее вызываем функцию, на которую указавает указатель, передвая ей два числа для ее параметров:
int result = operation(6, 4); // выполняем функцию
Подобным образом можно получить и другие функции:
int main(void) < int (*operation)(int, int) = select(2); // получаем указатель на функцию subtract printf("result: %d \n", operation(6, 4)); // result: 2 operation = select(3); // получаем указатель на функцию multiply printf("result: %d \n", operation(6, 4)); // result: 24 return 0; >
Для хранения всех действий в функции select определен массив указателей на функции actions:
int (*actions[])() = ;
С помощью введенного с клавиатуры числа определяем номер нужного действия, которое надо выполнить. Если номер меньше 1 или больше 3, то возвращается константа NULL.
В главной функции main() в бесконечном цикле вызываем функцию select, получая в качестве результата указатель на функцию:
action = select();
И если указатель не равен NULL, то после этого мы сможем вызвать функцию по указателю и получить ее результат:
actionNumber = action();
Консольный вывод работы программы:
Select action (1, 2, 3): 1 Action 1 selected action 1 Select action (1, 2, 3): 3 Action 3 selected action 3 Select action (1, 2, 3): 4 End
Функции в языке C. Передача аргументов по значению и по ссылке
Язык C как и большинство других языков программирования позволяет создавать программы, состоящие из множества функций, а также из одного или нескольких файлов исходного кода. До сих пор мы видели только функцию main , которая является главной в программе на C , поскольку выполнение кода всегда начинается с нее. Однако ничего не мешает создавать другие функции, которые могут быть вызваны из main или любой другой функции. В этом уроке мы рассмотрим создание только однофайловых программ, содержащих более чем одну функцию.
При изучении работы функций важно понимать, что такое локальная и что такое глобальная переменные. В языке программирования C глобальные (внешние) переменные объявляются вне какой-либо функции. С их помощью удобно организовывать обмен данными между функциями, однако это считается дурным тоном, т.к. легко запутывает программу. Локальные переменные в Си называют автоматическими. Область действия автоматических переменных распространяется только на ту функцию, в которой они были объявлены. Параметры функции также являются локальными переменными.
Структурная организация файла, содержащего несколько функций, может выглядеть немного по-разному. Так как выполнение начинается с main() , то ей должны быть известны спецификации (имена, количество и тип параметров, тип возвращаемого значения) всех функций, которые из нее вызываются. Отсюда следует, что объявляться функции должны до того, как будут вызваны. А вот определение функции уже может следовать и до и после main() . Рассмотрим такую программу:
#include // объявление функции float median (int a, int b); int main () { int num1 = 18, num2 = 35; float result; printf("%10.1f\n", median(num1, num2)); result = median(121, 346); printf("%10.1f\n", result); printf("%10.1f\n", median(1032, 1896)); } // определение функции float median (int n1, int n2) { float m; m = (float) (n1 + n2) / 2; return m; }
В данном случае в начале программы объявляется функция median . Объявляются тип возвращаемого ею значения ‒ float , количество и типы параметров ( int a, int b ). Обратите внимание, когда объявляются переменные, то их можно группировать: int a, b; . Однако с параметрами функций так делать нельзя, для каждого параметра тип указывается отдельно: (int a, int b) .
Далее идет функция main , а после нее ‒ определение median . Имена переменных-параметров в объявлении функции никакой роли не играют. Их вообще можно опустить, например, float median (int, int); . Поэтому когда функция определяется, то имена параметров могут быть другими, однако тип и количество должны строго совпадать с объявлением.
Функция median() возвращает число типа float . Оператор return возвращает результат выполнения переданного ему выражения; после return функция завершает свое выполнение, даже если далее тело функции имеет продолжение. Функция median() вычисляет среднее значение от двух целых чисел. В выражении (float) (n1 + n2) / 2 сначала вычисляется сумма двух целых чисел, результат преобразуется в вещественное число и только после этого делится на 2. Иначе мы бы делили целое на целое и получили целое (в таком случае дробная часть просто усекается).
В теле main функция median() вызывается три раза. Результат выполнения функции не обязательно должен быть присвоен переменной.
Эту же программу можно написать и так:
#include float median (int n1, int n2) { float m; m = (float) (n1 + n2) / 2; return m; } int main () { int num1 = 18, num2 = 35; float result; printf("%10.1f\n", median(num1, num2)); result = median(121, 346); printf("%10.1f\n", result); printf("%10.1f\n", median(1032, 1896)); }
Хотя такой способ и экономит одну строчку кода, однако главная функция, в которой отражена основная логика программы, опускается вниз, что может быть неудобно. Поэтому первый вариант предпочтительней.
Напишите функцию, возвращающую куб числа, переданного ей в качестве аргумента. Вызовите эту функцию с разными аргументами.
Статические переменные
В языке программирования C существуют так называемые статические переменные. Они могут быть как глобальными, так и локальными. Перед именем статической переменной пишется ключевое слово static .
Внешние статические переменные, в отличие от обычных глобальных переменных, нельзя использовать из других файлов в случае программы, состоящей из нескольких файлов. Они глобальны только для функций того файла, в котором объявлены. Это своего рода сокрытие данных, когда наружу не выставляют ничего лишнего, чтобы извне нельзя было случайно испортить данные.
Статические переменные, объявленные внутри функций, имеют такую же область действия, как автоматические. Однако в отличие от автоматических, значения локальных статических переменных не теряются, а сохраняются между вызовами функции:
#include int hello(); int main() { printf(" - %d-й вызов\n", hello()); printf(" - %d-й вызов\n", hello()); printf(" - %d-й вызов\n", hello()); } int hello () { static count = 1; printf("Hello world!"); return count++; }
Hello world! - 1-й вызов Hello world! - 2-й вызов Hello world! - 3-й вызов
В этом примере в функции hello() производится подсчет ее вызовов.
Передача аргументов по ссылке
В первом примере этого урока мы передавали в функцию аргументы по значению. Это значит, что когда функция вызывается, ей передаются в качестве фактических параметров (аргументов) не указанные переменные, а копии значений этих переменных. Сами переменные к этим копиям уже никакого отношения не имеют. В вызываемой функции эти значения присваиваются переменным-параметрам, которые, как известно, локальны. Отсюда следует, что изменение переданных значений никакого влияния на переменные, переданные в функцию при вызове, не оказывают. В примере выше даже если бы в функции median() менялись значения переменных n1 и n2, то никакого влияния сей факт на переменные num1 и num2 не оказал.
Однако можно организовать изменение локальной переменной одной функции с помощью другой функции. Сделать это можно, передав в функцию адрес переменной или указатель на нее. На самом деле в этом случае также передается копия значения. Но какого значения?! Это адрес на область памяти. На один и тот же участок памяти может существовать множество ссылок, и с помощью каждой из них можно поменять находящееся там значение. Рассмотрим пример:
#include void epow(int *, int); int main() { int x = 34, y = 6; epow(&x, 3); epow(&y, 1); printf("%d %d\n", x, y); // 34000 60 } void epow(int *base, int pow) { while (pow > 0) { *base = *base * 10; pow--; } }
Функция epow ничего не возвращает, о чем говорит ключевое слово void . Принимает эта функция адрес, который присваивается локальной переменной-указателю, и целое число. В теле функции происходит изменение значения по адресу, содержащемуся в указателе. Поскольку это адрес то переменной x , то y из функции main , то epow() меняет их значение.
Когда epow() вызывается в main , то в качестве первого параметра мы должны передать адрес, а не значение. Поэтому, например, вызов epow(x, 3) привел бы к ошибке, а вызов epow(&x, 3) ‒ правильный, т. к. мы берем адрес переменной x и передаем его в функцию. При этом ничего не мешает объявить в main указатель и передавать именно его (в данном случае сама переменная p содержит адрес):
int x = 34, y = 6; int *p; p = &x; epow(p, 3); p = &y; epow(p, 1); printf("%d %d\n", x, y);
Кроме того, следует знать, что функция может возвращать адрес.
Важно понять механизм так называемой передачи аргументов по ссылке, т.к. это понимание пригодится при изучении массивов и строк. Использовать указатели при работе с простыми типами данных не стоит. Лучше возвращать из функции значение, чем менять локальные переменные одной функции с помощью кода другой. Функции должны быть достаточно автономными.
Напишите программу, в которой помимо функции main были бы еще две функции: в одной вычислялся факториал переданного числа, в другой ‒ находился n-ый элемент ряда Фибоначчи ( n ‒ параметр функции). Вызовите эти функции с разными аргументами.
Курс с решением задач:
pdf-версия
Как в функции вызвать другую функцию c
Указатель на функцию фактически представляет некоторый тип, и функция также может иметь параметр, который представляет тип указателя на функцию. Таким образом, мы можем через параметр на функцию передавать в одну функцию другую. То есть функция может быть аргументом другой функции.
#include int add(int, int); int subtract(int, int); int operation(int(*)(int, int), int, int); // первый параметр — указатель на функцию int main() < int a; int b; int result = operation(add, a, b); std::cout int add(int x, int y) < return x + y; >int subtract(int x, int y) < return x - y; >int operation(int(*op)(int, int), int a, int b)
В данном случае первый параметр функции operation — int (*op)(int, int) — представляет указатель на функцию, которая возвращает значение типа int и принимает два параметра типа int . Результатом функции является вызов той функции, на которую указывает указатель.
Определению указателя соответствуют две функции: add и subtract, поэтому их адрес можно передать в вызов функции operation: operation(add, a, b); .
Результат работы программы:
result: 16 result: 4
Функция, передаваемая другой функции в качестве аргумента, называется функцией обратного вызова или коллбек (callback). А функция, которая принимает другую функцию в качестве аргумента, является функцией высшего порядка. Таким образом, в примере выше функция operation представляет функцию высокого порядка, а функции add и subtract — функции обратного вызова.
Рассмотрим другой пример — определим функцию, которая может принимать в качестве параметра некоторое условие и вычислять все элементы массива, которые соответствуют этому условию:
#include // функции, которые представляют условия bool isEven(int); // если число четное bool isPositive(int); // если число положительное // Функция для определения элементов массива, которые соответствуют некоторому условию // функция принимает условие — bool(*)(int) // массив — int[] // размер массива — unsigned void action(bool(*)(int), int[], unsigned); int main() < int numbers[]< -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5 >; const unsigned n < std::size(numbers)>; // находим длину массива std::cout bool isEven(int x) < return x % 2 == 0; >bool isPositive(int x) < return x >0; > void action(bool(*condition)(int), int numbers[], unsigned n) < // перебираем массив for (unsigned i<>; i < n; i++) < // если число (numbers[i] соответствует условию condition if (condition(numbers[i])) < std::cout > std::cout
Функция action в качестве первого параметра принимает некоторую функцию, которая задает условие, которому должны соответствовать элементы массива. Это условие представляет указатель bool (*condition)(int) . То есть это некоторая функцию, которая принимает целое число и в зависимости от того, соответствует оно условию или нет, возвращает значение типа bool ( true , если число из массива соответствует условию, и false , если не соответствует). На момент определения функции action точное условие может быть неизвестно.
В текущей программе условия представлены двумя функциями. Функция isEven() возвращает true, если число четное, и false, если число нечетное. А функция isPositive() возвращает true, если число положительное, и false, если отрицательное.
Второй параметр функции action — массив чисел int, для которых вызываем условие. А третий параметр — размер массива. Если число соотвествует условию, то выводим это число на консоль
void action(bool(*condition)(int), int numbers[], unsigned n) < // перебираем массив for (unsigned i<>; i < n; i++) < // если число (numbers[i] соответствует условию condition if (condition(numbers[i]))
При вызове функции action() в нее можно передать нужное условие:
action(isEven, nums, n); action(isPositive, numbers, n);
В итоге программа выведет на экран числа из массива nums, которые соответствуют переданному условию:
Even numbers: -4 -2 0 2 4 Positive numbers: 1 2 3 4 5