Опубликован: 16.09.2005 | Уровень: для всех | Доступ: свободно | ВУЗ: Московский государственный университет имени М.В.Ломоносова
Лекция 10:

Технология программирования на Си: представление матриц, работа с файлами и с текстами

< Лекция 9 || Лекция 10: 123 || Лекция 11 >

Работа с текстами

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

  1. функции, определяющие тип символа, - является ли он буквой, цифрой, пробелом, знаком препинания и т.п. Эти функции описаны в стандартном заголовочном файле "ctype.h". Увы, функции, касающиеся букв, работают только для латинского алфавита;
  2. функции для работы с текстовыми строками. Строкой в Си считается последовательность байтов, ограниченная в конце нулевым байтом. Функции работы со строками описаны в стандартном заголовочном файле "string.h".

Определение типов символов

Библиотека Си предоставляет следующие функции для определения типа символов, описанные в стандартном заголовочном файле "ctype.h":

int isdigit(int c); символ c - цифра;
int isalpha(int c); c - латинская буква;
int isspace(int c); c - пробел, перевод строки и т.п.;
int ispunkt(int c); c - знак препинания;
int isupper(int c); c - прописная латинская буква;
int islower(int c); c - строчная латинская буква;
int toupper(int c); если c -- лат. буква, то преобразовать c к прописной букве;
int tolower(int c); если c -- лат. буква, то преобразовать c к строчной букве.

Функции, начинающиеся с префикса is, возвращают ненулевое значение (т.е. истину), если символ с кодом c принадлежит указанному классу, и нулевое значение (ложь) в противном случае. Функции toupper и tolower преобразуют латинские буквы к верхнему или нижнему регистру, на остальных символах они действуют тождественно.

В качестве примера использования функции isspace модифицируем программу "wc.cpp", подсчитывающую число строк и символов в текстовом файле. Добавим в нее подсчет слов. Будем считать словами любые связные группы символов, разделенные пробелами, табуляциями или разделителями строк.

//
// Файл "wc2.cpp"
// Подсчет числа символов, слов и строк в текстовом файле
//
#include <stdio.h>  // Описания функций ввода-вывода
#include <ctype.h>  // Описания типов символов

int main() {
    char fileName[256]; // Путь к файлу
    FILE *f;            // Структура, описывающая файл
    int c;              // Код введенного символа
    int numChars = 0;   // Суммарное число символов := 0
    int numLines = 0;   // Суммарное число строк := 0
    int numWords = 0;   // Суммарное число слов := 0
    bool readingWord = false; // Читаем слово := false

    printf("Введите имя файла: ");
    scanf("%s", fileName);

    f = fopen(fileName, "rb"); // Открываем файл
    if (f == 0) { // При ошибке открытия файла
        // Напечатать сообщение об ошибке
        perror("Не могу открыть файл для чтения");
        return 1; // закончить работу программы с кодом 1
    }

    while ((c = fgetc(f)) != EOF) { // Читаем символ
        // Цикл продолжается, пока c != -1 (конец файла)

        ++numChars; // Увеличиваем число символов

        // Подсчитываем число символов перевода строки
        if (c == '\n') {
            ++numLines; // Увеличиваем число строк
        }

        // Подсчитываем число слов
        if (!isspace(c)) {      // если c не пробел
            if (!readingWord) { //  если не читаем слово
                ++numWords;     //   увеличить число слов
                readingWord = true; // читаем слово:=true
            }                   //  конец если
        } else {                // иначе
            readingWord = false;//  читаем слово:=false
        }                       // конец если
    }

    fclose(f);

    // Печатаем результат
    printf("Число символов в файле = %d\n", numChars);
    printf("Число слов = %d\n", numWords);
    printf("Число строк = %d\n", numLines);

    return 0; // Возвращаем код успешного завершения
}

Пример выполнения программы wc2 в применении к собственному тексту, записанному в файле "wc2.cpp":

Введите имя файла: wc2.cpp
Число символов в файле = 1998
Число слов = 260
Число строк = 57

Работа с текстовыми строками

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

Определение длины строки

size_t strlen(const char *s); длина строки.

Копирование строк

char *strcpy(char *dst, const char *src); копировать строку src в строку dst ;

char *strncpy(char *dst, const char *src, size_t maxlen); копировать строку src в dst, не более maxlen символов;

char *strcat(char *dst, const char *src); копировать строку src в конец dst (конкатенация строк).

Работа с произвольными массивами байтов

void *memmove(void *dst, const void *src, size_t len); копировать область памяти с адресом src размером len байтов в область памяти с адресом dst ;

void *memset(void *dst, int value, size_t len); записать значение value в каждый из len байтов, начиная с адреса dst.

Сравнение строк

int strcmp(const char *s1, const char *s2); лексикографическое сравнение строк s1 и s2. Результат нулевой, если строки равны, отрицательный, если первая строка меньше второй, и положительный, если первая строка больше второй;

int strncmp(const char *s1, const char *s2, size_t maxlen); сравнение строк s1 и s2, сравнивается не более maxlen символов;

int memcmp(const void *m1, const void *m2, size_t len); сравнение областей памяти с адресами m1 и m2 размером len каждая.

Поиск

char *strchr(const char *s, int c); найти первое вхождение символа c в строку s. Функция возвращает указатель на найденный символ или ноль в случае неудачи;

char *strstr(const char *s1, const char *s2); найти первое вхождение строки s2 в строку s1. Функция возвращает указатель на найденную подстроку в s1, равную строке s2, или ноль в случае неудачи.

Пример: программа "Записная книжка"

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

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

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

Для записей используется структурный тип NameRecord, определенный следующим образом:

typedef struct {
   char *name;
    char *phone;
} NameRecord;

Здесь поле name структуры является указателем на имя абонента, которое представляет собой текстовую строку. Номер телефона также задается строкой, указатель на которую записывается в поле phone. Пространство под строки захватывается в динамической памяти с помощью функции malloc. При удалении записи память освобождается с помощью функции free.

Записи хранятся в массиве records:

const int MAXNAMES = 1000;
static NameRecord records[MAXNAMES];
static int numRecords = 0;

Константа MAXNAMES задает максимально возможный размер массива records. Текущее количество записей (т.е. реальный размер массива) хранится в переменной numRecords.

В начале работы программа вводит содержимое записной книжки из файла "NoteBook.dat, имя которого задается как константный указатель на константную строку:

const char* const NoteBookFile = "NoteBook.dat";

В конце работы содержимое записной книжки сохраняется в файле. Для ввода используется функция loadNoteBook, для сохранения - функция saveNoteBook с прототипами

static bool loadNoteBook();
static bool saveNoteBook();

Каждой записи соответствует пара строк файла. Пусть, например, имя абонента "Иван Петров", телефон - "123-45-67". Этой записи соответствует пара строк

name=Иван Петров
phone=123-45-67

Записи в файле разделяются пустыми строками.

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

static bool modified;

которая принимает значение true, если хотя бы раз была выполнена одна из команд, меняющих содержимое записной книжки.

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

  • add - добавить запись. Программа просит ввести имя абонента, если оно не указано непосредственно после команды, затем его телефон, после этого запись добавляется в записную книжку, или телефон изменяется, если данное имя уже содержится в книжке;
  • remove - удалить запись. Программа просит ввести имя абонента, если оно не указано непосредственно после команды, и удаляет запись из книжки, если она там присутствует;
  • find - найти запись. Программа при необходимости просит ввести имя абонента и печатает либо его телефон, либо сообщение, что данного имени в книжке нет;
  • modify - изменить номер телефона. Программа просит ввести имя абонента, если оно не указано непосредственно после команды. Затем осуществляется поиск записи с данным именем. В случае успеха программа просит ввести новый номер телефона, после этого старый номер заменяется на новый; в противном случае, печатается сообщение, что имя не содержится в книжке;
  • show - напечатать записи, для которых имена начинаются с данного префикса. Если имя или начало имени не указано непосредственно после команды, то печатается все содержимое записной книжки. Если указано начало имени, то печатаются все записи, для которых начало имени совпадает с заданным;
  • help - напечатать подсказку;
  • quit - закончить работу.

Каждой команде соответствует отдельная функция, выполняющая команду. Например, команде add соответствует функция onAdd, команде find - функция onFind и т.д. Начало искомого имени и его длина, если имя указано в командной строке, передаются через статические переменные

static char namePrefix[256];
    static int namePrefixLen;

Если имя не указано в командной строке, то для его ввода вызывается функция readName, которая просит пользователя ввести имя с клавиатуры, считывает имя и заполняет переменные namePrefix и namePrefixLen.

Для поиска записи с заданным началом имени используется функция search с прототипом

static int search(
    const char *namePrefix, // Начало искомого имени
    int startPosition       // Позиция начала поиска
);

Ей передается начало искомого имени и индекс элемента массива, с которого начинается поиск. Второй аргумент нужен для того, чтобы последовательно находить имена с одинаковым префиксом (например, имена, начинающиеся с заданной буквы). Функция возвращает индекс найденного элемента в случае успеха или отрицательное значение -1 при неудаче. Применяется последовательный поиск, при котором просматриваются все элементы, начиная со стартовой позиции. Для сравнения имен используется стандартная функция strncmp(s1, s2, n), которая сравнивает n первых символов строк s1 и s2. При поиске в качестве s1 используется заданное начало имени, в качестве s2 -- очередное имя из записной книжке, n равно длине начала имени.

Для ввода строки с клавиатуры мы используем вспомогательную функцию readLine с прототипом

static bool readLine(
    char *buffer, int maxlen, int *len
);

Функция вводит строку из стандартного входного потока, используя библиотечную функцию fgets. Затем из конца строки удаляются символы-разделители строк " \r " и " \n ". Введенная строка помещается в массив buffer с максимальной длиной maxlen. Реальная длина введенной строки записывается в переменную, адрес которой передается через указатель len.

Полный текст программы:

// Файл "NoteBook.cpp"
// Программа "Записная книжка"
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

typedef struct {
    char *name;  // Указатель на имя
    char *phone; // Указатель на телефон
} NameRecord;

// Максимальный размер записной книжки
const int MAXNAMES = 1000;

// Массив записей
static NameRecord records[MAXNAMES];

// Текущее количество записей в книжке
static int numRecords = 0;

// Имя файла для сохранения содержимого книжки
const char* const NoteBookFile = "NoteBook.dat";

static bool modified;
static char namePrefix[256]; // Начало имени,
static int namePrefixLen;    //   его длина
static char phone[256];      // Телефон,
static int phoneLen;         //   его длина

// Прототипы функций
static bool loadNoteBook(); // Загрузить книжку из файла
static bool saveNoteBook(); // Сохранить книжку в файле
static bool readLine(       // Ввести строку с клавиатуры
    char *line, int maxlen, int *len
);
static bool readName();     // Ввести имя с клавиатуры

static int search(          // Поиск имени в массиве
    const char *namePrefix, //   начало искомого имени
    int startPosition       //   позиция начала поиска
);

static void releaseMemory(); // Освободить память

// Прототипы функций, реализующих команды
static void onAdd();
static void onRemove();
static void onFind();
static void onModify();
static void onShow();
static void onHelp();

int main() {
    char line[256];      // Введенная строка
    int lineLen;         // Длина строки
    int comBeg,          // Индексы начала
        comEnd;          //     и за-конца команды
    int comLen;          // Длина команды
    const char *command; // Указатель на начало команды
    int nameBeg;         // Индекс начала имени
    int i;               // Индекс в массиве line

    printf("Программа \"Записная книжка\"\n");
    onHelp();           // Напечатать подсказку

    loadNoteBook(); // Загрузить содержимое книжки
                    //           из файла
    while (true) {
        printf(">");    // Приглашение на ввод команды

        // Ввести командную строку
        if (!readLine(line, 256, &lineLen)) {
            break;  // При ошибке завершить программу
        }

        // Разбор командной строки
        // 1. Пропустить пробелы в начале строки
        i = 0;
        while (i < lineLen && isspace(line[i])) {
            ++i;
        }

        // 2. Выделить команду
        comBeg = i;
        while (i < lineLen && isalpha(line[i])) {
            ++i;
        }
        comEnd = i;

        command = line + comBeg;  // Указ.начала команды
        comLen = comEnd - comBeg; // ее длина
        if (comLen <= 0)          // Команда пустая =>
            continue;             //   ввести следующую

        // 3. Выделить префикс имени
        i = comEnd;
        // Пропустить пробелы перед именем
        while (i < lineLen && isspace(line[i])) {
            ++i;
        }
        nameBeg = i;
        if (nameBeg >= lineLen) {
            // Имя не указано в команде
            namePrefix[0] = 0; // Запомним, что имя
            namePrefixLen = 0; //            пустое
        } else {
            // Имя задано в команде, запомним его.
            // Указ. на начало имени равен line+nameBeg,
            // длина имени равна lineLen-nameBeg.
            strcpy(namePrefix, line + nameBeg);
            namePrefixLen = lineLen - nameBeg;
        }

        // Разбор строки закончен.
        // Вызовем функцию, реализующую команду
        if (strncmp(command, "add", comLen) == 0) {
            onAdd();
        } else if (
            strncmp(command, "remove", comLen) == 0
        ) {
            onRemove();
        } else if (
            strncmp(command, "find", comLen) == 0
        ) {
            onFind();
        } else if (
            strncmp(command, "modify", comLen) == 0
        ) {
            onModify();
        } else if (
            strncmp(command, "show", comLen) == 0
        ) {
            onShow();
        } else if (
            strncmp(command, "help", comLen) == 0
        ) {
            onHelp();
        } else if (
            strncmp(command, "quit", comLen) == 0
        ) {
            break;      // Завершить работу
        } else {        // Неправильная команда =>
            onHelp();   //    напечатать подсказку
        }
    } // конец цикла while

    if (modified) {     // Если книжка модифицирована,
        saveNoteBook(); // то сохранить ее содержимое
    }

    releaseMemory(); // Освободить память
    return 0;   // Завершить программу с кодом успеха
}

static bool readLine( // Считать строку с клавиатуры
    char *line, int maxlen, int *len
) {
    int size;

    *line = 0; *len = 0; // Инициализация пустой строкой
    if (fgets(line, maxlen, stdin) == 0)
        return false;    // Ошибка ввода

    size = strlen(line); // Длина введенной строки

    // Удалить разделители строк из конца строки
    if (size > 0 && line[size - 1] == '\n') {
        line[size - 1] = 0; --size;
    }
    if (size > 0 && line[size - 1] == '\r') {
        line[size - 1] = 0; --size;
    }

    *len = size;    // Выдать длину строки
    return true;
}

static int search(          // Поиск имени в массиве
    const char *namePrefix, //  искомый префикс имени
    int startPosition       //  позиция начала поиска
) {
    int i = startPosition;
    int len = strlen(namePrefix);

    if (len == 0)
        return startPosition;

    while (i < numRecords) {
        if (
            strncmp( // Сравнить имя и префикс
                records[i].name, namePrefix, len
            ) == 0
        ) {
            return i;   // Нашли имя с данным префиксом
        }
        ++i;            // К следующей записи
    }
    return (-1);    // Имя не найдено
}

static bool readName() {    // Ввести имя с клавиатуры
    int size;
    printf("Введите имя:\n");
    return readLine(namePrefix, 256, &namePrefixLen);
}

static void onAdd() { // Добавить или изменить запись
    int i;

    if (namePrefixLen == 0) { // Если имя не задано в
        if (!readName()) {    // команде, то ввести его
            return;           // Ошибка ввода
        }
    }

    if (namePrefixLen == 0) {
        return;
    }

    // Ищем имя в книжке
    i = search(namePrefix, 0);
    if (i < 0) {
        // Имя не содержится в книжке, добавим его
        if (numRecords >= MAXNAMES) {
            printf("Переполнение книжки.\n");
            return;
        }
        i = numRecords; ++numRecords;

        // Захватим память под имя
        records[i].name = (char *)
            malloc(namePrefixLen + 1);
        // Запишем имя в книжку
        strcpy(records[i].name, namePrefix);
    }

    printf("Введите телефон:\n");
    readLine(phone, 256, &phoneLen);

    // Захватим память под телефон
    records[i].phone = (char *) malloc(phoneLen + 1);
    // Запишем телефон
    strcpy(records[i].phone, phone);

    modified = true; // Запомним, что содержимое менялось
}

static void onRemove() {    // Удалить запись
    int i;

    if (namePrefixLen == 0) { // Если имя не задано в
        if (!readName()) {    // команде, то ввести его
            return;           // Ошибка ввода
        }
    }

    if (namePrefixLen == 0) {
        return;
    }

    // Ищем имя в книжке
    i = search(namePrefix, 0);
    if (i < 0) { // Если имя не содержится в книжке,
        return;  // то ничего не делать
    }

    // Освободим память
    free(records[i].name);
    free(records[i].phone);

    // Перепишем последнюю запись на место удаляемой
    if (i >= 2 && i != numRecords - 1) {
        records[i] = records[numRecords - 1];
    }
    --numRecords;    // Уменьшим число записей

    modified = true; // Запомним, что содержимое менялось
}

static void onFind() {      // Найти запись
    int i;
    if (namePrefixLen == 0) {
        if (!readName()) {
            return; // Ошибка ввода
        }
    }
    i = search(namePrefix, 0);
    if (i < 0) {
        printf("Имя не найдено.\n");
    } else {
        printf("Имя: %s\n", records[i].name);
        printf("Телефон: %s\n", records[i].phone);
    }
}

static void onModify() { // Изменить номер телефона
    int i;
    if (namePrefixLen == 0) {
        if (!readName()) {
            return; // Ошибка ввода
        }
    }
    if (namePrefixLen == 0) {
        return;
    }

    // Ищем имя в книжке
    i = search(namePrefix, 0);
    if (i < 0) {
        printf("Имя не найдено.\n");
    } else {
        onAdd(); // Добавление модифицирует телефон,
    }            //     когда имя уже в книжке
}

// Показать все записи с данным префиксом
static void onShow() {
    int pos = 0;
    while (pos < numRecords) {
        if (namePrefixLen > 0) {
            pos = search(namePrefix, pos);
            if (pos < 0) {  // Имя не найдено =>
                break;      // выйти из цикла
            }
        }
        printf("Имя: %s\n", records[pos].name);
        printf("Телефон: %s\n\n", records[pos].phone);
        ++pos;
    }
}

static void onHelp() {
    printf(
        "Список команд:\n"
        "  add    добавить пару (имя, телефон)\n"
        "  remove удалить имя\n"
        "  find   найти имя и напечатать телефон\n"
        "  modify изменить телефон\n"
        "  show   напечатать записи с данным префиксом\n"
        "         (если префикс пустой, то все записи)\n"
        "  help   напечатать этот текст\n"
        "  quit   закончить работу\n"
    );
    printf("Введите команду и (не обязательно) имя.\n");
}

static bool loadNoteBook() { // Загрузить книжку из файла
    char line[256]; // Буфер для ввода строки
    int i;
    FILE *f = fopen(NoteBookFile, "rt");
    if (f == 0) {
        return false;
    }
    numRecords = 0;
    while (fgets(line, 256, f) != 0) {
        int len = strlen(line);
        char *name;
        char *phone = 0;

        // Удалим разделители строк
        if (len > 0 && line[len - 1] == '\n') {
            line[len - 1] = 0; --len;
        }
        if (len > 0 && line[len - 1] == '\r') {
            line[len - 1] = 0; --len;
        }

        if (len < 6 || strncmp(line, "name=", 5) != 0) {
            continue;   // К следующей строке
        }

        // Запомним имя
        name = (char *) malloc((len - 5) + 1);
        strcpy(name, line + 5);

        // Считаем строку с телефоном
        if (fgets(line, 256, f) != 0) {
            len = strlen(line);
            // Удалим разделители строк
            if (len > 0 && line[len - 1] == '\n') {
                line[len - 1] = 0; --len;
            }
            if (len > 0 && line[len - 1] == '\r') {
                line[len - 1] = 0; --len;
            }
            if (
                len >= 7 &&
                strncmp(line, "phone=", 6) == 0
            ) {
                // Запомним телефон
                phone = (char *) malloc((len - 6) + 1);
                strcpy(phone, line + 6);
            }
        }

        // Заполним новую запись
        records[numRecords].name = name;
        if (phone == 0) {
            phone = (char *) malloc(1);
            phone[0] = 0;   // Пустая строка
        }
        records[numRecords].phone = phone;
        ++numRecords;   // Увеличим число записей
    }
    return true;
}

static bool saveNoteBook() { // Сохранить книжку в файле
    int i;
    FILE *f = fopen(NoteBookFile, "wt");
    if (f == 0) {
        return false;
    }
    for (i = 0; i < numRecords; ++i) {
        fprintf(f, "name=%s\n", records[i].name);
        fprintf(f, "phone=%s\n\n", records[i].phone);
    }
    return true;
}

static void releaseMemory() {
    int i;
    for (i = 0; i < numRecords; ++i) {
        free(records[i].name);
        free(records[i].phone);
    }
}

Аргументы командной строки

До сих пор во всех примерах программ использовался ввод исходных данных либо с клавиатуры (т.е. из входного потока), либо из файла. Язык Си предоставляет также возможность указывать аргументы программы в командной строке.

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

int main(int argc, char *argv[]) { . . . }

Здесь целая переменная argc равна числу аргументов, т.е. отдельных слов командной строки, а массив argv содержит указатели на строки, каждая из которых равна очередному слову командной строки. Нулевой элемент argv[0] равен имени программы. Таким образом, число аргументов argc всегда не меньше единицы.

Например, при запуске программы testprog с помощью командной строки

testprog -x abcd.txt efgh.txt

значение переменной argc будет равно 4, а массив argv будет содержать 4 строки " testprog ", " -x ", " abcd.txt " и " efgh.txt ".

В операционной системе Unix нулевой элемент массива argv содержит полный путь к файлу с выполняемой программой. В системах MS DOS и MS Windows строка argv[0] может быть равна как полному пути к файлу, так и первому слову командной строки (зависит от используемого командного процессора).

Пример программы, печатающей аргументы своей командной строки:

// Файл "comargs.cpp"
// Напечатать аргументы командной строки
#include <stdio.h>

int main(int argc, char *argv[]) {
    int i;
    printf("Число аргументов ком. строки = %d\n", argc);
    printf("Аргументы командной строки:\n");
    for (i = 0; i < argc; ++i) {
        printf("%s\n", argv[i]);
    }
    return 0;
}

Разработка больших проектов

До сих пор все рассмотренные примеры программ на Си имели небольшой объем (за исключением, возможно, программы Записная книжка). Такие маленькие программы помещаются в один файл. Однако реальные проекты имеют, как правило, значительно больший объем, измеряемый десятками, а чаще сотнями тысяч строк. Реализовать такую программу в виде одного непрерывного текста, помещающегося в одном файле, невозможно. Большой проект разбивается на более или менее независимые модули, которые можно написать и отладить по отдельности. Обычно в один такой модуль выделяется группа функций, работающих над общими глобальными данными и в той или иной мере связанных логически между собой. В простейшем случае каждому модулю соответствуют два файла с исходными текстами: заголовочный, или h -файл, описывающий интерфейс модуля, и файл реализации - c - или cpp -файл. Заголовочный файл содержит прототипы функций модуля, описания констант и глобальных переменных, структур, определения используемых типов и т.п. Файл реализации содержит определения глобальных переменных (т.е. их описания без слова extern ), определения статических переменных, которые не экспортируются за пределы данного файла (т.е. описания со словом static ), реализацию глобальных функций, а также описания прототипов и реализацию вспомогательных функций, которые не экспортируются за пределы файла.

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

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

Удобнее всего разрабатывать большие проекты в объектно-ориентированных языках (C++, Java, C#, Visual Basic и др.). В них имеются понятия класса и пространства имен, использование которых значительно облегчает создание больших проектов. Класс - это, говоря упрощенно, набор функций, которые работают над общими данными. Функции называются методами класса, а общие данные - членами класса. Объектно-ориентированный язык позволяет создавать объекты класса, т.е. однотипные наборы данных, соответствующие описанию класса. Пространством имен в C++ или пакетом в Java называется набор классов и функций, логически связанных между собой, реализуемых и используемых совместно. Отдельные классы или пространства имен соответствуют модулям, на которые разбивается проект.

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

Пример небольшого проекта "Стековый калькулятор" будет рассмотрен в следующей главе, посвященной структурам данных.

< Лекция 9 || Лекция 10: 123 || Лекция 11 >
Кирилл Юлаев
Кирилл Юлаев
Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?