- •СПбГУТ им. проф. М.А. Бонч–Бруевича Кафедра программной инженерии и вычислительной техники (ПИ и
- •1. Функции в Си
- •Функции в Си
- •Функции в Си
- •Функция. Структурная декомпозиция программы
- •Функция. Структурная декомпозиция программы
- •Функция. Пример
- •Объявление функции и определение функции
- •Объявление функции и определение функции
- •Область видимости переменных
- •Глобальные
- •Область видимости переменных. Упрощенная структура исполняемого файла (приложения) в ОП
- •2. Функции. Механизм вызова
- •Формальные и фактические параметры
- •Передача аргументов
- •Передача аргументов
- •Передача аргументов по значению
- •Передача аргументов по указателю
- •3. Передача массивов в функцию
- •Передача динамического массива в качестве аргумента
- •4. Функции с переменным числом параметров
- •Функции с переменным числом параметров
- •Функции с переменным числом параметров. Неправильное использование
- •5. Функции. Что ещё?..
- •5.3 Параметры командной строки
- •5.4 Функциональное программирование
- •Функциональное программирование
СПбГУТ им. проф. М.А. Бонч–Бруевича Кафедра программной инженерии и вычислительной техники (ПИ и ВТ)
ПРОГРАММИРОВАНИЕ
Единственный способ изучать новый язык программирования – писать на нем программы.
Лекция 8: Функции
1.Функции в Си
2.Формальные и фактические параметры
3.Передача массивов в функцию
4.Функции с переменным числом параметров
5.Функции. Что ещё?..
Санкт–Петербург, 2021г.
|
Введение: Что такое функция? |
|||
Чем дальше мы изучаем Си, тем больше становятся программы. |
В языке Си подпрограммы реализованы в виде функций. |
|||
Мы собираем все действия в одну функцию main и по несколько |
Теперь мы научимся создавать функции на Си. |
|||
раз копируем одни и те же действия, создаём десятки |
Функции: |
|||
переменных с уникальными именами. |
|
|||
|
во-первых, помогут выделить в отдельные подпрограммы |
|||
Наши программы распухают и становятся всё менее и менее |
||||
понятными, ветвления становятся всё длиннее и ветвистее. |
дублирующийся код, |
|||
|
|
во-вторых, помогут логически разбить программу на части, |
||
Но из сложившейся ситуации есть выход! |
|
в-третьих, с функциями в си связано много особенностей, |
||
|
которые позволят использовать новые подходы к |
|||
Использование вспомогательных алгоритмов ─ |
||||
структурированию приложений. |
||||
ПОДПРОГРАММ |
|
|||
|
Процедура( функция) представляет собой |
|||
|
Есть выход: |
|||
Основная (вызывающая) |
последовательность операторов, которая имеет имя, |
|||
Divide et impera – |
список параметров и может быть вызвана из различных |
|||
программа |
частей программы. |
|||
Разделяй и властвуй! |
||||
|
Имя процедуры в тексте программы называется вызовом. |
|||
Вызов подпрограммы |
|
|||
Подпрограмма |
Вызов активирует процедуру (функцию) ─ начинают |
|||
|
выполняться её операторы. |
|||
Продолжение основной |
|
После выполнения процедуры программа продолжается с |
||
программы |
Структурная |
оператора стоящего за вызовом. |
||
|
Отличие процедур от функций в том, что функции |
|||
|
декомпозиция |
|||
|
программы! |
возвращают значение. |
||
Подпрограммы применяются когда: |
|
При совместной работе функции должны обмениваться |
||
|
информацией. |
|||
часть алгоритма неоднократно повторяется в программе; |
||||
Это можно осуществить с помощью: |
||||
можно использовать фрагменты разработанных ранее |
||||
|
глобальных переменных; |
|||
алгоритмов; |
|
|||
для разбиения крупных программ на части в соответствии |
|
через параметры; |
||
с модульным принципом программирования. |
|
через возвращаемое функцией значение. |
1. Функции в Си
Что такое функция?
Функция – это именованная последовательность описаний и операторов, выполняющее какое-либо законченное действие.
Функция может принимать параметры и возвращать значение.
Подпрограмма или, другими словами, функция должна быть связана (интегрирована) с основной программой, так сказать, со своим внешним окружением.
С целью обеспечения взаимодействия с остальной частью программы для функции можно предусмотреть так называемые вход и выход.
Вход в функцию — это передача ей аргументов — данных, полученных во внешней части программы. Получив данные из своего внешнего окружения (внешней программы), функция должна их как-то обработать: выполнить некоторые действия, вычислить какое-то значение.
Выход из функции — значение, вычисленное блоком кода данной функции и передаваемое во внешнюю часть программы.
Входные данные называют параметрами, а выходные —
возвращаемым значением.
Впрочем, функция может и не принимать никаких параметров, а также ничего не возвращать.
Что принимает в качестве параметров и что возвращает функция в результате своей работы, определяет программист, т. е. автор-разработчик программного кода.
Функция — важнейший элемент структурного программирования, позволяющий группировать и обобщать программный код, который может позднее использоваться произвольное число раз. Она является законченной подпрограммой, поэтому у нее есть свои "ввод" и "вывод" — параметры (аргументы) и возвращаемое значение:
С точки зрения внешней программы функция — это "черный |
|
ящик". Функция определяет собственную (локальную) область |
|
видимости, куда входят входные параметры, а, также, те |
|
переменные, которые объявляются непосредственно в теле самой |
|
функции. |
|
Главное, что должно быть можно сделать с функцией — это |
|
возможность ее вызвать. |
|
Перед использованием функция должна быть объявлена и |
|
соответствующим образом определена. |
|
Объявление (declaration) функции содержит список параметров |
|
вместе с указанием типа каждого параметра, а, также, тип |
|
возвращаемого функцией значения. |
|
Определение (definition) функции содержит исполняемый код |
|
функции. |
|
Вызов функции может встретиться в программе до определения, |
|
но обязательно после объявления. |
|
Функции, которые не возвращают значений, иногда называют |
|
процедурами. |
3 |
|
Функции в Си
Подпрограмма (ПП) – это поименованный или иным образом идентифицированный фрагмент компьютерной программы, которому можно передать управление (вызвать) в любой её точке и который имеет возможность вернуть управление в точку, следующую за точкой своего вызова
Плюсы ПП:
Уменьшение размера памяти, занимаемой кодом программы
–почти неактуально в настоящее время.
Структуризация программы с целью удобства её понимания и сопровождения:
Исправление ошибок, оптимизация, расширение функциональности в ПП автоматически отражается на всех её вызовах
Вынесение в ПП даже однократно выполняемого набора действий делает программу более понятной и обозримой
В языке Си подпрограммы называются функциями
Описание функции делится на:
Заголовок – тип результата, имя функции и список параметров функции
Если тип-результата есть void, то функция не возвращает результата – аналог процедуры в языке Паскаль
Тело – это набор инструкций, который будет выполнен, когда функция будет вызвана
Вызов – это частный случай постфиксного выражения языка Си (A + B → A B +)
Вам уже знакомы функции: Функции ввода-вывода – <stdio.h>:
printf() – форматный вывод в stdout
scanf() – форматный ввод (чтение данных) из потока stdin
getchar(), putchar(int c) и т.д.
Функции работы с файлами:
FILE *fopen(char *filename, char *mode)
int fflush(FILE *f)
int fclose(FILE *f) и т.д.
* * *
Функции работы со строками – <string.h> Математические функции – <math.h> Функции общего назначения – <stdlib.h>
Функции работы с датой и временем – <time.h>
** *
Функции – это самостоятельные единицы программы, предназначенные для решения конкретных подзадач, обычно повторяющиеся несколько раз.
Перед использованием функция должна быть объявлена
Все функции в языке Си – глобальные, т.е. функция не
может быть объявлена внутри другой функции
В Си можно объявить функцию с помощью прототипа, т.е. заголовка функции, а полное ее описание сделать после функции main()
4
Функции в Си
Функция может принимать фиксированное либо переменное число аргументов, а может не иметь аргументов.
Функция может как возвращать значение, так и быть пустой (void) и ничего не возвращать (аналог процедуры в Pascal)
Мы уже знакомы с многими функциями и знаем, как их вызывать – это функции библиотек stdio, stdlib, string, conio и пр.
Более того, main – это тоже функция.
Она отличается от остальных только тем, что является точкой входа при запуске приложения.
Функция в Си определяется в глобальном контексте.
Синтаксис функции:
тип_функции имя_функции ([список_параметров]), ...)
{
тело функции
}
Функции — это средство проектирования, которое позволяет осуществить декомпозицию программы на достаточно простые и легко управляемые части.
Значительно проще написать решение маленьких задач по отдельности, чем реализовать весь процесс целиком.
Устранение избыточности программного кода улучшает сопровождаемость кода — если что-то необходимо будет исправить, достаточно будет внести изменения всего в одном месте, а не во многих.
Сиспользованием функций в языке Си связаны три понятия:
1.Определение функции (описание действий, выполняемых функцией)
2.Объявление функции (задание формы обращения к функции)
3.Вызов функции.
В языке Си нет требования, чтобы определение функции обязательно предшествовало ее вызову.
Определения используемых функций могут следовать за определением функции main, перед ним, или находится в другом файле.
//Примеры:
//Объявление функции:
int sum (int a, int b);
// Определение функции: int sum (int a, int b)
{
return (a+b);
}
5
Функция. Структурная декомпозиция программы
Информация в функцию передается с помощью аргументов (фактических параметров), задаваемых при ее вызове.
Эти аргументы должны соответствовать формальным параметрам, указанным в описании функции.
аргументы
int k = add_ints (2,3);
Значения аргументов заменяют соответствующие параметры в определении функции
Возвращается значение 5
int add_ints (int a, int b) |
|
||
{ |
|
|
|
|
формальные |
||
return a+b; |
|
||
|
параметры |
||
} |
|
|
|
|
|
||
|
|
|
|
|
|
|
|
6
Функция. Структурная декомпозиция программы
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void my_f1 (double v, int dim) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
Выполнение |
|
|
|
|
|
|
|
|
|
|
{ |
|
|||||||||
|
|
|
|
|
|
|
|
|
|
… |
|||||||||||
начинается с main |
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
return; |
||||||||
int main() |
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
||||||||||
|
|
|
|
|
|
|
|
|
|
|
} |
|
|||||||||
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||||
… |
|
|
|
|
|
|
|
|
|
|
|
|
|
void my_f2 (double v1, double v2, |
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|||||||||||||
my_f1 (a,n); |
|
|
|
|
|
|
|
|
|
|
|
double res, int dim) |
|||||||||
|
|
|
|
|
|
|
|
|
|
||||||||||||
… |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
… |
|
my_f2 (a,b,c,n); |
|
|
|
|
|
|
|
|
|
|
|
|
|
return; |
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||
… |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||
output_vect (c,n); |
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
|
|||||||||||
|
|
|
|
void output_vect (double v, int dim) |
|
||||||||||||||||
|
|
|
|
|
|
|
|||||||||||||||
… |
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||
return 0 |
|
|
|
|
|
|
|
|
|
|
… |
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return; |
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
Возврат из main в ОС |
|
|
|
|
|
|
|||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|||||||||||
|
|
|
|
|
|
|
|
|
|
|
7
Функция. Пример
Дублирование кода является признаком «низкого» или «ленивого» стиля программирования.
Хороший стиль программирования обычно основан на повторном использовании кода.
Проблемы, к которым приводит дублирование кода:
большое количество кода: дублирование часто приводит к созданию длинных, повторяющихся последовательностей кода, которые отличаются лишь несколькими строками или символами, что в итоге затрудняет понимание программы;
скрытое значение: трудно уловить разницу в повторяющихся участках кода и поэтому становится тяжелее понимать, для чего именно предназначен тот или иной кусок кода, зачастую единственная разница заключается в параметрах;
аномалии обновления: при обновлении дублированного кода необходимо обновить несколько аналогичных участков, что сильно увеличивает затраты на обслуживание;
размер исходного текста: без применения какого-либо сжатия исходный текст занимает больше места.
//Вычисляет среднее значение массива целых чисел:
int array1[N]; int array2[N];
int sum1 = 0; int sum2 = 0; int average1; int average2; int i;
for (i = 0; i < 4; ++i) |
sum1 += array1[i]; |
average1 = sum1 / 4; |
|
for (i = 0; i < 4; ++i) |
sum2 += array2[i]; |
average2 = sum2 / 4;
Существует определённое количество алгоритмов, позволяющих
отыскать дубликаты кода, среди них:
алгоритм Бейкер;
алгоритм Рабина-Карпа;
использование абстрактных синтаксических деревьев.
В ряде случаев эффективно визуальное определение дубликатов
NB: Одна из основных причин проявления дублирования — программирование копированием-вставками, при котором участки кода копируются просто потому, что «это работает».
// Два цикла могут быть выделены в отдельную функцию:
int calcAverage (int* Array_of_4)
{
int sum = 0;
for (int i = 0; i < 4; ++i) sum += Array_of_4[i];
return sum / 4;
}
// Использование этой функции избавит код от дубликатов: int array1[N];
int array2[N];
int average1 = calcAverage(array1); int average2 = calcAverage(array2);
8
Объявление функции и определение функции
Определение функции задает тип возвращаемого значения, имя функции, типы и число формальных параметров, а также объявления переменных и операторы, называемые телом функции, и определяющие действие функции (сама функция и её полный текст).
Пример: |
Оператор return |
|
|
int max ( int a, int b) |
– вызывает немедленный выход из текущей |
||
{ |
if (a>b) |
функции и возврат в вызывающую функцию |
|
|
return a; |
– используется для возврата значения функции |
|
|
else |
– в теле функции может быть несколько |
|
|
return b; |
операторов return, но может не быть ни одного |
|
} |
|
|
В данном примере определена функция с именем max, имеющая 2 параметра. Функция возвращает целое максимальное значение из а и b.
Чтобы компилятор мог осуществить проверку соответствия типов передаваемых фактических параметров типам формальных параметров до вызова функции нужно поместить
объявление (прототип) функции.
Объявление (прототип) функции имеет такой же вид, что и определение функции, с той лишь разницей, что тело функции отсутствует, и имена формальных параметров тоже могут быть опущены.
Для функции, определенной в последнем примере, прототип может иметь вид:
int max (int a, int b);
– если нет return, возврат в вызывающую программу происходит после выполнения последнего оператора тела функции
В программах на языке Си широко используются, так называемые, библиотечные функции, т.е. функции предварительно разработанные и записанные в библиотеки.
Прототипы библиотечных функций находятся в специальных заголовочных файлах, поставляемых вместе с библиотеками в составе систем программирования, и включаются в программу с помощью директивы #include.
В Си можно объявить функцию до её определения.
Объявление функции, её прототип, состоит из возвращаемого значения, имени функции и типа аргументов. Имена аргументов можно не писать.
Например:
#include <stdio.h> |
|
|
|
|
|
|
||
//Прототипы функций. Имена аргументов можно не писать |
|
|
|
|||||
int odd(int); |
Это смешанная рекурсия – функция odd |
|
||||||
int even(int); |
возвращает 1, если число нечётное и 0, |
|
||||||
void main() |
|
|||||||
если чётное. |
|
|||||||
{ |
|
|
|
|||||
printf("if %d odd? %d\n", 11, odd(11)); |
|
|||||||
|
printf("if %d odd? %d\n", 10, odd(10)); |
|
||||||
} |
getch(); |
|
|
|
|
|
|
|
//Определение функций. Смешанная рекурсия |
|
|||||||
int even(int a) |
|
|
|
|
|
|
||
{ |
if (a) odd(--a); |
|
|
|
|
|
|
|
} |
else return 1; |
int odd(int a) |
|
|
|
|
||
|
|
|
|
|
||||
|
{ |
if (a) even(--a); |
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
} |
else return 0; |
|
|
|
9 |
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
Объявление функции и определение функции
Обычно объявление функции помещают отдельно, в .h файл, а |
|
Особенности каждого файла в каталоге (с библиотекой): |
|||||||||||||
|
определение функций в .c файл. |
|
|
Наш файл, который содержит функцию main, подключает |
|||||||||||
Таким образом, заголовочный файл представляет собой |
|
необходимые ему библиотеки а также заголовочный файл |
|||||||||||||
|
интерфейс библиотеки и показывает, как с ней работать, не |
|
File1.h. |
||||||||||||
|
вдаваясь в содержимое кода. |
|
|
|
|
|
|
Теперь компилятору известны прототипы функций, то есть он |
|||||||
|
Создание простой библиотеки |
|
|
знает возвращаемый тип, количество и тип аргументов и |
|||||||||||
|
Для этого нужно будет создать два файла – один с |
|
|
имена функций. |
|||||||||||
|
|
Заголовочный файл, как и оговаривалось ранее, содержит |
|||||||||||||
расширением .h и поместить туда прототипы функций, а другой с |
|||||||||||||||
|
прототип функций. |
||||||||||||||
расширением .c и поместить туда определения этих функций. |
|
||||||||||||||
Также здесь могут быть подключены используемые |
|||||||||||||||
|
Если вы работаете с IDE, то .h файл необходимо создавать в |
||||||||||||||
|
|
библиотеки. |
|||||||||||||
каталоге «Заголовочные файлы», а файлы кода в каталоге |
|
||||||||||||||
Макрозащита #define _FILE1_H_ и т.д. используется для |
|||||||||||||||
«Файлы исходного кода» (условные имена каталогов). |
|||||||||||||||
|
Пусть файлы называются File1.h и File1.c Перепишем код с |
|
предотвращения повторного копирования кода библиотеки |
||||||||||||
|
|
при компиляции. |
|||||||||||||
предыдущего слайда: |
|
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
Эти строчки можно заменить одной #pragma once |
|||||||
|
Заголовочный файл |
|
Содержимое файла |
|
|
|
|||||||||
|
|
|
|
|
|
Файл File1.c исходного кода подключает свой заголовочный |
|||||||||
|
File1.h: |
|
исходного кода File1.c: |
|
|
|
|||||||||
|
|
|
|
|
|
файл. |
|||||||||
|
#ifndef _FILE1_H_ |
|
#include "File1.h" |
|
|
|
|
||||||||
|
#define _FILE1_H_ |
|
|
|
|
Всё как обычно логично и просто. |
|||||||||
|
|
int even(int a) |
|
|
|
||||||||||
|
int odd(int); |
|
|
|
|
|
В заголовочные файлах принято кроме прототипов функций |
||||||||
|
|
{ |
if (a) |
odd(--a); |
|
|
|
||||||||
|
int even(int); |
|
|
else |
return 1; |
|
|
|
|
выносить константы, макроподстановки и определять новые |
|||||
|
#endif |
|
} |
|
|
|
|
типы данных. |
|||||||
|
|
|
int odd(int a) //******* |
|
|
|
|
|
|
|
Именно в заголовочных файлах можно |
||||
|
Макрозащита #define |
|
|
||||||||||||
_FILE1_H_ и т.д. |
|
{ |
if (a) |
even(--a); |
|
|
|
|
|
|
|
обширно комментировать код и писать |
|||
используется для |
|
} |
else |
return 0; |
|
|
Функция main: |
|
|
|
|
примеры его использования. |
|||
|
|
|
|
|
|
|
|||||||||
предотвращения |
|
|
|
|
|
|
#include <stdio.h> |
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
||||
повторного копирования |
|
|
|
|
|
|
|
#include "File1.h" |
|
|
|
|
|
||
|
Или File1.h: |
|
|
|
|
|
|
|
|
||||||
кода библиотеки при |
|
|
|
void main() |
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|||||||
компиляции. |
|
#pragma once |
|
|
{ printf("if %d odd? %d\n", 11, odd(11)); |
|
|||||||||
|
Эти строчки можно |
|
#include "File1.c" |
|
|
printf("if %d odd? %d\n", 10, odd(10)); |
|
||||||||
заменить одной: |
|
int odd(int); |
|
|
getch(); |
|
|
|
|
|
|||||
#pragma once |
|
int even(int); |
|
|
} |
|
|
|
|
10 |