- •Министерство образования Республики Беларусь
- •1. Что такое программа на языке программирования
- •2. Общее знакомство с языком с
- •3. Структура простой программы на языке с
- •4. Что такое программа на языке с
- •5. Представление информации и типы данных в языке с
- •6. Константы
- •7. Переменные
- •8. Элементарный вВод и вывод информации
- •9. Выражения и операции
- •9.1. Арифметические операции
- •9.2. Операция изменения знака
- •9.3. Операции инкремента и декремента
- •9.4. Операция присваивания
- •9.6. Поразрядные логические операции
- •9.8. Логические операции и операции отношения
- •9.9. Условная операция «? :»
- •9.10. Операция последовательного вычисления
- •9.11. Операция определения требуемой памяти в байтах sizeof
- •9.12. Операция приведения типа (type)
- •10. Операторы управления вычислительным процессом
- •10.1. Операторы ветвления if и else
- •10.2. Оператор switch
- •10.3. Оператор цикла while
- •10.4. Оператор цикла do…while
- •10.5. Оператор цикла for
- •10.6. Бесконечные циклы
- •10.7. Другие управляющие средства языка с
- •10.8. Стандартные математические функции
- •11. Вычисление выражений и побочные эффекты
- •11.1. Преобразования типов при вычислении выражений
- •11.2. Побочные эффекты при вычислении выражений
- •12. Массивы
- •12.1. Описание массива
- •12.2. Инициализация массива
- •12.3. Ввод-вывод массива
- •12.4. Двумерные массивы (массивы массивов)
- •13. Указатели
- •14. Адресная арифметика
- •15. Массивы и указатели
- •15.1. Указатели и одномерные массивы
- •15.2. Указатели и двумерные массивы
- •16. Строки
- •17. Массивы строк
- •18. Функции
- •18.1. Определение функции в языке с
- •18.2. Возвращение значений из функции
- •18.3. Формальные и фактические параметры функции
- •18.4. Вызов функции
- •18.5. Объявление и определение функции: прототип функции
- •19. Передача параметров в функции
- •19.1. Способы передачи параметров в функции
- •19.2. Передача параметров в функции в языке с
- •19.3. Передача указателей в функции
- •20. Классы хранения и видимость переменных
- •20.1. Общие положения
- •20.2. Спецификаторы класса памяти
- •20.3. Область видимости функций
- •20.4. Глобальные переменные
- •20.5. Глобальные статические переменные
- •20.6. Локальные переменные
- •20.7. Статические локальные переменные
- •20.8. Регистровые переменные
- •20.9. Выводы
- •21. Организация памяти программы
- •22. Многофайловая компиляция (проекты)
- •23. Передача в функции массивОв
- •23.1. Передача одномерных массивов в функции
- •23.2. Передача двумерных массивов в функции
- •23.3. Передача в функции символьных строк
- •23.4. Возвращение указателей из функций
- •24. Функции с переменным количеством аргументов
- •24.1. Соглашения о вызовах: модификаторы функций
- •24.2. Объявление списка параметров переменной длины
- •25. Передача параметров в функцию main()
- •26. Указатели на функцию
- •27. Стандартные функцИи языка с
- •27.1. Функции для работы со строками
- •27.2. Функции для проверки символов и преобразования данных
- •27.3. Функция быстрой сортировки – gsort()
- •27.4. Функция двоичного поиска – bsearch()
- •28. Работа с файлами
- •28.1. Основные понятия
- •28.2. Основные функции для работы с файлами
- •28.3. Открытие и закрытие файлов
- •28.4. Ввод/вывод символов
- •28.5. Ввод/вывод строк
- •28.6. Форматированный ввод/вывод
- •28.7. Ввод/вывод блоков данных
- •28.8. Другие средства для работы с файлами
- •28.9. Ввод/вывод низкого уровня (префиксный доступ к файлам)
- •29. Типы, определяемые пользователем: Перечисления, структуры и объединения
- •29.1. Переименование типов – оператор typedef
- •29.2. Перечисления (enum)
- •29.3. Основные сведения о структурах
- •29.4. Структурные переменные в памяти компьютера
- •29.5. Доступ к полям структуры
- •29.6. Массивы структур
- •29.7. Структуры и функции
- •29.8. Объединения (union)
- •30. Динамическая память
- •30.1. Понятие динамического объекта
- •30.2 Создание и уничтожение динамических объектов
- •30.3 Динамическое размещение одномерных массивов и строк
- •30.4 Динамическое размещение двумерных массивов
- •30.5. Функции для работы с блоками памяти
- •31. Динамические структуры данных
- •31.1. Понятие структуры данных
- •31.2. Структуры, ссылающиеся на себя
- •31.3. Связанные списки
- •31.5. Очереди
- •Ниже приводятся примеры функций для очереди (структура элемента очереди совпадает со структурой элемента стека в примере выше):
- •32. Препроцессор языка с
- •32.1 Директива включения файлов
- •32.2. Директива определения макрокоманд (макросов)
- •32.3 Директива условной компиляции
- •32.4 Дополнительные директивы препроцессора
26. Указатели на функцию
С функцией в языке С можно проделать только две вещи: вызвать ее и получить ее адрес. Указатели на функции – очень мощное средство языка С.
Функции для процессора – те же двоичные коды, и с виду они мало отличаются от строк или массивов. Так же, как и массивы, они занимают последовательные ячейки памяти, где хранятся двоичные числа. Разница в том, что эту последовательность ячеек процессор воспринимает не как идущие подряд числа или буквы, а как последовательность команд.
Функция располагается в памяти по определенному адресу, который можно присвоить указателю в качестве его значения. Адресом функции является ее точка входа. Именно этот адрес используется при вызове функции. Так как указатель хранит адрес функции, то она может быть вызвана с помощью этого указателя. Он позволяет также передавать ее другим функциям в качестве аргумента.
Указатель на функцию имеет тип «указатель на функцию, которая возвращает значение заданного типа и имеет аргументы заданного типа»:
тип (*имя)(список типов аргументов);
Например, объявление:
int (*pfunc)(int, int);
задает указатель с именем pfunc на функцию, возвращающую значение типа int и имеющую два аргумента типа int.
Указателю на функцию, как и любому указателю, нужно перед использованием придать разумное значение. В программе на С адресом функции служит ее имя без скобок и аргументов (это похоже на адрес массива, который равен имени массива без индексов).
int sum(int a,int b){
return a+b;
}
void main(){
int (*f)(int, int); // описание указателя на функцию
int (*ff)(int, int); // описание указателя на функцию
int k;
k = sum(5,9); // обычный вызов функции sum()
f = ∑ // можно так занести в указатель адрес функции sum(),
ff = sum; // а можно и так занести в указатель адрес функции sum()
k = (*f)(4,1); // можно так вызвать функцию через указатель,
k = ff(11,22); // а можно и так вызвать функцию через указатель
}
Обратить внимание! При присваивании тип функции и тип указателя на функцию должны в точности совпадать.
Вывод: Имя функции и указатель на нее практически равноправны. Разница между ними в том, что имя функции нельзя отделить от нее самой, оно всегда связано с постоянным адресом. Указатель же более свободен, и его можно направить куда угодно.
Какая польза от вызова функции с помощью указателя на функцию? Ведь вроде бы никаких преимуществ не достигнуто, этим только усложняется программа. Тем не менее, во многих случаях оказывается более выгодным передать имя функции как параметр или даже создать массив функций.
Например, в программе обработки меню часто вызываются различные функции для разных пунктов меню. В таких случаях чаще всего используют оператор switch с длинным списком меток case. Другой подход – создать массив функций и вызывать их с помощью индексов – делает программу менее громоздкой и, соответственно, менее подверженной ошибкам.
Задача. Организовать обработку пунктов меню с помощью оператора switch.
#include <stdio.h>
#include <stdlib.h> // для функции exit()
#include <bios.h>
void f0() { // обработка пункта меню 0 – выход из программы
printf("menu 0\n");
exit(0); // завершить работу программы
}
void f1() { // обработка пункта меню 1
printf("menu 1\n");
}
void f2() { // обработка пункта меню 2
printf("menu 2\n");
}
void f3() { // обработка пункта меню 3
printf("menu 3\n");
}
void main() {
int i;
while(1) { // цикл по обработке пунктов меню
printf("Введите номер пункта меню : ");
scanf("%d",&i);
switch(i) {
case 0: f0(); break;
case 1: f1(); break;
case 2: f2(); break;
case 3: f3();
}
bioskey(0);
}
}
Задача. Организовать обработку пунктов меню с помощью массива указателей на функции.
void f0() {
printf("menu 0\n");
exit(0);
}
void f1() {
printf("menu 1\n");
}
void f2() {
printf("menu 2\n");
}
void f3() {
printf("menu 3\n");
}
void main() {
int i;
// описание массива указателей на функции и его инициализация
void (*ff[4])(void) = {f0, f1, f2, f3};
// описание массива указателей на функции и занесение в него адресов функций
// void (*ff[4])(void);
// ff[0] = f0;
// ff[1] = f1;
// ff[2] = f2;
// ff[3] = f3;
while(1) {
printf("Введите номер пункта меню : ");
scanf("%d",&i);
ff[i](); // вызов функции, адрес которой занесен в i-ый элемент массива
bioskey(0);
}
}
Задача. Пример передачи адреса функции как параметра в функцию.
void f1() {
printf("Функция 1\n");
}
void f2() {
printf("Функция 2\n");
}
void fp(void (*ptrF)(void)) {
ptrF(); // вызов функции, адрес которой передан как параметр
}
void main() {
void (*f)(void);
fp(f1);
f = f2;
fp(f);
}