Подпрограммы (функции)
При решении новых задач можно попытаться воспользоваться ранее написанными программами. Алгоритм, ранее разработанный и целиком используемый в составе других алгоритмов, называется вспомогательным . Применение вспомогательных алгоритмов позволяет разбить задачу на части, структурировать ее.
Вся программа условно может быть разделена на две части: основную и вспомогательную. В основной части производится простейшая обработка информации, организуется обращение к разным вспомогательным модулям (подпрограммам) .
Вспомогательный алгоритм тоже может вызывать другие вспомогательные, длина такой цепочки вызовов теоретически не ограничена. Здесь и далее следующие пары слов используются как синонимы: алгоритм и программа, вспомогательный алгоритм и подпрограмма, команда и оператор, программа и модуль. Вспомогательными и основными алгоритмы являются не сами по себе, а по отношению друг к другу.
При использовании вспомогательных алгоритмов необходимо учитывать способ передачи значений исходных данных для них и получения результата от них. Аргументы вспомогательного алгоритма — это переменные, в которых должны быть помещены исходные данные для решения соответствующей подзадачи. Результаты вспомогательного алгоритма — это также переменные, где содержаться результаты решения этих подзадач, а также результатом может быть конкретное действие, которое совершает компьютер под действием подпрограммы.
Подпрограммы могут быть двух видов: подпрограмма без параметров и подпрограмма с параметрами. Обращение к подпрограмме может быть организовано из любого места основной программы или другой подпрограммы сколько угодно раз.
При работе с подпрограммами важными являются понятия формальных и фактических параметров . Формальные параметры это идентификаторы входных данных для подпрограммы. Если формальные параметры получают конкретные значения, то они называются фактическими . Формальные параметры могут получить конкретные значения только в той программе, где производится обращение к данному модулю-подпрограмме. Тип и порядок записи фактических параметров должны быть такими же, как и формальных параметров. В противном случае результат работы программы будет непредсказуемым. Из этого следует, что фактические параметры используются при обращении к подпрограмме из основной, а формальные параметры только в самом модуле.
Подпрограмма с параметрами используется для записи многократно повторяющихся действий при разных исходных данных.
При составлении подпрограмм с параметрами надо соблюдать следующие правила:
1) каждая подпрограмма имеет свое имя и список формальных параметров;
2) процедура из основной программы вызывается командой вызова, которая по форме ничем не отличается от вызова команды исполнителя. Результат присваивается одной или нескольким переменным, которые находятся в списке формальных параметров. Но результатом могут быть, конечно, не только значения переменных, но какое либо действие, выполненное ЭВМ.
Пример 1. Используем алгоритм нахождения наибольшего общего делителя двух натуральных чисел в качестве вспомогательного при решении задачи: составить программу вычитания дробей ( a , b , c , d натуральные числа). Результат представить в виде обыкновенной несократимой дроби.
- Ввести натуральные числа M, N.
- Если M=N, перейти к п. 5, иначе к следующему пункту.
- Если M>N, то M:=M-N, иначе N:=N-M.
- Перейти к п. 2.
- Передать значение M в основную программу.
- Конец подпрограммы.
- Ввести значения A, B, C, D.
- E:=A*D — B*C.
- F:= B*D.
- Если E=0, вывести значение E и перейти к п. 9, иначе перейти к следующему пункту.
- M:=|E|, N:=F, перейти к подпрограмме вычисления НОД.
- G := M.
- E и F нацело разделить на G.
- Вывести значения E и F на печать.
- Конец программы.
// программа НОД #include #include void Nod(int m, int n, int &k); void main() < int a, b, c, d, g, e, f; cout > a >> b >> c >> d; e = a * d - b * c; f = b * d; if (e==0) cout > void Nod(int m, int n, int &k) < while (m != n) if (m >n) m -= n; else n -= m; k = m; >
Как видно из примера, объявление подпрограммы-функции находится в разделе описаний прототипов функций, а реализация после основной функции main. В заголовке подпрограммы содержится список формальных параметров с указанием их типа, которые условно можно разделить на входные и выходные (перед ними стоит &). Вообще при обращении к функции со списком параметров без &, внутри функции используются копии параметров, которые после выполнения удаляются. Знак & указывает компилятору что необходимо использовать саму переменную, а не ее копию. При обращении к функции указывается ее имя и список фактических параметров. Формальные и фактические параметры должны соответствовать по количеству и по типу.
Описание функции в С++ осуществляется следующим образом:
тип_возвращаемого_значения ();
void Nod(int e, int f, int &k); int f1(float a); long f2();
Функция всегда возвращает единственное значение. Как видно из примера 1, мы использовали тип void в качестве возращаемого типа. Т.е. указали компилятору, что наша функция не возвращает никакого значения.
Покажем, как изменится подпрограмма из примера, если ее записать в виде функции, возвращающей само значение НОД (без использования возвращаемой переменной).
int Nod(int m, int n) < while (m!=n) if (m >n) m -=n; else n -= m; return (n); >
Итак, в теле функции хотя бы один раз встречается команда return, которая указывает, какое значение вернуть в качестве значения функции.
Вызов функции в основной будет следующим:
g = Nod(fabs(e), f);
Вообще, вызов функции может присутствовать в выражении, стоящем: в правой части операции присваивания, в операторе вывода, в качестве фактического параметра в вызове другой подпрограммы и т.д.
При решении задач целесообразно проанализировать условие, записать решение в крупных блоках (не являющихся операторами C++), детализировать каждый из блоков (записав в виде блоков, возможно, по-прежнему не операторов C++), и т.д., продолжать до тех пор, пока каждый из блоков не будет реализован с помощью операторов языка.
Пример 2. Дано натуральное число n . Переставить местами первую и последнюю цифры этого числа.
void main() < int n; cout > n; if (Impossible(n)) cout >
Можно заметить, что необходимо детализировать логическую функцию Impossible, которая диагностирует, возможна ли перестановка, и функцию Change, которая эту перестановку (в случае, если она возможна) выполняет.
int Impossible(int n) < if(Number(n)<5) return (0); else return ((n % 10 >= 3)&&((((n % 10000)/10)*10 + n / 10000)> 32768 % 10000)); >
Здесь необходимо детализировать функцию Number, возвращающую количество цифр в записи натурального числа (т.к. функция Impossible содержит ее вызов, то в разделе описаний прототипов функция Number должна ей предшествовать).
int Number(int n) < int vsp = 0; while (n >0) < vsp++; n = n / 10; >return (vsp); >
Наконец, последняя функция.
void Change(int &n) < int kol = Number(n); int p = n % 10; if(kol>1) s = n / pow(10, kol - 1); else s = 0; int r = (n % pow(10, kol - 1)) /10; n = p * pow(10, kol - 1) + r*10 + s; >
Возможны также подпрограммы, которые вызывают сами себя. Они называются рекурсивными . Создание таких подпрограмм является красивым приемом программирования, но не всегда целесообразно из-за чрезмерного расхода памяти ЭВМ.
Пример 3. Найти максимальную цифру в записи данного натурального числа.
// программа максимальная цифра #include int Maximum(long n); void main() < long A; cout > A; cout int Maximum(long n); < if (n < 10) return(n); else if ((n % 10) >Maximum (n / 10)) return (n % 10); else return (Maximum(n / 10)) >
- Какие алгоритмы называют вспомогательными?
- Какое количество вспомогательных алгоритмов может присутствовать в основном алгоритме?
- Можно ли вспомогательные алгоритмы, написанные для решения данной задачи, использовать при решении других задач, где их применение было бы целесообразно?
- Какие параметры называют формальными? фактическими?
- Какое соответствие должно соблюдаться между формальными и фактическими параметрами?
- Может ли фактических параметров процедуры (функции) быть больше, чем формальных? А меньше?
- Существуют ли подпрограммы без параметров?
- Существуют ли ограничения на число параметров подпрограмм? Если нет, то чем же всё-таки ограничивается это количество в С++?
- В каком разделе объявляются и в каком реализуются подпрограммы в С++?
- Какого типа может быть значение функции?
- Расскажите о методе последовательной детализации при разработке программ.
- Какие подпрограммы называют рекурсивными?
- Что такое граничное условие при организации рекурсивной подпрограммы?
© Шестаков А.П., 2000-2007
Подпрограмма
Язык G- и М-кодов, как и любой другой язык программирования, позволяет работать с подпрограммами и совершать переходы. Посредством функции подпрограммы основная (главная) управляющая программа может вызывать из памяти другую программу (подпрограмму) и выполнить ее определенное число раз. Если УП содержит часто повторяемое действие или работает по определенному шаблону, то использование подпрограмм позволяет упростить программу обработки и сделать ее гораздо меньшей в размере.
Оглавление
- Основы числового программного управления
- Автоматическое управление
- Особенности устройства и конструкции фрезерного станка с ЧПУ
- Функциональные составляющие (подсистемы) ЧПУ
- Языки для программирования обработки
- Процесс фрезерования
- Режущий инструмент
- Вспомогательный инструмент
- Основные определения и формулы
- Рекомендации по фрезерованию
- Прямоугольная система координат
- Написание простой управляющей программы
- Создание УП на персональном компьютере
- Передача управляющей программы на станок
- Проверка управляющей программы на станке
- Советы по технике безопасности при эксплуатации станков с ЧПУ
- Нулевая точка станка и направления перемещений
- Нулевая точка программы и рабочая система координат
- Компенсация длины инструмента
- Абсолютные и относительные координаты
- Комментарии в УП и карта наладки
- G- и М-коды
- Структура программы
- Слово данных, адрес и число
- Модальные и немодальные коды
- Формат программы
- Строка безопасности
- Ускоренное перемещение – G00
- Линейная интерполяция – G01
- Круговая интерполяция – G02 и G03
- Введение
- Останов выполнения управляющей программы – М00 и М01
- Управление вращением шпинделя – М03, М04, М05
- Управление подачей СОЖ – М07, М08, М09
- Автоматическая смена инструмента – М06
- Завершение программы – М30 и М02
- Основные принципы
- Использование автоматической коррекции на радиус инструмента
- Активация, подвод и отвод
- Подпрограмма
- Работа с осью вращения (4-ой координатой)
- Параметрическое программирование
- Методы программирования
- Что такое CAD и САМ?
- Общая схема работы с CAD/САМ-системой
- Виды моделирования
- Уровни САМ-системы
- Геометрия и траектория
- Алгоритм работы в САМ-системе и постпроцессор
- Ассоциативность
- Пятикоординатное фрезерование и ЗD-коррекция
- Высокоскоростная (ВСО) и высокопроизводительная обработка
- Критерии для оценки, сравнения и выбора CAM-систем
© 2015-2022 Планета CAM
Информационно-аналитический электронный журнал «Планета CAM» (12+)
Адрес редакции: 192102, г. Санкт-Петербург, ул. Фучика д. 4, литер К
Главный редактор: Ловыгин А. А.
Контактная информация: Тел. +7 (812) 407-14-04, Email: mail@planetacam.ruИспользование подпрограмм в языке программирования С-51*
В отличие от других языков программирования высокого уровня в языке С нет деления на основную программу, процедуры и функции. В этом языке вся программа строится только из функций. Мощность языка С во многом определяется легкостью и гибкостью объявления и реализации функций. Функция — это совокупность объявлений и операторов, обычно предназначенная для решения определенной задачи. Термин функция в языке программирования С эквивалентен понятию подпрограммы. Действия, выполняемые основной программой в других языках программирования такие как очистка внутреннего ОЗУ и присваивание начального значения переменным, выполняются автоматически при включении питания. После завершения этих действий вызывается подпрограмма с именем main. Каждая функция должна иметь имя, которое используется для ее объявления, определения и вызова. В любой программе, написанной на языке программирования С-51 должна быть функция с именем main (главная функция), именно с этой функции, в каком бы месте программы она не находилась, начинается выполнение программы. Обратите внимание, что на языке программирования С-51 пишутся программы для микроконтроллеров. Поэтому эти программы не должны завершаться пока включено питание микроконтроллера. Это значит, что в функции main обязательно должен быть бесконечный цикл. В противном случае при выходе из этой функции управление будет передано случайному адресу памяти программ. Это может привести к непредсказуемым результатам, вплоть до выхода микроконтроллерной системы из строя. При вызове функции ей при помощи аргументов (формальных параметров) могут быть переданы некоторые значения (фактические параметры), используемые во время выполнения функции. Функция может возвращать некоторое, но только одно, значение. Это возвращаемое значение и есть результат выполнения функции, который при выполнении программы подставляется в выражение, из которого производился вызов функции. Допускается также использовать функции не имеющие аргументов и функции не возвращающие никаких значений. Действие таких функций может состоять, например, в изменении значений некоторых переменных, выводе на экран жидкокристаллического индикатора текстовых надписей и т.п.. Иными словами можно сказать, что такая функция работает подобно подпрограмме-процедуре. С использованием функций в языке С-51 связаны три понятия — определение функции (описание действий, выполняемых подпрограммой-функцией), объявление функции (задание формы обращения к функции) и вызов функции.
Определение подпрограмм
Определение функции состоит из заголовка и тела. Определение функции записывается в следующем виде:
[спецификатор класса памяти] [спецификатор типа] имя функции ([список формальных параметров]) //Заголовок функции < //тело функции >
Заголовок функции задает тип возвращаемого значения, имя функции, типы и число формальных параметров. Тело функции — это составной оператор, содержащий операторы, определяющие действие функции. Тело функции начинается с фигурной скобки ‘<' и состоит из объявления переменных и исполняемых операторов. Именно эти операторы, входящие в тело функции, и определяют действие функции. Завершается тело функции закрывающей фигурной скобкой '>‘. Пример определения функции:'>
bit SostKnIzm(void)//Заголовок функции //---------------начало тела функции-------------- bit tmp=0; if(P0!=0xff)tmp=1; return tmp; >//---------------конец тела функции----------------- //================== Вызывающая подпрограмма ========== . if(SostKnIzm()) //Вызов подпрограммы SostKnIzm DecodSostKn();
В приведенном примере показано как при помощи функции, возвращающей битовую переменную можно повысить наглядность исходного текста программы. Оператор if (SostKnIzm()) DecodSostKn(); практически не требует комментариев. Имя функции SostKnIzm показывает что контролирует эта функция. Необязательный спецификатор класса памяти задает класс памяти функции, который может быть static или extern. При использовании спецификатора класса памяти static функция становится невидимой из других файлов программного проекта, то есть информация об этой функции не помещается в объектный файл. Использование спецификатора класса памяти static может быть полезно для того, чтобы имя этой функции могло быть использовано в других файлах программного проекта для реализации совершенно других задач. Если функция, объявленная с спецификатором класса памяти static, ни разу не вызывалась в данном файле, то она вообще не транслируется компилятором и не занимает места в программной памяти микроконтроллера, а программа-компилятор языка программирования С-51 выдает предупреждение об этом. Это свойство может быть полезным при отладке программ и программных модулей. Использование спецификатора класса памяти extern используется для связывания подпрограмм, находящихся в других модулях (и возможно написанных на других языках программирования) с вызовом подпрограммы из данного программного модуля. В основном использование этого спецификатора класса памяти эквивалентно предварительному объявлению функции, которое будет рассмотрено далее. Если спецификатор класса памяти функции не указан, то подразумевается класс памяти extern, то есть по умолчанию все подпрограммы считаются глобальными и доступными из всех файлов программного проекта. Спецификатор типа функции задает тип возвращаемого значения и может задавать любой тип. Если спецификатор типа не задан, то по умолчанию предполагается, что функция возвращает значение типа int. Функция не может возвращать массив или функцию, но может возвращать указатель на любой тип, в том числе и на массив и на функцию. Тип возвращаемого значения, задаваемый в определении функции, должен соответствовать типу в объявлении этой функции. Функция возвращает значение если ее выполнение заканчивается оператором return, содержащим некоторое выражение. Указанное выражение вычисляется, преобразуется, если необходимо, к типу возвращаемого значения и возвращается в точку вызова функции в качестве результата. Пример определения подпрограммы-функции:
#include //Подключить описания внутренних регистров микроконтроллера char getkey () //Заголовок функции, возвращающей байт, принятый по последовательному порту while (!RI); //Если последовательный порт принял байт, RI = 0; //то подготовиться к приёму следующего байта return (SBUF); //и передать принятый байт в вызывающую подпрограмму. >
В операторе return возвращаемое значение может записываться как в скобках, так и без них. Если функция определена как функция, возвращающая некоторое значение (подпрограмма-функция), а в операторе return при выходе из нее отсутствует выражение, то это может привести к непредсказуемым результатам. Для функций, не использующих возвращаемое значение (подпрограмм-процедур), должен быть использован тип void, указывающий на отсутствие возвращаемого значения. Если оператор return не содержит выражения или выполнение функции завершается после выполнения последнего ее оператора (без выполнения оператора return), то возвращаемое значение не определено.
void putchar (char c) //Заголовок функции, передающей один байт через последовательный порт while (!TI); //Если передатчик последовательного порта готов к передаче байта SBUF = c; //то занести в буфер передатчика последовательного порта байт TI = 0; //и начать передачу >
Все переменные, объявленные в теле функции без указания класса памяти, имеют класс памяти auto, т.е. они являются локальными. Так как глубина стека в процессорах семейства MCS-51 ограничена 256 байтами, то при вызове функций аргументам назначаются конкретные адреса во внутренней памяти микроконтроллера и производится их инициализация. Управление передается первому оператору тела функции и начинается выполнение функции, которое продолжается до тех пор, пока не встретится оператор return или последний оператор тела функции. Управление при этом возвращается в точку, следующую за точкой вызова, а локальные переменные становятся недоступными. При выходе из функции значения этих переменных теряются, так как при вызове других функций эти же ячейки памяти распределяются для их локальных переменных. Если необходимо, чтобы переменная, объявленная внутри подпрограммы сохраняла своё значение при следующем вызове подпрограммы, то ее необходимо объявить с классом памяти static. Параметры подпрограмм. Список формальных параметров — это последовательность объявлений формальных параметров, разделенная запятыми. Формальные параметры — это переменные, используемые внутри тела функции и получающие значение при вызове функции путем копирования в них значений соответствующих фактических параметров. Пример определения функции с одним параметром:
int rus (unsigned char r) //Заголовок функции //---------------начало тела функции-------------- if (r>='А' && creturn 1; else return 0; >//---------------конец тела функции-----------------
В данном примере определена функция с именем rus, имеющая один параметр с именем r и типом unsigned char. Функция возвращает целое значение, равное 1, если параметр функции является буквой русского алфавита, или 0 в противном случае. Если функция не использует параметров, то наличие круглых скобок обязательно, а вместо списка параметров рекомендуется указать слово void. Например:
void main(void) //Зажигание светодиода while(1); //Бесконечный цикл >
Порядок и типы формальных параметров должны быть одинаковыми в определении функции и во всех ее объявлениях. Поэтому желательно объявление функции поместить в отдельный файл, который затем можно включить в исходные тексты программных модулей при помощи директивы #include. Типы фактических параметров при вызове функции должны быть совместимы с типами соответствующих формальных параметров. Тип формального параметра может быть любым основным типом, структурой, объединением, перечислением, указателем или массивом. Если тип формального параметра не указан, то этому параметру присваивается тип int. Для формального параметра можно задавать класс памяти register, при этом для величин типа int спецификатор типа можно опустить. Однако все известные мне компиляторы языка программирования С-51 игнорируют этот спецификатор, так как расположение параметров в памяти микроконтроллера оптимизируется с точки зрения использования минимального количества необходимой внутренней памяти. По умолчанию компиляторы стараются передавать параметры в функцию через регистры, тем самым максимально сохраняя внутреннюю память микроконтроллеров. Номера регистров, используемые для передачи параметров в функцию в зависимости от типа аргументов приведены в таблице 1 Таблица 1
Номер аргумента char, однобайтовый указатель int, двухбайтовый указатель long,float Нетипизированные указатели 1 R7 R6,R7 R4 — R7 R1 — R3 2 R5 R4,R5 R4 — R7 R1 — R3 3 R3 R2,R3 R1 — R3 Поскольку при вызове функции значения фактических параметров копируются в локальные переменные, в теле функции нельзя изменить значения переменных в вызывающей функции. Например нужно поменять местами значения переменных x и y:
/* Неправильное использование параметров функции */ void change (int x, int y) int k=x; x=y; y=k; >
В данной функции значения локальных переменных x и y, являющихся формальными параметрами, меняются местами, но поскольку эти переменные существуют только внутри функции change, значения фактических параметров, используемых при вызове функции, останутся неизменными. Однако, если в качестве параметра функции использовать указатель на переменную, то можно изменить значение переменной, адрес которой будет содержаться в указателе. Для того чтобы менялись местами значения фактических аргументов можно использовать функцию приведенную в следующем примере:
/* Правильное использование параметров функции */ void change (int *x, int *y) int k=*x; *x=*y; *y=k; >
При вызове такой функции в качестве фактических параметров должны быть использованы не значения переменных, а их адреса:
change (&a,&b);
Предварительное объявление подпрограмм.
В языке С-51 нет требования, чтобы определение функции обязательно предшествовало ее вызову. Определения используемых функций могут следовать за определением функции main, перед ним, или находится в другом файле. Однако для того, чтобы компилятор мог осуществить проверку соответствия типов передаваемых фактических параметров типам формальных параметров и, если необходимо, выполнил соответствующие преобразования, до вызова функции нужно поместить объявление (прототип) функции. Если объявление функции не задано, то по умолчанию строится прототип функции на основе анализа первого вызова функции. Тип возвращаемого значения создаваемого прототипа int, а список типов и числа параметров функции формируется на основании типов и числа фактических параметров используемых при данном вызове. Однако такой прототип не всегда согласуется с последующим определением функции. При размещении функции в другом файле или после оператора ее вызова рекомендуется задавать прототип функции. Это позволит компилятору либо выдавать диагностические сообщения, при неправильном использовании функции, либо правильным образом преобразовывать типы аргументов при её вызове. Прототип — это явное объявление функции, которое предшествует определению функции. Тип возвращаемого значения при объявлении функции должен соответствовать типу возвращаемого значения в определении функции. Если прототип задан с классом памяти static, то и определение функции должно иметь класс памяти static. Объявление (прототип) функции имеет следующий формат: [спецификатор класса памяти] [спецификатор типа] имя функции ([список формальных параметров]); В отличие от определения функции, в прототипе за заголовком сразу же следует точка с запятой, а тело функции отсутствует. Правила использования остальных элементов формата такие же, как и при определении функции. Для функции, определенной в предыдущем параграфе, прототип может быть записан в следующем виде:
int rus (unsigned char r);
При этом в объявлении функции имена формальных параметров могут быть опущены:
int rus (unsigned char);
Вызов подпрограмм. Вызов функции имеет следующий формат: имя функции ([список выражений]); Список выражений представляет собой список фактических параметров, передаваемых в функцию. Этот список может быть и пустым, если в определении функции отсутствуют параметры, но наличие круглых скобок обязательно. Примеры вызова функции (подпрограммы-процедуры):
DecodSostKn(); VypFunc();
Функция, если она возвращает какое-либо значение (подпрограмма-функция), может быть вызвана и в составе выражения, например:
y=sin(x); //sin - это имя подпрограммы-функции if(rus(c))SvDiod=Gorit; //rus - это имя подпрограммы-функции
- Вычисляются выражения в списке выражений и подвергаются обычным арифметическим преобразованиям. Затем тип полученного фактического аргумента сравнивается с типом соответствующего формального параметра. Если они не совпадают, то либо производится преобразование типов, либо формируется сообщение об ошибке. Число выражений в списке выражений должно совпадать с числом формальных параметров. Если в прототипе функции указано, что ей не требуются параметры, а при вызове они указаны, формируется сообщение об ошибке.
- Происходит присваивание значений фактических параметров соответствующим формальным параметрам.
- Управление передается на первый оператор функции.
Поскольку имя функции является адресом начала тела функции, то в качестве обращения к функции может быть использовано не только имя функции но и значение указателя на функцию. Это значит что функция может быть вызвана через указатель на функцию. Пример объявления указателя на функцию:
int (*fun)(int x, int *y);
Здесь объявлена переменная fun как указатель на функцию с двумя параметрами: типа int и указателем на int. Сама функция должна возвращать значение типа int. Круглые скобки, содержащие имя указателя fun и признак указателя *, обязательны, иначе запись
int *fun (int x,int *y);
будет интерпретироваться как объявление функции fun возвращающей указатель на int.
Вызов функции возможен только после инициализации значения указателя:
float (*funPtr)(int x, int y); float fun2(int k, int l); . funPtr=fun2; /* инициализация указателя на функцию */ (*funPtr)(2,7); /* обращение к функции */
В рассмотренном примере указатель на функцию funPtr описан как указатель на функцию с двумя параметрами, возвращающую значение типа double, и также описана функция fun2. В противном случае, т.е. когда указателю на функцию присваивается функция описанная иначе чем указатель, произойдет ошибка.
Рассмотрим пример использования указателя на функцию в качестве параметра функции вычисляющей производную от функции cos(x).
float proiz(float x,float dx,float(*f)(float x)); float fun(float z); int main() float x; /* точка вычисления производной */ float dx; /* приращение */ float z; /* значение производной */ scanf("%f,%f",&x,&dx); /* ввод значений x и dx */ z=proiz(x,dx,fun); /* вызов функции */ printf("%f",z); /* печать значения производной */ > float proiz(float x,float dx,float (*f)(float z))/* функция вычисляющая производную */ float xk,xk1; xk=fun(x); xk1=fun(x+dx); return (xk1/xk-1e0)*xk/dx; > float fun( float z) /* функция от которой вычисляется производная */ return (cos(z)); >
Для вычисления производной от какой-либо другой функции можно изменить тело функции fun или использовать при вызове функции proiz имя другой функции. В частности, для вычисления производной от функции cos(x) можно вызвать функцию proiz в форме
z=proiz(x,dx,cos);
а для вычисления производной от функции sin(x) в форме
z=proiz(x,dx,sin);
Рекурсивный вызов подпрограмм.
В стандартном языке программирования С все функции могут быть вызваны сами из себя или использоваться различными программными потоками одновременно. Для этого все локальные переменные располагаются в стеке. В микроконтроллерах семейства MCS-51 ресурсы внутренней памяти данных ограничены, поэтому в языке программирования С-51 для функций локальные переменные по умолчанию располагаются не в стеке, а непосредственно во внутренней памяти микроконтроллера. Если же подпрограмма должна вызываться рекурсивно, то ее необходимо объявить как программу с повторным вызовом (reentrant):
return_type funcname ([args]) reentrant
Классический пример рекурсии — это математическое определение факториала n!:
n! = 1 при n=0; n*(n-1)! при n>1 .
Функция, вычисляющая факториал, будет иметь следующий вид:
long fakt(int n) reentrant return ((n==1)?1:n*fakt(n-1)); >
Подпрограммы обработки прерываний.
Атрибут INTERRUPT позволяет объявить подпрограмму-процедуру обработки сигналов прерываний, поступающих от внешних устройств. Подпрограмма процедура с этим атрибутом вызывается при получении микроконтроллером соответствующего сигнала прерывания. Подпрограмма обработки прерываний не может быть подпрограммой функцией и не может иметь переменные-параметры. Формат использования атрибута:
interrupt N;
где N-любое десятичное число от 0 до 31.
Число N определяет номер обрабатываемого прерывания. При этом номер 0 соответствует внешнему прерыванию от ножки INT0, номер 1 соответствует прерыванию от таймера 0, номер 2 соответствует внешнему прерыванию от ножки INT1 и так далее. Пример подпрограммы-обработчика прерывания от таймера 0:
void IntTim0(void) interrupt 2 //Задать новый интервал времени таймера T0 TF=0; //Сбросить флаг таймера T0 для разрешения следующего прерывания от данного таймера >
При работе с прерываниями определяющим фактором является время реакции на прерывание. Для того, чтобы не сохранять содержимое используемых регистров микроконтроллера в стеке, в микроконтроллерах предусмотрено использование отдельных регистровых банков. В языке программирования С-51 для этого необходимо в объявлении подпрограммы указать используемый ею банк регистров. Для этого служить атрибут using:
void IntTim0(void) interrupt 2 using 1 //Задать новый интервал времени таймера T0 TF=0; //Сбросить флаг таймера T0 для разрешения следующего прерывания от данного таймера >
* Для читателей, которые вышли на эту страницу по поиску, прошу обратить внимание, что здесь рассматривается не стандартный язык программирования С, а язык, адаптированный к микроконтроллерам серии MCS-51. Имеются отличия!
Что такое подпрограмма в c
Когда алгоритм решаемой задачи несложен, и размер программы невелик, то она обычно пишется как одно целое. То есть текст программы состоит только из основной части — главной программы (на языке С++ — это функция main() ). С ростом размера программы появляется необходимость разбивать решаемую задачу на подзадачи и оформлять их в виде отдельных частей (подпрограмм). В этом случае основная программа служит только для вызова этих подпрограмм.
Подпрограмма — это часть всей программы, оформленная особым образом. Как правило, в виде подпрограммы записывается какая-то логически завершённая часть программы. Активное использование подпрограмм при разработке программного обеспечения составляет основу модульного программирования.
Модульное программирование — такая технология программирования, когда алгоритм всей решаемой задачи разбивается на отдельные, логически завершенные части. Если эти части сложны для кодирования, то они снова разбиваются на более простые части. Этот процесс продолжается до тех пор, пока не получатся простые для программирования алгоритмы, предназначенные для реализации несложных действий. Основные принципы модульного программирования были сформулированы ещё в 60-х годах 20-го века.
Главное отличие подпрограммы от основной (главной) программы заключается в том, что управление может быть передано только главной программе . Подпрограмма может быть откомпилирована, но не может быть запущена на исполнение .
Преимущества от использования подпрограмм :
Возможность создания достаточно больших программ (ограничение — порядка 50000 строк. Разработка программ практически неограниченного размера требует применения классов. Речь о них пойдёт дальше).
Достаточно просто повторно использовать ранее написанный код.
Разработку отдельных частей программы, т.е. подпрограмм, можно поручить разным людям.
Сокращается срок разработки программы в целом за счёт повторного использования кода и благодаря возможности привлечения к программному проекту целой группы программистов.
Повышается надёжность программы, потому что подпрограммы, как правило, не велики, их можно досконально изучить, к тому же повторное использование в новых программах подпрограмм, которые уже применялись ранее и не давали «сбоев», повышает надёжность новой программы в целом.
В ряде случаев уменьшается размер программы: если, к примеру, для сортировки пяти массивов, что использованы в программе, применена одна и та же подпрограмма, а не пишется практически один и тот же код для каждого массива по отдельности, то экономия будет существенной. Конечно, если подпрограмма используется в программе только один раз, то размер программы в целом не только не сокращается, а наоборот, возрастает.
Недостатки от применения подпрограмм :
Использование подпрограмм всегда уменьшает скорость работы программы. Это становится заметным, когда размер подпрограммы слишком мал, например, один-два оператора.
Размер исходного кода и исполняемого модуля не всегда, но часто возрастает при использовании подпрограмм.
Грамотное применение подпрограмм требует более высокой квалификации от программиста, чем работа без подпрограмм.
Виды подпрограмм
Существует две категории подпрограмм: процедуры и функции.
Процедура — это подпрограмма, которая не возвращает через своё имя результата работы, поэтому она вызывается как отдельный оператор. Процедура «общается с внешним миром» через список параметров. Часть из них будут входными, часть — выходными, т.е. результатом работы.
Функция — это подпрограмма, которая через имя возвращает результат своей работы. Функция вызывается в выражении, а не как отдельный оператор. Через список параметров она может получить входные данные и вернуть результаты работы.
Традиционно подпрограмму оформляют в виде функции, если результатом работы является одиночный объект (число, символ, строка). Пример алгоритмов, которые удобно оформить функцией: вычисление значения определенного интеграла, нахождение минимального числа в массиве, подсчет количества пробелов в строке и т.д.
Если результатом работы подпрограммы является несколько объектов, или выходным данным является массив, то необходимо использовать процедуру, так как через имя подпрограммы можно вернуть только один простой объект (число, адрес, символ), а через список параметров — любое количество и одиночных и составных объектов. Возврат результата одновременно и через имя, и через список параметров плох, так как вводит пользователя подпрограммы в некоторое заблуждение относительно того, что может изменить подпрограмма.
Функции на языке С++
Формально в языках С/С++ нет процедур, а имеются только функции. Если же необходима именно процедура, то достаточно создать функцию, возвращающую тип void .
Рассмотрим каким образом применить свою функцию в программе на языке С++. Чтобы использовать в программе свою собственную функцию, необходимо выполнить три действия:
задать прототип функции;
вызвать функцию в необходимом месте, например, в функции main()
дать определение функции.
Разберёмся немного подробнее с тем, что мы перечислили.
Прототип функции — это заголовок функции, который заканчивается точкой с запятой. Прототипы всех функций, используемых в программе, обычно записывают в начале текста программы до определения функций. Прототип необходим для того, чтобы у компилятора была полная информация о типе результата работы функции и о списке параметров, передаваемых в функцию. Когда в тексте программы встретится обращение к функции, то компилятор проверяет правильность её вызова, сверяясь с информацией из прототипа.
Для программиста прототипы тоже полезны: можно в сжатой форме сразу увидеть, какие функции используются в программе, какие параметры им необходимы и т.д.
Вызов функции возможен в любой функции, где это потребуется. Если тип результата функции — void , то вызов записывается отдельным оператором, в остальных случаях — в выражении того же типа, что и тип результата функции. В прочем, языки С/С++ позволяют записать вызов функции в виде отдельного оператора даже в том случае, когда она возвращает тип результата, отличного от void . К примеру, оператор
совершенно законен, хотя вряд ли стоит так делать.
Определение функции состоит из её заголовка и тела, записанного в виде блока. Допустимо записывать определение функций в любой последовательности. Нельзя определять функцию внутри другой функции (С/С++ — это не Паскаль!).
Теперь на конкретном примере рассмотрим применение своей функции.
Пример . Найти максимум из двух чисел. Алгоритм поиска максимума оформить в виде функции.
Возможный вариант программы:
using namespace std;
double fmax(double a, double b);
double a = 4, b = 3, max;
double fmax(double x, double y)
Формальные и фактические параметры
В работе с подпрограммами приходится иметь дело с двумя видами параметров: список фактических параметров и список формальных параметров.
Список параметров (фактических или формальных — всё равно) записывается в круглых скобках сразу за именем функции. В принципе, список параметров может быть пустым, но скобки за именем функции обязательны !
Список фактических параметров — это те реальные данные, с помощью которых можно настроить алгоритм подпрограммы на обработку конкретных данных. Фактические параметры указываются в списке параметров при вызове функции и передаются в эту функцию. В нашем примере при вызове функции fmax() в списке фактических параметров указаны две переменные: a и b . Это реальные объекты, под которые в функции main() выделена память, они могут иметь какие-то значения и т.д.
Список формальных параметров — это по сути набор требований, которые предъявляет подпрограмма к передаваемым в неё данным. Список фактических параметров записывается в заголовке функции при её определении в скобках. Для каждого параметра необходимо указать тип и имя. При необходимости задаётся и способ передачи (об этом позже — в следующей теме). Имена формальных параметров локализованы в подпрограмме и дополняют перечень локальных объектов этой подпрограммы. Так, в нашей функции fmax() формальные параметры x и y типа double дополняют список локальных переменных, состоящий из одной переменной max типа double . В итоге функция fmax() имеет три локальных переменных, известных только в этой функции.
Между списками фактических и формальных параметров должно выдерживаться полное соответствие по количеству параметров, их типу, порядку следования и способу передачи в функцию . Несоблюдение этих требований в лучшем случае (для программиста) приводит к ошибке на стадии компиляции, в худшем — к «багам» программы, которые будут время от времени проявляться во время выполнения программы. «Выловить» же такого рода ошибки очень непросто. Как правило, они остаются в программе в течение всего периода её использования. Поэтому исключительно важно соблюдать правильность передачи данных в подпрограмму.