Модульность – это возможность разбивать программу на небольшие логически законченные фрагменты.
Трудные задачи часто разбиваются на отдельные логически законченные куски, которые связываются по данным, и выполняются различными людьми. Каждый фрагмент можно создавать независимо от других.
Повторяемость – это возможность в различных местах пользоваться одним и тем же нужным фрагментом без его повторного написания.
В таких случаях каждый раз вызывается одна и та же подпрограмма.
Рассмотрим простейшие подпрограммы.
Заданы 2 целых числа, используя подпрограмму, определить максимальное из них.
#include<stdio.h>
int max(int a, int b)
{
if(a>b) return a;
else return b;
}
main()
{
int m, n, maxim;
scanf (“%d”, &m);
scanf (“%d”, &n);
maxim=max(m, n); // вызов функции max
printf (“максимальное число = %d “, maxim);
}
Заданы два целых числа, поменять местами их значения.
#include<stdio.h>
void change (int a, int b)
{
int x;
x=a; a=b; b=x; // обмен значениями а и b через промежуточную переменную – х.
}
main ()
int a=5, b=10;
printf (“%d %d”, a, b);
change (a, b);
printf (“\n%d %d”, a, b);
|
|
}
На экране вы увидите неожиданную вещь:
5 10
5 10
Значения переменных не переставляются! Где же ошибка? Если вставить печать в функцию change, то увидим, что там все в порядке и значения a и b переставляются. Почему же в main не изменилось ни чего? Для того чтобы это понять, нужно знать, как осуществляется движение информации в подпрограмму и обратно. Разберемся в этом..
В момент вызова функции, значение фактического параметра - а присваивается первому формальному параметру. Переменные - а в main и – а в change физически совершенно разные, между ними нет никакой связи после заполнения - а из change в момент вызова. Поэтому их можно обозначать разными именами. Совершенно аналогично для переменной - b. После выполнения перестановки нужно вернуть значения двух переменных, но это невозможно результат может быть только один. Т.е. в этом случае назад в главную подпрограмму ничего не возвращается, поэтому значения a и b в ней не изменяются.
Исправим эту ошибку. В главной функции main изменится только вызов функции:
change (&a, &b);
что означает передать в подпрограмму не значения переменных –а и b, а их адреса.
Функция будет выглядеть по другому:
void change (int *a, int *b)
{
int x;
x=*a; *a=*b; *b=x; // перестановка значений в главной программе через их адреса.
}
В этом случае работа в подпрограмме change производится не с формальными параметрами, а с фактическими через их адреса, т.е. из функции change мы ссылаемся на место в памяти отведенное на а и b в main.
Интересные примеры работы с подпрограммами при использовании векторов и матриц.
Обнулить вектор при помощи функции.
#include<stdio.h>
|
|
void arrz (int [], int); // декларация функции
void main (void)
{
int arr[10], i;
for (i=0: i<10; i++)
scanf (“%d”, &arr[i]);
arrz (arr,10); // вызов функции
for (i=0: i<10; i++)
printf (“%d”, &arr[i]);
}
void arrz (int array[], int num) // заголовок функции
{
int i;
for (i=0: i<num; i++)
array[i]=0;
}
В данном случае функция arrz тоже работает с адресом вектора arr[10], расположенного в main. И обнуляет она именно его, поэтому функция arrz не имеет результата, а имеет эффект.
При работе с матрицей в подпрограмме нужно соблюдать большую осторожность. Необходимо указывать структуру матрицы, хотя бы в одном индексе array[10][], или работать через адрес, для матрицы этот формальный параметр будет выглядеть так int **array. Подробнее об этом можно прочесть у Т.А. Павловской, С/С++ программирование на языке высокого уровни, С-П, 2001, стр.78.
Рассмотрим пример: каждую из произвольного количества матриц разной размерности транспонировать.
#include<stdio.h>
void mtran(int matr[10][], int n)
{
int i, j, k;
for (i=0; i<n; i++)
for (j=i+1; j<n; j++)
{
k=matr[i][j];
matr[i]j]=matr[j][i];
matr[j]i]=k;
}
}
void main (void)
{
int arr[10][10], i, j, k, n;
do // цикл для обработки произвольного количества матриц «пока не надоест»
{
printf(“ введите размерность матрицы “);
scanf(“%d”, &n);
printf(“ введите матрицу \n “);
for (i=0; i<n; i++)
for (j=0; j<n; j++)
scanf(“%d”, &arr[i][j]);
mtran(arr, n); // вызов функции, транспонирующей матрицу
printf(“ транспонированная матрица \n“);
for (i=0; i<n; i++)
{
for (j=0; j<n; j++)
printf(“%d”, arr[i][j]);
printf(“ \n“);
}
printf(“ повторить? 1-да, 0-нет “);
scanf(“%d”, &k);
while (k==1);
}
}
ДИРЕКТИВА ПРЕПРОЦЕССОРА #define.
Мы уже сталкивались с директивами препроцессору #include для включения информации из другого файла в программу. При помощи директивы препроцессора #define можно задавать константы.
#define PI 3.14
При компиляции программы каждый раз вместо PI будет подставлено ее значение. Знак; не ставится, т.к. это не оператор языка С. Использовать эту директиву можно для любых констант, даже для звукового сигнала ‘007’, и признака конца -‘\0’.
#define BEEP ‘007’
#define NULL ‘\0’
При чем использовать эти константы можно в любой подпрограмме, они являются глобальными.
Могут быть и такие макроопределения:
#define FMT “ x %d \n”
далее в программе к нему обращаются так:
printf (FMT, x);
Макроопределения еще их называют - макросами – это текстовые подстановки, происходящие до компиляции программы. Вы заметили, что все макросы пишутся с большой буквы – это договоренность между программистами. Если видишь, что-либо написанное с большой буквы, ищи определение объекта как макроса в директиве препроцессора #define.
Хотя это требование не правило, если вы напишите с маленькой буквы, все будет работать.
Могут быть макроопределения и с параметрами:
#define SQR (X) X*X
далее в программе к нему обращаются так:
x=4; y=12;
z1= SQR(x);
z2= SQR(y);
Могут быть и более сложные условные макросы:
#define MAX(X, Y) ((X) > (Y)? (X): (Y))
#define ABS (X) (X < 0)? -(X): (X))
далее в программе к ним обращаются так:
y=2*MAX(a+b, r+c);
z= ABS ((x-y)/2.);
ОБЛАСТИ ВИДИМОСТИ.
Рассмотрим программу из двух функций:
#include …
void func (int *a, float b) // в скобках формальные параметры
{ int x, y; // локальные переменные
…
}
main()
{
int x, y, z; // y и z локальные переменные
float b;
…
func(&x, b); // в скобках фактические параметры
}