Лекция № 9

Тема: «Модульное программирование»

План.

1. Основные понятия

2. Директивы препроцессора

3. Особенности использования глобальных переменных

 

1. Основные понятия

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

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

Преимущества использования модулей:

1. Разбиение полного текста программы на несколько файлов.

2. Использование как заготовки для других программ.

 

Модульность в языке C++ поддерживается с помощью директив препроцессора, пространств имен, классов памяти, исключений и раздельной компиляции (строго говоря, раздельная компиляция не является элементом языка, а относится к его реализации). (начало)

 

2. Директивы препроцессора

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

Директивы – это специальные команды, которые начинаются с символа # и НЕ заканчиваются точкой с запятой. Есть несколько типов директив, которые мы рассмотрим ниже.

 

Заголовочные файлы включаются в текст программы с помощью директивы препроцессора #include. Директивы препроцессора начинаются со знака "диез" (#), который должен быть самым первым символом строки. Программа, которая обрабатывает эти директивы, называется препроцессором (в современных компиляторах препроцессор обычно является частью самого компилятора).

Директива #include включает в программу содержимое указанного файла. Имя файла может быть указано двумя способами:

 

#include <some_file.h>

#include "my_file.h"

 

#include <some_file.h> сообщает препроцессору искать некий стандартный заголовочный файл в системных путях. Компилятор ищет этот файл в предопределенных местах. (Способ определения этих мест сильно различается для разных платформ и реализаций).

#include "my_file.h" означает, что заголовочный файл – пользовательский, и его поиск начинается с того каталога, где находится исходный текст программы.

Заголовочный файл также может содержать директивы #include. Поэтому иногда трудно понять, какие же конкретно заголовочные файлы включены в данный исходный текст, и некоторые заголовочные файлы могут оказаться включенными несколько раз. Избежать этого позволяют условные директивы препроцессора. Рассмотрим пример:

 

#ifndef MYFILE_H    //если не объявлена константа MYFILE_H

#define MYFILE_H    //объявляем её и продолжаем обрабатывать заголовочный файл до конца блока проверки

 

/* содержимое файла myfile.h */

 

#endif              //конец блока проверки

 

Условная директива #ifndef (англ. «if not defined» = «если не определено») проверяет, не было ли значение MYFILE_H определено ранее. (MYFILE_H – это константа препроцессора; такие константы принято писать заглавными буквами.) Препроцессор обрабатывает следующие строки вплоть до директивы #endif. В противном случае он пропускает строки от  #ifndef  до  #endif.

 

Директива

 

#define MYFILE_H

 

определяет константу препроцессора MYFILE_H. Поместив эту директиву непосредственно после директивы #ifndef, мы можем гарантировать, что содержательная часть заголовочного файла myfile.h будет включена в исходный текст только один раз, сколько бы раз ни включался в текст сам этот файл.

 

Пример. Пусть имеется программа вычисления факториала числа.

 

//подключение заголовочного файла               

#include <iostream.h>                       

//прототип функции вычисления факториала

long int fact(int n);                                           

 

//главная программа

int main()                                  

{                                           

     system("chcp 1251>nul");                   

     int n;

     cout<<"Введите число: ";

     cin>>n;

     //вывод результата вычисления факториала                       

     cout<<n<<"!="<<fact(n)<<endl;                                               

     system("pause");                       

     //завершение работы программы   

     return 0;                                  

}

 

//реализация функции вычисления факториала

long int fact(int n)

{

     //начальное значение для значения факториала

     long int rez=1;

     //цикл подсчета произведения

     for(int i=1; i<=n; i++)                                              

         rez*=i;                   

     //вернуть результат в главную программу

     return rez;

}

 

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

 

Выносим функцию для вычисления факториала в отдельный модуль mod1.h

 

//если не объявлена константа MOD1_H

#ifndef MOD1_H           

//объявляем ее

#define MOD1_H           

 

//прототип функции вычисления факториала

long int fact(int n);

 

//реализация функции вычисления факториала

long int fact(int n)

{

     //начальное значение для значения факториала

     long int rez=1;

     //цикл подсчета произведения

     for(int i=1; i<=n; i++)                                               

         rez*=i;                   

     //вернуть результат в главную программу

     return rez;

}

//конец блока проверки

#endif       

 

Главная программа имеет вид:

//подключение заголовочного файла               

#include <iostream.h>                       

//подключение пользовательского заголовочного файла               

#include "mod1.h"

 

//главная программа

int main()                                  

{                                           

     system("chcp 1251>nul");                    

     int n;

     cout<<"Введите число: ";

     cin>>n;

     //вывод результата вычисления факториала                       

     cout<<n<<"!="<<fact(n)<<endl;                                               

     system("pause");                       

     //завершение работы программы    

     return 0;                                  

}

 

Пример. Дан одномерный массив. Реализовать следующее:

·      ввод элементов массива;

·      вывод элементов массива на печать;

·      вычисление суммы элементов массива.

Создать главную программу main.cpp. В программе подключить три заголовочных файла с именами vvod.h, vivod.h и summ.h. В первом файле описать функцию заполнения элементов одномерного массива, во втором – функцию вывода элементов массива, в третьем – функцию расчета суммы элементов.

 

Модуль vvod.h для заполнения массива будет иметь вид:

//если константа MOD1_H не объявлена

#ifndef MOD1_H

//объявляем константу MOD1_H

#define MOD1_H

//прототип функции заполнения массива

void formmas(float *m,int n);

//реализация функции заполнения массива

void formmas (float *m, int n)

{

//локальные переменные для диапазона формирования массива

float a,b;

//локальная переменная номер элемента в массиве

int i;

cout<<"Введите диапазон (a, b): ";

cin>>a>>b;

//цикл заполнения массива случайными числами

for (i=0;i<=n-1;i++)

         m[i]=(b-a)*rand()/32767+a;

//завершение подпрограммы

return;

}

//конец блока проверки

#endif

 

Модуль vivod.h для вывода элементов массива на печать будет иметь вид:

//если константа MOD2_H не объявлена

#ifndef MOD2_H

//объявляем константу MOD2_H

#define MOD2_H

//прототип функции вывода элементов массива на экран

void vivodmas(float *m,int n);

//реализация функции вывода элементов массива на экран

void vivodmas (float *m, int n)

{

//локальная переменная номер элемента в массиве

int i;

//указываем число цифр в выводимом числе – элементе массива

cout.precision(3);

//цикл вывода элементов на экран

for (i=0;i<=n-1;i++)

         cout<<m[i]<<" ";

cout<<"\n";

return;

}

//конец блока проверки

#endif

 

Модуль summ.h для вычисления суммы элементов массива будет иметь вид:

//если константа MOD3_H не объявлена

#ifndef MOD3_H

//объявляем константу MOD3_H

#define MOD3_H

//прототип функции расчета суммы элементов массива

float summa(float *m,int n);

//реализация функции расчета суммы элементов массива

float summa (float *m, int n)

{

//локальная переменная номер элемента в массиве

int i;

//локальная переменная сумма элементов массива

float sum=0;

//цикл расчета суммы

for (i=0;i<=n-1;i++)

     sum+=m[i];

//вернуть значение суммы в главную программу

return sum;

}

//конец блока проверки

#endif

 

Главная программа main.cpp содержит следующий текст:

//подключение заголовочных файлов

#include <iostream.h>                       

//подключение созданных файлов из текущей папки

#include "mod1.h"

#include "mod2.h"

#include "mod3.h"

//главная программа

int main()                                  

{                                           

     system("chcp 1251>nul");                   

     //переменная количество элементов массива

int n;

//одномерный динамический массив вещественных чисел

     float *mas;

     cout<<"Введите количество элементов массива n: ";  

     cin>>n;                    

     //выделяем память под n элементов массива

mas = new float[n];

     //вызываем функцию заполнения массива

     formmas(mas,n);   

     //вызываем функцию вывода элементов массива на экран

     vivodmas(mas,n);  

     //выводим результат вычисления суммы массива

     cout<<"Сумма элементов массива = "<<summa(mas,n)<<endl;

     //пауза для просмотра результата

     system("pause");

//завершение работы программы

     return 0;                                  

}                                            

(начало)

3. Особенности использования глобальных переменных

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

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

·       Если в файле a.cpp объявлена переменная за пределами любой функции (например, так: int count), то она является глобальной для всех файлов проекта. Чтобы получить значение этой переменной в файле aa.cpp достаточно просто указать ее имя (если в функции нет локальной переменной с тем же именем). Чтобы получить значение из других файлов, надо указать, что имеется в виду глобальная переменная, а не локальная. Делается это с помощью ключевого слова extern (например, extern count).

·       Бывают ситуации, когда в одном файле для нескольких содержащихся в нем функций нужна глобальная переменная. Но эта переменная не должна быть доступна функциям, содержащимся в других файлах. В таком случае глобальная переменная объявляется с ключевым словом static (например, static int count). Тем самым мы как бы скрываем глобальную переменную.

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

1.    Объявите глобальную переменную в одном файле, а получите ее значение в другом файле (выведите на экран).

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

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