Глава 7. Блоки и процедуры

7.1 Блоки

 

БЛОК - это ограниченная последовательность операторов, задающая границы области действия имен, описанных (или определенных) в нем, и управляющая динамическим распределением памяти для этих данных.

Такие объекты называют ЛОКАЛИЗОВАННЫМИ в блоке или просто ЛОКАЛЬНЫМИ данными. Блок выполняется в естественном порядке следования операторов.

Формат

{ <описания и определения> <выполняемые операторы>}

Пример:

{int i, j; void main ( )

for( i=0; i<l; i++ ) { {

s[i]=0; <блок>

for ( j=0; j<k; j++ ) { }

s[i] += p[i][j]*q[j]; } } }

Описания и определения, заданные внутри текущего блока, действуют (выделяется память под переменные) только внутри блока. Этим блок отличается от составного оператора. Блоки могут быть вложенными.

7.2 Процедуры

ПРОЦЕДУРА – это часть программы, которая не выполняется в естественном порядке, а вызывается специальным способом.

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

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

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

Существует два вида процедур: ПОДПРОГРАММЫ и ФУНКЦИИ. Отличие подпрограмм от функций – первые не возвращают значения в основную программу.

ПОБОЧНЫЙ ЭФФЕКТ. В Си формально определены только ФУНКЦИИ.

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

Схема обмена данными между процедурами

 

 


Возвращаемое значение функции разумно считать специфическим выходным параметром.

 

ФорматОПИСАНИЯ функции

[<тип_функции>] <имя_функции>

( [<описания_формальных_параметров>] )

<блок>

Если <тип_функции> отсутствует, то компилятор по умолчанию подставляет int. ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ. Если возвращаемое значение имеет тип не int, то указание типа в заголовке функции ОБЯЗАТЕЛЬНО.

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

float min_fun (float x[], int n) {

int i, j; // Локальные переменные функции

float min_value;

................

return (min_value) }

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

Подпрограмма – это функция, не возвращающая значения. Ключевое слово <типа_функции> void.

void <имя_функции> ([<описания_формальных_параметров>])

<блок>

Тип void: Ключевое слово void используется для нейтрализации значения объекта, например, для определения функции не возвращающей никаких значений.

 

Пример:

void putmsg ( void ) {

printf("Hello, world!\n");

} /* End putmsg */

 

Для обращения к функции используется выражение с операцией “круглые скобки”:

Обозначение_функции ( список_фактических_параметров ) ;

 

Операндами операции () служат Обозначение_функции и список_фактических_параметров.

Наиболее естественное и понятное Обозначение_функции – это ее имя. Кроме того, функцию можно обозначить, разыменовав указатель на нее.

Сторонники Паскаля! Обратите внимание на разницу между mufunc и myfunc(). myfunc – это ФУНКЦИЯ, а myfunc( ) – это РЕЗУЛЬТАТ ЕЕ РАБОТЫ. Поэтому если вы напишете: myfunc ;– то ошибки компиляции не будет (в выражениях допускается отсутствие операторов), но ничего не произойдет – для того, чтобы вызвать функцию, необходимо подействовать на нее оператором вызова функции ( ).

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

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

 

В языке Си нет предопределенных функций. Каждая используемая функция должна быть где-то определена (ОБЪЯВЛЕНА) до ее использования. Поскольку все функции могут транслироваться автономно, то в вызывающей процедуре необходимо ОПРЕДЕЛЕНИЕ вида (прототип функции):

(<тип_возвращаемого_значения>)<имя_функции>

( [ описание_параметров] );

Без него возвращаемое значение функции будет интерпретироваться как типа int и возможна ошибка. Поэтому подпрограмма не описывается.

Здесь возможны два варианта:1. Если Вы используете встроенные функции среды Си, то самому писать многие требуемые функции (ввод-вывод, подключение файлов, математические функции и т.д.) каждый раз несколько скучно К счастью, Си снабжен библиотекой, содержащей в себе множество разнообразных функций. Для того, чтобы использовать ее, подключите к вашей программе требуемый ЗАГЛОВОЧНЫЙ ФАЙЛ (он имеет расширение .h) к вашей программе следующей строкой: #include <Имя_заголовочного_файла> На место этой строки в программу при компиляции будет подставлено содержание указанного файла.

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

Например, пусть определена функция с прототипом:

int g ( int x, long y );

Далее в программе использован вызов :

double m;

rez = g ( 3.0+m, 6.4e+2 );

Оба фактических параметра в этом вызове имеют тип double. Компилятор, ориентируясь на прототип функции, автоматически предусмотрит такие преобразования:

(int) rez = g ( (int)(3.0+m), (long)(6.4e+2) )

 

Пример (функция):

void main ( ) {

double max (double, double ) // Прототип функции

max_value = max (cos(x), sin(x)); // Обращение к функции

… }

 

double max (double a, double b ) // Описание функции

{ double y;

if ( a > b ) y = a;

else y=b;

return y; }

 

Пример (подпрограмма):

/* Умножение матрицы на вектор */

matrix(a, b, c, m, n);

void matrix ( double p[10][10], /* Исходная матрица */

double q[10], /* Исходный вектор */

double s[10], /* Вектор–результат */

int l, /* Число строк */

int k) /* Число столбцов */

{ int i, j;

for ( i=0; i<l; i++ ) {

for ( s[i]=j=0; j<k; j++ ) {

s[i] += p[i][j]*q[j]; } }

} /* End matrix */

 

В каждой программе должна быть процедура, которой передает управление ОС и которая остается активной в течение всей работы программы. Эта процедура называется ГЛАВНОЙ и имеет заголовок вида

int main ([<параметры>])

<блок>

О параметрах пока умолчим, поэтому обычно имеем

void main (void) { … }

Функции в Си совершенно равноправны. И функция main тоже может вызываться другими функциями:Например: int func (int a); int main (void) { return func (5); } int func (int a) { return main ( ); } В этом случае выход из программы, конечно, произойдет только после завершения работы "самой наружной" функции main.

 

7.2.1 Передача скаляров

 

ФАКТИЧЕСКИЕ ПАРАМЕТРЫ. Перед передачей входные аргументы типа float и char АВТОМАТИЧЕСКИ преобразуются в типы double и int, следовательно, соответствующие параметры должны быть описаны надлежащим образом, иначе произойдет ошибка.

Пример:

Вызывающая процедура Вызываемая процедура

float a, b, max(); float max (float a, float b) { //Ошибка

..................

y=2+3.5*max(a, b); float max (double a, double b) {//Верно

 

ФОРМАЛЬНЫЕ ПАРАМЕТРЫ. При передаче СКАЛЯРНЫХ ДАННЫХ в вызываемую процедуру передаются только копии аргументов. Физически происходит выделение нового участка оперативной памяти и копирование аргументов в память, распределенную вызываемой процедуре.

ВНИМАНИЕ! Поэтому в теле функции мы можем изменить ТОЛЬКО КОПИЮ и не повлияем на АРГУМЕНТ в вызывающей процедуре!

 

Вопрос? А как же передать результаты работы в вызывающую процедуру, если реально можно передать только одно значение?

Для этого надо передавать АДРЕС области памяти, где хранится аргумент. При этом функция не может изменить этот адрес, а содержание может. Для получения адреса операция &. Следовательно, соответствующий параметр – УКАЗАТЕЛЬ!!

Пример:

Дана матрица {a[i][j]}, i,j = 1...10.

Найти max{ a [i][j] } и его индексы.

Вызывающая процедура

void main (void) {

float maxmatr(), maxim, a[10][10];

int m, n;

..................................

maxim = maxmatr (a, &m, &n);

 

 

Функция

float maxmatr (float a[10][10], int *k, int *l ) {

float max;

int i, j;

max = a[0][0];

for( *k = *l = i =0; i<10; i++ ) {

for ( j = 0; j < 10; j++ ) {

if ( max<a[i][j] ) {

max=a[i][j];

*k=i;

*l=j; } } }

return max;

} /* End maxmatr */

 

Подпрограммы

void maxmatr (float a[10][10], int *k, int *l, float *max) {

/* float max; везде заменить max на *max и убрать return/

 

Примером использования ПП может служить scanf: список данных – выходные аргументы, поэтому надо адреса(&), printf: список данных – входные аргументы, поэтому значения, без адресов.

 

 

7.2.2. Передача массивов

 

Фактическим аргументом при передаче массива служит его имя, т.е. передается АДРЕС 1-го элемента (индексы–0) и массив НЕ копируется в локальную память функции.

Это передача ПО ИМЕНИ | ПО ССЫЛКЕ | ПО АДРЕСУ. Такой механизм присущ передаче всех данных в языке FORTRAN.

При описании вида

float a[10][10];

обращение к функции вида: <имя_функции> ( a )

эквивалентно обращению: <имя_функции> ( &a[0][0] ).

Следовательно, массивы-параметры занимают память, отводимую в вызывающей процедуре массивам-аргументам, поэтому в вызываемой процедуре допустимы описания вида:

int myfun ( float b[], a[][10] );

Память не выделяется, выход за пределы памяти, отведенной массивам–аргументам, компилятором НЕ контролируется.

Максимальные значения всех индексов, кроме 1-го, надо задавать, чтобы правильно извлечь из памяти значения нужного элемента массива. Допустимо даже несоответствие размерностей массива-аргумента и массива-параметра только без выхода за пределы оперативной памяти уже выделенной для массива-аргументов.

 

Пример: Аргументы Параметры

float a[5][5], b[36]; float a[], b[][6];

 

Вычислить z = , где {u[i]}, i=1...4; {b[i][j]}, i, j = 1...4.

 

/* Вычисление квадратичной формы */

void main(void) {

float u[4], /* Входной вектор */

b[4][4], /* Входная матрица */

v[4], /* Вектор b*u */

z, /* Результат */

scalar( ); /* Скалярное произведение векторов */

int i, j;

 

printf ( "Исходный вектор: \n" );

for ( i=0; i<4; i++ ) {

scanf ( "%f", &u[i] ); }

printf ( "Исходная матрица: \n" );

for ( i=0; i<4; i++ ) {

for ( j=0; j<4; j++ ){

scanf ( "%f", &b[i][j] );

} }

matrix(b, u, v);

z = scalar(v, u);

 

printf ( "\n \n \n Квадратичная форма равна %.5g \n", z );

} /* End main */

 

/* Умножение матрицы на вектор */

void matrix ( float a[][4], float x[], float y[] ) {

int i, j;

for ( i=0; i<4; i++ ) {

for ( y[i]=j=0; j<4; j++ ) {

y[i] += a[i][j]*x[j];

} }

} /* End matrix */

 

/* Скалярное произведение векторов */

float scalar ( float x[], float y[] ) {

int i;

for ( z = i = 0; i<4; i++ ) {

z += x[i]*y[i];

}

return z;

} /* End scalar */

 









Дата добавления: 2015-06-10; просмотров: 1255;


Поиск по сайту:

При помощи поиска вы сможете найти нужную вам информацию.

Поделитесь с друзьями:

Если вам перенёс пользу информационный материал, или помог в учебе – поделитесь этим сайтом с друзьями и знакомыми.
helpiks.org - Хелпикс.Орг - 2014-2024 год. Материал сайта представляется для ознакомительного и учебного использования. | Поддержка
Генерация страницы за: 0.051 сек.