Функции в языке СИ Всякая программа на языке Си представляет собой совокупность функций, выполняющих основную работу по реализации некоторого алгоритма.

Презентация:



Advertisements
Похожие презентации
Практическое занятие 6. Функции. Большинство языков программирования используют понятия функции и процедуры. C++ формально не поддерживает понятие процедуры,
Advertisements

Общая структура программы на языке Си Всякая программа на языке Си представляет собой совокупность одной или более функций, каждая из которых есть независимый.
Основы информатики Лекция. Функции Заикин Олег Сергеевич
Идентификаторами в языке Си являются последовательности букв и цифр, начинающиеся с буквы, причем символ подчеркивания рассматривается компилятором как.
Работа с файлами Сазонов Д.О. ПМиЭММ Часть 2. Тема занятия: Работа с файлами через потоки Для реализации файлового ввода/вывода, необходимо включить в.
Лекция 8 Область видимости Время жизни. Область видимости Область видимости – характеристика именованного объекта Область видимости - часть текста программы,
ПРОЦЕДУРЫ И ФУНКЦИИ CPascal Подпрограмма – группа операторов реализующая законченный алгоритм и оформленная как самостоятельная синтаксическая единица.
Функции Функция – именованная последовательность описаний и операторов, выполняющая некоторое действие. Может иметь параметры и возвращать значение. Функция.
Лекция 9 Функции. Массивы-параметры функции Передача массива в функцию Пример: void array_enter(int a[], int size) { int i; for (i = 0; i < size; i++)
Часть 1: «Основы программирования». Содержание Основные понятия. Структура программы. Ввод-вывод Программирование циклов. Операторы цикла while, for и.
Процедуры и функции Вербицкая Ольга Владимировна, Заозерная школа 16.
Информационные технологии Классы памяти auto static extern register Автоматические переменные создаются при входе в функцию и уничтожаются при.
Основы информатики Классы Заикин Олег Сергеевич zaikin.all24.org
Обработка исключительных ситуаций Исключительная ситуация (исключение) – это ошибка, возникающая во время выполнения программы. Например, ошибка работы.
Переменные и операторы УРОК 2. Переменные ПЕРЕМЕННАЯ – ?... контейнер для хранения данных. Переменная имеет имя – это….? последовательность букв, цифр.
Процедуры и функции. Разработал учитель информатики МБОУ СОШ 50 г. Краснодара Ракута Елизавета Григорьевна « Учиться и, когда придет время, прикладывать.
Преобразования типов В языке C/C++ имеется несколько операций преобразования типов. Они используются в случае, если переменная одного типа должна рассматриваться.
Лекция 1 Классификация С++. Парадигмы программирования Императивная Функциональная Декларативная (логическая) Инструкция 1 Инструкция 2 Инструкция 3 Инструкция.
ЛЕКЦИЯ 7 КЛАССЫ ПАМЯТИ И ОБЛАСТЬ ДЕЙСТВИЯ ОБЪЕКТОВ.
Подпрограммы в Паскале Подпрограммы в Паскале (Технология нисходящего программирования)
Транксрипт:

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

Функция, с которой начинается выполнение программы, называется главной функцией и должна иметь предопределенное имя void main() { } Все остальные функции, входящие в программу, запускаются в работу путем их прямого или опосредованного (через другие функции) вызова из главной функции

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

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

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

В языке Си определение функции имеет следующий формат: declarator ( ) function-body Например, void work(n, beta) void work(int n, float beta) int n; float beta; { } sc-specifier - описатель класса памяти ( static или extern ) type-specifier - тип возвращаемого функцией значения declarator – имя (идентификатор) функции, перед которым может стоять символ звездочка (*), если эта функция возвращает указатель на элемент данных соответствующего типа

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

Память под размещение формальных параметров выделяется динамически из ресурса программного стека в момент обращения к соответствующей функции. Это означает, что параметры всегда должны иметь класс памяти auto или register, причем первый из них назначается компилятором по умолчанию локальными Переменные, которые описаны в теле функции будут локальными для этой функции, ибо область их видимости ограничена соответствующим блоком. Они создаются в момент обращения к данной функции и исчезают по окончании ее работы (класс памяти auto ), а их имена не должны совпадать с именами формальных параметров Поскольку память под размещение локальных переменных выделяется исполняющей системой динамически из программного стека, последний должен иметь достаточную для этого длину Инициализация локальных объектов допустима лишь в случае простых переменных и невозможна для массивов и других структурированных данных

Выполнение инструкций в теле функции начинается с самого первого оператора и продолжается до тех пор, пока не встретится оператор возврата return, либо пока не будет достигнут конец внешнего блока ( } ) Возвращаемое функцией значение равно значению выражения в операторе return, а при его отсутствии считается неопределенным. В случае необходимости тип результата преобразуется к типу функции стандартным образом

Инструкция вызова функции в общем случае имеет следующий формат: expression ( ) Например: void work(int n, float beta) void main() { { int n; float b; work(n,b); } Вызов функции может представлять собой выражение, которое может играть роль операнда в составе более сложного выражения

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

Для иллюстрации приведем пример программы, вычисляющей квадратный корень из числа, введенного с клавиатуры консольного терминала. Основная работа по нахождению корня выполняется функцией sqrt(), реализующей итерационный метод Ньютона с фиксированным числом итераций

#include void main() { float dat; float sqrt(float); /* Описание функции */ printf("\nЗадайте положительное вещественное число... "); scanf("%f", &dat); printf("\nКорень из числа %.3f равен %.3f", dat,sqrt(dat)); } float sqrt(float arg) { int count; float root = arg/2.0; for (count = 1; count

Если теперь в ответ на запрос программы ввести, например, число 25, то по окончании ее работы будет напечатан следующий результат: Корень из числа равен Заметим, что значение переменной dat в главной функции ни при каких обстоятельствах не может быть изменено при выполнении итерационного алгоритма в теле функции sqrt(), поскольку вся работа здесь ведется с копией значения, переданного через параметр arg

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

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

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

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

В самом общем случае такое предварительное описание имеет следующий формат: declarator ( ) )... >; Здесь sc-specifier задает класс памяти ( static или extern), который имеет вызываемая функция, type-specifier устанавливает тип возвращаемого ей значения, а arg-list определяет количество и тип аргументов, declarator в приведенной схеме является идентификатором функции, возможно модифицированным при помощи круглых скобок и символа звездочка для указателей на функции и функций, возвращающих указатели double sin(float a); double cos(float);

Введенное таким образом понятие предварительного описания функции дает возможность компилятору построить некоторый шаблон этой функции до ее фактического определения в текущем или внешнем файле Этот шаблон может быть использован для контроля правильности типа возвращаемого функцией значения и соответствия формальных и фактических параметров

Ниже приведены несколько характерных примеров построения предварительных описаний 1. В этом примере описана функция с именем add, возвращающая значение типа double и оба аргумента которой являются указателями на тип float : double add(float*, float*); 2. Если функция с именем sum имеет два аргумента типа double и возвращает указатель на массив трех элементов типа double, то ее предварительное описание должно иметь следующий вид: double (*sum(double, double))[3]; 3. В том случае, когда функция не имеет аргументов и возврашает указатель неопределенного типа, в ее предварительном описании необходимо использовать ключевое слово void на месте имени типа возвращаемого значения и списка аргументов: void *draw(void);

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

Рассмотрим простой случай передачи адресов скалярных переменных Напишем функцию, заменяющую значение своего аргумента на его абсолютную величину void abs(int *arg) {*arg = (*arg >= 0) ? (*arg) : -(*arg);} Теперь в случае обращения abs(&value); адрес переменной value будет передан в тело функции abs(), которая заменит числовое значение, размещенное по этому адресу, на его абсолютную величину

Другим уже знакомым нам примером передачи адресов через аппарат формальных/фактических параметров может служить использование стандартной функции scanf() для форматированного ввода значений с клавиатуры консольного терминала Действительно, обращение вида scanf("%d", &alpha); делает переменную alpha доступной в теле этой функции непосредственно через ее адрес, в результате чего введенное числовое значение будет известно и в точке вызова

Наиболее важную роль при разработке программ на языке Си играет возможность передачи между отдельными функциями массивов переменных и, в частности, символьных строк Здесь вновь оказывается полезным аппарат указателей, ибо для обеспечения доступа к массиву в теле всякой функции ей достаточно передать адрес его нулевого элемента, причем носителем последнего является само имя этого массива В следующем примере функция summa(), выполняющая суммирование элементов числового массива, получает от вызывающей ее функции main() адрес начала массива vector и общее количество его элементов:

void main() { float s, vector[100], float summa(); /* Описание функции*/ s = summa(vector, 100); } float summa(mas, n) float mas[]; int n; { int i; float sum = 0; for (i = 0; i < n; i++) sum += mas[i]; return (sum); }

Обратим внимание на то, что при описании формального массива mas в заголовке функции summa() его размерность явно не указана. В этом нет необходимости, поскольку фактическая работа будет выполняться здесь над массивом vector, адрес начала и длина которого передаются при обращении к этой функции Вместо описания массива неопределенной длины можно иметь эквивалентное ему описание float *mas; определяющее указатель на начало обрабатываемого массива Примером использования параметров для передачи символьных строк могут служить функции strcmp(), strcpy(), strlen () и другие подпрограммы обработки символьных строк. Аргументами каждой из них являются массивы элементов типа char, идентифицируемые своими начальными адресами. В этом случае уже не нужно дополнительно передавать количество обрабатываемых символов, конец всякой строки легко находится по завершающему ее нуль- символу

Использование указателей для передачи одних функций в другие Определим понятие указателя на функцию. Указатель на функцию связывается с адресом первого исполняемого оператора функции, задающем ее входную точку Указатель на функцию определяется в программе подобно тому, как и сами функции, с той лишь разницей, что в этом случае заголовок выглядит следующим образом: (*identifier) ( ) float (*calc)(float alpha, beta) { }

float (*calc)(float alpha, beta) { } Обращение же к этой функции будут выглядеть так (*calc)(x, y); где переменные x и y имеют тип float и играют роль фактических параметров Рассмотрим фрагмент программы, вычисляющей проекцию отрезка длины len, составляющего угол alpha с осью абсцисс, на одно из двух координатных направлений в зависимости от значения ключа direct :

double len; /* Длина отрезка */ double alpha; /* Угол с осью x */ void main() { char direct; /* Ключ направления */ double px, py; /* Длины проекций */ double cos(double), sin(double); /* Описания вызываемых */ double proect(double (*)(double)); /* функций */ switch (direct) { case 'x': px = proect(cos); /* Проекция на ось x */ break; case 'y': py = proect(sin); /* Проекция на ось y */ break; } } double proect(double (*func)(double)) /* Указатель на функцию */ { return (len*(*func)(alpha)); }

В этом примере формальным параметром функции proect является указатель func на функцию, возвращающую значение типа double double proect(double (*func)(double)) { return (len*(*func)(alpha)); } Фактически ми же параметрами при ее вызове становятся имена функций cos() и sin(), задающие соответствующие входные точки px = proect(cos); py = proect(sin);

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

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

В качестве примера реализации рекурсивного алгоритма рассмотрим функцию printd(), печатающую целое число в виде последовательности символов ASCII (т.е. цифр, образующих запись этого числа): void printd(int num) { int i; if (num < 0) { putchar('-'); num = -num; } if ((i = num/10) != 0) printd(i); putchar(num % 10 + '0') ; } Если значение переменной value равно 123, то в случае вызова void printd(value); эта функция дважды обратится сама к себе для печати цифр заданного числа

Классическим примером написания рекурсивной функции является вычисление факториала целого числа. Разумеется, эту задачу легко решить при помощи обычного цикла, но на этом простом примере наглядно видна идея рекурсивного алгоритма Текст такой функции достаточно прост: /* Рекурсивное вычисление n! */ int fact(int n) { if (n==0) return (1); else return(n*fact(n-1)); } Если обратиться к этой функции, например, так: int m;... m = fact(5);

то, прежде чем получить значение 5!, функция должна вызвать самое себя как fact(4), та, в свою очередь, вызывает fact(3). Так будет продолжаться до вызова fact(0). Лишь после этого вызова будет получено конкретное число (единица). Затем все пойдет в обратном порядке, и в конце концов мы получим результат от обращения fact(5) : 120 Порядок вызовов ¦ Порядок возвратов | Возвращаемое ¦ | значение fact(5) ¦ return(1) | 1 fact(4) ¦ return(1*0!) | 1 fact(3) ¦ return(2*1!) | 2 fact(2) ¦ return(3*2!) | 6 fact(1) ¦ return(4*3!) | 24 fact(0) ¦ return(5*4!) | 120 Последнее возвращаемое значение будет присвоено переменной m. Обратите внимание на то, что при каждом новом вызове предыдущий еще не закончил работу, поэтому параметры, переданные функции при прежнем обращении, еще хранятся в стеке При очередном вызове стек наращивается, т.к. в него загружаются копии других параметров. Очистка стека будет происходить постепенно, и он полностью будет очищен лишь после возврата окончательного результата

Аргументы командной строки Те, кому хоть раз приходилось работать с командной строкой операционной среды, видимо обратили внимание на то, что большинство команд пользовательского интерфейса могут иметь один или более параметров, называемых аргументами командной строки Так, например, обращение к команде copy, выполняющей копирование файлов, обычно выглядит следующим образом: copy oldfile.txt newfile.txt где параметры oldfile.txt и newfile.txt определяют имена файла-источника и файла-приемника соответственно. Эти параметры обрабатываются командным процессором и передаются в тело программы copy, в результате чего последняя узнает о файлах, над которыми должна быть выполнена операция копирования

Поскольку язык Си часто применяется при разработке системного программного обеспечения, он имеет встроенные средства для получения аргументов команды непосредственно от командного процессора Любая функция, входящая в состав Си-программы, может иметь параметры, через которые она получает необходимую информацию от вызывающей ее функции Совершенно аналогично, главная функция main(), с которой начинается выполнение всякой программы, могла бы в момент вызова получать исходные данные через аргументы командной строки. Для этого достаточно снабдить функцию main() набором параметров, которые обычно имеют имена argc и argv : main(argc, argv) { }

Параметр argc (ARGument Count) является переменной типа int, получающей от командного процессора информацию о количестве аргументов, набранных в командной строке, включая и имя самой команды Второй параметр argv (ARGument Vector) обычно определяется как массив указателей типа char, каждый из которых хранит адрес начала отдельного слова командной строки. Их описание в программе может выглядеть следующим образом: int argc; char *argv[]; В соответствии с принятым соглашением, нулевой элемент argv[0] массива указателей ссылается на строку символов, содержащую имя самой команды и поэтому параметр argc всегда имеет значение большее или равное единице. Следующий элемент argv[1] задает адрес размещения в памяти первого аргумента команды, также представленного последовательностью символов, и т.д. Обращаясь к очередному элементу массива argv нетрудно получить доступ ко всем аргументам командной строки

В качестве примера, иллюстрирующего работу с параметрами функции main(), рассмотрим программу, выводящую на экран терминала свои собственные аргументы и реализующую команду echo: #include main(argc, argv) int argc; char *argv[]; { int i; for (i = 1; i echo first second third получим на экране терминала такое сообщение first second third C:\>

Поскольку массив указателей в определенном смысле эквивалентен "указателю на указатель", мы могли бы определить переменную argv в заголовке функции main() как косвенный указатель типа char : char **argv; что полностью равносильно предыдущему описанию. В этих терминах наша программа echo могла бы выглядеть, например, следующим образом: #include main(int argc, char **argv) { while (--argc > 0) printf((argc > 1) ? "%s " : "%s\n", *++argv); } где выражение ++argv увеличивает на единицу значение указателя, заставляя его ссылаться на очередную строку, полученную от командного процессора