|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомДругоеЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция |
ПРОГРАММА ВЫПОЛНЕНИЯ РАБОТЫЕсли в программе все операторы выполняются последовательно, один за другим, такая программа называется линейной. Рассмотрим в качестве примера программу, вычисляющую результат по заданной формуле. 2.1 ЗАДАЧА 1.1. РАСЧЕТ ПО ФОРМУЛЕ Написать программу, которая переводит температуру в градусах по Фаренгейту в градусы Цельсия по формуле: , где С – температура по Цельсию, a F – температура по Фаренгейту. Перед написанием любой программы надо четко определить, что в нее требуется ввести и что мы должны получить в результате. В данном случае сомнений нет: в качестве исходных данных выступает одно вещественное число, представляющее собой температуру по Цельсию, в качестве результата – другое вещественное число. Алгоритмических сложностей решение этой задачи также не представляет, поэтому давайте попробуем написать эту программу «в лоб» и посмотрим, что получится. Сразу же оговоримся, что первый блин будет комом, но тем программирование и отличается в лучшую сторону от реальной жизни, что ошибки можно найти и исправить. #include <iostream.h> int main() { // 1 float fahr, cels;. // 2 cout << endl << " Введите температуру по Фаренгейту" << endl; // 3 cin >> fahr; // 4 cels = 5 / 9 * (fahr - 32); // 5 cout << " По Фаренгейту: " << fahr << ", в градусах Цельсия: " << cels << endl; // 6 return 0; // 7 } Рассмотрим каждую строку программы отдельно. В начале программы записана директива препроцессора[6], по которой к исходному тексту программы подключается заголовочный файл <iostream.h>. Это текстовый файл, который содержит описания элементов стандартной библиотеки, необходимых для выполнения ввода-вывода. Если говорить более конкретно, то в этом файле описан набор классов для управления вводом/выводом, стандартные объекты-потоки cin для ввода с клавиатуры и cout для вывода на экран, а также операции помещения впоток << (вывода на экран) и извлечения из потока >> (ввода с клавиатуры). Объекты мы будем рассматривать во второй части методических указаний, а пока давайте пользоваться стандартными объектами и операциями с ними как некими волшебными словами, не пытаясь полностью осознать их смысл, ведь и в реальной жизни большинством благ цивилизации мы пользуемся подобным же образом. ВНИМАНИЕ: Директивы препроцессора записываются в отдельной строке, перед знаком # могут находиться только пробельные символы. Программа на C++ состоит из функций. Функция – это именованная последовательность операторов. Функция состоит из заголовка и тела. Строка, помеченная комментарием 1, представляет собой заголовок главной (а в данном случае и единственной) функции программы. Она должна иметь имя main, указывающее, что именно с нее требуется начинать выполнение. Заголовок любой функции пишется по определенным правилам. За именем функции в скобках обычно следует список передаваемых ей параметров. В данном случае он пуст, но скобки необходимы для того, чтобы компилятор мог распознать, что это именно функция, а не другая конструкция языка. Перед именем записан тип значения (int – целое), возвращаемого функцией в точку ее вызова (в данном случае – во внешнюю среду). По стандарту главная функция должна возвращать целочисленное значение. Забегая вперед, скажем, что этим в нашей программе занимается оператор 7. Впрочем, многие компиляторы реагируют спокойно и в случае, если функция ничего не возвращает. После круглых скобок в фигурных скобках записывается тело функции, то есть те операторы, которые требуется выполнить. Для удобства восприятия принято располагать тело с отступом в 3-4 позиции от заголовка. Обратите внимание, что закрывающая скобка находится в той же колонке, что и первый символ заголовка функции. Это требование хорошего стиля, а не синтаксиса. Для хранения исходных данных и результатов надо выделить достаточно места в оперативной памяти. Для этого служит оператор 2. В нашей программе требуется хранить два значения: температуру по Цельсию и температуру по Фаренгейту, поэтому в операторе определяются две переменные. Одна, для хранения температуры по Фаренгейту, названа fahr, другая (по Цельсию) – сels. ВНИМАНИЕ: Имена переменным дает программист, исходя из их назначения. Имя может состоять только из латинских букв, цифр и знака подчеркивания и должно начинаться не с цифры. От того, насколько удачно подобраны имена в программе, зависит ее читаемость – одна из важнейших характеристик программы. При описании любой переменной нужно указать ее тип, чтобы компилятор знал, сколько выделить места в памяти, как интерпретировать значение переменной (то есть ее внутреннее представление), а также какие действия можно будет выполнять с этой величиной. Например, для вещественных чисел в памяти хранится мантисса и порядок, а целые представляются просто в двоичной форме, поэтому внутреннее представление одного и того же целого и вещественного числа будет различным. Более того, для действий с целыми и вещественными величинами формируются различные наборы машинных команд. Поэтому-то указание типа для каждой переменной является таким важным. Поскольку температура может принимать не только целые значения, для переменных выбран вещественный тип float. Можно также выбрать тип double, позволяющий представлять вещественные числа большего диапазона значений и с большей точностью, но для данной задачи это нам кажется излишней роскошью. ПРИМЕЧАНИЕ: В общем случае тип переменных выбирается исходя из возможного диапазона значений и требуемой точности представления данных. Например, нет необходимости заводить переменную вещественного типа для хранения величины, которая может принимать только целые значения – хотя бы потому, что целочисленные операции выполняются гораздо быстрее. Для того чтобы пользователь программы знал, в какой момент требуется ввести с клавиатуры данные, применяется так называемое приглашение к вводу (оператор 3). На экран выводится указанная в операторе строка символов, и курсор переводится на следующую строку. Строка символов, более строго называемая символьным литералом, –это последовательность любых представимых в компьютере символов, заключенная в кавычки. Стандартный объект, с помощью которого выполняется вывод на экран, называется cout. Ему с помощью операции << передается то, что мы хотим вывести. Для вывода нескольких элементов используется цепочка таких операций. Для перехода на следующую строку записывается волшебное слово endl. Это – так называемый манипулятор; он управляет, то есть «манипулирует» стандартным объектом cout. Существуют и другие манипуляторы, с помощью которых можно задать вид выводимой информации. Мы будем рассматривать их по мере необходимости. СОВЕТ: Не забывайте в дружелюбной манере, но без излишнего многословия, пригласить пользователя ввести исходные данные и указать порядок их ввода. В некоторых случаях может понадобиться указать тип величин. В операторе 4 выполняется ввод с клавиатуры одного числа в переменную fahr. Для этого используется стандартный объект cin и операция извлечения (чтения) >>. Как видите, семантика ввода очень проста и интуитивно понятна: значение со стандартного ввода передается в переменную, указанную справа. Если требуется ввести несколько величин, используется цепочка операций >>. В процессе ввода число преобразуется из последовательности символов, набранных на клавиатуре, во внутреннее представление вещественного числа и помещается в ячейку памяти, зарезервированную для переменной fahr. В операторе 5 вычисляется выражение, записанное справа от операции присваивания (обозначаемой знаком =), и результат присваивается переменной cels, то есть заносится в отведенную этой переменной память. Выражение – это правило для вычисления некоторого значения, можно назвать его формулой. Порядок вычислений определяется приоритетом операций. Уровней приоритетов в языке C++ огорчительно много, поэтому в случае сомнений надо не лениться обращаться к справочной информации, а вот запоминать подобные вещи в деталях мы бы не рекомендовали – есть и более полезные применения головному мозгу. Основные правила, тем не менее, просты и соответствуют принятым в математике: вычитание имеет более низкий приоритет, чем умножение, поэтому для того, чтобы оно было выполнено раньше, соответствующая часть выражения заключается в скобки. Деление и умножение имеют одинаковый приоритет и выполняются слева направо, то есть сначала целая константа 5 будет поделена на целую константу 9, а затем результат этой операции умножен на результат вычитания числа 32 из переменной fahr. Мы вернемся к обсуждению этого оператора позже, а пока рассмотрим два оставшихся. Для вывода результата в операторе 6 применяется уже знакомый нам объект cout. Выводится цепочка, состоящая из четырех элементов. Это строка " По Фаренгейту: ", значение переменной fahr, строка ", в градусах Цельсия:" и значение переменной сels. Обратите внимание, что при выводе строк все символы, находящиеся внутри кавычек, включая и пробелы, выводятся без изменений. При выводе значений переменных выполняется преобразование из внутреннего представления числа в строку символов, представляющую это число. Под значение отводится ровно столько позиций, сколько необходимо для вывода всех его значащих цифр. Это значит, что если вывести две переменные подряд, их значения «склеятся», например: cout << fahr << сels; // плохо cout << fahr << ' ' << сels; // чуть лучше СОВЕТ: Рекомендуется всегда предварять выводимые значения текстовыми пояснениями. В одиночные кавычки в языке C++ заключается отдельный символ. В данном примере это пробел. Наконец, последний оператор (оператор 7) этой программы предназначен для возврата из нее и передачи значения во внешнюю среду. В главной функции разрешается его опускать, но в этом случае компилятор может выдать предупреждение (warning) и сформирует возвращаемое значение по умолчанию, равное нулю. Предупреждение можно проигнорировать, но, на наш взгляд, приятнее видеть в результате компиляции сообщение «0 errors, 0 warnings» (0 ошибок, 0 предупреждений) или «Success» (успешное завершение) – при большом количестве запусков программы это экономит время, не отвлекая внимания. Наберите текст программы и скомпилируйте ее. Если вы видите сообщения об ошибках, сличите текст на экране с текстом в книге (последний – лучше). ВНИМАНИЕ: При работе в интегрированной среде Borland C++ 3.1 (или любой другой оболочке, рассчитанной на работу в среде MS DOS) у вас не будет проблем с компиляцией и выполнением приводимых примеров. Напротив, работа в среде Microsoft Visual C++ 6.0 в режиме консольных приложений сопряжена с неудобствами, вызванными различными стандартами кодировки символов кириллицы в операционных системах MS DOS и Windows. Ниже мы рассмотрим этот вопрос подробнее. 2.2 ОСОБЕННОСТИ РАБОТЫ В ИНТЕГРИРОВАННОЙ СРЕДЕ VISUAL C++ 6.0 Мы предполагаем, что вы уже посмотрели (см. методические указания по интегрированной среде), как создаются приложения консольного типа, набрали текст приведенной выше программы, откомпилировали ее и запустили на выполнение. Вам приготовлен неприятный сюрприз: вместо приглашения «Введите температуру по Фаренгейту» вы увидите набор каких-то странных символов. Этому, конечно, должно быть какое-то объяснение. Начнем издалека. В старой доброй операционной системе MS DOS для кодировки символов используется стандарт ASCII, являющийся международным только в первой половине кодов (от 0 до 127), вторая половина кодов (от 128 до 255) является национальной и различна для разных стран. Например, в России для второй половины таблицы ASCII используется так называемая «альтернативная кодировка ГОСТа». В Windows же используется стандарт ANSI, в первой половине совпадающий с ASCII, а во второй половине отличающийся от его российского варианта. Разработчики интегрированной среды Visual C++ решили, что режим консольных приложений должен как бы имитировать работу в среде MS DOS, поэтому ввод-вывод выполняется в этом режиме в кодировке ASCII. В то же время в текстовом редакторе Visual C++, как и во всех Windows-приложениях, используется кодировка ANSI. Теперь происхождение этих странных символов должно стать вам понятным. Покажем, как будет выглядеть наша первая программа при ее реализации в среде Visual C++ 6.0 (приводится одно из возможных решений): #include <iostream.h> #include <windows.h>
char* Rus(const char* text); // 0
float fahr, cels; // 2 cout << endl << Rus(" Введите температуру по Фаренгейту") << endl; // 3 cin >> fahr; // 4 cout << Rus(" По Фаренгейту: ") << fahr; // 6 cout << Rus(". в градусах Цельсия: ") << cels << endl; // 7 } ////////////////////////////////////////////////////////// char bufRus[256]; // 8 char* Rus(const char* text){ // 9 CharTo0em(text, bufRus); // 10 return bufRus; // 11 } // 12 ////////////////////////////////////////////////////////// Для решения проблемы мы написали свою небольшую функцию Rus(), которая, обращаясь к функции CharTo0em(), возвращает в качестве адреса преобразованной строки указатель на char (строки 9-12). В качестве временного хранилища преобразуемой строки функция использует глобальный 2 массив bufRus длиной 256 символов (предполагается, что в качестве аргумента будут подставляться строки, не превышающие эту длину), описанный в операторе 8. Использовать функцию очень просто: любая строковая_константа заменяется выражением Rus(строковая_константа). Кроме того, мы добавили в нашу программу так называемый прототип функции Rus (оператор 0). Он требуется компилятору для проверки правильности ее использования. ПРИМЕЧАНИЕ: Строго говоря, для использования такой функции в операциях вывода с объектом cout было бы правильнее потребовать, чтобы функция Rus () возвращала значение типа ссылки на класс ostream, но для этого нужно владеть основами работы с классами, поэтому к этому вопросу мы вернемся во второй части практикума. То, что функция Rus () возвращает значение типа char*, приводит к следующему ограничению: ее нельзя использовать более одного раза в цепочке операций << для одного объекта cout. Проблема кодировок рассматривается также на шестом семинаре, там же приведен еще один пример ее решения. Во всех остальных программах, приведенных в этой книге, мы делаем вид, что этой проблемы не существует, поскольку к изучению собственно языка она отношения не имеет. Вернемся к тестированию и отладке нашей программы. 2.3 ОТЛАДКА ПРОГРАММЫ Запустите программу на выполнение несколько раз, задавая различные значения температуры. Не забудьте, что число, имеющее дробную часть, при вводе следует записывать с точкой, а не с запятой. Можно задавать и целые числа – они будут автоматически преобразованы в вещественную форму. Как вы можете видеть, результат выполнения программы со стабильностью, достойной лучшего применения, оказывается равным нулю! Это происходит из-за способа вычисления выражения. Давайте вновь обратимся к оператору 4. Константы 5 и 9 имеют целый тип, поэтому результат их деления также целочисленный. Округления при этом не происходит, дробная часть всегда отбрасывается. Естественно, что результат дальнейших вычислений не может быть ничем, кроме нуля. Исправить эту ошибку просто – достаточно записать хотя бы одну из констант в виде вещественного числа, например: cels = 5. / 9 * (fahr - 32); // 5 Вещественная константа «5.» по умолчанию имеет тип double, и при выполнении деления происходит автоматическое преобразование к этому же типу другой константы, а затем и результата вычитания. Можно записать это выражение и по-другому, просто изменив порядок действий: cels = 5 * (fahr - 32) /9; //5 В этом случае будет выполнено преобразование констант к типу float, как к наиболее длинному из участвующих в выражении. Теперь программа работает верно и выдает в результате, например, следующее: Введите температуру по Фаренгейту По Фаренгейту: 451, в градусах Цельсия: 232.778 Обратите внимание, что на экран наряду с результатом вычислений мы вывели и исходные данные. Это правильная привычка, и надеемся, что вы тоже будете ей следовать. СОВЕТ: Начинающие часто тратят время на поиск ошибки в алгоритме, не удостоверившись, что программа работает с правильными данными. Рекомендуется для контроля выводить исходные данные сразу же после ввода, чтобы исключить ошибки. Попробуйте при вводе температуры задать нецифровые символы и проинтерпретировать полученный результат. Как видите, даже в таком простом примере можно допустить ошибки! В данном случае заметить их легко, но так происходит далеко не всегда, поэтому запомните важное правило: надо всегда заранее знать, что должна выдать программа. Добиться этого можно разными способами, например, вычислением результатов в уме, на бумаге или с помощью калькулятора, а в более сложных случаях – расчетами по альтернативной или упрощенной методике. Это убережет вас от многих часов, бесплодно и безрадостно проведенных за компьютером. Не поленимся повторить еще раз: ВНИМАНИЕ: Перед запуском программы необходимо иметь тестовые примеры, содержащие исходные данные и ожидаемые результаты. Количество этих примеров зависит от алгоритма. Мы будем неоднократно возвращаться к обсуждению состава тестовых примеров, поскольку самое важное качество любой программы – надежность. Программы, работающие только с определенными исходными данными, да и то лишь в бережных руках хозяина, никому не нужны. Давайте теперь напишем ту же программу вторым способом, с использованием функций библиотеки C++, унаследованных из языка С. Этот способ также применяется достаточно часто, потому что в использовании этих функций есть свои преимущества. Когда вы их оцените, то сможете выбирать для каждой программы наиболее подходящий способ ввода-вывода. #include <stdio.h> int main(){ // 1 float fahr, cels; // 2 printf("\n Введите температуру по Фаренгейту\n"); // 3 scanf ("%f", &fahr); // 4 cels = 5 * (fahr - 32) /9; // 5 printf(" По Фаренгейту: %6.2f, вградусах Цельсия: %6.2f\n", fahr, cels); // 6 return 0: // 7 } Как видите, к программе подключается другой заголовочный файл – <stdio.h>. Он содержит описание функций, констант и других элементов, относящихся ко вводу-выводу «в стиле С». Рассмотрим отличия этой программы от предыдущей. Функция printf в операторе 3 выполняет вывод переданного ей в качестве параметра строкового литерала, то есть последовательности любых символов в кавычках, на стандартное устройство вывода (дисплей). Символы \n называются управляющей последовательностью. Есть разные управляющие последовательности, все они начинаются с обратной косой черты. Эта последовательность задает переход на следующую строку. Для ввода исходных данных в операторе 4 используется функция scanf. В ней требуется указать формат вводимых значений, а также адреса переменных, которым они будут присвоены. Параметры любой функции перечисляются через запятую. В первом параметре функции scanf в виде строкового литерала задается спецификация формата вводимой величины, соответствующая типу переменной. Спецификация %fсоответствует типу float[7]. В качестве второго параметра функции передается адрес переменной, по которому будет помещено вводимое значение. Операция взятия адреса обозначается &. Для вывода результата в операторе 6применяется уже знакомая нам функция printf. Теперь в ней три параметра. Первый, имеющий вид строкового литерала, задает вид и формат выводимой информации. Второй и третий параметры представляют собой имена переменных. Все символы литерала, кроме спецификаций формата %fи управляющей последовательности \n, выводятся на дисплей без изменений. При выводе форматные спецификации будут заменены конкретными значениями переменных fahr и cels. Формат вывода чисел можно уточнить при помощи так называемых модификаторов формата — чисел, которые записаны перед спецификацией. Первое число задает минимальное количество позиций, отводимых под выводимую величину, второе – сколько из этих позиций отводится под дробную часть величины. Необходимо учитывать, что десятичная точка тоже занимает одну позицию. Если заданного количества позиций окажется недостаточно для размещения числа, компилятор простит нам этот промах и автоматически выделит поле достаточной длины. На наш взгляд, в данном случае первый вариант программы (с использованием классов) более прост и нагляден, а также лучше защищен от ошибок кодирования. 1 Приведем еще две наиболее употребительные спецификации: %й –для величин целого типа в десятичной системе счисления, 81 f – для величин типа double. Более полный список спецификаций см. в Учебнике на с. 387. С другой стороны, с помощью функций в стиле С легче управлять видом выводимой информации. Однако при записи форматов легко ошибиться, и, к сожалению, компилятор ничего об этом не сообщит; вы будете ломать голову в поисках алгоритмической ошибки, в то время как источником неприятностей будет функция printf. Надо сказать, что при использовании классов также можно задавать любую форму представления информации, но мы пока не будем вдаваться в подобные детали. Мы не рекомендуем начинающим изучать на этом семинаре теоретический материал по классам ввода/вывода, поскольку для их полного понимания требуется освоить большой объем материала. Всему свое время! Для каждой программы необходимо выбрать один наиболее удобный способ вывода (либо с помощью функций, либо с помощью классов), поскольку смешивать оба варианта не рекомендуется. СОВЕТ: Используйте функции ввода/вывода в тех программах, где требуется тщательное форматирование результатов, а классы – в остальных случаях. Рассмотрим в более общем виде очень важный для дальнейшей работы вопрос – описание переменных. 2.4 ОПИСАНИЕ ПЕРЕМЕННЫХ Любая переменная обладает двумя основными характеристиками: временем жизни и областью действия. Они зависят от места и способа описания переменной. Если переменная описана вне любого блока (в частности, функции), она называется глобальной, размещается в сегменте данных и изначально обнуляется, если вы не предусмотрели ее инициализацию каким-то другим значением. Время жизни глобальной переменной – с начала выполнения программы и до ее окончания, а область действия (область, в которой эту переменную можно использовать, обратившись к ней по имени) – весь файл, в котором она описана, начиная с точки описания. Переменная, описанная внутри блока (в частности, внутри функции main), является локальной, память под нее выделяется в сегменте стека в момент выполнения оператора описания. Для локальных переменных автоматическое обнуление не производится. Областью действия локальной переменной является блок, в котором она описана, начиная с точки описания. Время ее жизни также ограничено этим блоком. Сколько раз выполняется блок, столько раз «рождается» и «умирает» локальная переменная. Есть один вид локальных переменных, которые, подобно глобальным, размещаются в сегменте данных, существуют на всем протяжении выполнения программы и инициализируются однократно. Это статические переменные. С другой стороны, они, как локальные переменные, видны только в своем блоке. Для описания статических переменных используется ключевое слово static. Ниже приведен пример описания трех переменных и таблица 6, в которой суммируются сведения о видах переменных: int a; // глобальная переменная int main() { static int b = 1: // локальная статическая переменная int с: // локальная переменная } Таблица 6-Сведения о видах переменных
Память под все эти переменные выделяет компилятор. Кроме перечисленных, существуют динамические переменные, память под которые резервируется во время выполнения программы с помощью операции new в динамической области памяти, или хипе (heap). Доступ к таким переменным осуществляется не по имени, а через указатели. Мы рассмотрим их на третьем семинаре. Во всех рассмотренных выше программах переменные являются локальными. Вариант с глобальными переменными выглядит так: #include <iostream.h> float fahr, сels; // глобальные переменные int main() { cout << endl << " Введите температуру по Фаренгейту" << endl; cels = 5 * (fahr - 32) / 9; cout << " По Фаренгейту: " << fahr << ", в градусах Цельсия: " << cels << endl; return 0; } Для данной простой программы разницы в этих способах нет, но в общем случае глобальные переменные нужно стремиться использовать как можно реже. Запомните, что переменная должна иметь минимальную из возможных областей действия, поскольку это значительно облегчает поиск ошибок. На следующих семинарах нам предстоит неоднократно в этом убедиться. 2.5 ЗАДАЧА 1.2. ВРЕМЕННОЙ ИНТЕРВАЛ Заданы моменты начала и конца некоторого промежутка времени в часах, минутах и секундах (в пределах одних суток). Найти продолжительность этого промежутки; в тех же единицах. Исходными данными для этой задачи являются шесть целых величин, задающих моменты начала и конца интервала, результатами – три целых величины. Вы уже знаете, что тип переменной выбирается, исходя из диапазона и требуемой точности представления данных, а имя дается в соответствии с ее содержимым. Нам потребуется хранить исходные данные, не превышающие величины 60 для минут и секунд и величины 24 для часов, поэтому можно ограничиться коротким целым типом (short int, сокращенно short). Назовем переменные для хранения начала интервала hourl, mini и seel, для хранения конца интервала – hour2, min2 и sec2, а результирующие величины – hour, min и sec. Для решения этой задачи необходимо преобразовать оба момента времени в секунды, вычесть первый из второго, а затем преобразовать результат обратно в часы, минуты и секунды. Следовательно, нам потребуется промежуточная переменная, в которой будет храниться интервал в секундах. Она может иметь весьма большие значения, ведь в сутках 86400 секунд. В величинах типа short могут храниться значения, не превышающие 32767 для величин со знаком (signed short) и 65535 для величин без знака (unsigned short), поэтому тип short здесь использовать нельзя. Вот почему для этой переменной следует выбрать длинный целый тип (long int, сокращенно long). «Обычный» целый тип int в зависимости от архитектуры компьютера может совпадать либо с коротким, либо с длинным целым типом. Ниже приведен текст программы: #include <iostream.h> int main() { short hour1, min1, sec1, hour2, min2, sec2, hour, min, sec; cout << endl << " Введите время начала интервала (час мин сек)" << endl; cin >> hour1 >> min1 >> sec1; cout << endl << " Введите время конца интервала (час мин сек)" << endl; cin >> hour2 >> min2 >> sec2; long sum_sec = (hour2 – hour1) * 3600 + (min2 – min1) * 60 + sec2 – sec1; hour = sum_sec / 3600; min = (sum_sec - hour * 3600) / 60; sec = sum_sec - hour * 3600 - min * 60; cout << " Продолжительность промежутка от " << hour1 << ':' << min1 << ':' << sec1 «" до " << hour2 << ':' << min2 << ':' << sec2 << endl << " равна " << hour << ':' << min << ':' << sec << endl; return 0; } Для перевода результата из секунд обратно в часы и минуты используется уже знакомый нам эффект отбрасывания дробной части при делении целого числа на целое. ВНИМАНИЕ: Данные при вводе разделяются пробелами, символами перевода строки или табуляции (но не запятыми!). Протестируйте программу на различных наборах исходных данных. Для обладателей старых компиляторов приведем текст программы с использованием функций ввода-вывода в стиле С: #include <stdio.h> int main() { short hour1, min1, sec1. hour2. min2, sec2. hour, min, sec; printf(" Введите время начала интервала (час мин сек)\n"); scanf ("%i%i%i", &hour1, &min1, &sec1); printf(" Введите время конца интервала (час мин сек)\n"); scanf("%i%i%i". &hour2, &min2, &sec2); long sum_sec = (hour2 – hour1) * 3600 + (min2 – min1) * 60 + sec2 – sec1; hour = sum_sec / 3600; min = (sum_sec - hour * 3600) / 60; sec = sum_sec - hour * 3600 - min * 60; printf(" Длительность промежутка от %2i:%2i:%2i до %2i:%2i:l2i\n", hour1, min1, sec1, hour2, min2. sec2); printf(" равна %2i:%2i:%2i\n", hour, min, sec); return 0; } Давайте повторим наиболее важные моменты этой лабораторной работы. 1. Выбирайте тип переменных с учетом диапазона и требуемой точности представления данных. 2. Давайте переменным имена, отражающие их назначение. 3. Ввод с клавиатуры предваряйте приглашением. Для контроля сразу же после ввода выводите исходные данные на дисплей (по крайней мере, в процессе отладки). 4. До запуска программы подготовьте тестовые примеры, содержащие исходные данные и ожидаемые результаты. Отдельно проверьте реакцию программы на неверные исходные данные. 5. При записи выражений обращайте внимание на приоритет операций. 6. В функциях printf и scanf для каждой переменной указывайте спецификацию формата, соответствующую ее типу. Не забывайте, что в scanf передается адрес переменной, а не ее значение. 7. При использовании стандартных функций или классов требуется с помощью директивы #iDelude подключить к программе соответствующие заголовочные файлы. Установить, какой именно файл необходим, можно с помощью справочной системы. 8. Не смешивайте в одной программе ввод/вывод с помощью классов (в стиле C++) и с помощью функций библиотеки (в стиле С). 9. Отдавайте предпочтение локальным переменным перед глобальными. Переменная должна иметь минимальную из возможных областей действия. 10. Данные при вводе разделяйте пробелами, символами перевода строки или табуляции. Основные моменты правильного написания программ приведены в приложении 3. 3. СОДЕРЖАНИЕ ОТЧЕТА 1. Титульный лист установленного образца. 2. Формулировка цели лабораторной работы. 3. Постановка задачи лабораторной работы. 4. Приведение блок-схемы задачи. 5. Листинг программы. 6. Результат счета или выполнения программы. 7. Обязательным является наличие электронного варианта программы. 4. КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Что такое структурное программирование? 2. Основные элементы языка С++. 3. В чем заключается роль компилятора? 4. Что такое исходная программа? 5. Что называется препроцессором? 6. Назовите основные типы данных. 7. Какие типы данных относятся к целочисленным, а какие к типам с плавающей точкой? 8. Назовите основные функции ввода/вывода. 9. Назовите тип данных, множество значений которого пусто. Для чего он используется? 10. Что такое переменная? 11. Что называется инициализацией? 12. Поясните понятие локальной и глобальной переменной. 13. В какой форме может выполняться описание переменной? 14. В соответствии с количеством операндов, которые используются в операциях, они делятся на… (какие?). 15. Объясните понятие, приоритет выполнения операции. 5. ЗАДАНИЯ Напишите программу для расчета по двум формулам. Список математических функций библиотеки C++ приведен в приложении 4. Для их использования необходимо подключить к программе заголовочный файл <math.h>. БИБЛИОГРАФИЧЕСКИЙ СПИСОК 1. Иванова Г.С. Технология программирования: учебник для вузов/ Г. С. Иванова; доп. М-вом образования РФ. -2-е изд., стереотип. -М.: Изд-во МГТУ им. Н. Э. Баумана, 2003. -320 с. -(Информатика в техническом университете). 2. Климова Л.М, Основы практического программирования на языке СИ++: учеб. пособие/ Л. М, Климова; рек. кафедрой прикладной математики МГТУ ГА. -М.: Приор, 1999. -464 с. 3. Колдаев В.Д. Основы алгоритмизации и программирования: Учебное пособие / Под ред. Проф. Л.Г. Гагариной. – М.: ИД «ФОРУМ»: ИНФРА-М, 2006. – 416 с. (Профессиональное образование). 4. Комаров Н.А. Программирование на персональном компьютере: Практическое руководство с примерами по языкам программирования высокого уровня – специальное изд. – М.: Альянс-пресс, 2003. – 688 с. (Серия книг «ПК с нуля»). 5. Павловская Т.А. С/С++. Программирование на языке высокого уровня: учебник для вузов/ Т. А. Павловская; доп. М-вом образования РФ. -СПб.: Питер, 2005. -461 с. -(Учебник для вузов). 6. Павловская Т. А., Щупак Ю.А. Структурное программирование: Практикум. -СПб.: Питер, 2002. -240 с. 7. Семакин И.Г., Шестаков А.П. Основы программирования: Учебник. – М.: Мастерство; НМЦ СПО; Высшая школа, 2001. – 432с. 8. Фридман А.Л. Язык программирования Си++: Курс лекций для студ. вузов/ А.Л. Фридман; Рек. УМО в обл. прикл. информатики. -М.: ИНТУИТ. У "Интернет-университет Информационных Технологий", 2003. -288 с. -(Основы информационных технологий). Приложение 1. Спецификации формата для функций семейства printf
Модификаторы формата Модификаторы формата применяются для управления шириной поля, отводимого для размещения значения. Модификаторы – это одно или два числа, первое из которых задает минимальное количество позиций, отводимых под число, а второе – сколько из этих позиций отводится под дробную часть числа (точность). Если указанного количества позиций для размещения значения недостаточно, автоматически выделяется большее количество позиций: %-minC или %minC; %-min.precisionC или %min.precisionC. Здесь С – спецификация формата из приведенной выше таблицы, min – число, задающее минимальную ширину поля. Смысл модификатора precision, также задаваемого десятичным числом, зависит от спецификации формата, с которой он используется: - при выводе строки (спецификация %s) precision указывает максимальное число символов для вывода; - при выводе вещественного числа (спецификации %fили %е) precision указывает количество цифр после десятичной точки; - при выводе целого числа (спецификации %dили %i), precision указывает минимальное количество выводимых цифр. Если число представляется меньшим числом цифр, чем указано в precision, выводятся ведущие (начальные) нули. - при выводе вещественного числа (спецификации %d или %G) precision указывает максимальное количество значащих цифр, которые будут выводится. Символ минус (-) указывает на то, что значение выравнивается по левому краю и, если нужно, дополняется пробелами справа. При отсутствии минуса значение выравнивается по правому краю и дополняется пробелами слева. Перед спецификацией могут использоваться префиксы l и h, например, %l f, %hu. Префикс h с типами d, i, о, х и X указывает на то, что тип аргумента short int, а с типом u – short unsigned int. Префикс l с типами d, i, о, х и X указывает на то, что тип аргумента long int, с типом и - long unsigned int, а с типами e, E, f, g и G – что тип аргумента double, а не float. Пример: #include <stdio.h> int main(){ int int1 = 45, int2 = 13; float f = 3.621; double dbl = 2.23; char ch = 'z’, *str = "ramambahari"; printf("int1 - %d| int2 - %3d| int2 - %-4d|\n", int1, int2, int2); printf("int1 = %X| int2 - %3x| int2 = %4o|\n", int1, int2, int2); printf("f - %f| f - %4.2f| f - %6.1f|\n", f, f, f); printf("f - %g| f - %e| f = %+E|\n", f, f, f); printf("dbl - %5.21f| dbl - %e| dbl = %4.1G|\n", dbl, dbl, dbl); printf("ch - %c| ch - %3c|\n", ch, ch); printf("str = %14s|\nstr - %-14s|\nstr = %s|\n", str, str, str); return 0; } Результат работы программы: int1 = 45| int2 = 13| int2 = 13 | int1 = 2D| int2 = d| int2 = 15 | f = 3.621000| f = 3.62 | f = 3.6 | f = 3.621 | f = 3.621000e+000 | f = +3.621000E+000| dbl = 2.23 | dbl = 2.230000e+000 | dbl = 2 | ch = z| ch = z | str = ramambahari| str = ramambahari | str = ramambahari| Приложение 2. Арифметические преобразования типов Преобразования типов выполняются, если операнды, входящие в выражения, имеют различные типы. Ниже приведена последовательность преобразований. Ø Любые операнды типа char, unsigned char или short преобразуются к типу int по правилам: • char расширяется нулем или знаком в зависимости от умолчания для char; • unsigned char расширяется нулем; • signed char расширяется знаком; • short, unsigned short и enum при преобразовании не изменяются. • Затем любые два операнда становятся либо int, либо float, double или long double. Ø Если один из операндов имеет тип long double, то другой преобразуется к типу long double. Ø Если один из операндов имеет тип double, то другой преобразуется к типу double. Ø Если один из операндов имеет тип float, то другой преобразуется к типу float. Ø Иначе, если один из операндов имеет тип unsigned long, то другой преобразуется к типу unsigned long. Ø Иначе, если один из операндов имеет тип long, то другой преобразуется к типу long. Ø Иначе, если один из операндов имеет тип unsigned, то другой преобразуется к типу unsigned. Ø Иначе оба операнда должны иметь тип int. Тип результата тот же, что и тип участвующих в выражении операндов.
Приложение 3. Правила оформления текстов программ Создание программы – это систематический процесс, состоящий из определенных этапов. В результате этого процесса разработчик получит программу: · ясную для понимания, т. е. читабельную. Ясность означает, что любой, кто знаком с языком программирования и прикладной областью, поймет алгоритм, читая текст программы, комментарии и спецификацию; · эффективную, т. е. экономно расходующую ресурсы и быстро выполняемую. Эффективность предполагает, что алгоритм и программа составлены так, чтобы минимизировать по возможности ресурсы вычислительной системы, необходимые для ее решения; · правильную, т. е. не содержащую ошибок. Корректность означает, что любое исполнение программы с допустимыми исходными данными дает правильный результат. Под методикой создания какого-либо продукта понимают четко определенную последовательность этапов, выполнив которую, можно получить желаемый продукт с нужными характеристиками. При написании программ обычно используется принцип «лишь бы работало», т. е. совершенно игнорируются элементарные правила оформления текстов программ. Исходный текст программы – это такой же текст, как стихотворение, эссе, рассказ, web-страничка. Посмотрим на фрагмент сильно искаженного текста (рис. 3).
Рис. 3. Фрагмент искаженного текста Для грамотного написания программы необходимо форматировать исходные тексты. Вспомним, что стандартный размер экрана – 80 х 25 символов (знакомест). Именно такие размеры наиболее комфортны для восприятия. Правило 1, про количество операторов на одной строке Количество операторов на строке должно быть равно одному. Неправильно: а = b; do while (0); printf("MIEE"); Правильно: a = b; do while (0); printf(MIEE"); Правило 2, про горизонтальные отступы Все операторы, входящие в составной оператор (напоминаем, составной оператор – это все, что между begin и end в языке программирования Turbo Pascal и между { } в языке программирования С), должны быть сдвинуты вправо (клавиша Tab). Кроме того, размер отступа должен быть восемь символов. Неправильно: if (a < b) { j = m; i = 1; for (k = 0; k < N; k++) { c[k] += (m-1); u[k-j] = 2*j; if(1>a) { break; }; }; printf("ooo"); }; Правильно: if (a < b) { j = m; i = 1; for (k = 0; k < N; k++) { c[k] += (m-1); u[k-j] = 2*j; if(1>a) { break; }; }; printf("ooo"); }; В том, что размер отступа должен быть восемь, есть простой смысл и практическая польза. Если текст начинает залезать вправо за границу, то, стало быть, слишком велика вложенность блоков (не больше трех). Правило 3, про операторные скобки Операторные скобки (это то, что ограничивает составной оператор), относящиеся к одному блоку, должны располагаться следующим образом. Открывающая скобка должна находиться на той же строчке, что и следующий до блока оператор, а закрывающая должна находиться строго на одной линии по вертикали с началом оператора, предшествующем данному составному. Неправильно: for (;;) { printf("Кафедра ИСТ"); }; for j:=0 to N do begin WriteLn(‘ Кафедра ИСТ '); end; Правильно: for (;;) { printf("Кафедра ИСТ"); }; for j:=0 to N do begin WriteLn(‘ Кафедра ИСТ '); end; Допускается такой вариант: for (;;) { printf("истина"); }; for j:=0 to N do begin WriteLn('истина'); end; Предыдущий вариант, однако, предпочтительней, так как никакого смысла в размещении открывающей операторной скобки на одной линии по вертикали с закрывающей, кроме удлинения исходного текста в вертикальном направлении, нет. Последний вариант следует использовать для подпрограмм (процедур и функций). Неправильно: int my_favorite_function(void) { return 1; } function MyFavoriteFunction:boolean; begin MyFavoriteFunction:= TRUE; end; Правильно: int my_favorite_function(void) { return 1; } function MyFavoriteFunction:boolean; begin MyFavoriteFunction:= TRUE; end; Начинающие программисты обычно набирают текст последовательно, и поэтому: а) он у них получается кривой и б) они постоянно забывают закрыть блок или закрывают его не там, где надо. Чтобы избежать этого, текст программы нужно набирать не последовательно. К этому располагает сама грамматика и синтаксис почти любого языка программирования. Всегда есть ПАРНЫЕ символы – скобки, составные операторы. Если написали begin, то почему бы сразу не написать end? Если набрали открывающую скобку, то почему бы сразу не набрать закрывающую, а потом не вернуться назад? Например: for (i = 0; i < N; i++) { /* тело цикла */ } Правило 4, про горизонтальные пробелы В конце строки пробелов быть не должно. Пробелы полезны вокруг знака какой-нибудь операции. for (k = NUM; k; k--) { int tmp; tmp = some_func(k, arg2, arg3); printf("%d\n", tmp); } Правило 5, про вертикальные пробелы (пустые строки) Не жалейте пустые строки, вставляйте где вам хочется. Обязательны пустые строки перед и после определений функций. Логические части функции тоже нужно отделять друг от друга пустой строкой, например цикл от предшествующих или последующих операторов. Неправильно: int func_1(void) { return 1; }
int func_1(void) { return 2; }
int main(void) { LIST *1 = get_head(); double a, b; printf("Hello.\n"); for (1;1->next;1=1->next) printf("%s\n", 1->name); }; printf("Bye.\n"); } Правильно: int func_1(void) { return 1; }
int func_1(void) { return 2; }
int main(void) { LIST *1 = get_head(); double a, b; printf("Hello.\n"); for (1; 1->next; 1 = 1->next) printf("%s\n", 1->name); printf("Bye.\n"); } Правило 6, про объявления переменных Тип у переменной следует писать для каждой из них. На одной строчке – одна переменная. Неправильно: int a,b,c; float *q,w,u,z,tmp,ttt,qqq,aaa,bbb; …….. a,b,c,d,ttt,test,tt0: integer; q,w,u,z,ppp,ddd,dddO,ddd23: real; Правильно: int a; int b; int c; float *q; …….. a: integer; b: integer; c: integer; q: real; Правило 7, про говорящие идентификаторы Идентификаторы (названия переменных, типов, подпрограмм) должны быть значимыми настолько, чтобы читающий текст программы мог понимать смысл всех переменных и т. д. без присутствия рядом автора. Правило 8, про инициализацию массивов Массивы с заранее предопределенными значениями (если их не требуется по заданию вводить с клавиатуры или из файла) не инициализировать операторами присваивания, а делать это с использованием инициализаторов и типизированных констант в языках C++ и Turbo Pascal, соответственно. Неправильно: double matrix[3][3]; …. matrix[0][0] = 1.5; matrix[0][1] = 2.5; …… Matrix: array[1..3, 1..3] of extended; …… Matrix[1,1]:= 1.5; Matrix[1,2]:= 2.5; Правильно: double matrix[3][3] = { {1.5, 2.5,... }, {…}, {...}, }; const Matrix: array[1..3, 1..3] of extended = (
(1.5, 2.5,...), (…), (...) ); Правило 9, про функции и процедуры Программа без подпрограмм – почти всегда заведомо плохая программа. У функций должны быть параметры. Функция, использующая глобальные переменные – почти наверняка плохая, ибо ситуации, когда внутри функции необходимо использовать глобальную переменную, крайне редки. Функции должны быть короткими – 2 экрана (50 строк) максимум. Единственное оправдание длинной функции – это наличие в ней оператора множественного выбора с большим числом альтернатив. Функции должны выполнять только одно действие. Например, подпрограмма, выполняющая решение системы линейных уравнений, не должна заниматься считыванием исходных данных, она должна получить их в виде аргументов. Она также не должна выводить результаты куда бы то ни было. Это должна делать другая подпрограмма. Правило 10, про ошибки Если функция может потенциально завершиться неудачно, то эту ситуацию следует проверять. Неправильно: char * buf; buf = (char*)malloc(/*много-много байт*/); buf[0] = some_value; Правильно: char * buf; buf = (char*)malloc(/*много-много байт*/); if (!buf) { реггог("нет памяти"); exit(1); }; buf[0] = some_value; Если возникшая ошибка такова, что программа (подпрограмма) далее выполняться не может, условие проверки на ошибку лучше писать так, чтобы оно было истинным, если ошибка произошла, а не наоборот, и алгоритм строить так, чтобы избегать оборота else в операторе if. Помните, что из программы (подпрограммы) можно выйти в любом месте, не обязательно в конце ее текста. Неправильно: char * buf; buf = (char*)malloc(/*много-много байт*/); if (buf) { /* здесь страница кода */ } else { реггог("нет памяти"); }; Этот else может быть далеко от if, что уменьшает ясность программы. Правильно: char * buf; buf = (char*)malloc(/*много-много байт*/); if (!buf) { реггог("нет памяти"); /* выходим */ exit(BAD_EXIT_CODE) /* или return BAD_RET__VAL; */ }; /* в этом месте все хорошо, продолжаем */ Правило 11, про данные Организация данных в программе – это вопрос не менее важный, чем алгоритмическая структурированность программы. Специально для структуризации данных существуют типы данных, структуры (или записи в терминах языка программирования Pascal) и классы (в ООП-языках). Если программа работает с какой-либо сущностью, то эта сущность должна быть описана в программе посредством структуры или класса, если используется объектно-ориентированный язык. Сложная программа, в которой нет ни одного массива или структуры, – это плохая программа. Рассмотрим пример программы вычисления определенного интеграла методом прямоугольников в двух вариантах. Первый будет написан с нарушением всех указанных выше требований, а второй – с соблюдением. Обе программы работают и дают одинаковый результат. Неправильно: #include <stdio.h> #include <math.h> double a=0.2,b=1.0,i,h,x,s; int n=50,k; void ffa(void) {h=(b-a)/n;x=a+h/2;s=0; for(k=1;k<=n;k++){s+=sin(x);x+=h;} s*=h;} void main(void) { ffa(); рrintf("значение=%.8f\n",s);} Правильно: #include <stdio.h> #include <math.h> #define INTEGR_FUNCTION(x) sin((x)) #define INTEGR_FUNCTION_STR "sin(x)" #define LOWER_LIMIT 0.2 #define UPPER_LIMIT 1.0 #define INTERVALS 50
typedef struct integr_task { double from; double to; double (*what)(double); /* интегрируемая функция */ int nint; /* число интервалов */ } INTEGR_TASK; typedef enum integr_error { INTEGR_OK = 0, INTEGR_NO_TASK_OR_RES, INTEGR_NO_FUNC, INTEGR_BAD_LIMITS, INTEGR_BAD_NINT, INTEGR_LAST_ERROR } INTEGR_ERROR;
char *integr_error_str[INTEGR_LAST_ERROR+1] = { "Все хорошо", "Указатель на задание или результат равен NULL", "Указатель на функцию равен NULL", "Верхний предел меньше нижнего", "Неправильное число интервалов", "_INTEGR_LAST_ERROR_" }; INTEGR_ERROR integrate_rect(INTEGR_TASK * task, double * result); #define INTEGRATE integrate_rect
double function(double arg) { return INTEGR_FUNCTION(arg); } int main(void) { double integral_val; INTEGR_ERROR error;
INTEGRJTASK test_task = { LOWER_LIMIT, UPPER_LIMIT, function, INTERVALS }; error = INTEGRATE(&test_task, &integral_val);
if (error) { printf("Ошибка в данных (%s).\n", integr_error_str[error]); return 1; }; printf( "\nЗначение определенного интеграла от функции f(x)= %s\nв пределах от %.8f до %.8f: %.8f\n", INTEGR_FUNCTION_STR, LOWER_LIMIT, UPPER_LIMIT, integral_val ); return 0; } INTEGR_ERROR integrate_rect(INTEGR_TASK * task, double * result) { double x, s, step; int j;
/* проверка на корректность данных */ if ((!task) || (!result)) { return INTEGR_NO_TASK_OR_RES; };
if (!task->what) { return INTEGR_NO_FUNC; };
if (task->from > task->to) { return INTEGR_BAD_LIMITS; };
if (task->nint < 1) { return INTEGR_BAD_NINT; };
/* собственно вычисление */ step = (task->to - task->from)/task->nint; x = task->from+step/2.0; s = 0;
for (j = 1; j <= task->nint; j++, s += task->what(x), x += step);
s *= step; *result = s;
return INTEGR_OK; }
Приложение 4. Заголовочный файл <math.h> {<cmath.h>} – математические функции
[1] Программист может задать тип константы самостоятельно. [2] Могут быть опущены либо целая часть, либо дробная, но не обе сразу. [3] Могут быть опущены либо целая часть, либо дробная, но не обе сразу. Если указаны обе части, символ точки обязателен. [4] Если переменная в том же операторе инициализируется, спецификатор extern игнорируется. [5] Пробелы между символами внутри операции не допускаются. [6] Препроцессором называется предварительная фаза компиляции, то есть перевода программы с С++ на машинный язык. [7] Приведем еще две наиболее употребительные спецификации: %d–для величин целого типа в десятичной системе счисления, %lf – для величин типа double. Более полный список спецификаций см. в Приложении 1.
Поиск по сайту: |
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Студалл.Орг (0.171 сек.) |