Программа 50. Сумматор чисел
Напишем программу, которая вводит с клавиатуры числа с плавающей точкой и суммирует их. Числа поступают с клавиатуры в виде строк, которые следует преобразовывать в числовые значения.
Строки символов можно вводить стандартным образом с помощью cin >>, разделяя вводимые числа пробелами, но в данной программе будем читать с клавиатуры строки символов, а затем преобразовывать строки в числа.
Для чтения строк напишем функцию
int readline(),
которая читает не более MAXLINE символов в массив line, извлекает из входного потока символ новой строки \n, но не помещает его в формируемую строку, и возвращает длину прочитанной строки. Массив line и предельный размер строки MAXLINE зададим как внешние переменные. Функцию readline разместим в файле Readline.cpp.
Для преобразования строк в числа с плавающей точкой напишем функцию strtof и поместим ее в отдельном файле StrTof.cpp.
Главную функцию main, которая будет вводить и суммировать числа, поместим в файле SumNumb.cpp.
Функция strtof преобразует строку цифр, в которой может быть знак и десятичная точка, отделяющая целую часть от дробной, в число двойной точности. Сначала устанавливается знак числа, затем вычисляется целая часть val. Если присутствует десятичная точка, продолжается вычисление val как целого и параллельно накапливается степень 10, на которую надо поделить val, чтобы сформировать правильное значение.
// Файл Strtof.cpp
#include <ctype.h>
// strtof: преобразование строки s в double
double strtof(char s[])
{
double val, pow10;
int i, sign;
for(i = 0; isspace(s[i]); i++) // Пропуск начальных пробелов
;
sign = (s[i] == '-') ? -1: 1; // Получение знака
if('+' == s[i] || '-' == s[i]) // Если есть знак,
i++; // переходим к следующему символу
for(val = 0.0; isdigit(s[i]); i++) // Получаем целую часть
val = val * 10 + s[i] - '0';
if('.' == s[i]) // Если есть десятичная точка
i++; // Переход к дробной части
for(pow10 = 1.0; isdigit(s[i]); i++){ // Получение дробной части
val = val * 10 + s[i] - '0';
pow10*=10.0; // Степень 10
}
return sign * val / pow10;
}
Для проверки того, что символ является пробельным, применяется функция isspace, а для проверки того, что символ есть цифра – isdigit.
// Файл ReadLine.cpp
#include <iostream.h>
// Объявление внешних переменных
extern int MAXLINE;
extern char line[];
/* readline: читает не более MAXLINE символов в массив line, возвращает длину строки */
int readline()
{
int c, i;
for(i = 0; i < MAXLINE - 1 && (c = cin.get()) != EOF && c != '\n'; ++i)
line[i] = c;
line[i] = '\0';
return i; // Возвращаем длину строки
}
Для чтения отдельных символов в readline применена библиотечная функция int cin.get().
В функции main читаются строки, пока не встретится строка нулевой длины (пустая), строки преобразуются в числа, которые добавляются к сумме. Очередное значение суммы выводится.
// Файл SumNumb.cpp
#include <iostream.h>
#include <conio.h>
int readline(); // Прототип функции чтения строки
double strtof(char[]); // Прототип функции преобразования строки в число
// Сумматор вводимых чисел
int main()
{
double sum = 0.0; // Сумма
extern char line[]; // Объявление массива line
while(readline() > 0) // Пока есть строки, преобразуем
cout << (sum+=strtof(line)) << endl; // их в числа и суммируем
getch();
return 0;
}
// Определение внешних переменных
int MAXLINE = 100;
char line[100]; // Массив под строку
В файл SumNumb.cpp необходимо поместить объявления используемых в main функций strtof и readline, иначе при компиляции данного файла будет выдана ошибка вида:
•Error LECTC_~1\SUMNUMB.CPP 18: Function 'readline' should have a prototype
Ниже приведен пример работы программы
33.21
33.21
-10.12
23.09
65.66
88.75
^Z
Часто кажется удобным делать переменные внешними потому, что в этом случае становятся короче списки аргументов функций, так как переменные видны везде. Например, в программе 50 массив для вводимой строки и его размер не передаются в функцию readline потому, что они доступны в этой функции как внешние переменные. Однако чрезмерное использование внешних переменных приводит к программам, в которых связи по данным не очевидны, поэтому такие программы трудны для понимания и модификации, их трудно отлаживать, так как сложно следить за изменениями внешних переменных.
Кроме того, функции, в которые данные передаются с помощью внешних переменных, являются менее общими, так как в них используются конкретные имена. Это относится, например, к функции readline, в которой использовано имя конкретного внешнего массива line. При изменении имени массива придется переписывать и функцию readline.
Заголовочные файлы
При написании программы 50 нужно было заботиться об объявлениях внешних функций и переменных в файлах программы. Для облегчения этого, принято размещать необходимые объявления в отдельных файлах с расширениями .h, которые называют заголовочными (от header – заголовок), так как их основное содержание составляют объявления или заголовки функций.
Страж включения
Заголовочные файлы вставляются в другие файлы директивами препроцессора #include, причем делать это надо однократно, так как при повторном включении возможны ошибки из-за дублирования определения какого-либо объекта. Чтобы не было повторного включения заголовочных файлов, используется прием, называемый стражем включения.
Заголовочный файл оформляется в виде
// Файл HEADER.H
#ifndef HEADERH // Страж
#define HEADERH // включения
// Содержимое HEADER.H
// …
#endif
Условная директива препроцессора #ifndef проверяет, определен или нет макрос HEADERH. Если этот макрос не определен, то в текст программы включаются все строки, вплоть до строки #endif, при этом определяется макрос HEADERH. Если же обнаружится, что макрос HEADERH определен, то в обработанный препроцессором текст не включаются все строки, расположенные между строками
#ifndef HEADERH
и
#endif
благодаря чему не будет повторного включения файла HEADER.H.
Имя макроса, который фигурирует в страже включения, совершенно произвольно, но принято выбирать его похожим на имя заголовочного файла.
Таким образом, если в некотором файле будут две директивы:
// Файл File1.cpp
#include HEADER.H
#include HEADER.H
то файл HEADER.H будет вставлен только одной первой директивой, а вторая будет фактически проигнорирована благодаря стражу включения.
Понятие стека
Стеком называется набор элементов одного типа, организованный по принципу “последний пришел – первый вышел” или LIFO (last in- first out). Английское слово stack переводится как копна сена, стопа бумаги. Для стопы бумаги действительно легче всего взять верхний лист, который попадает в стопу последним. Место, куда помещаются и из которого извлекаются элементы стека, называется вершиной.
Для работы со стеком определяют два основных действия или операции:
push – поместить новый элемент в стек и
pop – извлечь последний элемент, помещенный в стек.
Для наглядности стек можно представлять себе как вертикальную стопку книг или кирпичей, положенных друг на друга. Очередной кирпич кладется на верх стопки, извлекается же из стопки проще всего самый верхний кирпич. Таким образом, LIFO – это естественная дисциплина работы со стопкой кирпичей. В программировании стеки часто бывают полезны для решения различных задач.
Реализовать стек можно различными способами. В приводимой далее программе элементы стека размещаются в массиве.
Модули
Объявления функций для работы со стеком поместим в заголовочном файле Stack.h, определения функций и переменных поместим в файл реализации Stack.cpp. Совокупность заголовочного файла и файла реализации принято называть модулем. В C++ Builder для этого используется термин unit. В C++Builder создание модулей автоматизировано: заготовка нового модуля добавляется в проект командой File, New, Unit, при этом заголовочный файл модуля будет иметь страж включения. В TC заголовочный файл и файл реализации создаются по отдельности.