Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторная работа 7.doc
Скачиваний:
4
Добавлен:
22.07.2019
Размер:
1.21 Mб
Скачать

Лабораторная работа № 7 Разработка и отладка алгоритмов и программ с применением пользовательских функции.

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

Основные теоретические сведения

Создание и использование функций

Принципы программирования на языке Си основаны на понятии функции. Уже были рассмотрены некоторые функции: printf( ), scanf( ), getchar( ), putchar( ). Эти функции являются системными, однако мы создали и несколько собственных функций под общим именем main( ). Выполнение программы всегда начинается с команд, содержащихся в функции main( ), затем последняя вызывает другие функции. Рассмотрим вопрос, как создавать свои собственные функции и делать их доступными для функции main( ), а также для других функций.

Функция – это самостоятельная единица программы, спроектированная для реализации конкретной задачи. Вызов функций приводит к выполнению некоторых действий. Например, при обращении к функции printf( ) осуществляется вывод данных на экран. Функции могут выполнять различные действия и получать значения величин, используемых в программе.

Использование функций избавляет от многократного «переписывания» кода. Если конкретную задачу в программе необходимо выполнить несколько раз, можно написать соответствующую функцию только один раз, а затем по необходимости вызывать ее кроме того мы можем применять одну функцию, например putchar( ), в различных программах.

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

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

Определение функций, возвращающих значение, имеет следующий простейший формат:

тип-результата имя-функции (формальные аргументы)

{

тело функции

}

Пример:

float f(float x){

float y;

y=x*x;

return y;

}

или

float f(float x){

return x*x;

}

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

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

return e;

которая обеспечивает передачу результата e.

Функция, возвращающая значение, может содержать более одной операции return, однако только в различных ветках операций ветвления, например,

int f(int a){

if (a>5) return a;

else return 0;

}

Если функция не возвращает значения, то в заголовке функции указывается тип void.

Например:

void f(int a){

for (int i=0;i<a;i++)printf("Hello world");

}

В такой функции наличие операции return необязательно. Если return все же присутствует, то после него не должно указываться никаких параметров:

return;

Оператор return оказывает и другое действие. Он завершает выполнение функции и передает управление следующему оператору в вызывающей функции. Это происходит даже в том случае, если оператор return является не последним оператором тела функции:

int abs(int x)

{

if(x < 0) return(-x);

else return(x);

printf("Работа завершена!\n");

}

Наличие оператора return препятствует тому, чтобы оператор печати когда-нибудь выполнился в программе.

Вызов функций

Вызов функции производится следующим образом:

f(b);

или таким

int t = f(b);

В первом случае результат передается системе и не сохраняется в переменной.

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

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

Параметры функции.

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

Формальный параметр – параметр, который описывается в заголовке функции и значение которого считается неопределенным до вызова данной функции. Фактический параметр – параметр, который передается в функцию при ее вызове (под фактическими параметрами мы в данном случае будем подразумевать и константы или сложные выражения). Независимо от типа фактического параметра он вначале вычисляется, а затем его величина передается функции. Фактический параметр - это конкретное значение, которое присваивается переменной, называемой формальным параметром.

Если для связи с некоторой функцией требуется более одного аргумента, то наряду с именем функции можно задать список аргументов, разделенных запятыми. Например:

print_num(int i, int j)

{

printf("значение i=%d значение j=%d", i,j);

}

Обращение в программе к данной функции будет таковым:

print_num(6,19);

либо таковым:

int k = print_num(6,19);

Обратите внимание, аргументы функции при ее объявлении нельзя записать так: print_num(int i, j)

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

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

Пример:

#include <stdio.h>

/* Функция суммирует значения своих параметров */

long summa(int m,...) /*m - число параметров*/

{

int *p=&m; /*настроили указатель на параметр m*/

long t=0;

for(;m>=0;m--) t+=*(++p);

return t;

}

void main()

{

printf("\n summa(2,6,4)=%d",summa(2,6,4));

printf("\n summa(6,1,2,3,4,5,6)=%d", summa(6,1,2,3,4,5,6));

Передача параметров в функцию и из нее в языке Си может осуществляться двумя способами:

  • по значению

  • по указателю

В языке Си++ добавляется еще один способ передачи параметров – по ссылке.

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

int a = 5;

int f(int a){

a+=3;

return a;

}

int k = f(a);

В данном случае в результате a= 5, k= 8.

Если же осуществлять передачу параметров по указателю, то в данном случае изменяется и содержимое внешней по отношению к функции переменной.

int *a;

int = 5;

void f(int *a){

(*a)+=3;

}

f(&a);

int k = *a;

В данном случае в результате a= 8, k= 8. Обратите внимание, что в функцию необходимо передать адрес переменной (&)

Передача по ссылке является, фактически, упрощенной формой передачи параметра по указателю.

int a = 5;

void f(int &a){

a+=3;

}

f(a);

int k = a;

Обратите внимание, что в данном случае не нужно писать значек «*». Результат, по прежнему, a= 8, k= 8

Передача нескольких результатов

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

void f(int a, int *r1, int r2){

(*r1)+=a;

(*r2)-=a;

}

или в языке Си++ так:

void f(int a, int &r1, int &r2){

r1+=a;

r2-=a;

}

где r1 и r2 результаты функции.

Кроме того, возможен следующий вариант:

int f(int a, int &r1){

r1+=a;

r2-=a;

return r2

}

Существует еще один вариант передачи результатов: при помощи структуры.

struct myStruct {

int r1;

int r2;

};

myStruct ms;

myStruct f(int a){

ms.r1+=a;

ms.r2-=a;

return ms;

}

Локальные переменные

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

Это означает, что формальные параметры (аргументы) функции локализованы в ней, то есть недоступны вне определения функции и никакие операции над формальными параметрами в теле функции не изменяют значения фактических параметров.

Следует учесть, что не рекомендуется, например, следующая запись:

int *f(){

int local = 5;

return &local;

}

Переменная local является локальной поэтому невозможно вернуть указатель на данную переменную, т.к. ее не существует вне данной функции. Вместе с тем, если записать так:

int f(){

int local = 5;

return local;

}

то такое объявление вполне законно, т.к. в точку вызова функции передается копия локальной переменной.

Передача массивов в качестве параметров

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

int a[3]={2,5,-6};

int f(int *a){

if (*(a+1)==5) return 1;

else return 0;

}

Возможна и следющая запись:

int a[3]={2,5,-6};

int f(int *a){

if (a[1]==5) return 1;

else return 0;

}

Вызов функции будет, например, следующим:

f(a);

Обратите внимание, т.к. массив уже является указателем на его нулевой элемент, то знак & при вызове функции писать не нужно.

Передавать многомерные массивы лучше всего следующим способом:

void print_mij(int *m, int dim1, int dim2)

{

for(int i=0;i<dim1;i++){

for(int j=0;j<dim2;j++)

cout << m[i*dim2+j] ;

cout << endl;

}

}

Однако, если размерность массива известна заранее, то можно передать массив и следующим способом:

void print_m35(int m[3][5])

{

for(int i=0;i<3;i++){

for(int j=0;j<5;j++)

cout << m[i][j] ;

cout << endl;

}

}

Массивы передаются в любом случае как указатель, а не копируются

Возвращение значений в качестве результата функции по ссылке и указателю.

Существует возможность возврата указателя или ссылки в операции return. Не стоит только забывать, что нельзя возвращать указатель или ссылку на локальную переменную.

Прототипы функций.

Для более ясной структуры программы, а также для удобства обработки компилятором функций использовать предварительные объявления функций, так называемые прототипы.

Пример.

void f(int a);

void f(int a){

for (int i=0;i<a;i++)printf("Hello world");

}

имя переменной в прототипе можно опустить:

void f(int);

void f(int a){

for (int i=0;i<a;i++)printf("Hello world");

}

Использование прототипов может быть выгодно в следующем случае:

void f(){

g(3);

}

void g(int a){

for (int i=0;i<a;i++)printf("Hello world");

}

В данном участке кода будет выдана ошибка, т.к. функцию g компилятор еще не «знает», а она уже вызывается в функции f. Для нормальной работы программы следует написать так:

void g(int a){

for (int i=0;i<a;i++)printf("Hello world");

}

void f(){

g(3);

}

либо так:

void g(int);

void f(){

g(3);

}

void g(int a){

for (int i=0;i<a;i++)printf("Hello world");

}

Обратите внимание, в конце описания прототипа ставится «;»