Программная инженерия. 1 курс 1 семестр / Лекции / L-08.Fynkcii
.pdfСПбГУТ им. проф. М.А. Бонч–Бруевича Кафедра программной инженерии и вычислительной техники (ПИ и ВТ)
ПРОГРАММИРОВАНИЕ
Единственный способ изучать новый язык программирования – писать на нем программы.
Брайэн Керниган
Лекция 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);
int even(int); 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 |
|
} |
|
|
|
типы данных. |
||||
|
Макрозащита #define |
|
int odd(int a) //******* |
|
|
|
|
|
Именно в заголовочных файлах можно |
||
_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 |
||||
|
|
|
|
|
|
|
|
|
|
|