КаталогИндекс раздела
НазадОглавлениеВперед


Лабораторная работа ╧14

Конструирование программ из нескольких файлов

1. Цель работы

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

2. Темы для предварительной проработки

3. Задания для выполнения

    Для данных, которые обрабатывались в лабораторных работах ╧2 и ╧10, разработать программу, которая обеспечивает:

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

4. Варианты индивидуальных заданий

 1        2        3        4        5        6        7        8        9       10      
11       12       13       14       15       16       17       18       19       20      
21       22       23       24       25       26       27       28       29       30      

5. Пример решения задачи (вариант 30)

    Решение приводится со ссылками на работы ╧2, ╧10.

5.1. Определение основных переменных программы

    Условия задания позволяют использовать в этой работе "наследство" от работы ╧10. И прежде всего это - описания элемента массива, который представляется в виде структуры:

    struct mon { . . . }; 

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

    #define MON struct mon

    #define SMON sizeof(struct mon)

    Так же, как и в работе ╧10, данные в оперативной памяти будут находиться в массиве:

    MON mmm[N]; 

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

    #define N 100

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

    extern MON mmm[]; 

    То же относится и к переменной, которая сохраняет количество информативных элементов в массиве: она должна быть определена в одном модуле:

    int n;    
и описана как внешняя в других:
    extern int n;    

    Предполагается, что этим будет исчерпан список тех структур данных и переменных, которые будут общими для всех модулей программы.

5.2. Разработка программных модулей

    5.2.1. Структура программы
    Ниже приведено описание программы, подобное тем, которые мы давали в предыдущих работах, но тут оно дается отдельно для каждого программного модуля (разработка алгоритма, определение переменных, разработка текста), а для модулей, в которых содержится несколько функций, - для каждой функции.
    Но прежде всего мы должны определить модульный состав программы. Согласно условию, отдельный модуль должна составлять главная функция. Это соответствует некоторым общепринятыми подходами к конструированию программ: в таком модуле размещается программа-монитор, которая управляет порядком выполнения функций программного изделия. При необходимости изменить порядок их выполнения может быть разработан новый модуль-монитор при сохранении других модулей. В мониторе, как в наиболее общей составной части программы, могут быть определены также все глобальные переменные программы. Назовем текстовый модуль этой части программы: L14.C.
    Хотя условия требуют еще только одного модуля, мы введем их два. Разделение функций между первым и вторым модулем произведем по такому принципу: в первом (L14-1.C) определим те функции, которые обращаются к глобальным переменным, а во втором (L14-2.C) - те, которые не зависят от них.
    Сразу же договоримся, что для каждого программного модуля ми сделаем еще и файл-заголовок (L14-1.H и L14-2.H), в котором поместим описания всех функций, которые в этом модуле определены.

    5.2.2. Модуль L14.C
    Прежде всего, в этом модуле должны быть определены глобальные переменные программы и функция main().
    Локальные переменные функции main(): переменная op, в которую будет вводиться код того действия, которое должно выполнятся программой; num - номер записи для тех действий, которые его требуют; eoj - признак конца работы. Хотя алгоритм функции main() очень прост, мы приводим его схему (рисунок 1), потому что в соответствии с методом проектирования "сверху вниз" при его разработке определяются уточненные спецификации для других функций программы.
    Описание алгоритма мы будем вести параллельно с описанием текста программы.
    Начальное значение количества элементов в массиве должно быть 0 (блок 2), в программе это реализовано присваиванием начального значения переменной n. Признак конца устанавливается в 0 (блок 3), остальные блоки алгоритма (4 - 22) образуют цикл, который выполняется, пока этот признак не примет ненулевое значение. В программе начальная установка eoj и проверка предусловия цикла заложены в заголовке цикла for.
    В каждой итерации цикла программа выводит на экран меню - перечисление возможных действий (блок 5). Для удобства оператора каждому действию соответствует числовой код, вводя этот код (блок 6), оператор выбирает нужное действие. В программе это выполнено рядом обращений к функции printf() и вводом при помощи функции scanf() значения переменной op.
    Остальная часть программного кода цикла разветвляется (блок 7) в зависимости от того, какое значение введено в переменную op. Поскольку разветвление имеет несколько ветвей, для его программной реализации удобно применить оператор switch.

    При значении op=0 (в программе - case 0:) достаточно установить в 1 признак конца работы (блок 8), тогда при следующей проверке предусловия произойдет выход из цикла.

Рис.1. Функция main(). Схема алгоритма

    При значении op=1 (в программе - case 1: ) выполняется добавление в массив нового элемента. Для этого надо будет ввести с клавиатуры значения составных частей нового элемента и записать их в память - в первый свободный элемент массива. Вполне возможно, что операция ввода элемента может использоваться отдельно от хранения элементов в массиве. Поэтому предусмотрим функцию ent_data (), которая должна вводить новый элемент с клавиатуры (блок 9). Адрес памяти, по которому надо разместить введенный элемент, будет передаваться этой функции как параметр. В данном случае это будет адрес первого свободного элемента, который вычисляется как начальный адрес массива плюс количество элементов в массиве. Как мы установили еще в работе ╧10, оператор может отказаться от ввода или выполнить его с ошибками. Поэтому функция должна возвращать какое-то значение, из которого будет видно, выполнен ввод или нет, и это значение должно проверятся после обращения к функции (блок 10). Как это сделано во многих функциях библиотек языка C, пусть функция возвращает 0 при нормальном выполнении и какое-то отрицательное число - при ненормальном. Следовательно, условие нормального выполнения функции ent_data() в программе записывается как:

    if (!ent_data(mmm+n)) . . . 

    Если ввод прошел нормально, количество элементов в массиве увеличивается (блок 11, в программе - n++) и на этом заканчивается ветвь выполнения (break) и выполнение итерации цикла.

    При значении op=2 (в программе - case 2:) удаляется элемент из массива. Для этого прежде всего нужно ввести номер того элемента, который будет удаляться (блок 12). Для ввода номера предусмотрим функцию get_number(), которая будет запрашивать у оператора номер и вводить (возвращать) его. Введений оператором номер должен лежать в границах 1 - n, это надо проверить (блок 13). Для проверки предусмотрим функцию check_number(), которой будет передаваться номер, а она будет возвращать 0 или -1 - номер правильный/неправильный. В программе оба обращения и проверка результата закодированы одним оператором:

    if (!check_number(num=get_number())) . . .
который полностью реализует последовательность действий в блоках 12 - 14. Если номер правильный, обращаемся к функции del_item(), которая удалит элемент из массива (блок 15). Этой функции нужно передать введений ранее номер элемента. Поскольку номер гарантированно правильный, никаких вариантов в работе этой функции быть не может, следовательно, нет нужды в возвращении функцией какого-либо значения. После выполнения функции уменьшаем на 1 количество элементов в массиве (блок 16).

    При значении op=3 (в программе - case 3:) нужно вывести значение выбранного элемента. Для этого требуется сначала ввести и проверить номер элемента. Эти элементы схемы алгоритма (блоки 17 - 19) и соответствующая строка текста программы полностью аналогичны соответствующим действиям и для предыдущего случая. При вводе правильного номера обратимся к функции show_1() (блок 20), которая получит параметр - адрес элемента и выведет его на экран. Адрес элемента определяется как: mmm+num-1: имеем в виду, что для оператора нумерация элементов начинается с 1, а в массиве - с 0.

    При значении op=4 (в программе - case 4:) нужно вывести весь массив. Это делаем обращением к функции show_all (блок 21).

    Следует предусмотреть также ввод оператором какого-то непредвиденного кода. В этом случае (в программе - default) следует вывести сообщение про ошибочный код (блок 22) и перейти на следующую итерацию цикла. В тексте программы есть еще несколько строк, которые не предусмотрены в схеме алгоритма: в конце каждой итерации цикла происходит останов до нажатия оператором любой клавиши.

    В начало программного текста включаются (#include) файлы stdio.h, conio.h (в последнем описана функция getch()) и файлы с описаниями собственных функций. Первые два файла находятся в каталоге H-файлов системы программирования, два последние - в текущем каталоге.

    5.2.3. Модуль L14-1.C
    В этом модуле сосредоточены функции, которые иметь доступ к глобальным переменным программы - массива и количества элементов в нем. Поэтому в тексте этого модуля содержится описание внешних переменных, а также определения функций check_number(),del_item(),show_all().
    В начало текста включаются также библиотечные файлы stdio.h, mem.h (описание функции memcpy()) и собственные файлы l14.h (описание структуры данных) и l14-2.h (описание функций пользователя, которые определены в файле l14-2.C).

    5.2.3.1. Функция check_number(). Функция выполняет проверку правильности номера элемента. Ее параметр - номер элемента. Функция возвращает int - 0, если номер элемента правильный, или - если неправильный.
    Функция состоит и двух условных операторов. В первом проверяется, не меньше ли заданный номер 1. Если так, то выдается сообщение про ошибочный номер и функция возвращает -1.
    Во втором условном операторе проверяется, не больше ли заданный номер n - количества элементов в массиве. Если так, выдается сообщение про ошибочный номер и функция возвращает -1.
    Если оба условия не выполняются, функция возвращает 0.

    5.2.3.2. Функция del_item(). Функция выполняет удаление элемента с заданным номером из массива. Ее параметр - номер элемента. Функция не возвращает никакого значения.
    Выполнение функции состоит из единственного оператора цикла, в котором перебираются элементы массива, начиная с заданного. В каждой итерации следующий элемент переписывается на место текущего. Таким образом, заданный элемент пропадает, а все элементы, которые размещались справа от него сдвигаются на одно место влево. Перебор элементов выполняется модификацией адресов. Для перезаписи применяется библиотечная функция memcpy().

    5.2.3.3. Функция show_all(). Функция выполняет вывод на экран всего массива в форме таблицы. Она не имеет параметров и не возвращает значения.
    Выполнение функции начинается с обращения к функции print_head(), которая выводит на экран заголовок таблицы. Далее в функции идет цикл, в котором перебираются все элементы массива, начиная с нулевого. В каждой итерации этого цикла - обращение к функции show_row(), которая выводит одну строку таблицы. Этой функции передается адрес текущего элемента массива. После выхода из цикла мы обращаемся к функции print_line() для вывода нижней линии таблицы.

    5.2.4. Модуль L14-2.C
    Здесь определены функции, которые не зависят от общих переменных.

    В начало текста включаются также библиотечные файлы stdio.h, string.h (описание строковых функций) и собственные файлы l14.h (описание структуры данных) и l14-2.h (описание функций, которые определены ниже).

    5.2.4.1. Функция get_number(). Функция выполняет ввод с клавиатуры номера элемента массива. Она не имеет параметров, а возвращает int - введенное значение.
    Выполнение функции состоит из вывода (printf()) приглашения на ввод и ввода (scanf()) значения.

    5.2.4.2. Функция ent_data(). Функция выполняет ввод с клавиатуры значений для одного элемента массива. Ее параметр - MON* - указатель на структуру-монастырь, в которую вводятся значения а возвращает она int - 0, если ввод произошел, отрицательное число - если нет.
    В основном код функции ent_data() тождественен аналогичным фрагментам кода в работах ╧╧ 2 и 10. Разница состоит в том, что тут мы вводим дополнительную проверку того, что значение кода школы, введенное оператором, лежит в допустимом диапазоне значений.
    Функция возвращает -1, если оператором введено название "***" и -2, если допущена ошибка в коде школы.

    5.2.4.3. Функция show_1(). Функция выполняет вывод на экран одного элемента массива. Ее параметр - MON* - указатель на структуру-монастырь, значения которой выводятся, она не возвращает значения.
    Выполнение функции состоит из ряда обращений к функции printf() для вывода значений всех составляющих структуры. При выводе школы анализируется ее код и, в зависимости от кода, выводится полное название школы. В конце функция обращается к функции print_line() для подчеркивания выведенного текста.

    5.2.4.4. Функция show_row(). Функция выполняет вывод на экран одной строки таблицы. (Фактически, она делает то же самое, что и функция show_1(), но в другом формате). Ее параметр - MON* - указатель на структуру-монастырь, значения которой выводятся, она не возвращает значения.
    Код функции - точная копия оператора вывода строки из работ ╧╧ 2 и 10.

    5.2.4.5. Функция print_line(). Функция выполняет вывод на экран линии, которая подчеркивает таблицу. Она не имеет параметров и не возвращает значения. Функция состоит из единственного обращение к printf().

    5.2.4.6. Функция print_head(). Функция выполняет вывод на экран заголовка таблицы. Она не имеет параметров и не возвращает значения.
    Код функции - точная копия оператора вывод строки из работ ╧╧ 2 и 10.

5.4. Создание программного проекта

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

Рис.2. Проект программы из нескольких модулей

    Файлы, в которых содержится текст программы, - текстовые модули - имеют расширение .C. Каждый текстовый модуль компилируется отдельно. Результатом компиляции является файл - объектный модуль, имя которого совпадает с именем текстового модуля, а расширение - .OBJ. Следующий шаг - компоновка. На этом шаге из нескольких объектных модулей (а также из библиотечных модулей) создается единый загрузочный модуль. Но компоновщик для выполнения своей работы должен "знать", какие именно объектные модули следует включать в программу. Эта информация - перечень модулей, из которых складывается программа, - содержится в отдельном файле-проекте. Этот файл имеет произвольное имя и расширение .PRJ. Результатом работы компоновщика является файл, имя которого совпадает с именем проекта, а расширение - .EXE.

    В системе программирования Borland C++ файл-проект создается самой системой программирования по указаниями программиста. Для создания проекту нужно выбрать: Главное Меню -> Project -> Open Project... Затем на экране появляется окно Open Project File, в котором можно выбрать уже существующий проект, или создать новый проект, вводя новое имя. Когда проект выбран/создан, на экране появляется окно Project: <имя>, в котором отображен перечень модулей в составе проекта. Пользуясь функциональной клавиатурой можно добавить в проект новые модули или удалить какие-то модули.

    Когда проект открыт, компоновщик в своей работе пользуется именно информацией, которая содержится в открытом проекте, независимо от того, какие файлы загружены в окнах редактирования. Для закрытия проекта нужно выбрать: Главное Меню -> Project -> Close Project...

5.5. Текст программы

/*******************************************************/
/*               Лабораторная работа ╧14               */
/*   Конструирование программ из нескольких файлов     */
/*            Пример выполнения. Вариант ╧30.          */
/*******************************************************/
/*                      Файл L14.H                     */
/*******************************************************/
/* Описание структуры, представляющей монастырь */
#define MON struct mon
#define SMON sizeof(struct mon)
struct mon {
  char name[15]; /* название */
  char sc;       /* школа */
  int cnt;       /* количество монахов */
  float sq;      /* площа */
  };

/*******************************************************/
/*               Лабораторнаф работа ╧14               */
/*                    Файл L14-1.H                     */
/*******************************************************/
/* Описания функций файла L14-1.C */
int check_number(int);
void del_item(int);
void show_all(void);


/*******************************************************/
/*               Лабораторная работа ╧14               */
/*                    Файл L14-2.H                     */
/*******************************************************/
/* Описания функций файла L14-2.C */
void print_head(void);
void print_line(void);
void show_row(MON *);
int get_number(void);
void show_1(MON *);
int ent_data(MON *);

/*******************************************************/
/*               Лабораторная работа ╧14               */
/*                     Файл L14.C                      */
/*******************************************************/
#include <stdio.h>
#include <conio.h>
#include "l14.h"
#include "l14-1.h"
#include "l14-2.h"
#define N 100
MON mmm[N]; /* массив-таблица */
int n=0;    /* количество элементов в массиве */

/**** главная функция ****/
int main(void) {
 int op;   /* операция */
 int num;  /* номер элемента */
 char eoj; /* признак конца */
  /*  */
  for (eoj=0; !eoj; ) {
    /* выводення меню */
    printf("1 - Добавить элемент\n");
    printf("2 - Удалить элемент\n");
    printf("3 - Показать элемент по номеру\n");
    printf("4 - Показать все\n");
    printf("0 - Выход\n");
    printf("Вводите >");
    /* вибор из меню  */
    scanf("%d",&op);
    switch(op) {
      case 0: /* выход */
        eoj=1;
        break;

      case 1: /* добавить */
        if (!ent_data(mmm+n)) n++;
        break;
      case 2: /* удалить */
        if (!check_number(num=get_number())) {
          del_item(num);
          n--;
          }
        break;
      case 3: /* показать один */
        if (!check_number(num=get_number())) 
          show_1(mmm+num-1);
        break;
      case 4: /* показать все */
        show_all();
        break;
      default:
        printf("Неправильная операция\n");
        break;
      }  /* switch */
    if (op) {
      printf("Нажмите любую клавишу\n");
      getch();
      } /* if */
    } /* for */
  return 0;
}  /* main */

/*******************************************************/
/*               Лабораторная работа ╧14               */
/*                    Файл L14-1.C                     */
/*******************************************************/
/*  Функции, которые используют глобальные переменные  */
#include <stdio.h>
#include <mem.h>
#include "l14.h"
#include "l14-2.h"
extern MON mmm[];
extern int n;

/**** проверка номера элемента ****/
int check_number(int a) {
  if (a<1) {
    printf("Минимальный номер : 1\n");
    return -1;
    }
  if (a>n) {
    printf("Максимальный номер : %d\n",n);
    return -1;
    }
  return 0;
}

/**** удаление элемента ****/
void del_item(int m) {
 int i;
  for (; m<n; m++)
    memcpy(mmm+m-1,mmm+m,SMON);
}

/**** выво всего массива ****/
void show_all() {
 int i;
  print_head();
  for (i=0; i<n; i++)
    show_row(mmm+i);
  print_line();
}

/*******************************************************/
/*               Лабораторная работа ╧14               */
/*                    Файл L14-2.C                     */
/*******************************************************/
#include <stdio.h>
#include <string.h>
#include "l14.h"
#include "l14-2.h"

/****  ввод номера ****/
int get_number() {
 int b;
  printf("Введите номер>");
  scanf("%d",&b);
  return b;
}

/**** ввод данных про один монастырь ***/
int ent_data(MON *m) {
 float sqx;
  /* Ввод данных */
  printf("Введите название, школу, количество, площадь>");
  scanf("%s %c %d %f",m->name,&m->sc,&m->cnt,&sqx);
  m->sq=sqx;
  if (!strcmp(m->name,"***")) return -1;

  if (strchr("ТСД",m->sc)==NULL) {
    printf("Ошибка\n");
    return -2;
    }
  return 0;

}

/**** вывод данных про один монастырь ***/
void show_1(MON *m) {
  printf("\nНазвание           : %s\n",m->name);
  printf("Школа              : ");
  switch(m->sc) {
    case 'Т': printf("Тендай"); break;
    case 'С': printf("Сингон"); break;


    case 'Д': printf("Дзедзицу"); break;
    }
  printf("\nКоличество монахов : %d\n",m->cnt);
  printf("Площадь земель     : %6.2f\n",m->sq);
  print_line();
}

/**** вывод строки таблицы ****/
void show_row(MON *m) {
  printf("| %9s |   %c   |       %3d | %-5.1f       |\n",
    m->name,m->sc,m->cnt,m->sq);
}

/**** вывод подчеркивания ****/
void print_line() {
  printf("-----------------------------------------------\n");
  }

/**** вывод заголовка таблицы ****/
void print_head() {
  print_line();
  printf("|Буддийские монастыри Японии периода Нара     |\n");
  printf("|---------------------------------------------|\n");
  printf("| Название  | Школа | Количество| Площадь     |\n");
  printf("|           |       | монахов   | земель (га) |\n");
  printf("|-----------|-------|-----------|-------------|\n");
}

5.4. Отладка программы

    Отладку программы следует вести поэтапно - "сверху вниз": сначала убедиться в правильном выполнении монитора (функции main()), который управляет порядком выполнения остальных функций, а потом по очереди проверять остальные функции, начиная, конечно, с функции ввода значений. При проверке работы удаления необходимо обязательно убедиться в правильном его функционировании при удалении первого и последнего элементов.

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

5.5. Результаты работы программы

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

5.6. Выводы

    При выполнении лабораторной работы изучены вопросы:


КаталогИндекс раздела
НазадОглавлениеВперед