1 СОДЕРЖАНИЕ ЛАБОРАТОРНАЯ РАБОТА №1................................................................................ 5 ОСНОВЫ РАБОТЫ В СРЕДЕ C++ BUILDER. ОСНОВЫ ЯЗЫКА СИ ................. 5 КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ ............................................................ 5 1 Основы работы в среде C++Builder................................................................. 5 1.1 Создание консольного приложения ......................................................... 5 1.2 Отладка программы ................................................................................... 8 1.2.1 Установка точек останова (Add breakpoint)...................................... 8 1.2.2 Минимальные правила редактирования текста программы ........... 9 1.2.3 Прерывание выполнения программы................................................ 9 2 Основы языка Си ............................................................................................... 9 2.1 Алфавит языка ............................................................................................ 9 2.2 Идентификаторы ...................................................................................... 10 2.3 Ключевые слова........................................................................................ 10 2.4 Знаки операций ......................................................................................... 11 2.5 Константы ................................................................................................. 11 2.6 Комментарии ............................................................................................ 12 2.7 Типы данных C++ .................................................................................... 13 2.7.1 Понятие типа данных ........................................................................ 13 2.7.2 Основные типы данных .................................................................... 13 2.7.3 Целый тип (int)................................................................................... 14 2.7.4 Символьный тип (char). .................................................................... 15 2.7.5 Логический тип (bool). ...................................................................... 15 2.7.6 Вещественный тип (float, double и long double). ............................ 15 2.7.7 Тип void .............................................................................................. 16 2.8 Переменные .............................................................................................. 16 2.9 Функции и объекты ввода/вывода .......................................................... 20 2.9.1 Основные функции ввода/вывода в стиле С: ................................. 20 2.9.2 Основные объекты ввода/вывода в стиле С++: ............................. 22 2.10 Операции ............................................................................................... 23 2.10.1 Операции увеличения и уменьшения на 1 (++ и - -)...................... 24 2.10.2 Операция определения размера sizeof ............................................ 24 2.10.3 Операции отрицания (-, ! и ~). ......................................................... 25 2.10.4 Деление (/) и остаток от деления (%). ............................................. 25 2.10.5 Операции сдвига (<< и >>) ............................................................. 26 2.10.6 Операции отношения (<, <=, >, >=, = =, ! =) ............................. 26 2.10.7 Поразрядные операции (& , | , ^) ................................................... 26 2.10.8 Логические операции (&& и ||). ....................................................... 27 2.10.9 Операции присваивания (=, +=, -=, *= и т. д.). ............................... 27 2.10.10 Условная операция (?:). ................................................................. 28 2.11 Выражения ............................................................................................. 29 2.11.1 Преобразование типов в выражении. .............................................. 29 ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ №1 ...................................................... 31 КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №1 ...................... 34 2 ГЛАВА 2 ..................................................................................................................... 36 ОПЕРАТОРЫ ВЕТВЛЕНИЯ И ОПЕРАТОРЫ ПЕРЕДАЧИ УПРАВЛЕНИЯ ...... 36 КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ .......................................................... 36 1 Базовые конструкции структурного программирования ............................ 36 1.1 Оператор «выражение» ........................................................................... 37 1.2 Операторы ветвления............................................................................... 38 1.2.1 Условный оператор if... else ............................................................. 38 1.2.2 Об условиях в операторе if ............................................................... 39 1.2.3 Оператор выбора switch .................................................................... 41 1.3 Операторы передачи управления ........................................................... 44 1.3.1 Оператор goto..................................................................................... 44 1.3.2 Оператор break ................................................................................... 45 2 Тестирование программ ................................................................................. 45 ДОМАШНЕЕ ЗАДАНИЕ ......................................................................................... 46 ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ №2 ...................................................... 46 1 Задание 1 .......................................................................................................... 46 2 Задание 2 .......................................................................................................... 49 СОДЕРЖАНИЕ ОТЧЕТА ......................................................................................... 53 КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №2 ...................... 53 ЛАБОРАТОРНАЯ РАБОТА №3.............................................................................. 54 ОПЕРАТОРЫ ЦИКЛА И ОПЕРАТОРЫ ПЕРЕДАЧИ УПРАВЛЕНИЯ ............... 54 КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ .......................................................... 54 1 Организация циклов с помощью операторов if и goto ................................ 54 2 Операторы цикла ............................................................................................. 56 2.1 Цикл с предусловием (while) .................................................................. 57 2.2 Цикл с постусловием (do … while)......................................................... 57 2.3 Цикл с параметром for ............................................................................. 58 3 Дополнительные операторы передачи управления ..................................... 60 3.1 Оператор break.......................................................................................... 60 3.2 Оператор continue ..................................................................................... 61 4 Вложенные циклы ........................................................................................... 61 ДОМАШНЕЕ ЗАДАНИЕ ......................................................................................... 62 ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ №3 ...................................................... 62 1 Задание 1 .......................................................................................................... 62 2 Задание 2 .......................................................................................................... 66 СОДЕРЖАНИЕ ОТЧЕТА ......................................................................................... 68 КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №3 ...................... 68 ЛАБОРАТОРНАЯ РАБОТА №4.............................................................................. 69 ОДНОМЕРНЫЕ МАССИВЫ .................................................................................... 69 КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ .......................................................... 69 3 Одномерные массивы ..................................................................................... 69 4 Программа пузырьковой сортировки............................................................ 70 ДОМАШНЕЕ ЗАДАНИЕ ......................................................................................... 71 ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ №4 ...................................................... 72 СОДЕРЖАНИЕ ОТЧЕТА ......................................................................................... 74 3 КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №4 ...................... 74 ЛАБОРАТОРНАЯ РАБОТА №5.............................................................................. 75 МНОГОМЕРНЫЕ МАССИВЫ ................................................................................. 75 КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ .......................................................... 75 1 Многомерные массивы ................................................................................... 75 2 Ввод и вывод многомерных массивов .......................................................... 76 ДОМАШНЕЕ ЗАДАНИЕ ......................................................................................... 77 ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ №5 ...................................................... 77 СОДЕРЖАНИЕ ОТЧЕТА ......................................................................................... 79 КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №5 ...................... 79 ЛАБОРАТОРНАЯ РАБОТА №6.............................................................................. 80 УКАЗАТЕЛИ. ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ ....................... 80 КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ .......................................................... 80 3 Указатели ......................................................................................................... 80 4 Ссылки.............................................................................................................. 84 5 Динамическое распределение памяти........................................................... 84 5.1 Использование стандартных функций malloc и free ............................ 85 5.2 Использование операторов new и delete ................................................ 86 ДОМАШНЕЕ ЗАДАНИЕ ......................................................................................... 89 ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ № 6 ..................................................... 89 СОДЕРЖАНИЕ ОТЧЕТА ......................................................................................... 91 КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №6 ...................... 91 ЛАБОРАТОРНАЯ РАБОТА №7.............................................................................. 92 ОТЛАДКА ПРОГРАММЫ......................................................................................... 92 КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ .......................................................... 92 6 Отладочные пункты меню ............................................................................. 92 7 Элементы отладки ........................................................................................... 94 7.1 Контрольные точки .................................................................................. 94 7.1.1 Контрольные точки в исходном коде .............................................. 95 7.1.2 Адресные контрольные точки .......................................................... 98 7.1.3 Контрольные точки данных ............................................................. 98 7.2 Команда Run to Cursor ........................................................................... 100 7.3 Команда Pause......................................................................................... 100 8 Наблюдение за переменными ...................................................................... 100 8.1 Быстрый просмотр данных ................................................................... 102 9 Инспектор отладки ........................................................................................ 103 9.1 Инспекция локальных переменных...................................................... 104 10 Другие инструменты отладки................................................................... 105 10.1 Диалог Evaluate/Modify ...................................................................... 105 10.2 Окно CPU ............................................................................................. 106 10.3 Стек вызовов ....................................................................................... 107 10.4 Команда Go to Address ....................................................................... 107 10.5 Команда Program Reset ....................................................................... 108 10.6 Step Over .............................................................................................. 108 10.7 Trace Into .............................................................................................. 108 4 ЗАДАНИЕ НА ЛАБОРАТОРНУЮ РАБОТУ №7................................................ 109 КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №7 .................... 109 Приложение I ............................................................................................. 110 Таблица I.1. Список ключевых слов C++................................................ 110 Таблица I.2. Константы в языке C++ ....................................................... 111 Таблица I.3. Управляющие последовательности в языке C++ ............. 112 Таблица I.4. Основные операции языка C++ .......................................... 113 Приложение II ............................................................................................ 117 Блоки схем алгоритмов согласно ГОСТ 19.002 – 80 и 19.003 – 80 ...... 117 Приложение III ........................................................................................... 120 Оформление алгоритма программы в виде блок-схемы ....................... 120 Приложение IV .......................................................................................... 121 Базовая программа для выполнения лабораторных работ .................... 121 Приложение V ............................................................................................ 122 Таблица V.1 Флаги форматирования....................................................... 122 Приложение VI .......................................................................................... 122 Таблица VI.1. Функции работы со строками символов......................... 122 СПИСОК ИСПОЛЬЗУЕМЫХ ИСТОЧНИКОВ ................................................... 125 5 ЛАБОРАТОРНАЯ РАБОТА №1 ОСНОВЫ РАБОТЫ В СРЕДЕ C++ BUILDER. ОСНОВЫ ЯЗЫКА СИ КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ 1 Основы работы в среде C++Builder 1.1 Создание консольного приложения Запустите любым способом C++Builder, если он еще не запущен. Выберите пункт меню File>New. В окне диалога New Items на вкладке New выберите «иконку» Console Wizard – мастер консольного приложения. В появившемся окне диалога установите «радиокнопку» C++ и «флажок» Console Application. Остальные флажки сбросьте, если они установлены. Нажмите кнопку OK. Мастер создал минимальное (пустое) консольное приложение. C++Builder готов к работе. Каждое из окон, которые Вы видите на экране, имеют определенное назначение. В верхней части экрана находится главное окно. Оно содержит меню, панель управления, панель с палитрой компонентов и предназначено для управления разработкой программ. Ниже слева находится инспектор объектов (Object Inspector), который используется, если программа пишется в стиле объектно-ориентированного программирования. Пока это окно не будет использоваться, поэтому закройте его щелчком мыши на кнопке . Оставшееся окно – редактор кода. Оно состоит из двух стыкованных окон. Первое – обозреватель классов находится слева. Как и инспектор объектов, оно не используется. Закройте его. Второе окно – редактор исходного кода программы. Для удобства работы его следует расширить щелчком мыши на кнопке . В результате интегрированная среда разработки программ С++Builder приобретает вид изображенный на рис. 1.1. Исходный код программы на языке Си вводится как дополнение к минимальной программе, созданной мастером консольного приложения. Как сделать дополнение поясняет пример первой программы, приведенной на рис. 1.2. Введите эту программу в редактор, используя электронный файл (документ Word) с текстом программы и следуя рекомендациям раздела 1.2.2. После ввода вполне возможно неправильное воспроизведение русских букв. Для правильного воспроизведения следует установить шрифт редактора: меню Tools>Editors Options…>Display Editor Font: Courier New Cyr. Чтобы запустить введенную программу следует нажать кнопку с зеленым треугольником на панели управления. С++Builder предложит задать имя программы, если оно еще не задано. Первым вводится имя модуля программы (Unit). Лучше ввести уникальное имя, например, U_katia_lab1. Во втором окне диалога введите имя проекта, например, Pr_ katia_lab1. После запуска программы на экране появляется окно консоли (рис.1.3). По сути, запускается программа Pr_ katia_lab1. exe, которая может быть зависимой и независимой от среды разработки (см. лабораторную работу №5), 6 но C++Builder – интегрированная среда и позволяет выполнять все операции по созданию программы, в том числе проверять программу в работе и производить поиск ошибок программирования. Последний процесс называется отладкой программы и требует знания соответствующей системы управления. Главное окно Палитра компонентов Панель управления Окно редактора исходного кода Строка состояния редактора кода Панель задач Windows Рис. 1.1 Среда разработки C++Builder //--------------------------------------------------------------------------#pragma hdrstop //эта директива имеет спец. назначение //ее не следует удалять //--------------------------------------------------------------------------//здесь подключаются библиотеки, например // #include <conio.h> //библиотека подключения функции getch() #include <iostream.h> //библиотека для cin и cout #include <malloc.h> //библиотека для динамического выделения памяти #include <fstream.h> //библиотека для ввода/вывода в файл #include <windows.h> //библиотека используется в функции RUS #include <math.h> //библиотека математических функций #include <ctype.h> // библиотека проверки принадлежности символов #include <stdlib.h> // библиотека некоторых стандартных функций #include <string.h> // библиотека функций работы со строками // char bufRus[256]; //глобальная переменная bufRus используется // в функции RUS //--------------------------------------------------------------------------char* RUS(const char*text) //функция поддержки русского языка { CharToOem(text,bufRus); 7 return bufRus; } //--------------------------------------------------------------------------#pragma argsused //эта директива имеет спец. назначение //ее не следует удалять int main(int argc, char* argv[]) { cout<<RUS("***Напишите здесь название программы*** \n"); //**************************************************** //Здесь введите свою программу, например: cout<<"Hello World from Console !\n"; cout<<RUS("Привет Всем из Консоли !\n"); //**************************************************** cout<<RUS("\nНажмите любую клавишу для завершения программы ...\n"); getch();//консольное окно ожидает нажатия клавиши return 0; } //--------------------------------------------------------------------------Рис. 1.2 Язык Си. Пример первой программы Рис. 1.3 Окно консоли программы с именем – Pr_ katia_lab1.exe Данная программа является базовой для написания собственных программ с использованием русского языка. Она содержит подключение наиболее 8 широко используемых библиотек и поэтому, частично, снимает эффект неопытности программиста. 1.2 Отладка программы C++ Builder имеет мощный отладчик программ, помогающий исправлять ошибки программирования. Однако для простых консольных приложений достаточно иметь минимальные сведения о нем. 1.2.1 Установка точек останова (Add breakpoint) Выполните щелчок мышью на вертикальной полоске серого цвета идущей по левому краю окна редактора кода. На ней появится кружок красного цвета (рис. 4). Это и есть точка останова. Она находится в начале выделенной красным цветом строки программы. Рис. 4 Установка точки останова Поэтому программа, после запуска, прекратит выполнение кода точно в начале строки и будет ожидать действий по отладке. Такими действиями могут быть: Пошаговое выполнение программы. Для этого следует нажать кнопку Step Over на панели управления или клавишу клавиатуры F8. Программа будет выполняться построчно. Функции пользователя будут выполняться целиком. Для построчного выпол- 9 нения внутреннего кода функций пользователя следует нажать кнопку Trace Into на панели управления или клавишу клавиатуры F7. Тогда пошаговое выполнение будет включать и строки функций; Продолжение выполнения программы до конца. Для этого следует нажать кнопку запуска программы (зеленый треугольник) или клавишу F9 (до строки с курсором F4); Ввод дополнительных точек останова. После ввода нажимается кнопка запуска для достижения следующего останова или конца программы; Во время прерывания программы в точке останова можно просматривать содержимое переменных и изменять их значение. Просмотр удобно выполнять, останавливая курсор мыши над соответствующей переменной в тексте программы. Изменить переменную можно в окне диалога, запускаемом через меню Run>Evaluate/Modify (Ctrl F7). Эксперименты с меню Run и Project могут подсказать еще ряд приемов по отладке программы. Следует постепенно изучать эти приемы, тогда все будет легко и просто. 1.2.2 Минимальные правила редактирования текста программы Правила совпадают с правилами для любого текстового редактора, например, Word. Особенно эффективно использование операций с текстовыми блоками: Сut – вырезать выделенный блок (Ctrl X), Copy – копировать выделенный блок (Ctrl C), Paste – вставить выделенный блок (Ctrl V). При этом поддерживаются комбинации клавиш Ctrl Ins – копировать, Shift Ins – вставить. 1.2.3 Прерывание выполнения программы Иногда программа в каком-либо месте выполнения ведет себя непредсказуемо, например, требует введения дополнительных данных, не заканчивается, где надо или вовсе не заканчивается. Тогда ее можно прервать нажатием комбинации клавиш Ctrl Break или переключиться в окно исходного кода и нажать Ctrl F2. 2 Основы языка Си 2.1 Алфавит языка Алфавит C++ включает: прописные и строчные латинские буквы, причем прописная и строчная буквы – это разные символы; знак подчеркивания; 10 арабские цифры от 0 до 9; специальные знаки: "{}, | [] () + - / % * . \ : ‘ ? < = > ! & # ~ ; ^ пробельные символы: пробел, символы табуляции, символы перехода на новую строку. Из символов алфавита формируются лексемы языка: идентификаторы; ключевые (зарезервированные) слова; знаки операций; константы; разделители (специальные знаки, пробельные символы). Границы лексем определяются другими лексемами, такими, как разделители или знаки операций. 2.2 Идентификаторы Идентификатор – это имя программного объекта ( имя переменной, имя функции, метка). В идентификаторе могут использоваться латинские буквы, цифры и знак подчеркивания. Прописные и строчные буквы различаются, например, sysop, SySoP и SYSOP – три различных имени. Первым символом идентификатора может быть буква или знак подчеркивания, но не цифра. Пробелы внутри имен не допускаются. Для улучшения читаемости программы следует давать объектам осмысленные имена. Существует соглашение о правилах создания имен, называемое венгерской нотацией (поскольку предложил ее сотрудник компании Microsoft венгр по национальности), по которому каждое слово, составляющее идентификатор, начинается с прописной буквы, а вначале ставится префикс, соответствующий типу величины, например, iMaxLength, lpfnSetFirstDialog. Другая традиция – разделять слова, составляющие имя, знаками подчеркивания: max_length, number_of_galosh. Длина идентификатора по стандарту языка не ограничена, но компиляторы и компоновщики налагают на нее ограничения. При выборе идентификатора необходимо иметь в виду следующее: идентификатор не должен совпадать с ключевыми словами и именами используемых стандартных объектов языка; не рекомендуется начинать идентификаторы с символа подчеркивания, поскольку они могут совпасть с именами системных функций или переменных; 2.3 Ключевые слова Ключевые слова – это зарезервированные идентификаторы, которые имеют специальное значение для компилятора. Их можно использовать только 11 в том смысле, в котором они определены. Список ключевых слов C++ приведен в табл. I.1 Приложения I. 2.4 Знаки операций Знак операции – это один или более символов, определяющих действие над операндами. Операндами называются объекты, на которые направлена операция. Например: y x5 Здесь x и 5 операнды, + операция, а y – результат операции. Внутри знака операции пробелы не допускаются. Операции делятся на унарные (действия с одним операндом), бинарные (действия с двумя операндами) и тернарные(действия с тремя операндами). Один и тот же знак может интерпретироваться по-разному в зависимости от контекста. Все знаки операций за исключением [ ], ( ) и ? : представляют собой отдельные лексемы. Знаки операций C++ описаны в разделе 2.10. 2.5 Константы Константами называют неизменяемые величины. Константы бывают литеральными (литералами) и типизированными. Литеральные константы характеризуются только своей величиной и неадресуемы. Типизированные константы имеют адрес и величину. Литеральные константы различаются на целые, вещественные (с плавающей точкой), символьные и строковые константы. Компилятор, выделив литеральную константу в качестве лексемы, относит ее к одному из типов по ее внешнему виду. Форматы констант, соответствующие каждому типу, приведены в табл. I.2 Приложения I. Просмотрите их прямо сейчас. Если требуется сформировать отрицательную целую или вещественную константу, то перед константой ставится знак унарной операции изменения знака (-), например: -218, -022, -0хЗС, -4.8, -0.1е4. Вещественная константа в экспоненциальном формате представляется в виде мантиссы и порядка. Мантисса записывается слева от знака экспоненты (Е или е), порядок – справа от знака. Значение константы определяется как произведение мантиссы и возведенного в указанную в порядке степень числа 10. Пробелы внутри числа не допускаются, а для отделения целой части от дробной используется не запятая, а точка. Символьные константы, состоящие из одного символа, занимают в памяти один байт и имеют стандартный тип char. Двухсимвольные константы занимают два байта и имеют тип int, при этом первый символ размещается в байте с меньшим адресом (о типах данных рассказывается в разделе 2.7). Последовательности символов, начинающиеся с обратной косой черты, называют управляющими, или escape-последовательностями (табл. I.3 Приложения I). Символ обратной косой черты используется для представления: 12 кодов, не имеющих графического изображения (например, \а – звуковой сигнал, \n – перевод курсора в начало следующей строки); символов апострофа ( ' ), обратной косой черты ( \ ), знака вопроса (?) и кавычки ("); любого символа с помощью его шестнадцатеричного или восьмеричного кода, например, \073, \0xF5. Числовое значение должно находиться в диапазоне от 0 до 255. Управляющая последовательность интерпретируется как одиночный символ. Если непосредственно за обратной косой чертой следует символ, не предусмотренный табл. I.3 Приложения I, результат интерпретации не определен. Если в последовательности цифр встречается недопустимая, она считается концом цифрового кода. Управляющие последовательности могут использоваться и в строковых константах, называемых иначе строковыми литералами. Например, если внутри строки требуется записать кавычку, ее предваряют косой чертой, по которой компилятор отличает ее от кавычки, ограничивающей строку: "Издательский дом \"Питер\"" Все строковые литералы рассматриваются компилятором как различные объекты. Две одинаковые строки будут занимать две различные области памяти. Длинную строковую константу можно разместить на нескольких строках, используя в качестве знака переноса обратную косую черту, за которой следует перевод строки. Эти символы игнорируются компилятором, а следующая строка воспринимается как продолжение предыдущей. Например, строка "Никто не доволен своей \ внешностью, но все довольны \ своим умом" полностью эквивалентна строке "Никто не доволен своей внешностью, но все довольны своим умом". Строковый литерал представляется массивом символов и в конец каждого строкового литерала компилятором добавляется нулевой символ, представляемый управляющей последовательностью \0. Поэтому длина строки всегда на единицу больше количества символов в ее записи. Пустая символьная константа недопустима. 2.6 Комментарии Комментарии помогают при чтении программ. Их наличие в программе можно отнести к правилам хорошего тона программистов. В них можно сформулировать алгоритм функции, указать назначение переменных и пояснить, что выполняется в данной части программы. Комментарии не влияют на размер выполняемой программы. Компилятор удаляет их до генерации кода. Комментарий либо начинается с двух символов // и заканчивается символом перехода на новую строку, либо заключается между символамискобками /* и */. Примеры: 13 /* Простейшая консольная программа C++Builder. Выводит на экран "Hello World" и ждет, пока пользователь не нажмет какую-нибудь клавишу. */ getch(); //Ожидание нажатия клавиши. Пару символов, которая ограничивает комментарии обоих видов, нельзя разбивать пробелом. Внутри комментария можно использовать любые допустимые на данном компьютере символы, а не только символы из алфавита языка C++, поскольку компилятор комментарии игнорирует. Вложенные комментарии стандартом не допускаются, хотя в некоторых компиляторах разрешены. Рекомендуется использовать для пояснений //, а скобки /* */ применять для временного исключения блоков кода при отладке. 2.7 Типы данных C++ 2.7.1 Понятие типа данных Тип данных определяет: внутреннее представление данных в памяти компьютера; множество значений, которые могут принимать величины этого типа; операции и функции, которые можно применять к величинам этого типа. Все типы языка C++ можно разделить на основные и составные. В языке C++ определено шесть основных типов данных для представления целых, вещественных, символьных и логических величин. На основе этих типов программист может вводить описание составных типов. К ним относятся массивы, перечисления, функции, структуры, ссылки, указатели, объединения и классы. 2.7.2 Основные типы данных Основные (стандартные) типы данных часто называют арифметическими, поскольку их можно использовать в арифметических операциях. Для описания основных типов определены следующие ключевые слова: int (целый); char (символьный); wchar_t (расширенный символьный); bool (логический); float (вещественный); double (вещественный с двойной точностью). 14 Первые четыре типа называют целочисленными (целыми), последние два – типами с плавающей точкой. Код, который формирует компилятор для обработки целых величин, отличается от кода для величин с плавающей точкой. Существует четыре спецификатора типа, уточняющих внутреннее представление и диапазон значений стандартных типов: short (короткий); long (длинный); signed (знаковый); unsigned (беззнаковый). 2.7.3 Целый тип (int). Размер типа int не определяется стандартом, а зависит от компьютера и компилятора. Для 16-разрядного процессора под величины этого типа отводится 2 байта, для 32-разрядного – 4 байта. Для точного определения количества байт следует написать тестовую программу и включить в нее операцию: s=sizeof(int); Значение переменной s будет равно количеству байт занимаемому объектами указанного в скобках типа. Для определения диапазона значений целого типа данных следует воспользоваться формулой: Ds 28s 1... 28s 1 1 для знаковых типов (int – знаковый тип) и Dus 0...28s 1 для беззнаковых (unsigned) типов. Спецификатор short перед именем типа указывает компилятору, что под число требуется отвести 2 байта независимо от разрядности процессора. Спецификатор long означает, что целая величина будет занимать 4 байта. Таким образом, на 16-разрядном компьютере эквиваленты int и short int, а на 32разрядном – int и long int. Внутреннее представление величины целого типа – целое число в двоичном коде. При использовании спецификатора signed старший бит числа интерпретируется как знаковый (0 – положительное число, 1 – отрицательное). Спецификатор unsigned позволяет представлять только положительные числа, поскольку старший разряд рассматривается как часть кода числа. Таким образом, диапазон значений типа int зависит от спецификаторов. По умолчанию все целочисленные типы считаются знаковыми, то есть спецификатор signed можно опускать. Константам, встречающимся в программе, приписывается тот или иной тип в соответствии с их видом. Если этот тип по каким-либо причинам не устраивает программиста, он может явно указать требуемый тип с помощью суффиксов L, l (long) и U, u (unsigned). Например, константа 32L будет иметь тип long и занимать 4 байта. Можно использовать суффиксы L и U одновременно, например, Ox22UL или 05Lu. 15 Типы short int, long int, signed int и unsigned int можно сокращать до short, long, signed и unsigned соответственно. 2.7.4 Символьный тип (char). Под величину символьного типа отводится количество байт, достаточное для размещения десятичного кода любого символа из набора символов для данного компьютера, что и обусловило название типа. Как правило, это 1 байт. Тип char, как и другие целые типы, может быть со знаком или без знака. В величинах со знаком можно хранить значения в диапазоне от -128 до 127. При использовании спецификатора unsigned значения могут находиться в пределах от 0 до 255. Этого достаточно для хранения любого символа из 256символьного набора ASCII. Величины типа char применяются также для хранения целых чисел, не превышающих границы указанных диапазонов. 2.7.5 Логический тип (bool). Величины логического типа могут принимать только значения true и false, являющиеся зарезервированными словами. Внутренняя форма представления значения false – 0 (нуль). Любое другое значение интерпретируется как true. При преобразовании к целому типу true имеет значение 1. 2.7.6 Вещественный тип (float, double и long double). Стандарт C++ определяет три типа данных для хранения вещественных значений: float, double и long double. Вещественные типы данных хранятся в памяти компьютера иначе, чем целочисленные. Внутреннее представление вещественного числа состоит из двух частей – мантиссы и порядка. В IBM PC-совместимых компьютерах величины типа float занимают 4 байта, из которых один двоичный разряд отводится под знак мантиссы, 8 разрядов под порядок и 23 под мантиссу. Для величин типа double, занимающих 8 байт, под порядок и мантиссу отводится 11 и 52 разряда соответственно. Длина мантиссы определяет точность числа, а длина порядка – его диапазон. Спецификатор long перед именем типа doublе указывает, что под величину отводится 10 байт. Диапазон значений вещественных типов определяется с помощью тестовой программы, в которой следует каким-либо образом узнать значения следующих констант: FLT_MIN…FLT_MAX – диапазон типа float, DBL_MIN…DBL_MAX – диапазон типа double, LDBL_MIN…LDBL_MAX – диапазон типа long double. 16 Эти константы находятся в библиотеке <float.h> и следует убедиться, что она подключена. Константы с плавающей точкой имеют по умолчанию тип double. Можно явно указать тип константы с помощью суффиксов F, f (float) и L, 1 (long). Например, константа 2E+6L будет иметь тип long doublе, а константа 1.82f – тип float. В стандарте ANSI диапазоны значений для основных типов не задаются, определяются только соотношения между их размерами, например: sizeof(float) < sizeof(double) < sizeof(long double) sizeof(char) < sizeof(short) < sizeof(int) <, sizeof(long) Различные виды целых и вещественных типов, различающиеся диапазоном и точностью представления данных, введены для того, чтобы дать программисту возможность наиболее эффективно использовать возможности конкретной аппаратуры, поскольку от выбора типа зависит скорость вычислений и объем памяти. Но оптимизированная для компьютеров какого-либо одного типа программа может стать не переносимой на другие платформы, поэтому в общем случае следует избегать зависимостей от конкретных характеристик типов данных. 2.7.7 Тип void Кроме перечисленных, к основным типам языка относится тип void, но множество значений этого типа пусто. Он используется для определения функций, которые не возвращают значения (такие функции в языке Паскаль называют процедурами, но в языке Си процедур нет), для указания пустого списка аргументов функции, как базовый тип для указателей и в операции приведения. 2.8 Переменные Переменная – это именованная область памяти, в которой хранятся данные определенного типа. У переменной есть имя (идентификатор) и значение. Имя служит для обращения к области памяти, в которой хранится значение. Во время выполнения программы значение переменной можно изменять. Перед использованием любая переменная должна быть описана. Пример описания целой переменной с именем а и вещественной переменной х: int a; float x; Общий вид оператора описания переменных: [класс памяти] [const] тип имя [инициализатор]; 17 Здесь и далее на общем виде квадратные скобки [] указывают на необязательные составные части объектов и структур. Рассмотрим правила задания составных частей этого оператора. Необязательный класс памяти может принимать одно из значений auto, extern, static и register. О них рассказывается чуть ниже. Модификатор const показывает, что значение переменной изменять нельзя. Такую переменную называют именованной (типизированной) константой, или просто константой. При описании можно присвоить переменной начальное значение, это называется инициализацией. Инициализатор можно записывать в двух формах: со знаком равенства: = значение, например: short int а = 1; const char С = 'С'; или в круглых скобках: ( значение ), например: float x(3); char t (54); Константа обязательно должна быть инициализирована при описании. В одном операторе можно описать несколько переменных одного типа, разделяя их запятыми, например: float с = 0.22, x(3), sum; Описание переменной явно или по умолчанию задает ее область действия. Область действия переменной – это часть программы, в которой ее можно использовать для доступа к связанной с ней области памяти. В зависимости от области действия переменная может быть локальной или глобальной. Если переменная определена внутри блока (блок ограничен фигурными скобками {}), она называется локальной, область ее действия – от точки описания до конца блока, включая все вложенные блоки. Если переменная определена вне любого блока, она называется глобальной и областью ее действия считается файл (файл иногда называют модулем (Unit)), в котором она определена, от точки описания до его конца. Класс памяти определяет время жизни и область видимости программного объекта (в частности, переменной). Время жизни может быть постоянным (в течение выполнения программы) и временным (в течение выполнения блока). Это поясняется ниже, при описании классов. 18 Областью видимости идентификатора называется часть текста программы, из которой допустим обычный доступ к связанной с идентификатором областью памяти. Чаще всего область видимости совпадает с областью действия. Исключением является ситуация, когда во вложенном блоке описана переменная с таким же именем. В этом случае внешняя переменная во вложенном блоке невидима, хотя он и входит в ее область действия. Тем не менее, к этой переменной, если она глобальная, можно обратиться, используя операцию доступа к области видимости два двоеточия, например ::a=1. Для задания класса используются следующие спецификаторы: auto – автоматическая переменная. Память под нее выделяется в стеке и при необходимости инициализируется каждый раз при выполнении оператора, содержащего ее определение. Освобождение памяти происходит при выходе из блока, в котором описана переменная. Время ее жизни – с момента описания до конца блока. Для глобальных переменных этот спецификатор не используется, а для локальных он принимается по умолчанию, поэтому задавать его явным образом большого смысла не имеет, что и не делается в программе. Таким образом, по умолчанию все переменные автоматические. extern – означает, что переменная определяется в другом месте программы (в другом файле или дальше по тексту). Используется для создания переменных, доступных во всех модулях программы, в которых они объявлены. Если переменная в том же операторе инициализируется, спецификатор extern игнорируется (переменная уже не может быть внешней). static – статическая переменная. Время жизни – постоянное. Инициализируется один раз при первом выполнении оператора, содержащего определение переменной. В зависимости от расположения оператора описания статические переменные могут быть глобальными и локальными. Глобальные статические переменные видны только в том модуле, в котором они описаны. register – аналогично auto, но память выделяется по возможности в регистрах процессора. Если такой возможности у компилятора нет, переменные обрабатываются как auto. volatile – применяется для объявления переменных, которые можно назвать нестабильными. Спецификатор volatile сообщает компилятору, что значение переменной может изменяться как бы само по себе, например, некоторым фоновым процессом или аппаратно. Поэтому компилятор не должен пытаться как-либо оптимизировать выражения, в которые входит переменная, – предполагая, например, что ее значение не менялось с предыдущего раза и потому выражение не нужно заново пересчитывать. Есть и другой момент. В программе могут быть так называемые критические участки кода, во время исполнения которых изменение значения нестабильной переменной приведет к абсурдным результатам. (Возьмите случай оценки “А или не-А”, если А нестабильно!) Для таких критических участков компилятор должен создавать копию, например, в регистре, и пользоваться этой копией, а не самой переменной. Можно написать такое объявление: volatile const int vciVar = 10; 19 Другими словами, “нестабильная константа” типа int. В этом нет никакого противоречия – компилятор не позволит вашей программе изменять переменную, но и не будет предполагать ее значение известным априори, так как оно может меняться в силу внешних причин. Примеры описания переменных: int a; // 1 глобальная переменная а int main() { int b; // 2 локальная переменная b extern int x; // 3 переменная х определена в другом месте static int с; // 4 локальная статическая переменная с а = 1; //5 присваивание глобальной переменной int а; //6 локальная переменная а а = 2; //7 присваивание локальной переменной ::а = 3; //8 присваивание глобальной переменной return 0; } int x = 4; //9 определение и инициализация х В этом примере глобальная переменная «а» определена вне всех блоков. Память под нее выделяется в сегменте данных в начале работы программы, областью действия является вся программа. Область видимости – вся программа, кроме строк 6-8, так как в первой из них определяется локальная переменная с тем же именем, область действия которой начинается с точки ее описания и заканчивается при выходе из блока. Переменные «b» и «с» – локальные, область их видимости – блок, но время жизни различно: память под «b» выделяется в стеке при входе в блок и освобождается при выходе из него, а переменная «с» располагается в сегменте данных и существует все время, пока работает программа. Если при определении начальное значение переменных явным образом не задается, компилятор присваивает глобальным и статическим переменным нулевое значение соответствующего типа. Автоматические переменные в этом случае не инициализируются (содержимое переменной будет произвольным). Имя переменной должно быть уникальным в своей области действия (например, в одном блоке не может быть двух переменных с одинаковыми именами). Не жалейте времени на придумывание подходящих имен. Имя должно отражать смысл хранимой величины, быть легко распознаваемым и, желательно, не содержать символов, которые можно перепутать друг с другом, например, 1,1 (строчная L) или I (прописная i). Для разделения частей имени можно использовать знак подчеркивания. Не давайте переменным имена, демонстрирующие знание иностранного слэнга. Как правило, переменным с большой областью видимости даются более длинные имена (желательно с префиксом типа), а для переменных, вся жизнь которых проходит на протяжении 20 нескольких строк исходного текста, хватит одной буквы с комментарием при объявлении. Описание переменной может выполняться в форме объявления или определения. Объявление информирует компилятор о типе переменной и классе памяти, а определение содержит, кроме этого, указание компилятору выделить память в соответствии с типом переменной. В C++ большинство объявлений являются одновременно и определениями. В приведенном выше примере только описание внешней переменной 3 по счету является объявлением, но не определением. Переменная может быть объявлена многократно, но определена только в одном месте программы, поскольку объявление просто описывает свойства переменной, а определение связывает ее с конкретной областью памяти. 2.9 Функции и объекты ввода/вывода В языке C++ нет встроенных средств ввода/вывода – он осуществляется с помощью функций, типов и объектов, содержащихся в стандартных библиотеках. Используется два способа: функции, унаследованные из языка С, и объекты C++. Подчеркивая важность функций и объектов ввода/вывода их иногда называют операторами, хотя, по сути, это не так. 2.9.1 Основные функции ввода/вывода в стиле С: scanf (const char* format, [адрес переменной] , [адрес переменной]... ) // ввод printf(const char* format, [имя переменной] , [имя переменной]... ) // вывод Они выполняют форматированный ввод и вывод произвольного количества величин в соответствии со строкой формата format. printf () является функцией стандартной библиотеки с переменным числом аргументов. Она всегда имеет, по крайней мере, один аргумент – строку формата, чаще всего строковый литерал. Строка может содержать спецификаторы преобразования. Функция сканирует строку и передает ее символы на стандартный вывод программы, по умолчанию консоль, пока не встретит спецификатор преобразования. В этом случае printf () ищет дополнительный аргумент (используя имя переменной из списка), который форматируется и выводится в соответствии со спецификацией ( спецификация в строке вывода заменяется на значение переменной). Таким образом, вызов printf () должен содержать столько дополнительных аргументов, сколько спецификаторов преобразования имеется в строке формата. Спецификатор преобразования Синтаксис спецификатора преобразования имеет такой вид: %[флаги] [поле][.точность][размер]символ типа Как видите, обязательными элементами спецификатора являются только начальный знак процента (по знаку процента легко контролировать количество 21 переменных) и символ, задающий тип преобразования. Таблица I.5 Приложения I перечисляет возможные варианты различных элементов спецификации. Флаги задают “стиль” представления чисел на выводе, поле и точность определяют характеристики поля, отведенного под вывод аргумента, размер уточняет тип аргумента, символ типа задает собственно тип преобразования. В строке формата можно использовать escape-последовательности. В приведенном ниже примере используется последовательность \n, которая обеспечивает при выводе перевод строки. Следующий пример показывает возможности форматирования функции printf (). Если суть оператора и спецификации не понятна, выполните следующую далее программу в C++Builder. Примечание: следует напомнить, что на рис. 1.2 была базовая программа. Здесь и далее будет приведен текст, который нужно поместить между линиями из звездочек в базовую программу. /* **Демонстрация форматирования вывода на консоль **функцией printf(). */ double p = 27182.81828; int j = 255; /* Вывести 4 цифры; вывести обязательный знак числа */ printf(RUS(" Позиции: 1234567891234123456789\n")); printf(RUS("Тест целого формата: %13.4d %+8d\n"), j, j); /* Вывести по левому краю со знаком; заполнить нулями свободные позиции: */ printf(RUS(" Позиции: 1234567891234512345678\n")); printf(RUS("Еще один тест целых форматов: %-+13d %08d\n"), j, j); /* Самостоятельно определить формат вывода*/ printf(RUS(" Позиции: 1234567891234512345678\n")); printf(RUS("Тест o и h форматов: %#13o %#8.7x\n"), j, j); printf(RUS(" Позиции: 1234567891234512345678\n")); printf(RUS("Тест формата e и f: %13.7e %8.2f\n"), p, p); Обязательно, выполните эту программу в C++Builder! В языке С для ввода имеется “зеркальный двойник” printf() – функция scanf (). Функция читает данные со стандартного ввода, по умолчанию – клавиатуры. Она так же, как и printf () , принимает строку формата с несколькими спецификаторами преобразования и несколько дополнительных параметров, которые должны быть адресами переменных, куда будут записаны введенные значения. Примером вызова scanf () может служить следующий фрагмент кода: int age; printf("Enter your age: "); // Запросить ввод возраста пользователя. scanf ("%d", &age); // Прочитать введенное число. Знак & означает операцию получения адреса. 22 Для работы с этими функциями в программу необходимо включить заголовочный файл stdio.h. 2.9.2 Основные объекты ввода/вывода в стиле С++: cin>>[имя переменной] >> [имя переменной]... //ввод cout<<[имя переменной] << [имя переменной]... //вывод Программа с использованием для ввода/вывода библиотеки классов C++ выглядит так: int i; cout << RUS("Введите целое число\n"); cin >> i; cout<<RUS("Вы ввели число ")<<i; cout<<RUS(", спасибо!"); Примечание. К сожелению, следует избегать двух функций RUS в одной строке с объектом cout. Это приводит к неправильному выводу. Поэтому фраза “, спасибо” выведена отдельным объектом cout. Заголовочный файл <iostream.h> содержит описание набора классов для управления вводом/выводом. В нем определены стандартные объекты-потоки cin для ввода с клавиатуры и cout для вывода на экран, а также операции помещения в поток << и чтения из потока >>. Для вывода информации в стандартный поток используется формат cout << выражение ; где выражение может быть представлено переменной или некоторым смысловым выражением: cout << Z ; cout <<RUS("Сумма ="); cout << 7+3; Последовательно можно выводить несколько выражений: cout << выражение 1 <<выражение 2; или cout << выражение 1 << выражение 2 << выражение 3 23 << выражение 4; При выводе можно использовать escape-последовательности: cout << RUS("Введите целое число\n"); cout << z <<" \n"; Для ввода данных с клавиатуры используется формат записи: cin >> переменная ; или cin >> переменная 1>> переменная2; При этом тип вводимого значения и тип переменной в программе, которой передается значение, должны совпадать. Можно вводить несколько значений для разных переменных. Переменные отделяются пробелом или нажатием клавиши Enter. Если введенных значений больше, чем ожидается в программе, часть вводимых данных останется во входном буфере. В случае если вводится строка символов, ввод продолжается до первого символа пробела или нажатия клавиши Enter. Если при следующем коде char String[80]; cin>> String; будет введена строка "Да здравствует С++", то переменная String примет значение "Да". Остальная часть строки останется в буфере до тех пор, пока в программе не встретится следующий оператор ввода. Для работы с объектами cin и cout в программу необходимо включить заголовочный файл iostream.h. В дальнейшем изложении будут использоваться оба способа ввода/вывода, но в одной программе смешивать их не рекомендуется. 2.10 Операции В таблице 1.5 приложения I приведен список основных операций, определенных в языке C++, в соответствии с их приоритетами (по убыванию приоритетов, операции с разными приоритетами разделены чертой). В соответствии с количеством операндов, которые используются в операциях, они делятся на унарные (один операнд), бинарные (два операнда) и тернарную (три операнда). Пробелы между символами внутри операции не допускаются. Рассмотрим основные операции подробнее. 24 2.10.1 Операции увеличения и уменьшения на 1 (++ и - -) Эти операции, называемые также инкрементом ++ и декрементом --, имеют две формы записи – префиксную, когда операция записывается перед операндом, и постфиксную. В префиксной форме сначала изменяется операнд, а затем его значение становится результирующим значением выражения, а в постфиксной форме значением выражения является исходное значение операнда, после чего он изменяется. Например: int x=3,y=3; printf(RUS("Значение префиксного выражения: %d\n"), ++x); printf(RUS("Значение постфиксного выражения: %d\n"), y++); printf(RUS("Значение х после приращения: %d\n"), x); printf(RUS("Значение у после приращения: %d\n"), y); Результат работы программы: Значение префиксного выражения: 4 Значение постфиксного выражения: 3 Значение х после приращения: 4 . Значение у после приращения: 4 Если непонятно, выполните программу в C++Builder! Операндом операции инкремента в общем случае является так называемое L-значение (L-value). Так обозначается любое выражение, адресующее некоторый участок памяти, в который можно занести значение. Название произошло от операции присваивания, поскольку именно ее левая (Left) часть определяет, в какую область памяти будет занесен результат операции. Переменная является частным случаем L-значения. 2.10.2 Операция определения размера sizeof Эта операция предназначена для вычисления размера объекта или типа в байтах, и имеет две формы: sizeof выражение sizeof ( тип ) Пример: float x = 1; cout << "sizeof (float) :" << sizeof (float): cout << "\nsizeof x :" << sizeof x; 25 cout <<"\nsizeof (x + 1.0) :" << sizeof (x + 1.0); Результат работы программы: sizeof (float) : 4 sizeof x : 4 sizeof (x + 1.0) : 8 Если непонятно, выполните программу в C++Builder! Последний результат связан с тем, что вещественные константы по умолчанию имеют тип double, к которому, как к более длинному, приводится тип переменной х и всего выражения. Скобки необходимы для того, чтобы выражение, стоящее в них, вычислялось раньше операции приведения типа, имеющей больший приоритет, чем сложение. 2.10.3 Операции отрицания (-, ! и ~). Арифметическое отрицание (унарный минус -) изменяет знак операнда целого или вещественного типа на противоположный. Логическое отрицание (!) дает в результате значение 0, если операнд есть истина (не нуль), и значение 1, если операнд равен нулю. Операнд должен быть целого или вещественного типа, а может иметь также тип указатель. Поразрядное отрицание (~), часто называемое побитовым, инвертирует каждый разряд в двоичном представлении целочисленного операнда. 2.10.4 Деление (/) и остаток от деления (%). Операция деления применима к операндам арифметического типа. Если оба операнда целочисленные, результат операции округляется до целого числа, в противном случае тип результата определяется правилами преобразования. Операция остатка от деления применяется только к целочисленным операндам. int x=11,y=4; float z=4; printf(RUS("Результат от деления: %d %f\n"), x/y, x/z); printf(RUS("Остаток от деления: %d\n"), x%y); printf(RUS("Ощутите разницу: 1/3=%d 1.0/3=%1.3f\n"),1/3,1.0/3); Результат работы программы: Результат от деления: 2 2.750000 Остаток от деления: 3 26 Ощутите разницу: 1/3=0 1.0/3=0.333 Если непонятно, выполните программу в C++Builder! 2.10.5 Операции сдвига (<< и >>) Применяются к целочисленным операндам. Они сдвигают двоичное представление первого операнда влево или вправо на количество двоичных разрядов, заданное вторым операндом. При сдвиге влево << освободившиеся разряды обнуляются. При сдвиге вправо >> освободившиеся биты заполняются нулями, если первый операнд беззнакового типа, и знаковым разрядом в противном случае. Операции сдвига не учитывают переполнение и потерю значимости. 2.10.6 Операции отношения (<, <=, >, >=, = =, ! =) Сравнивают первый операнд со вторым. Операнды могут быть арифметического типа или указателями. Результатом операции является значение true или false (любое значение, не равное нулю, интерпретируется как true). Операции сравнения на равенство и неравенство имеют меньший приоритет, чем остальные операции сравнения. Обратите внимание на разницу между операцией проверки на равенство (= =) и операцией присваивания (=), используемой в выражениях. 2.10.7 Поразрядные операции (& , | , ^) Применяются только к целочисленным операндам и работают с их двоичными представлениями. При выполнении операций операнды сопоставляются побитово (первый бит первого операнда с первым битом второго, второй бит первого операнда со вторым битом второго, и т д.). При поразрядной конъюнкции, или поразрядном И (операция обозначается &) бит результата равен 1 только тогда, когда соответствующие биты обоих операндов равны 1. При поразрядной дизъюнкции, или поразрядном ИЛИ (операция обозначается | ) бит результата равен 1 тогда, когда соответствующий бит хотя бы одного из операндов равен 1. При поразрядном исключающем ИЛИ (операция обозначается ^ ) бит результата равен 1 только тогда, когда соответствующий бит только одного из операндов равен 1. … cout << "\n 6 & 5 = "<< (6 & 5); cout << "\n 6 | 5 = "<< (6 | 5); cout << "\n 6 ^5 = " << (6 ^ 5); … 27 Результат работы программы: 6&5=4 6 | 5=7 6 ^5=3 2.10.8 Логические операции (&& и ||). Операнды логических операций И (&&) и ИЛИ ( || ) могут иметь арифметический тип или быть указателями, при этом операнды в каждой операции могут быть различных типов. Преобразования типов не производятся, каждый операнд оценивается с точки зрения его эквивалентности нулю (операнд, равный нулю, рассматривается как false, не равный нулю – как true). Результатом логической операции является true или false. Результат операции логическое И имеет значение true только если оба операнда имеют значение true. Результат операции логическое ИЛИ имеет значение true, если хотя бы один из операндов имеет значение true. Логические операции выполняются слева направо. Если значения первого операнда достаточно, чтобы определить результат операции, второй операнд не вычисляется. Логические операции широко используются в условном операторе и операторах цикла для формирования условий. 2.10.9 Операции присваивания (=, +=, -=, *= и т. д.). Формат операции простого присваивания (=): операнд_1 = операнд_2; Первый операнд должен быть L-значением (переменной), второй – выражением. Сначала вычисляется выражение, стоящее в правой части операции, а потом его результат записывается в область памяти, указанную в левой части (мнемоническое правило: «присваивание – это передача данных "налево"»). То, что ранее хранилось в этой области памяти, естественно, теряется. int a = 3,b = 5,c = 7; a = b; b = а; с = с + 1; cout << "a = " << a; cout << "\t b = " << b; cout << "\t с = " << с; Результат работы программы: a=5 b=5 c=8 28 При присваивании производится преобразование типа выражения к типу L-значения, что может привести к потере информации. Пример: int p; double pReal = 2.718281828; p = pReal; // p получает значение 2 pReal = p; // pReal теперь равно 2.0 В сложных операциях присваивания ( +=, *=, /= и т п.) при вычислении выражения, стоящего в правой части, используется и L-значение из левой части. Например, при сложении с присваиванием ко второму операнду прибавляется первый, и результат записывается в первый операнд, то есть выражение а += b является более компактной записью выражения а = а + b, а /= b – компактной записью выражения а = а /b. 2.10.10 Условная операция (?:). Эта операция тернарная, то есть имеет три операнда. Ее формат: операнд_1 ? операнд_2 : операнд_3 Первый операнд может иметь арифметический тип или быть указателем. Он оценивается с точки зрения его эквивалентности нулю (операнд, равный нулю, рассматривается как false, не равный нулю – как true). Если результат вычисления операнда 1 равен true, то результатом условной операции будет значение второго операнда, иначе – третьего операнда. Вычисляется всегда либо второй операнд, либо третий. Их тип может различаться. Условная операция является сокращенной формой условного оператора if (он рассмотрен далее). int a = 11, b = 4, max; max = (b > a)? b : a; printf(RUS("Наибольшее число: %d"), max); Результат работы программы: Наибольшее число: 11 Другой пример применения условной операции. Требуется, чтобы некоторая целая величина i увеличивалась на 1, если ее значение не превышает n, а иначе принимала значение 1: i = (i < n) ? i + 1: 1; 29 Не рассмотренные в этом разделе операции будут описаны позже. 2.11 Выражения Выражения состоят из операндов, операций и скобок и используются для вычисления некоторого значения определенного типа. Каждый операнд является, в свою очередь, выражением или одним из его частных случаев – константой или переменной. Примеры выражений: (а + 0.12)/6 х && у || !z (t * sin(x)-1.05e4)/((2 * k + 2) * (2 * k + 3)) Операции выполняются в соответствии с приоритетами. Для изменения порядка выполнения операций используются круглые скобки. Если в одном выражении записано несколько операций одинакового приоритета, унарные операции, условная операция и операции присваивания выполняются справа налево, остальные – слева направо. Например, в выражении а = b = с сначала b = c, а затем a = b, выражение a + b + c выполняется как (а + b) + с. Порядок вычисления подвыражений внутри выражений не определен: например, нельзя считать, что в выражении (sin(x + 2) + cos(y + 1)) обращение к синусу будет выполнено раньше, чем к косинусу, и что х + 2 будет вычислено раньше, чем y + 1. Результат вычисления выражения характеризуется значением и типом. Например, если а и b – переменные целого типа и описаны так: int а = 2, b = 5; то выражение а + b имеет значение 7 и тип int, а выражение а = b имеет значение, равное помещенному в переменную а (в данному случае 5) и тип, совпадающий с типом этой переменной. Таким образом, в C++ допустимы выражения вида а = b = с: сначала вычисляется выражение b = с, а затем его результат становится правым операндом для операции присваивания переменной а. 2.11.1 Преобразование типов в выражении. В выражение могут входить операнды различных типов. Если операнды имеют одинаковый тип, то результат операции будет иметь тот же тип. Если операнды разного типа, перед вычислениями выполняются преобразования типов по умолчанию или явно. Преобразования бывают двух видов: изменяющие внутреннее представление величин (с потерей точности или без потери точности); изменяющие только интерпретацию внутреннего представления. 30 К первому виду относится, например, преобразование целого числа в вещественное (без потери точности) и наоборот (возможно, с потерей точности), ко второму – преобразование знакового целого в беззнаковое. По умолчанию преобразование осуществляется по определенным правилам, обеспечивающим преобразование более коротких типов в более длинные для сохранения значимости и точности. На каждом шаге оценки выражения выполняется одна операция, и имеются два операнда. Если их тип различен, операнд меньшего “ранга экстенсивности” приводится к типу более “экстенсивного”. Под экстенсивностью понимается диапазон значений, который поддерживается данным типом. По возрастанию экстенсивности типы следуют в очевидном порядке: char short int, long float double long double Ниже приведена последовательность преобразований. Любые операнды типа char, unsigned char или short преобразуются к типу int по правилам: char расширяется нулем или знаком в зависимости от умолчания для char; unsigned char расширяется нулем; signed char расширяется знаком; short, unsigned short при преобразовании не изменяются. Затем два операнда становятся либо int, либо float, double или long double. Причем, если один из операндов имеет тип float, то другой преобразуется к типу float; если один из операндов имеет тип double, то другой преобразуется к типу double; если один из операндов имеет тип long double, то другой преобразуется к типу long double. Таким образом операнды преобразуются к типу наиболее длинного из них, и он используется как тип результата (не путать с оператором присваивания). Кроме того, если в операции участвуют знаковый и беззнаковый целочисленные типы, то знаковый операнд приводится к беззнаковому типу. Результат тоже будет беззнаковым. Во избежание ошибок нужно точно представлять себе, что при этом происходит, и при необходимости применять операцию приведения, явно преобразующую тот или иной операнд. Некоторые считают, что в выражении все операнды заранее приводятся к наиболее экстенсивному типу, а уж потом производится оценка. Это не так. Приведение типов выполняется последовательно для каждой текущей пары операндов. Явное преобразование типа выполняется посредством операции приведения (тип) и может применяться к любому операнду в выражении, например: 31 р = р0 + (int)(pReal + 0.5); // Округление pReal Следует иметь в виду, что операция приведения типа может работать двояким образом. Во-первых, она может производить действительное преобразование данных, как это происходит при приведении целого типа к вещественному и наоборот. Получаются совершенно новые данные, физически отличные от исходных. Во-вторых, операция может никак не воздействовать на имеющиеся данные, а только изменять их интерпретацию. Например, если переменную типа short со значением -1 привести к типу unsigned short, то данные останутся теми же самыми, но будут интерпретироваться по-другому (как целое без знака), в результате чего будет получено значение 65535. ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ №1 Выполните обязательные и непонятные Вам примеры теоретических сведений и лишь затем «задание 1.N» по варианту N (N – номер компьютера). Задание 1.1 Создайте консольное приложение, используя для ввода/вывода объекты cin и cout. Используя созданное консольное, приложение написать программу, в которой: определяется размер типа int в байтах, вводятся с клавиатуры значения целых переменных a, b, вычисляется сумма a и b, вычисляется остаток от деления a на b, выполняется операция остаток от деления a на b с присваиванием результата b, используя условную операцию (?:): найти минимальное из чисел a и b, вычислить, если a < b, то a=a/b. Задание 1.2 Создайте консольное приложение, используя для ввода/вывода объекты cin и cout. Использую созданное консольное, приложение написать программу, в которой: определяется размер типа bool в байтах, вводятся с клавиатуры значения переменных a, b типа float, вычисляется разность a и b, вычисляется частное от деления a на b, выполняется операция умножение a на b с присваиванием результата a, используя условную операцию (?:): найти максимальное из чисел a и b, 32 вычислить, если a ≤ b, то с=true Задание 1.3 Создайте консольное приложение, используя для ввода/вывода объекты cin и cout. Использую созданное консольное, приложение написать программу, в которой: определяется размер типа char в байтах, вводятся с клавиатуры значения переменных a, b типа double, вычисляется частное от деления a на b, вычисляется произведение a и b, выполняется операция сложение a и b с присваиванием значения b, используя условную операцию (?:): найти максимальное из чисел a и b, вычислить, если a ≤ b, то a=a-b Задание 1.4 Создайте консольное приложение, используя для ввода/вывода объекты cin и cout. Использую созданное консольное, приложение написать программу, в которой: определяется размер типа long int в байтах, вводятся с клавиатуры значения целых переменных a, b, вычисляется частное от деления a на b, вычисляется остаток от деления a на b, выполняется операция остаток от деления a на b с присваиванием значения b, используя условную операцию (?:): найти минимальное из чисел a и b, вычислить, если a ≥ b, то b=a-b Задание 1.5 Создайте консольное приложение, используя для ввода/вывода объекты cin и cout. Использую созданное консольное, приложение написать программу, в которой: определяется размер типа unsigned char в байтах, вводятся с клавиатуры значения переменных a, b типа float, вычисляется разность a и b, вычисляется частное от деления a на b, выполняется операция умножение a на b с присваиванием значения b, используя условную операцию (?:): найти минимальное из чисел a и b, вычислить, если a > b, то a=a+b, Задание 1.6 33 Создайте консольное приложение, используя для ввода/вывода объекты cin и cout. Использую созданное консольное, приложение написать программу, в которой: определяется размер типа short int в байтах, вводятся с клавиатуры значения целых переменных a, b, вычисляется остаток от деления a на b, вычисляется произведение a и b, выполняется операция деление a на b с присваиванием значения a, используя условную операцию (?:): найти максимальное из чисел a и b, вычислить, если a < b, то с= false Задание 1.7 Создайте консольное приложение, используя для ввода/вывода объекты cin и cout. Использую созданное консольное, приложение написать программу, в которой: определяется размер типа double в байтах, вводятся с клавиатуры значения целых переменных a, b, вычисляется остаток от деления a на b, вычисляется произведение a и b, выполняется операция деление a на b с присваиванием значения a, используя условную операцию (?:): найти минимальное из чисел a и b, вычислить, если a ≤ b, то с= false Задание 1.8 Создайте консольное приложение, используя для ввода/вывода объекты cin и cout. Использую созданное консольное, приложение написать программу, в которой: определяется размер типа long double в байтах, вводятся с клавиатуры значения переменных a, b типа float, вычисляется разность a и b, вычисляется частное от деления a на b, выполняется операция умножение a на b с присваиванием значения b, используя условную операцию (?:): найти максимальное из чисел a и b, вычислить, если a ≥ b, то с= false Задание 1.9 Создайте консольное приложение, используя для ввода/вывода объекты cin и cout. 34 Использую созданное консольное, приложение написать программу, в которой: определяется размер типа float в байтах, вводятся с клавиатуры значения переменных a, b типа double, вычисляется сумма a и b, вычисляется произведение a и b, выполняется операция деление a на b с присваиванием значения a, используя условную операцию (?:): найти минимальное из чисел a и b, вычислить, если a > b, то с=false Задание 1.10 Создайте консольное приложение, используя для ввода/вывода объекты cin и cout. Использую созданное консольное, приложение написать программу, в которой: определяется размер типа long int в байтах, вводятся с клавиатуры значения переменных a, b типа long int, вычисляется разность a и b, вычисляется остаток от деления a на b, выполняется операция деление a на b с присваиванием значения b, используя условную операцию (?:): найти максимальное из чисел a и b, вычислить, если a < b, то с=true КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №1 1. Продемонстрируйте создание консольного приложения. 2. Как установить точку останова в программе и что она означает ? 3. Какие кнопки клавиатуры соответствуют пошаговому выполнению программы и выполнению программы до заданной строки ? 4. Как прервать выполнение программы, которая «зависла» (долго не выдает никаких сообщений) ? 5. Что такое идентификатор, ключевое слово ? 6. Приведите примеры литеральной константы: целой, вещественной, символьной, строковой. 7. Как вводится комментарий в программе ? 8. Дайте характеристику основным типам данных. 9. Что такое описание, определение, объявление, инициализация переменных ? 10. Что такое область действия и область видимости переменной ? 11. Какая переменная называется глобальной и какая локальной ? 12. Дайте характеристику классам памяти: auto, static, register, volatile ? 13. Как осуществляется форматирование вывода данных с помощью функции printf и объекта cout ? 14. Как осуществить ввод данных с помощью функции scanf и объекта cin ? 35 15. Запишите и объясните смысл одной из операций п. 2.10. 16. Запишите типы данных в порядке возрастания экстенсивности. Как используется этот порядок при преобразовании типа ? 36 ГЛАВА 2 ОПЕРАТОРЫ ВЕТВЛЕНИЯ И ОПЕРАТОРЫ ПЕРЕДАЧИ УПРАВЛЕНИЯ КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ 1 Базовые конструкции структурного программирования В теории программирования доказано, что программу для решения задачи любой сложности можно составить только из трех структур, называемых следованием, ветвлением и циклом. Этот результат установлен Боймом и Якопини в 1966 году путем доказательства того, что любую программу можно преобразовать в эквивалентную, состоящую только из этих структур и их комбинаций. Следование, ветвление и цикл называют базовыми конструкциями структурного программирования. Следованием называется конструкция, представляющая собой последовательное выполнение двух или более операторов (простых или составных)(рис.1.1). c = a+b d=a/b p=a*b Рис.1.1 Ветвление задает выполнение либо одного, либо другого оператора в зависимости от выполнения какого-либо условия (рис1.2). Если условие выполняется, то а = -а, иначе а = b. Цикл задает многократное выполнение оператора 1, пока выполняется условие (рис.1.3). Нет (False) Да (True) условие a = -a a=b Рис.1.2 37 Нет (False) условие Да (True) Оператор 1 Рис.1.3 Особенностью базовых конструкций является то, что любая из них имеет только один вход и один выход, поэтому конструкции могут вкладываться друг в друга произвольным образом, например, цикл может содержать следование из двух ветвлений, каждое из которых включает вложенные циклы. Целью использования базовых конструкций является получение программы простой структуры. Такую программу легко читать, отлаживать и при необходимости вносить в нее изменения. В большинстве языков высокого уровня существует несколько реализаций базовых конструкций. В C++ есть три вида циклов и два вида ветвлений (на два и на произвольное количество направлений). Они введены для удобства программирования, и в каждом случае надо выбирать наиболее подходящие средства. Рассмотрим операторы языка, реализующие базовые конструкции структурного программирования. 1.1 Оператор «выражение» Любое выражение, завершающееся точкой с запятой, рассматривается как оператор, выполнение которого заключается в вычислении выражения. Частным случаем выражения является пустой оператор ; (он используется, когда по синтаксису оператор требуется, а по смыслу – нет). Примеры: i++; // выполняется операция инкремента а* = b + с; // выполняется умножение с присваиванием fun( i, k); // выполняется вызов функции 38 1.2 Операторы ветвления 1.2.1 Условный оператор if... else Условный оператор реализует структуру выбора. Он имеет такой вид: if (условие) оператор1[ else оператор 2] Если условие оценивается как истинное (ненулевое), выполняется onepaтop1, если как ложное (нулевое), выполняется onepaтop2. Простейший пример: if (а > b) max_ab = a; else max_ab = b; Как было сказано чуть выше, вместо одиночного оператора всегда можно подставить блок из нескольких операторов, заключенный в фигурные скобки. Другими словами, возможна следующая синтаксическая форма: if (условие) {операторы_блока_if} else {операторы_блока_еlsе} В случае, когда при ложности условия не нужно выполнять никаких действий, а требуется только пропустить операторы блока if, ключевое слово else и соответствующий ему оператор (блок) могут отсутствовать, как в следующем примере: if (а > b) { temp = а; а = b; b = temp; } //...Продолжение программы... В этом фрагменте программы, если а > b, то значения a и b меняются местами, в противном случае блок не выполняется и все остаётся, как есть. В соответствии с правилом суперпозиции можно строить вложенные структуры if...else, например: if (a > b) if (a > с) 39 max_abc = а; else max abc = с; else if (b > с) max_abc = b; else max_abc = с ; Эта конструкция всего-навсего определяет наибольшее из трех чисел, но разобраться в ее логике не так-то просто. Кроме того, следует помнить, что если во вложенных условных структурах используются как полные, так и неполные операторы if (без else), то могут возникать неоднозначности. Попробуем, например, переписать предыдущий фрагмент чуть более экономно, заранее присвоив максимуму значение с: max_abc = с; if (a > b) if (a > c) max_abc = = a; else if (b > c) max_abc = b; К которому из двух первых if относится это else? По задуманной нами логике – к первому, однако компилятор считает по-другому; он разрешает подобные неоднозначности, ставя спорное else в соответствие ближайшему if, т. е. в данном случае второму. В результате все работает неправильно. Чтобы устранить неоднозначность, нужно применить операторные скобки: max_abc = с; if (а > b) { if (а > с) max_abc = а; } else if (b > с) max abc = b; 1.2.2 Об условиях в операторе if Условие оператора if может быть сколь угодно сложным выражением. Можно было бы сказать, что это выражение должно быть “логическим”, но в языке С (не в С++, а в С) нет логического типа данных, поэтому условие для большей общности сделали целого типа. Как уже говорилось, выражение считается ложным (false), если его значением является нуль, и истинным (true), ес- 40 ли значение ненулевое. В С++ введен логический тип данных bool. Но переменные логического типа конвертируются к целому типу true (равно 1) или false (равно 0), если они используются в условии. Вот несколько примеров условий оператора if: if (x) DoSomething(); //Если х не равно нулю, то выполняется функция DoSomething(). if (!x) DoAnotherThing(); //Если х равно нулю, то выполняется функция DoAnotherThing(). if (b = = с) DoAnotherThing(); /*Если b равно с (логическое сравнение), то выполняется функция DoAnotherThing().*/ if (b != с) DoSomething(); //Если b не равно с, то выполняется функция DoSomething(). if ((key = getch()) = = 'q') DoQuit(); /*Сохранить код клавиши в key и,если он равен ' q ' , выполнить функцию DoQuit().*/ if (a >= b && a <= c) DoSomething(); //Если а лежит между b и с, выполнить функцию DoSomething(). При записи условия используются логические операции (записаны в порядке убывания приоритета): ! < <= > >= == != && || – логическое НЕ (инверсия); – меньше; – меньше или равно; – больше; – больше или равно; – равно; – логическое И; – логическое И; – логическое ИЛИ. ! – логическое НЕ (инверсия, отрицание), дает в результате false (0), если операнд true(≠ 0 ) или 1 если операнд false (0). Операнды в этой операции могут иметь тип bool, int, вещественный. Пример: bool x,y; y=true; 41 x=!y; В результате: х будет присвоено значение false (0). Операции отношения (<, <=, >, > , = =. ! =) сравнивают первый операнд со вторым. Операнды могут быть арифметического типа ( int, float, double ) или указателями. Результатом операции является значение true или false (любое значение, не равное нулю, интерпретируется как true). Операции сравнения на равенство и неравенство имеют меньший приоритет, чем остальные операции сравнения. ПРИМЕЧАНИЕ: обратите внимание на разницу между операцией проверки на равенство (==) и операцией присваивания (=), результатом которой является значение, присвоенное левому операнду. Пример: a= =0; логическое значение этого выражения true, если а равно 0, иначе false. a=0; логическое значение этого выражения всегда false. Логические операции && (И) и || (ИЛИ). Операнды логических операций И (&&) и ИЛИ ( || ) могут иметь арифметический тип или быть указателями, при этом операнды в каждой операции могут быть различных типов. Преобразования типов не производятся, каждый операнд оценивается с точки зрения его эквивалентности нулю (операнд, равный нулю, рассматривается как false, не равный нулю – как true). Результатом логической операции является true или false. Результат операции логическое И имеет значение true только если оба операнда имеют значение true. Результат операции логическое ИЛИ имеет значение true, если хотя бы один из операндов имеет значение true. Логические операции выполняются слева направо. Если значения первого операнда достаточно, чтобы определить результат операции, второй операнд не вычисляется. Примеры: a>b && c<d если одновременно а больше b и с меньше d, то выражение равно true, иначе false. a>b || c<d если а больше b или с меньше d, то выражение равно true, иначе false. Можно было бы записать (a>b)&&(c<d), но в этом нет необходимости, поскольку приоритет операций > и < выше чем && и ||. 1.2.3 Оператор выбора switch Часто возникают ситуации, когда некоторая переменная может принимать несколько возможных значений-вариантов, и для каждого варианта требуется выполнить какие-то свои действия. Например, пользователю может быть 42 предложено меню, когда нажатие различных клавиш инициирует соответствующие команды. Управляющая конструкция, реализующая такую логику, может использовать “последовательно вложенные” операторы if...else if...: int key; printf(RUS("\nВыберите команду (F, M or Q): ")); // Вывести подсказку. key = getch(); // Прочитать символ. key = toupper(key); // Преобразовать в верхний регистр. // блок определения команды... if (key == 'F') printf(RUS("\nВыбрана команда \"F\" – означает Файл.\n")); else if (key == 'M') printf(RUS("\nВыбрана команда \"M\" – означает сообщение.\n")); else if (key == 'Q') printf(RUS("\nВыбрана команда \"Q\" – означает Выход.\n")); else printf(RUS("\nНеправиьная клавиша!")); Условия операторов if содержат проверку кода нажатой клавиши на равенство одному из допустимых символов. Если код клавиши не соответствует никакой команде, выводится сообщение об ошибке. Для подобных случаев в С существует специальная, более удобная, конструкция выбора switch. Выглядит она так: switch (выражение) { case константное_выражение: группа_операторов case константное_выражение: группа_операторов [default: группа операторов] } Сначала производится оценка выражения в операторе switch. Полученное значение последовательно сравнивается с каждым из константных_выражений, и при совпадении значений управление передается на соответствующую группу_операторов. Если значение выражения не подходит ни под один из вариантов, управление передается на группу операторов с меткой default или на следующий после блока switch оператор, если группа default отсутствует. Под группой _операторов подразумевается просто один или несколько произвольных операторов. Группа здесь вовсе не обязана быть блоком, т. е. заключать ее в операторные скобки не требуется. И еще одна особенность, о которой следует помнить при написании структур switch. Если найдена метка case, совпадающая со значением проверяемого выражения, то выполняется группа_операторов данного case. Однако дело на этом не заканчивается, поскольку, если не принять никаких дополнительных мер, управление “провалится” ниже, на следующую по порядку метку case 43 и т. д., и в результате будут выполнены все операторы до самого конца блока switch. Если это нежелательно (как чаще всего и бывает), в конце группы_операторов case нужно поставить оператор break. Он прерывает выполнение блока c совпавшей меткой и передает управление оператору, непосредственно следующему за блоком switch. Для иллюстрации мы перепишем предыдущий пример “меню”, оформив его в виде законченной программы, и продемонстрируем попутно еще один управляющий оператор С. Демонстрация структуры switch: int key; loop: //здесь можно добавить программу, выполняемую каждый раз //после выбора команд F, M or Q printf(RUS("\nВыберите команду (F, M or Q): ")); key = getche(); // Прочитать клавишу. // Определение команды... switch (key) { case 'f': case 'F': printf(RUS("\nВыбрана команда \"Файл\".\n"));break; case 'm': case 'M': printf (RUS("\nВыбрана команда \"Message\".\n"));break; case 'q': case 'Q': printf(RUS("\nВыбрана команда \"Quit\".\n")); printf(RUS("Нажмите любую клавишу для завершения программы ...\n")); getch() ; return 0; // Возврат в Windows. default: printf(RUS("\nНеправиьная клавиша!")); } goto loop; // переход в начало программы на метку loop Мы организовали в программе простейший “бесконечный” цикл, который все время просит пользователя ввести команду – до тех пор, пока не будет нажата клавиша “Q”. В этом случае происходит возврат в операционную систему. Чтение команды производится функцией getche(). Она, как и getch(), возвращает код нажатой клавиши, однако в отличие от getch() отображает введенный символ на экране. Для реализации цикла мы использовали оператор goto, исполнение которого приводит к передаче управления на метку, указанную в операторе. В при- 44 мере метка с именем loop стоит в самом начале программы. Таким образом, дойдя до конца функции main (), управление возвращается к ее началу и все повторяется снова. Результат работы программы: 1.3 Операторы передачи управления 1.3.1 Оператор goto Оператор безусловного перехода goto имеет формат: goto metka; В теле той же функции должна присутствовать ровно одна конструкция вида: metka: оператор; Оператор goto передает управление оператору с меткой metka. Метка – это обычный идентификатор. Оператор goto может передать управление на метку, обязательно расположенную в одном с ним теле функции. Операторы if и goto могли бы заменить все операторы ветвления, передачи управления и цикла, но такой подход оказался не очень удобен. Применение goto может нарушить принципы структурного и модульного программирования, по которым все блоки, из которых состоит программа, должны иметь только один вход и один выход. Кроме того, операторы выбора и цикла выглядят более изящно и позволяют ускорить выполнение программы. Использование оператора безусловного перехода оправдано в двух случаях: 45 принудительный выход вниз по тексту программы из нескольких вложенных циклов или переключателей; переход из нескольких мест функции в одно (например, если перед выходом из функции необходимо всегда выполнять какие-либо одни и те же действия). В остальных случаях для записи любого алгоритма существуют более подходящие средства, а использование goto приводит только к усложнению структуры программы и затруднению отладки. В любом случае не следует с помощью goto передавать управление внутрь операторов if, switch и циклов. Нельзя переходить внутрь блоков, содержащих инициализацию переменных, на операторы, расположенные после нее, поскольку в этом случае инициализация не будет выполнена: int k; ... goto metka; ... {int a = 3. b = 4; k = a + b; metka: int m = k + 1; ... } После выполнения этого фрагмента программы значение переменной m не определено. 1.3.2 Оператор break Оператор прерывает выполнение оператора switch. Управление передается следующему за ним оператору. Об этом было уже сказано выше. Здесь следует лишь подчеркнуть, что break относится к операторам передачи управления. 2 Тестирование программ Тестирование выполняется после отладки программы и заключается в проверке правильности ее функционирования при всех вариантах исходных данных, значений переменных, условий и т.п. Это означает, что нужно проверить проход программы по всем «путям» блок-схемы. Сколько путей – столько раз нужно выполнить программу с разными значениями переменных. Следует также проверять какие значения нельзя присваивать переменным в ходе выполнения программы по причине их несоответствия типу переменных или недопустимости этого значения в каком-либо выражении программы (например, нулевое значение переменной может вызвать ошибку деления на 0). Результат тестирования – примеры выполнения программы при разных исходных данных и инструкция пользователя, запрещающая вводить неверные исходные данные (например, вместо цифр буквы или цифры, но не те, что надо). В инструкции следует пояснить, почему вводится то или иное запрещение, рекомендация или требование. 46 ДОМАШНЕЕ ЗАДАНИЕ Изучите теоретические сведения и подготовьте блок-схемы и текст программ по заданию на лабораторную работу. Блок-схемы и программы записываются в «черновом» варианте в лекционной тетради. Образец блок-схемы для задания 1 приведен в приложении III. Рекомендуется сначала составить блоксхему, а затем, согласно ей, написать программу. Комментарии должны составлять не менее 10% от текста. Студенты, не выполнившие домашнего задания, не допускаются к выполнению лабораторной работы. ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ №2 Выполните обязательные и непонятные Вам примеры теоретических сведений и лишь затем «задание M.N» по варианту N (N – номер компьютера, M – номер задания). После выполнения задания 2 дополните программу циклом с оператором goto. В цикле с помощью оператора switch организуйте выбор: повторение или завершение программы (см. пример использования switch в теоретической части). Выполните тестирование разработанных Вами программ. 1 Задание 1 Задание 1.1 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. ax 2 b при x 0 и b 0 x-a F при x 0 и b 0 x-c x в остальных случаях c Задание 1.2 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. 1 ax b при x 5 0 и c 0 x a F при x 5 0 и c 0 x 10 x в остальных случаях c 4 Задание 1.3 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. 47 ax 2 bx c при a 0 и c 0 a F при a 0 и c 0 x c a ( x c ) в остальных случаях Задание 1.4 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. ax c при c 0 и x 0 x a F при c 0 и x 0 c bx в остальных случаях c a Задание 1.5 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. x a при x 0 и b 0 10 b x a F при x 0 и b 0 x c 3x 2 в остальных случаях c Задание 1.6 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. ax 2 b 2 x при c 0 и b 0 x a F при c 0 и b 0 x c x в остальных случаях c Задание 1.7 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. ax 2 b при x 5 и c 0 x a F при x 5 и с 0 x x в остальных случаях c 48 Задание 1.8 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. ax 2 при c 0 и a 0 a x F при c 0 и a 0 cx x в остальных случаях c Задание 1.9 Вычислить значения функции F при вещественных a, b, c, x, водимых с клавиатуры. ax 2 b 2 x при a 0 и x 0 a F x при a 0 и x 0 x c 1 x в остальных случаях c Задание 1.10 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. ax 2 bx c при x 3 и b 0 x a F при x 3 и b 0 x c x в остальных случаях c Задание 1.11 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. b 2 при x 1 и c 0 ax c xa F при x 1.5 и c 0 2 ( x2 c ) x в остальных случаях c 2 Задание 1.12 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. 49 ax 3 b 2 c при x 0.6 и b c 0 x a F при x 0.6 и b c 0 x c xx в остальных случаях c a Задание 1.13 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. ax 2 b при x - 1 0 и b - x 0 x a F при x - 1 0 и b x 0 x x в остальных случаях c Задание 1.14 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. ax 3 b при x c 0 и a 0 x a F при x c 0 и a 0 x c x c в остальных случаях c x Задание 1.15 Вычислить значения функции F при вещественных a, b, c, x, вводимых с клавиатуры. ax 2 b при x 0 и b 0 x F 5.5 при x 0 и b 0 xc x в остальных случаях c 2 Задание 2 Составить программу, которая печатает сообщение о принадлежности, если точка с координатами (х, у) принадлежит или не принадлежит заштрихованной области (номера вариантов на графиках): 50 51 52 53 СОДЕРЖАНИЕ ОТЧЕТА Отчет выполняется по выбору преподавателя – либо в редакторе Word, либо в черновом варианте в лекционной тетради. Отчет должен содержать: 1. Листинг программ на языке Си, решающих задачи в соответствии с вариантом (номером компьютера) задания; 2. Блок-схему алгоритма программ (см. Приложение II и III); 3. Пояснения по методу решения задач и по выбору исходных данных для тестирования; 4. Результаты тестирования программ (консольные окна и инструкция пользователя). При оформлении отчета следует пользоваться копированием листинга и результата тестирования в Word. Последний копируется с помощью комбинации клавиш ALT Prt Sc, при условии активности консольного окна, что означает копирование графики окна в буфер обмена Windows. Затем окно вставляется в документ Word как любой другой объект. КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №2 1. Запишите условный оператор if со словом else и без него. 2. Объедините два условия логической операцией И, ИЛИ. Добавьте логическое отрицание. 3. Запишите оператор выбора switch без оператора break и с ним. Чем отличаются эти варианты ? Добавьте слово default. Что оно означает ? 4. Объясните назначение операторов goto и break ? 54 ЛАБОРАТОРНАЯ РАБОТА №3 ОПЕРАТОРЫ ЦИКЛА И ОПЕРАТОРЫ ПЕРЕДАЧИ УПРАВЛЕНИЯ КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ 1 Организация циклов с помощью операторов if и goto Циклы используются для организации многократно повторяющихся вычислений. Любой цикл состоит из тела цикла, то есть тех операторов, которые выполняются несколько раз, начальных установок, модификации параметра цикла и проверки условия продолжения выполнения цикла, которое записывается в виде логического выражения (рис. 2.1). Выход из цикла может быть организован как при истинности выражения – «Да», так и при его неистинности «Нет» (на рис. 2.1 лишь пример выхода по – «Нет»). Один проход цикла называется итерацией. Проверка условия выполняется на каждой итерации либо до тела цикла (тогда говорят о цикле с предусловием), либо после тела цикла (цикл с постусловием). Разница между ними состоит в том, что тело цикла с постусловием всегда выполняется хотя бы один раз, после чего проверяется, надо ли его выполнять еще раз. Проверка необходимости выполнения цикла с предусловием делается до тела цикла, поэтому возможно, что он не выполнится ни разу. Начальные установки Выражение Начальные установки Нет Операторы Да Модификация параметров цикла Операторы Модификация параметров цикла Да Выражение Нет а б Рис. 3.1, Структурные схемы операторов цикла: а – цикл с предусловием; б – цикл с постусловием 55 Переменные, изменяющиеся в теле цикла и используемые при проверке условия продолжения, называются параметрами цикла. Целочисленные параметры цикла, изменяющиеся с постоянным шагом на каждой итерации, называются счетчиками цикла. Начальные установки могут явно не присутствовать в программе, их смысл состоит в том, чтобы до входа в цикл задать значения переменным, которые в нем используются. Цикл завершается, если условие его продолжения не выполняется. Передавать управление извне внутрь тела цикла, минуя его заголовок, не рекомендуется, так как параметры цикла могут быть не определены. Рассмотрим пример, который можно использовать для решения задания 1. Требуется вычислить арифметическое выражение, содержащее бесконечную сумму: 1 F n 0 n Для инициализации вычислений переменной n присваивается начальное значение 0 и переменной F также присваивается 0. В цикле переменная n после каждого вычисления выражения, стоящего под знаком суммы должна изменяться по формуле n = n + 1. Таким образом, n – счетчик цикла. Если бы был конечный верхний предел суммы, то условием завершения цикла было бы равенство n конечному пределу. Но в данном случае предел бесконечный и, следовательно, F может быть вычислена только приближенно. Для этого вводится понятие погрешности вычисления . Истинную погрешность вычислений найти можно аналитически, но не для всех выражений. Поэтому воспользуемся приближенными методами оценки погрешности. Один из вариантов такой оценки – отбросить все слагаемые, абсолютная величина которых меньше . Конечно, это не та , которая близка к истинной погрешности вычислений, но зато такая оценка универсальна (для любого выражения). Понятно, что истинная погрешность всегда больше . Поэтому следует выбирать меньше ожидаемой истинной погрешности раз в 10. В большинстве случаев это оправданно. Хотя, конечно, анализ выражения под знаком суммы никогда не помешает. Переменная F, то есть сумма должна накапливаться с каждым циклом, для этого слагаемые, содержащие в том или ином виде n, складываются с переменной F и результат заносится также в F. Цикл с постусловием для приведенного выше выражения будет выглядеть так: n=0; F=0;eps=0.001; //инициализация переменных // переменную eps в задании 3 следует вводить с клавиатуры m1: Fp=1.0/n; //вычисление выражения под знаком суммы F=F+Fp; //накопление суммы n++; //изменение переменной цикла if (Fp>=eps) goto m1; 56 n=0; F=0; eps=0.001 Fp=1/n; F=F+Fp n=n+1 Да Fp eps Нет Здесь eps – погрешность ; Переменная Fp вводится, чтобы при анализе условия не вычислять выражение повторно. После выхода из цикла переменная F будет содержать сумму вычисленную приближенно. Можно также контролировать число циклов n с целью защиты от ошибок программирования. Обычно это делают при отладке программы. Если число циклов программирования слишком велико (например, n>100000) можно выйти из цикла по этому условию. Для этого в последней строке программы можно связать 2 условия логическим оператором: if (Fp>=eps & n<=100000) goto m1; 2 Операторы цикла Операторы цикла используются для организации многократно повторяющихся вычислений. Любой цикл состоит из тела цикла, то есть тех операторов, которые выполняются несколько раз, начальных установок, модификации параметров цикла и проверки условия продолжения выполнения цикла (рис. 3.1). Выше было показано, как реализовать цикл с помощью двух операторов if и goto. Операторы цикла позволяют организовать цикл «явным» образом, что способствует большей оптимизации программного кода. Переменные, изменяющиеся в теле цикла и используемые при проверке условия продолжения, называются параметрами цикла. Целочисленные параметры цикла, изменяющиеся с постоянным шагом на каждой итерации, называются счетчиками цикла. Начальные установки могут явно не присутствовать в программе, их смысл состоит в том, чтобы до входа в цикл задать значения переменным, которые в нем используются. 57 Цикл завершается, если условие его продолжения не выполняется. Возможно принудительное завершение, как текущей итерации, так и цикла в целом. Для этого служат операторы break, continue, и goto. Передавать управление извне внутрь тела цикла, минуя его заголовок, не рекомендуется, так как параметры цикла могут быть не определены. Для удобства, а не по необходимости, в C++ есть три разных оператора цикла – while, do while и for. 2.1 Цикл с предусловием (while) Цикл с предусловием реализует структурную схему, приведенную на рис. 3.1, а, и имеет вид: while ( выражение ) оператор Выражение определяет условие повторения тела цикла, представленного простым или составным оператором. Выполнение цикла начинается с вычисления выражения. Если оно истинно (не равно false), выполняется оператор цикла. Если при первой проверке выражение равно false, цикл не выполнится ни разу. Тип выражения должен быть арифметическим или приводимым к нему. Выражение вычисляется перед каждой итерацией цикла. Когда выражение оказывается ложным, управление передается следующему после цикла оператору. Тело цикла может состоять не из одного оператора, а представлять собой блок. Пример (программа находит все делители целого положительного числа): Для краткости изложения начало всех программ этой главы не приводится, но подразумевается int num; cout <<RUS( "\nВведите число : "); cin>> num; int div = 2; // кандидат на делитель while (div <= num) {if (!(num % div))cout << div <<"\n"; div++;} Для краткости изложения начало всех программ этой главы не приводится, но подразумевается, что оно включает функцию RUS, известную из предыдущих примеров, а также необходимые директивы и библиотеки. 2.2 Цикл с постусловием (do … while) Цикл с постусловием реализует структурную схему, приведенную на рис. 3.1, б, и имеет вид: do оператор while (выражение); 58 Сначала выполняется оператор или блок, составляющий тело цикла, а затем вычисляется выражение. Если оно истинно (не равно false), тело цикла выполняется еще раз. Цикл завершается, когда выражение станет равным false или в теле цикла будет выполнен какой-либо оператор передачи управления. Тип выражения должен быть арифметическим или приводимым к нему. Пример (программа осуществляет проверку ввода): char answer; do{cout << RUS("\nКупи слоника! "); cin >> answer; } while (answer != 'y'); 2.3 Цикл с параметром for Цикл имеет следующий формат: for ( инициализация; выражение; модификации) оператор; Инициализация используется для объявления и присвоения начальных значений величинам, используемым в цикле. В этой части можно записать несколько операторов, разделенных запятой (операцией «последовательное выполнение»), например, так: for (int i = 0, j = 2; ... или int k, m: for (k = 1, m = 0; ... Областью действия переменных, объявленных в части инициализации цикла, является цикл. Инициализация выполняется один раз вначале исполнения цикла. Выражение определяет условие выполнения цикла: если его результат, приведенный к типу bool, равен true, цикл выполняется, иначе происходит выход из цикла и управление передается следующему за оператором цикла оператору. Цикл реализован как цикл с предусловием: сначала проверяется условие "на истинность", затем выполняется оператор (тело цикла), затем производятся модификации, после чего управление возвращается к проверке условия "на истинность" и т.д. Модификации выполняются после каждой итерации цикла и служат для изменения управляющих переменных цикла (параметров цикла). В части модификаций можно записать несколько операторов через запятую. Обычно здесь записывается модификация счетчика цикла. Пример (оператор, вычисляющий сумму чисел от 1 до 100): for (int i = 1, s = 0; i<=100; i++) s += i; 59 Здесь i – счетчик цикла. Он инициализируется значением 1. В начале каждой итерации проверяется, не достиг ли он значения 100. Как только i станет равным 101, оператор s+= i пропускается и управление передается следующему за оператором цикла оператору. В конце каждой итерации i увеличивается на 1 ( операция i++). Оператор в структуре цикла for может быть простым оператором или блоком. Любая из частей оператора for может быть опущена, но точки с запятой надо оставить на своих местах. Поэтому пример бесконечного цикла for выглядит следующим образом: for( ; ; ) cout<<” Бесконечный цикл”; Пример (программа печатает таблицу значений функции у=х 2+1 во введенном диапазоне): float Xn, Xk, Dx, X; printf(RUS("Введите диапазон и шаг изменения аргумента: ")); scanf("%f%f%f", &Xn, &Xk, &Dx); printf("| X | Y |\n"); for (X = Xn; X<=Xk; X += Dx); printf("| %5.2f | %5.2f |\n", X, X*X + 1); Часто встречающиеся ошибки при программировании циклов – использование в теле цикла неинициализированных переменных и неверная запись условия выхода из цикла. Чтобы избежать ошибок, рекомендуется: проверить, всем ли переменным, встречающимся в правой части операторов присваивания в теле цикла, присвоены до этого начальные значения (а также возможно ли выполнение других операторов); проверить, изменяется ли в цикле хотя бы одна переменная, входящая в условие выхода из цикла; предусмотреть аварийный выход из цикла по достижению некоторого количества итераций; если в теле цикла требуется выполнить более одного оператора, нужно заключать их в фигурные скобки; при программировании цикла for не допускать изменение значения счетчика в теле цикла. Это может приводить к " выпадению " итераций или к бесконечному циклу. В блок-схемах цикл с параметром for можно изображать с использованием блока «модификация». Внутри блока записывается начальное значение, конечное значение и шаг изменения параметра (рис. 3.2). Если шаг равен 1, его можно не указывать. 60 Начальные установки n=1,N n=n+2 Операторы Рис. 3.2 Цикл с параметром for Операторы цикла взаимозаменяемы, но можно привести некоторые рекомендации по выбору наилучшего в каждом конкретном случае. Оператор do … while обычно используют, когда цикл требуется обязательно выполнить хотя бы раз (например, если в цикле производится ввод данных). Оператором while удобнее пользоваться в случаях, когда число итераций заранее не известно, очевидных параметров цикла нет или модификацию параметров удобнее записывать не в конце тела цикла. Оператор for предпочтительнее использовать в большинстве остальных случаев (однозначно – для организации циклов со счетчиками). 3 Дополнительные операторы передачи управления 3.1 Оператор break Оператор прерывает выполнение не только оператора switch, но циклов. Управление передается следующему за циклом оператору. Пример. Программа вычисляет значение гиперболического синуса вещественного аргумента х с заданной погрешностью eps с помощью разложения в бесконечный ряд. sh х = x + х3/3! + х5/5! + х7/7! + ... Вычисление заканчивается, когда абсолютная величина очередного члена ряда, прибавляемого к сумме, станет меньше заданной погрешности. В программе предусмотрено прерывание цикла с помощью оператора break , если ряд расходится. const int MaxIter = 500; // ограничитель количества итераций double x, eps; cout « RUS("\nВведите аргумент и точность: "); 61 cin >> x >> eps; bool flag = true; // признак успешного вычисления double у = x, ch = x; // сумма и первый член ряда for (int n = 0; fabs(ch) > eps; n++) { ch *= x * x /(2 * n + 2)/(2 * n + 3); // очередной член ряда у += ch; if (n > MaxIter){ cout << RUS("\nРяд расходится!"); flag = false; break;} } if (flag) cout << RUS("\nЗначение функции: ") <<у; 3.2 Оператор continue Оператор перехода к следующей итерации цикла. При его выполнении все операторы следующие за ним в теле цикла не выполняются, а управление передается на начало следующей итерации. Пример: for(int j=2; j<i; j++) { if(i%j) continue; else {dev=true; break;} } j=2,i =0 i%j 0 Dev=true Если остаток от деления i на j не равен нулю, осуществляется продолжение цикла по оператору continue. Если остаток от деления равен нулю, выполняется выход из цикла по оператору break с установкой признака деления в логической переменной dev. 4 Вложенные циклы Разрешено и широко используется вложение рассмотренных выше циклов друг в друга. В этом случае границы циклов не должны пересекаться. В инициализации внутреннего цикла for может быть определена переменная с таким же именем, что и переменная, определенная во внешнем цикле. Это определение остается действительным до конца внешнего цикла. Но, чтобы не было путаницы, лучше этого не делать. Тогда параметры вешнего цикла можно использовать в качестве переменных во внутреннем цикле, но менять их там нельзя. 62 Пример: программа нахождения простых чисел, bool def = false; for(int i=2; i<50; i++) { for(int j=2; j<i; j++) { if(i%j) continue; else {dev=true; break;} } if(!dev) cout<<i<<”\n”; dev=false; } j=2,49 j=2,i-1 =0 i%j 0 dev=true i dev dev=false Программа организована в виде двух вложенных циклов таким образом, что осуществляется перебор и проверка остатка от деления пары чисел, первое из которых изменяется от 2 до 50 во внешнем цикле, а второе – от 2 до значения первого числа во внутреннем. Если остаток от деления не равен нулю, осуществляется продолжение внутреннего цикла по оператору continue. Если остаток от деления равен нулю, выполняется выход из внутреннего цикла по оператору break с установкой признака деления в логической переменной def. После выхода из внутреннего цикла производится анализ переменной def и вывод простого числа. ДОМАШНЕЕ ЗАДАНИЕ Изучите теоретические сведения и подготовьте блок-схемы и текст программ по заданию на лабораторную работу. Блок-схемы и программы записываются в «черновом» варианте в лекционной тетради. Комментарии должны составлять не менее 10% от текста. Студенты, не выполнившие домашнего задания, не допускаются к выполнению лабораторной работы. ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ №3 1 Задание 1 63 Задание 1.1 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. S ( n 2 2 n 1 n ) n 1 Задание 1.2 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. S n 1 1 (2n 1)( 2n 1) Задание 1.3 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. S n 1 n2 1 2 n n Задание 1.4 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. n5 S n n n 1 2 3 Задание 1.5 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. 64 n 1 S n 2 n 1 n ( n 1) Задание 1.6 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. S ( 1) n n 1 sin 2 n n Задание 1.7 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. ( 1) n 1 n 1 3 S n 1 n Задание 1.8 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. 1 S (n n 1 1) n 1 Задание 1.9 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. 3 ( 1) n 2 n 1 n 1 S Задание 1.10 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить 65 сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. S n 2 1 1 (ln n ) n Задание 1.11 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. n n 1 n 1 ( 2n 2 1) 2 S n 1 Задание 1.12 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. 1 n 1 S ln n n 1 n Задание 1.13 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. n ln n n n 2 (ln n ) S Задание 1.14 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. S n 1 1 1 ln 2 sin n 66 Задание 1.15 Вычислить сумму ряда S с погрешностью ε, задаваемой с клавиатуры. Вывести количество итераций, сделанных при вычислении. Если вычислить сумму S с погрешностью ε невозможно, вывести сообщение и завершить выполнение программы. 1 sin n n S ln(ln n ) n 2 2 Задание 2 Задание 2.1 Дано натуральное число N. Вычислить N i S sin( 0,1 * i 0,2 * j ) i 1 j 1 Задание 2.2 Дано натуральное число N. Вычислить N i S (i k ) 2 i 1 k 0 Задание 2.3 Дано натуральное число N. Вычислить N 2k m S sin 2k 1 k 1 m 1 Задание 2.4 Даны натуральные числа N, M. Вычислить N M S sin i k 2 8 4 i 1 k 1 Задание 2.5 Дано натуральное число N>2. Вычислить N k 1 S sin k 2 i 1 *i k Задание 2.6 Дано натуральное число N. Вычислить 67 N k S k 3 * (k i 2 ) k 1 i 1 Задание 2.7 Дано натуральное число N. Вычислить N 2k k 1 m 1 S ( 1) k 1 * cos m 1 2k Задание 2.8 Дано натуральное число N и вещественное f. Вычислить N P i i 1 1 ( f k ) k 0 Задание 2.9 Дано натуральное число N. Вычислить N N sin(0.01 * k * i ) k 1 k S i 1 Задание 2.10 Дано натуральное число N. Вычислить N i 1 P 1 k i i 1 k 1 Задание 2.11 Дано натуральное число N. Вычислить N i S ( 1) k 1 i 1 k 1 k2 i Задание 2.12 Дано натуральное число N. Вычислить N i k P k i 1 k 1 i Задание 2.13 Дано натуральное число N. Вычислить 68 ( 1) k k i 1 k 1 (i k ) N i S Задание 2.14 Дано натуральное число N. Вычислить N i i (i k ) S k i 1 k 1 k Задание 2.15 Дано натуральное число N. Вычислить ik k i 1 k 1 1 2 i N i P СОДЕРЖАНИЕ ОТЧЕТА Отчет выполняется по выбору преподавателя либо в редакторе Word, либо в черновом варианте в лекционной тетради. Отчет должен содержать: 1. Листинг программ на языке Си, решающих задачи в соответствии с вариантом (номером компьютера) задания; 2. Блок-схему алгоритма программ (см. примеры и Приложение II и III); 3. Пояснения по методу решения задач и выбору исходных данных для тестирования; 4. Результаты тестирования программ. При оформлении отчета следует пользоваться копированием листинга и результата тестирования в Word. Последний копируется с помощью комбинации клавиш ALT Prt Sc при условии активности консольного окна, что означает копирование графики окна в буфер обмена Windows. Затем окно вставляется в документ Word как любой другой объект. КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №3 1. Изобразите в черновике блок-схему цикла с постусловием, с предусловием, с параметром. 2. Может ли условие операторов цикла быть арифметическим выражением, например, x*x+1 ? 3. Можно ли переменную цикла с параметром изменять внутри тела цикла? 4. Как действуют операторы continue и break в теле цикла ? 5. Что такое вложенный цикл ? Изобразите блок-схему вложенного цикла. 69 ЛАБОРАТОРНАЯ РАБОТА №4 ОДНОМЕРНЫЕ МАССИВЫ КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ 3 Одномерные массивы В программах средней и высокой сложности используется, как правило, большое количество переменных. Присвоение имени каждой из них – процесс трудоемкий. Поэтому были предложены массивы – последовательности однотипных величин. Каждый элемент массива есть переменная заданного типа. Тип присваивается при описании массива и делается это тем же способом, что и описание простых переменных. Отличие состоит в указании количества элементов массива в конце описания в квадратных скобках. Например: float A[10]; //описание массива из 10 вещественных переменных Обращение к конкретному элементу массива осуществляется через его номер. При этом следует запомнить, что элементы массивов нумеруются с нуля, поэтому последний элемент имеет номер на 1 меньший общего количества элементов в массиве: A[0], A[1], A[2], …, A[9]. В программе это выглядит, например, так: x = A[2]; //x присвоено значение 3-го по счету элемента массива A Важно отметить, что при обращении к элементам массива автоматический контроль выхода индекса за границу массива не производится, что может привести к ошибкам. Как и переменные, элементы массива могут быть инициализированы. Инициализирующие значения для массивов записываются в фигурных скобках. Значения элементам присваиваются по порядку. Если элементов в массиве больше, чем инициализаторов, элементы, для которых значения не указаны, обнуляются: int b[5] = {3, 2, 1}; int a[5] = {0}; // b[0]=3, b[l]=2, b[2]=l, b[3]=0, b[4]=0 //обнуление всего массива Размерность массива вместе с типом его элементов определяет объем памяти, необходимый для размещения массива, которое выполняется на этапе компиляции, поэтому размерность может быть задана только целой положительной константой или константным выражением. Размерность массивов 70 предпочтительнее задавать с помощью именованных констант, как это сделано в примере ниже, поскольку при таком подходе для ее изменения достаточно скорректировать значение константы всего лишь в одном месте программы. Если при описании массива не указана размерность, должен присутствовать инициализатор, в этом случае компилятор выделит память по количеству инициализирующих значений: float a [10]; //размерность задана целой положительной константой const int n = 10; // размерность задана с помощью именованной int marks[n]; // константы int b[] = {3, 2, 1}; // размерность массива равна трем При обработке массивов удобно использовать операторы цикла. В следующем примере подсчитывается сумма элементов массива: const int n = 10; int i, sum; int marks[n] = {3, 4, 5, 4, 4}; for (i = 0, sum = 0; i<n; i++) sum += marks[i]; cout << RUS("Сумма элементов: ") << sum; Аналогично выполняется ввод и вывод элементов массива, например, с помощью объектов cin и cout: const int N=5; int i,massiv[N]; //описание массива cout<<RUS("Введите 5 целых чисел\n"); for(i=0;i<N;i++) cin>>massiv[i]; //ввод массива for(i=0;i<N;i++) //вывод массива cout<<"\n massiv["<<i<<"] = "<<massiv[i];//вывод массива 4 Программа пузырьковой сортировки Часто требуется расположить элементы массива по возрастанию или по убыванию. Это можно сделать с помощью следующей программы: const int N=5; int i,j,t,massiv[N]; cout<<RUS("Введите одномерный массив целых чисел:\n"); for(i=0;i<N;i++)cin>>massiv[i]; //ввод массива for(i=N-1;i>0;i--) //начало сортировки for(j=0;j<i;j++) if(massiv[j]>massiv[j+1]) 71 { t=massiv[j]; //тело внутреннего цикла massiv[j]=massiv[j+1]; // massiv[j+1]=t; // } //конец сортировки cout<<RUS("\nМассив упорядоченный по возрастанию:\n"); for(i=0;i<N;i++) //вывод массива cout<<"\nmassiv["<<i<<"] = "<<massiv[i]; //вывод массива Собственно алгоритм выделен комментариями "начало сортировки" и "конец сортировки". Его суть заключается в последовательной перестановке соседних элементов массива с целью продвижения самого большого элемента в конец массива. Этот процесс поясняется на рис. 1.1. Один проход внутреннего цикла for …(по переменной j) перемещает в конец 1 элемент. На следующем проходе, благодаря уменьшению на 1 переменной внешнего цикла (i), установленный в конце массива элемент уже не рассматривается. Его рассмотрение не изменило бы результата, но выполнение операций требует времени, которое тратить впустую нецелесообразно. Так после выполнения N-2 внутренних циклов массив будет упорядочен по возрастанию. Тело внутреннего цикла алгоритма решает задачу взаимной перестановки двух элементов массива. Задача решается перемещением значения переменных через третью вспомогательную переменную (t). j: 0 1 2 3 4 j: 0 2 10 6 1 3 2 1 2 3 6 10 1 t 4 3 t Рис. 1.1 Перемещение самого большого элемента по массиву подобно всплытию пузырька воздуха в воде. Это и определило название алгоритма. Алгоритм может иметь несколько модификаций. Например, упорядочение не по возрастанию, а по убыванию или перемещение элемента не в конец, а в начало массива. Для лучшего понимания рекомендуется реализовать эти модификации и сделать так, чтобы для пользователя все сообщения были одинаковы независимо от варианта программы. ДОМАШНЕЕ ЗАДАНИЕ Изучите теоретические сведения и подготовьте текст программы по заданию на лабораторную работу. Программа записывается в «черновом» варианте в лекционной тетради. Комментарии должны составлять не менее 10% от тек- 72 ста. Студенты, не выполнившие домашнего задания, не допускаются к выполнению лабораторной работы. ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ №4 Задание 1 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 целых элементов, вычислить: 1) произведение элементов массива с четными номерами; 2) сумму элементов массива, расположенных между первым и последним нулевыми элементами. Задание 2 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) сумму элементов массива с нечетными номерами; 2) сумму элементов массива, расположенных между первым и последним отрицательными элементами. Задание 3 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) максимальный элемент массива; 2) сумму элементов массива, расположенных до последнего положительного элемента. Задание 1.4 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) минимальный элемент массива; 2) сумму элементов массива, расположенных между первым и последним положительными элементами. Задание 5 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 целых элементов, вычислить: 1) номер максимального элемента массива; 2) произведение элементов массива, расположенных между первым и вторым нулевыми элементами. Задание 6 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) номер минимального элемента массива; 2) сумму элементов массива, расположенных между первым и вторым отрицательными элементами. Задание 7 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 73 1) максимальный по модулю элемент массива; 2) сумму элементов массива, расположенных между первым и вторым положительными элементами. Задание 8 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 целых элементов, вычислить: 1) минимальный по модулю элемент массива; 2) сумму модулей элементов массива, расположенных после первого элемента, равного нулю. Задание 9 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) номер минимального по модулю элемента массива; 2) сумму модулей элементов массива, расположенных после первого отрицательного элемента. Задание 10 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) номер максимального по модулю элемента массива; 2) сумму элементов массива, расположенных после первого положительного элемента. Задание 11 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) количество элементов массива, больших С (вводится с клавиатуры); 2) произведение элементов массива, расположенных после максимального по модулю элемента. Задание 12 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) количество отрицательных элементов массива; 2) сумму модулей элементов массива, расположенных после минимального по модулю элемента. Задание 13 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) количество положительных элементов массива; 2) сумму элементов массива, расположенных после последнего элемента, равного нулю. Задание 14 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) произведение отрицательных элементов массива; 74 2) сумму положительных элементов массива, расположенных до максимального элемента. Задание 15 В одномерном массиве, вводимом с клавиатуры и состоящем из 10 вещественных элементов, вычислить: 1) количество элементов массива, равных 0; 2) сумму элементов массива, расположенных после минимального элемента. СОДЕРЖАНИЕ ОТЧЕТА Отчет выполняется по выбору преподавателя либо в редакторе Word, либо в черновом варианте в лекционной тетради. Отчет должен содержать: 5. Листинг программы на языке Си, решающей задачи в соответствии с вариантом (номером компьютера) задания; 6. Результаты тестирования программы. КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №4 6. Может ли массив содержать переменные разного типа, например, целого (int) и целого типа без знака (unsigned int) ? 7. Запишите цикл ввода с клавиатуры массива A[7]. 8. Почему программа сортировки массива называтся пузырьковой ? 9. Модифицируйте алгоритм пузырьковой сортировки так чтобы, упорядочение было по убыванию, а перемещение элемента производилось в начало массива. 75 ЛАБОРАТОРНАЯ РАБОТА №5 МНОГОМЕРНЫЕ МАССИВЫ КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ 1 Многомерные массивы Многомерные массивы задаются указанием каждого измерения в квадратных скобках, например, оператор int matr [6][8]; задает описание двумерного массива из 6 строк и 8 столбцов. Массив состоит из 48 элементов. В памяти такой массив располагается в последовательных ячейках построчно. 0 0 строка 0 7 0 7 8 1 строка 7 15 0 … 40 5 строка 7 – смещение от начала строки 47 – смещение от начала массива Трехмерный массив будет описан как: double mass[4][3][2]; Многомерные массивы размещаются так, что при переходе к следующему элементу быстрее всего изменяется последний индекс. Для доступа к элементу многомерного массива указываются все его индексы, например, matr[2][3]=4; элементу матрицы matr2,3 присвоено значение 4; x= matr[2][3]; переменной x присвоено значение элемента матрицы matr2,3. При инициализации многомерного массива он представляется как массив из массивов, при этом каждый массив заключается в свои фигурные скобки (в этом случае величину первой размерности при описании можно не указывать), int mass2 [][2]={ {1, 1}, {0, 2}, {1, 0} }; В этом примере инициализирован массив матрицы: 1 1 0 2 1 0 Можно задать общий список элементов в том порядке, в котором элементы располагаются в памяти (тогда все размерности указываются): int mass2 [3][2]={1, 1, 0, 2, 1, 0}; Обнулить весь массив можно так: int mass2 [3][2]={0}; 76 2 Ввод и вывод многомерных массивов В программе осуществляется ввод/вывод массива massiv типа int, состоящего из трех строк и двух столбцов. При вводе массива во внешнем цикле изменяются строки, а во внутреннем – столбцы. В результате массив вводится по строкам. Вывод массива организован так же. Для удобного чтения, перед выводом каждой строки осуществляется перевод строки оператором cout<<”\n” и вставляются пробелы между элементами с помощью оператора cout<<' '. int i,j,massiv[3][2]; //описание массива cout<<RUS("Введите massiv[3][2]\n"); for(i=0;i<3;i++) //ввод массива for(j=0;j<2;j++) cin>>massiv[i][j]; cout<<"massiv[3][2]\n"; for(i=0;i<3;i++) //вывод массива {cout<<"\n"; for(j=0;j<2;j++) cout<<' '<<massiv[i][j];} Такой вывод, однако, недостаточно хорошо выравнивает элементы на экране. Функция cout имеет дополнительные средства, которые позволяют сделать вывод более точным. Для этого задаются поля структуры cout и задается формат выводимых данных. int i,j; float massiv[3][2]; //описание массива cout<<RUS("Введите massiv[3][2]\n"); for(i=0;i<3;i++) //ввод массива for(j=0;j<2;j++) cin>>massiv[i][j]; cout<<"massiv[3][2]\n\n"; cout<<fixed; for(i=0;i<3;i++) //вывод массива { for(j=0;j<2;j++) {cout.width(10); cout.precision(3); cout<<massiv[i][j];} cout<<"\n"; } Строка cout.width(10); задает число символов полей вывода. После нее каждый элемент массива, не зависимо от значения, будет занимать ровно 10 символов (т.е. число символов вывода задается в круглых скобках). Для данных различных типов может быть задан формат вывода, например, для вещественных чисел задается число символов после запятой: cout.precision(3); и тип формата: cout<<fixed;//вывод последующих элементов в десятичном формате; 77 cout<< scientific;//вывод последующих элементов в экспоненциальном формате. Полный перечень настроек приводится в приложении I I. ДОМАШНЕЕ ЗАДАНИЕ Изучите теоретические сведения текст программы по заданию на лабораторную работу. Программа записывается в «черновом» варианте в лекционной тетради. Комментарии должны составлять не менее 10% от текста. Студенты, не выполнившие домашнего задания, не допускаются к выполнению лабораторной работы. ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ №5 Задание 1 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) сумму положительных элементов в четных строках; 2).номера столбцов, не содержащих отрицательных элементов. Задание 2 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) сумму положительных элементов в нечетных строках; 2) количество строк, не содержащих ни одного нулевого элемента; Задание 3 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) сумму положительных элементов в четных столбцах; 2) количество столбцов, не содержащих ни одного нулевого элемента; Задание 4 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) сумму положительных элементов в нечетных столбцах; 2) количество столбцов, содержащих хотя бы один нулевой элемент; Задание 5 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) сумму отрицательных элементов в четных строках; 2) произведение элементов в тех строках, которые не содержат отрицательных элементов. Задание 6 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) сумму отрицательных элементов в нечетных строках; 78 2) сумму элементов в тех столбцах, которые не содержат отрицательных элементов. Задание 7 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) сумму отрицательных элементов в четных столбцах; 2) сумму элементов в тех строках, которые содержат хотя бы один отрицательный элемент. Задание 8 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) сумму отрицательных элементов в нечетных столбцах; 2) сумму элементов в тех столбцах, которые содержат хотя бы один отрицательный элемент. Задание 9 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) максимальный элемент на главной диагонали; 2) сумму модулей элементов, расположенных выше главной диагонали. Задание 10 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) минимальный элемент на главной диагонали; 2) количество строк, среднее арифметическое элементов которых меньше заданной величины, введенной с клавиатуры. Задание 11 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) максимальный элемент среди элементов, расположенных выше главной диагонали; 2) номера строк, содержащих хотя бы один нулевой элемент. Задание 12 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) максимальный элемент среди элементов, расположенных ниже главной диагонали; 2) номера столбцов, сумма элементов в которых равна нулю. Задание 13 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) минимальный элемент в матрице и указать строку и столбец, где он находится; 2) количество строк, содержащих хотя бы один нулевой элемент. Задание 14 79 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) максимальный элемент в матрице и указать строку и столбец, где он находится; 2) количество отрицательных элементов в тех строках, которые содержат хотя бы один нулевой элемент. Задание 15 Ввести с клавиатуры вещественную матрицу размерностью 55. Определить: 1) сроку и столбец, где находится элемент, значение которого равно значению, введенному с клавиатуры; 2) сумму элементов в тех строках, которые не содержат отрицательных элементов. СОДЕРЖАНИЕ ОТЧЕТА Отчет выполняется по выбору преподавателя либо в редакторе Word, либо в черновом варианте в лекционной тетради. Отчет должен содержать: 1. Листинг программы на языке Си, решающей задачи в соответствии с вариантом (номером компьютера) задания; 2. Результаты тестирования программы. КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №5 1. Изобразите структуру двумерного массива. 2. Как инициализировать двумерный массив ? Как упростить инициализацию, если часть элементов массива нулевые ? 3. Запишите цикл ввода с клавиатуры массива B[3][4]. 4. Что означают операторы cout.width(10); cout.precision(3); cout<<fixed; ? 80 ЛАБОРАТОРНАЯ РАБОТА №6 УКАЗАТЕЛИ. ДИНАМИЧЕСКОЕ РАСПРЕДЕЛЕНИЕ ПАМЯТИ КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ 3 Указатели Любой объект программы (переменная, массив, функция и т. д.) имеет имя и занимает в памяти определенную область. Местоположение объекта в памяти определяется его адресом. Обращение к объекту можно осуществить по его имени или косвенно, через адрес. Обращение к объекту через адрес осуществляется с помощью переменной-указателя, связанного с объектом. Указатель хранит адрес объекта (или, иначе, адрес ячейки памяти, с которой начинается объект). Для описания указателей используется операция косвенной адресации *. Например, указатель целого типа uk описывается так : int *uk. Унарная операция &, примененная к некоторой переменной, показывает, что нам нужен адрес этой переменной, а не ее текущее значение. Если переменная uk объявлена как указатель, то оператор присваивания uk=&x означает: "взять адрес переменной x и присвоить его значение переменной указателю uk". Теперь к переменной x можно обратиться как *uk. В одной программе можно переопределять указатель, присваивая ему разные адреса. Тогда одна и та же переменная будет использована для обращения к разным объектам. Такой стиль обращений используется, однако, редко. Чаще, указатели используются при работе с массивами, символьными строками, зарезервированными областями памяти и объектами, память для которых выделяется динамически во время выполнения программы (п. 2.3). Итак, указатели предназначены для хранения адресов областей памяти. В C++ различают три вида указателей – указатели на объект, на функцию и на void, отличающиеся свойствами и набором допустимых операций. Указатель не является самостоятельным типом, он всегда связан с каким-либо другим конкретным типом объекта. Указатель на объект содержит адрес области памяти, в которой хранятся данные определенного типа (основного или составного). Объявление указателя на объект (в дальнейшем называемого просто указателем) имеет вид: тип *имя; где тип – тип объекта, на который ссылается указатель. Он может быть любым, кроме ссылки и битового поля, причем тип может быть к этому моменту только объявлен, но еще не определен (следовательно, в структуре, например, может присутствовать указатель на структуру того же типа). Символ 81 "звездочка" сообщает компилятору, что объявленная переменная является указателем. Независимо от типа объекта, для указателя резервируется два или четыре байта в зависимости от используемой модели памяти. Пример объявления указателей на переменные целого типа: int *pi, *pbi, *pci; Звездочка относится непосредственно к имени. Поэтому для того, чтобы объявить несколько указателей, требуется ставить ее перед именем каждого из них. Существует соглашение: имя указателя начинать с буквы p. Это облегчает чтение программы. При объявлении указателя надо стремиться выполнить его инициализацию, то есть присвоение начального значения. Непреднамеренное использование неинициализированных указателей – распространенный источник ошибок в программах, который может привести к аварийному событию. Инициализатор записывается после имени указателя либо в круглых скобках, либо после знака равенства: тип *имя указателя = инициализирующее выражение; тип *имя указателя (инициализирующее выражение); Существуют следующие способы инициализации указателя: 1.Присваивание указателю адреса существующего объекта: – с помощью операции получения адреса: int a = 5; // целая переменная int *р = &а; //в указатель записывается адрес а int *р (&а); // в указатель записывается адрес а другим способом – с помощью значения другого инициализированного указателя p: int a = 5; int *р = &а; int *pr = р; //pr тоже указатель на a – с помощью имени массива : int b[10]; // массив int * pb = b; // присваивание адреса первого элемента массива 2. Присваивание указателю адреса области памяти в явном виде: char *pv = (char *)0xB8000000; Здесь 0xB8000000 – шестнадцатеричная константа (начальный адрес видеопамяти ПЭВМ ), (char *) – обязательная операция приведения типа: константа преобразуется к типу указателя ( char *). Таким образом, определяется, что в эту ячейку памяти будет записан код переменной типа char. 3. Присваивание пустого значения: int *psuxx = NULL; int *prulez = 0; В первой строке используется константа NULL, определенная в некоторых заголовочных файлах С как указатель, равный нулю. Рекомендуется использовать просто 0, так как это значение типа int будет правильно преобразовано стандартными способами в соответствии с контекстом. Поскольку гаран- 82 тируется, что объектов с нулевым адресом нет, пустой указатель можно использовать для проверки, ссылается указатель на конкретный объект или нет. После определения указателя и его инициализации адресом переменной или адресом области памяти, указатель можно использовать для записи и чтения значения, находящегося по этому адресу. Для этого применяется операция разыменования ' * ' ( получение значения через указатель ). Пример: int a; // целая переменная int *р = &а; //в указатель записывается адрес а *p = 5; //через операцию разыменования указателя переменной a присвоено значение 5 cout<< *p; // вывод значения переменной a через указатель но cout<< p; // вывод адреса переменной a Выражение *p обладает в данном случае правами имени переменной a и может использоваться везде, где допустимо использование имен объектов того типа, к которому относиться указатель. С помощью указателя можно записать и считать значение непосредственно из ячейки памяти с заданным адресом: char *pv = (char *)0xB8000000;// присваивание указателю адреса области памяти в явном виде *pv = '+'; // запись в ячейку с адресом 0xB8000000 символа + char v = *pv; // присваивание v значения из ячейки с адресом 0xB8000000, значение будет представлено в символьном виде Можно определить неизменяемый (константный) указатель. При инициализации ему присваивается значение адреса, которое невозможно изменить. То есть константный указатель всегда связан с конкретным фиксированным адресом участка основной памяти и является как бы его именем. Определение константного указателя имеет следующий формат: тип *const имя указателя инициализатор; Пример: char *const key_byte = (char*)1047; Значение указателя key_byte невозможно изменить, он всегда указывает на байт с адресом 1047. Содержимое участка памяти связанного с константным указателем с помощью разыменования можно читать и изменять. char *const key_byte = (char*)1047; cout<<"\nbyte key: "<< *key_byte; *key_byte = 'Ё'; cout<<"\nbyte key: "<< *key_byte; Попытку изменить значение самого константного указателя, т.е. операцию вида key_byte = NULL; не допустит компилятор и выдаст сообщение об ошибке. 83 Можно определить указатель на константу. Формат определения: тип const *имя указателя инициализатор; В этом случае значение переменной, хранящейся по адресу связанному с указателем невозможно изменить через операцию разыменования. Пример: const int zero = 0; //определение константы int const *pconst = &zero; //указатель на константу 0 Операторы вида *pconst = 1; cin>>*pconst; недопустимы, так как каждый из них – попытка изменить значение константы 0. Однако операторы pconst = &a; pconst = NULL; допустимы, так как разрывают связь указателя pconst с константой 0, но не меняют значение этой константы. Можно определить константный указатель на константу. После инициализации такого указателя невозможно изменить ни адреса, связанного с указателем, ни значения записанного по этому адресу с помощью разыменования указателя. Например: const float pi = 3.141593; float const *const ppi = &pi; Можно определить указатель на указатель и т.д. сколько нужно раз. В следующей программе определены такие указатели и с их помощью выполнен доступ к значению переменной: int i = 77; int *pi = &i; int **ppi = &pi; int ***pppi = &ppi; cout<<"i = "<< ***pppi; Указатель на тип void применяется и тех случаях, когда конкретный тип объекта, адрес которого требуется хранить, не определен (например, если в одной и той же переменной в разные моменты времени требуется хранить адреса объектов различных типов). Указателю на void можно присвоить значение указателя любого типа, а также сравнивать его с любыми указателями, но перед выполнением какихлибо действий с областью памяти, на которую он ссылается, требуется преобразовать его к конкретному типу явным образом. Возможности связывания указателя void * с объектами разных типов показаны в следующей программе: void *pv; int i = 77; float f = 2.3456; 84 cout<<RUS("\nНачальное значение pv = ")<< pv; pv = &i; //работаем с переменной типа int cout<<"i = "<< *(int *)pv; //перед разыменованием явное приведение типа указателя к типу int pv = &f; // работаем с переменной типа float cout<<"f = "<< *(float *)pv; //перед разыменованием явное приведение типа указателя к типу float 4 Ссылки Ссылка представляет собой синоним имени, указанного при инициализации ссылки. Ссылку можно рассматривать как указатель, который всегда разыменован. Формат объявления ссылки: тип & имя; где тип – это тип величины, на которую указывает ссылка, & – оператор ссылки, означающий, что следующее за ним имя является именем переменной ссылочного типа, например: int kol; int& pal = kol; // ссылка pal - альтернативное имя для kol const char& CR = '\n'; // ссылка на константу Необходимо помнить следующие правила: переменная-ссылка должна явно инициализироваться при ее описании, кроме случаев, когда она является параметром функции, описана как extern или ссылается на поле данных класса; после инициализации ссылке не может быть присвоена другая переменная; тип ссылки должен совпадать с типом величины, на которую она ссылается; не разрешается определять указатели на ссылки, создавать массивы ссылок и ссылки на ссылки. Ссылки применяются чаще всего в качестве параметров функций и типов возвращаемых функциями значений. Ссылки позволяют использовать в функциях переменные, передаваемые по адресу, без операции разыменования, что улучшает читаемость программы Ссылка, в отличие от указателя, не занимает дополнительного пространства в памяти и является просто другим именем величины. Операции над ссылкой приводят к изменению величины, на которую она ссылается, то есть с ней работают как с переменной, но обращение идет с использование адреса, а не копии переменной. 5 Динамическое распределение памяти До сих пор для данных, которые использовались, память выделялась при объявлении переменных. Такой способ выделения памяти называется статическим. 85 Однако иногда размер данных становится известным только во время выполнения программы. Например, если в процессе какого-либо измерения выполняется сохранение данных через определенные промежутки времени, объем этих данных зависит от времени, прошедшего с начала измерения. В таком случае рациональнее распределять память компьютера во время измерения. Процедура выделения памяти во время выполнения программы называется динамическим распределением (выделением) памяти. В C++ существует два способа динамического выделения памяти. Один из них, унаследованный от С, использует стандартные библиотечные функции malloc и free. Другой – операторы new и delete, которые отсутствуют в С. Для обоих способов необходимо применение переменных типа указатель. Как правило, конкретные адреса, содержащиеся в этих переменных, не используются. 5.1 Использование стандартных функций malloc и free Динамическое выделение памяти с помощью библиотечной функции malloc состоит из следующих шагов. 1. Включение в программу файла заголовков malloc.h директивой #include <malloc.h>. 2. Объявление указателя нужного типа, например int *p; 3. Вызов функции malloc с указанием в качестве параметра требуемого количества памяти в байтах. Так как функция выдает результат своей работы в виде указателя на тип void, выполняется приведение типа (преобразуется тип результата к типу, указанному в объявлении). Присваивается полученное значение объявленному указателю. Пример: p=(int *) malloc (число элементов массива*sizeof(int)); Вместо int может быть подставлен любой стандартный или введенный программистом тип. 4. Проверка факта выделения памяти. Если выделение памяти в нужном объеме невозможно, функция malloc возвращает в качестве своего результата нулевой указатель NULL, соответствующий значению ложь. Если выделение памяти выполнено, продолжаем выполнение программы, если нет, выходим из нее с соответствующей диагностикой о недостатке памяти. Пример: if (!p) сообщение, выход; else продолжение; 5. Освобождение памяти после окончания работы с ней. Для этого вызываем функцию fгее и используем указатель в качестве аргумента: free (p); Значение указателя, полученное после выполнения шага 3, должно сохраняться до выполнения шага 5. В противном случае вся память, выделенная 86 по этому адресу, будет потеряна при выходе из программы, что, в конечном счете, может привести к недостатку памяти и нарушению работы операционной системы. Наиболее частой причиной «зависания» компьютера при работе с динамически выделяемой памятью является несоответствие инструкций malloc и free (в обеих инструкциях должен использоваться один и тоже указатель) или недостаточный объем свободной памяти. В качестве примера рассмотрим ввод/вывод одномерного динамического массива произвольной длины, задаваемой с клавиатуры. int i,n,*massiv; //объявление указателя cout<<RUS("Введите размер массива\n");cin>>n;//ввод размера массива massiv=(int*)malloc(n*sizeof(int)); //выделение динам.памяти if(!massiv) //проверка факта выделения памяти {cout<<RUS("\nНедостаточно памяти"); cout<<RUS("\nНажмите любую клавишу для завершения программы ...\n"); getch(); return 0;} cout<<RUS("Введите массив\n"); for(i=0;i<n;i++)cin>>massiv[i]; //ввод массива cout<<RUS("\nmassiv\n"); for(i=0;i<n;i++)cout<<' '<<massiv[i]; //вывод массива free(massiv); //освобождение памяти В этой программе указатель используется только для выделения динамической памяти. Далее в программе обращение к элементам массива осуществляется через имя массива, которое совпадает с именем указателя. Для динамического выделения памяти можно также использовать функцию calloc( ). В отличии от malloc функция calloc кроме выделения области памяти под массив объектов еще производит инициализацию элементов массива нулевыми значениями. В зависимости от используемой версии C++ для работы с большими фрагментами динамической памяти возможно применение функций farmalloc( ), farcalloc( ), farcoreleft( ) и farfree(). 5.2 Использование операторов new и delete Операторы new и delete могут быть использованы везде, где используются функции malloc и free. Однако этот способ имеет несколько преимуществ: не нужно включать в программу файл malloc.h; не нужно выполнять приведение типов – это сделает оператор new; самое главное, эти операторы не просто выделяют и освобождают память. Когда оператор new используется для размещения объекта в памяти, он автоматически вызывает конструктор объекта, если таковой существует. Аналогично, 87 при освобождении памяти с помощью оператора delete вызывается деструктор объекта (также при его наличии). Выделение памяти с помощью оператора new имеет следующий синтаксис: указатель = new тип; указатель = new тип [число элементов]; Вторая строка описывает синтаксис оператора при выделении памяти для массивов. Так же как функция malloc, оператор new возвращает значение NULL, если объем требуемой памяти больше, чем объем свободной. Рекомендуется проверить факт выделения памяти прежде, чем продолжать выполнение программы. Освобождение памяти с помощью оператора delete производится только в том случае, если она была выделена с использованием оператора new и имеет следующий синтаксис: delete указатель; delete [число элементов] указатель; Использование квадратных скобок для освобождения памяти, выделенной под массив, обязательно. Как и при инициализации, его длину можно опустить, но скобки опускать нельзя, иначе освободится память, занимаемая только первым элементом. Указатель на константу удалить нельзя. Рассмотрим программу, ввода/вывода одномерного массива: int n; float *massiv; //объявление указателя cout<<RUS("Введите размер массива\n");cin>>n;//ввод размера массива massiv=new float[n];//динамическое выделение памяти if(!massiv) //проверка факта выделения памяти {cout<<RUS("\nНедостаточно памяти"); cout<<RUS("\nНажмите любую клавишу для завершения программы ...\n"); getch(); return 0;} cout<<RUS("Введите massiv[")<<n<<"]"<<"\n"; for(int i=0;i<n;i++) cin>>*(massiv+i); //вывод массива cout<<"\nmassiv\n"; for(int i=0;i<n;i++) cout<<" "<<*(massiv+i); delete [] massiv; //освобождение памяти В этой программе указатель используется для выделения динамической памяти и доступа к элементам массива. 88 Способ выделения памяти под двумерный массив, когда обе его размерности задаются на этапе выполнения программы, приведен ниже: int nstr,nstb; cout<<RUS(" Количество строк = ");cin>>nstr; //ввод кол. строк cout <<RUS(" Количество столбцов = ");cin>>nstb; //ввод кол. столбцов //динамическое выделение памяти int **a=new int *[nstr];//оператор 1 for(int i=0;i<nstr;i++) //оператор 2 a[i]=new int[nstb]; //оператор 3 if(!a) //проверка факта выделения памяти {cout<<RUS("\nНедостаточно памяти"); cout<<RUS("\nНажмите любую клавишу для завершения программы ...\n"); getch(); return 0;} //ввод массива cout<<RUS("\nВведите массив[")<<nstr<<"]["<<nstb<<"]\n"; for(int i=0;i<nstr;i++) for(int j=0;j<nstb;j++) cin>>a[i][j]; //оператор 3 //вывод массива cout<<RUS("Массив a\n"); for(int i=0;i<nstr;i++) { for(int j=0;j<nstb;j++) {cout.width(5);cout<<a[i][j];} cout<<"\n"; } delete [] a; //освобождение памяти Рис. 1.2. Выделение памяти под двумерный массив 89 В операторе 1 объявляется переменная типа «указатель на указатель на int» и выделяется память под массив указателей на строки массива (количество строк – nstr). В операторе 2 организуется цикл для выделения памяти под каждую строку массива. В операторе 3 каждому элементу массива указателей на строки присваивается адрес начала участка памяти, выделенного под строку двумерного массива. Каждая строка состоит из nstb элементов типа int (рис. 1.2). Освобождение памяти из-под массива с любым количеством измерений выполняется с помощью операции: delete [] указатель. ДОМАШНЕЕ ЗАДАНИЕ Изучите теоретические сведения текст программы по заданию на лабораторную работу. Программа записывается в «черновом» варианте в лекционной тетради. Комментарии должны составлять не менее 10% от текста. Студенты, не выполнившие домашнего задания, не допускаются к выполнению лабораторной работы. ЗАДАНИЕ К ЛАБОРАТОРНОЙ РАБОТЕ № 6 Выполнить задание с использованием указателей. Задание 1 С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Преобразовать массив таким образом, чтобы сначала располагались все положительные элементы, а потом – все отрицательные (элементы, равные 0, считать положительными). Задание 2. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Сжать массив, удалив из него все элементы, модуль которых не превышает 1. Освободившиеся в конце массива элементы заполнить нулями. Задание 3. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Сжать массив, удалив из него все элементы, модуль которых находится в интервале [а,b]. Освободившиеся в конце массива элементы заполнить нулями. Задание 4. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Преобразовать массив таким образом, чтобы сначала располагались все элементы, равные нулю, а потом – все остальные. Задание 5. 90 С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Преобразовать массив таким образом, чтобы в первой его половине располагались элементы, стоявшие в нечетных позициях, а во второй половине – элементы, стоявшие в четных позициях. Задание 6. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Преобразовать массив таким образом, чтобы сначала располагались все элементы, модуль которых не превышает 1, а потом – все остальные. Задание 7. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Преобразовать массив таким образом, чтобы элементы, равные нулю, располагались после всех остальных. Задание 8. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Преобразовать массив таким образом, чтобы в первой его половине располагались элементы, стоявшие в четных позициях, а во второй половине – элементы, стоявшие в нечетных позициях. Задание 9. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Сжать массив, удалив из него все элементы, величина которых находится в интервале [а,b]. Освободившиеся в конце массива элементы заполнить нулями. Задание 10. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Преобразовать массив таким образом, чтобы сначала располагались все элементы, целая часть которых лежит в интервале [а,b], а потом – все остальные. Задание 11. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Преобразовать массив таким образом, чтобы сначала располагались все отрицательные элементы, а потом – все положительные (элементы, равные 0, считать положительными). Задание 12. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Преобразовать массив таким образом, чтобы сначала располагались все элементы, целая часть которых не превышает 1, а потом – все остальные. Задание 13. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: 91 Преобразовать массив таким образом, чтобы сначала располагались все элементы, отличающиеся от максимального не более чем на 20%, а потом – все остальные. Задание 14. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Заменить все отрицательные элементы массива их модулями и изменить порядок следования элементов в массиве на обратный. Задание 15. С одномерным массивом, состоящим из n вещественных элементов, выполнить следующее: Сжать массив, удалив из него одинаковые элементы. Освободившиеся в конце массива элементы заполнить нулями. СОДЕРЖАНИЕ ОТЧЕТА Отчет выполняется по выбору преподавателя либо в редакторе Word, либо в черновом варианте в лекционной тетради. Отчет должен содержать: 1. Листинг программы на языке Си, решающей задачи в соответствии с вариантом (номером компьютера) задания; 2. Результаты тестирования программы. КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №6 Что такое переменная-указатель и какие виды указателей Вы знаеете ? Какие виды указателей допускают изменение, а какие нет ? Что такое ссылка? Запишите программу динамического выделения и освобождения памяти под массив A[5] с помощью функций malloc и free ? 5. Запишите программу динамического выделения и освобождения памяти под массив A[5] с помощью операторов new и delete ? 6. Запишите программу динамического выделения и освобождения памяти под массив B[5][6] с помощью операторов new и delete? 1. 2. 3. 4. 92 ЛАБОРАТОРНАЯ РАБОТА №7 ОТЛАДКА ПРОГРАММЫ КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ Отладка программы – процесс исправления ошибок в коде программы. Ошибки могут быть синтаксическими и логическими. Синтаксические ошибки выявляются на этапе компиляции программы. Сообщения об ошибках компилятор выводит в окне. Исправление синтаксических ошибок необходимо начинать с первой ошибки, так как она может вызвать серию связанных с ней ошибок. Для выявления логических ошибок требуется наблюдать за изменением переменных, поведением функций, объектов, процессов и линий потока управления. Это можно сделать с помощью встроенного отладчика IDE C++Builder 5. Отладчик IDE обладает широкими возможностями вплоть до отладки на уровне машинного кода. 6 Отладочные пункты меню При отладке вам понадобится обращаться в основном к трем меню: это каскадное меню View>Debug Windows, меню Run и контекстное меню редактора кода. Пункты этих меню приведены в таблицах 6.1 - 5.3. Таблица 5.1. Пункты меню Viev | Debug Windows Пункт Клавиша Описание Breakpoints Ctrl+Alt+B Открывает окно списка контрольных точек, показывающее активные контрольные точки и их свойства. Call Stack Ctrl+Alt+S Открывает окно стека вызовов. Стек показывает, какие и в каком порядке вызывались функции, прежде чем управление достигло текущей точки программы. Watches Ctrl+Alt+W Открывает окно наблюдения за переменными. Окно отображает список наблюдаемых переменных с их текущими значениями. Local Variables Ctrl+Alt+L Открывает окно локальных переменных. В нем отображаются значения всех локальных переменных текущей функции. Threads Ctrl+Alt+T Окно активных процессов и линий потока управления (threads). Modules Ctrl+Alt+M Окно загруженных модулей – исполняемых файлов, динамических библиотек и пакетов запущенного проекта. Event Log Ctrl+Alt+E Отображает протокол событий, происходящих при запуске проекта; какие события будут регистрироваться, можно задать на странице Event Log диалога Debugger Options. 93 CPU Ctrl+Alt+C Открывает окно состояния процессора. Отображает, в частности, компилированный код программы и содержимое регистров. FPU Ctrl+Alt+F Открывает окно состояния FPU, отражающее содержимое его регистров и флагов. Таблица 5.2. Пункты меню Run Пункт Клавиша Описание Run F9 Запускает программу, при необходимости производя перед этим её сборку (Make). Attach to Process... Прикрепляет отладчик к уже выполняющемуся в данный момент процессу. Parameters... Позволяет ввести аргументы командной строки или указать приложение, которое является “хозяином” отлаживаемой DLL. Step Over F8 Исполняет текущую строку исходного кода и переходит к следующей строке. Trace Into F7 Исполняет текущую строку исходного кода; если строка содержит вызов функции, переходит к трассировке последней. Trace to Next Source Line Shift+F7 Исполняет программу до следующей строки исходного кода. Например, если программа вызывает функцию API, требующую возвратно-вызываемой процедуры, отладчик остановит выполнение на входе в эту процедуру. Run to Cursor F4 Исполняет программу до строки исходного кода, в которой установлен курсор редактора. Run Until Return Shift+F8 Исполняет программу до возврата из текущей функции Show Execution Point Устанавливает курсор редактора кода на строку, в которой приостановлена программа. Program Pause Приостанавливает выполнение программы, как только управление попадает в наличный исходный код. Program Reset Ctrl+F2 Закрывает программу. Открывает диалог Inspect, в котором можно ввести имя инспектируемого объекта. Inspect... Evaluate/Modify... Ctrl+F7 Открывает диалог Evaluate/Modify Add Watch... Ctrl+F5 Открывает диалог Watch Properties Add Breakpoint Каскадное меню, позволяющее устанавливать контрольные точки различного вида (в исходном коде, на адресе, на данных, точки загрузки модуля). Для любой из вышеперечисленных команд меню можно поместить соответствующую кнопку на инструментальную панель. Откройте правой кнопкой мыши контекстное меню инструментальной панели и выберите Customize...; на странице Commands открывшегося диалога выберите нужную кнопку и пере- 94 тащите ее на инструментальную панель. Чтобы убрать с панели какую-нибудь кнопку, просто вытащите ее мышью за пределы главного окна C++Builder. По умолчанию на панели инструментов размещены кнопки Run, Pause, Trace Into и Step Over. Следующая таблица показывает пункты контекстного меню редактора в режиме приостановленной программы. В основном они дублируют перечисленные пункты главного меню, но в ряде случаев более удобны. Таблица 5.3. Отладочные пункты контекстного меню редактора Пункт Клавиша Описание Toggle Breakpoint F5 Переключает (устанавливает или сбрасывает) контрольную точку в строке, где находится курсор редактора. Run to Cursor F4 То же, что и в меню Run. Позволяет указать адрес области памяти, которая будет отображаться в панели дизассемблера окна CPU. Goto Address... Inspect... Alt+F5 Открывает окно инспекции объекта, на имени которого находится курсор. Evaluate/Modify... То же, что и в меню Run. Add Watch at Cur- Ctrl+F5 sor Вносит в список наблюдения переменную, на имени которой находится курсор. View CPU То же, что Viev меню.| Debug Windows| CPU в главном Ну а теперь мы поговорим о том, чем управляют все эти меню и какие вообще инструментальные средства отладки имеются в распоряжении программиста. 7 Элементы отладки Наиболее общими приемами отладки являются установка контрольных точек, наблюдение за переменными и пошаговое исполнение кода. 7.1 Контрольные точки Программа, запущенная под управлением отладчика IDE, исполняется как обычно, т. е. с полной скоростью, пока не будет встречена контрольная точка (breakpoint). Тогда отладчик приостанавливает программу, и вы можете исследовать и изменять содержимое переменных, исполнять операторы в пошаговом режиме и т. д. Контрольные точки в C++Builder 5 могут быть четырех видов: в исходном коде, на адресе, на данных и точки загрузки модуля. 95 7.1.1 Контрольные точки в исходном коде Это самый распространенный вид контрольных точек. Точка представляет собой маркер, установленный на некоторой строке исходного кода. Когда управление достигает этой строки, программа приостанавливается. Проще всего установить контрольную точку такого типа прямо в редакторе кода, щелкнув кнопкой мыши на пробельном поле редактора (слева от текста) рядом со строкой, на которой требуется приостановить программу. В пробельном поле появится красный маркер, и сама строка будет выделена красным цветом фона (рис. 5.1). Повторное нажатие кнопки мыши удаляет контрольную точку. Рис. 5.1 Установка контрольных точек Если теперь запустить программу кнопкой Run, она будет остановлена на контрольной точке (рис. 5.2). Зеленая пометка на маркере контрольной точки означает, что точка проверена и признана действительной. Могут быть и недействительные контрольные точки – такие, что установлены на строках, не генерирующих исполняемого кода. Это могут быть комментарии, объявления, пустые строки или операторы, исключенные при оптимизации программы. Текущая точка исполнения показана в пробельном поле зеленой стрелкой. Она указывает строку, которая должна исполняться следующей. Программу можно продолжить кнопкой Run или выполнять ее операторы в пошаговом режиме, о чем будет сказано ниже. 96 Рис. 5.2 Остановка программы на контрольной точке То, что мы сейчас показали – это простые контрольные точки в исходном коде; контрольные точки могут быть также условными, со счетчиком проходов или комбинированного типа. Если вы в данный момент экспериментируете с отладчиком, откройте окно списка контрольных точек (View Debug Windows Breakpoints). Оно отображает все имеющиеся контрольные точки. Контекстное меню окна позволяет запретить остановку программы на контрольной точки, не удаляя ее (пункт Enable). Кроме того, выбрав пункт Properties..., вы получите доступ к свойствам выбранной точки (рис. 5.3 и 5.4). Рис. 5.3 Окно Breakpoint List 97 Рис. 5.4 Диалог Source Breakpoint В поле Condition диалога Source Breakpoint Properties можно задать условие остановки на контрольной точке. Условие может быть любым допустимым выражением языка C/C++, которое можно оценить как истинное или ложное. Остановка по достижении контрольной точки будет происходить только в том случае, если условие истинно. Контрольные точки со счетчиком проходов можно считать разновидностью условных. Требуемое число проходов вводится в поле Pass count. Если число проходов установлено равным п, остановка программы произойдет только на n-ом проходе через контрольную точку. Точки со счетчиком удобны при отладке циклов, когда вам нужно выполнить тело цикла определенное число раз и только потом перейти к пошаговому выполнению программы. Счетчик может быть очень полезен, когда вам нужно определить, на каком проходе цикла возникает ошибка, вызывающая завершение программы. В окне списка контрольных точек отображается не только заданное, но и текущее число проходов точки (например, “7 of 16”). Задав число проходов, равное или большее максимальному числу итераций цикла, вы при завершении программы сразу увидите, сколько раз на самом деле он выполнялся. Возможна комбинация этих двух типов контрольных точек, которую можно назвать точкой с условным счетчиком. Если для контрольной точки задано и условие, и число проходов, то остановка произойдет только на п-ом 98 “истинном” проходе через нее. Проходы, для которых условие оказывается ложным, “не считаются”. Условия и счетчик можно задавать для всех видов контрольных точек кроме точек загрузки модуля, т. е. для исходных, адресных и точек данных. 7.1.2 Адресные контрольные точки Адресные контрольные точки во всем аналогичны точкам в исходном коде за исключением того, что при их установке указывается не строка исходного кода, а машинный адрес инструкции, на которой нужно приостановить программу. Такие контрольные точки полезны, если ваша программа завершается аварийно. В этом случае Windows выводит панель сообщения, в которой указывается адрес инструкции, вызвавшей ошибку. Адресные контрольные точки и их свойства устанавливаются в диалоге, вызываемом командой Run>Add Breakpoint>Address Breakpoint... главного меню или из контекстного меню окна Breakpoint List. Установить адресную точку можно только во время исполнения программы или при ее остановке (например, в другой контрольной точке). При дальнейшем выполнении программы отладчик приостановит ее на инструкции с указанным адресом. Если эта инструкция соответствует некоторой строке исходного кода, контрольная точка будет показана в окне редактора. В противном случае она будет отображена в панели дизассемблера окна CPU. 7.1.3 Контрольные точки данных Контрольные точки на данных также устанавливаются при запущенной программе в диалоге, вызываемом командной Run>Add Breakpoint>Data Breakpoint... или Add Data Breakpoint в контекстном меню списка контрольных точек (рис. 5.5). Рис. 5.5 Диалог Add Data Breakpoint 99 Контрольная точка на данных вызывает остановку программы, если в указанный элемент данных производится запись. В поле Address можно указать либо адрес, либо имя переменной. В поле Length указывается размер объекта, определяющий диапазон адресов, обращение к которым будет вызывать остановку. Для переменных встроенных типов размер устанавливается автоматически. Как и для двух предыдущих видов, для контрольных точек данных можно задать условие и счетчик. (левая половина окна) (правая половина окна) Рис. 5.6 Контрольные точки загрузки модуля 100 Команда Run Add Breakpoint>Module Load Breakpoint... открывает диалог Add Module, в котором задается имя файла (.exe, .dll, .осх или .bpl) для внесения его в список окна Modules. Загружаемые в память во время исполнения программы модули заносятся в это окно автоматически, однако если вы хотите, чтобы загрузка модуля вызывала остановку, то должны вручную ввести имя файла в список окна Modules до того, как модуль будет загружен (например, перед запуском программы). На рис. 5.6 показано окно Modules. Добавить новый модуль в окно можно и через его контекстное меню (Рис. 5.7). Рис. 5.7 Панель вверху слева показывает список модулей. Для выбранного модуля панель слева внизу показывает исходные файлы, входящие в его состав. Панель справа отображает список входных точек (глобальных символов) модуля. 7.2 Команда Run to Cursor Если установить курсор редактора кода на некоторую строку исходного кода и запустить программу командой Run to Cursor главного или контекстного меню редактора (можно просто нажать F4), то курсор будет играть роль “временной контрольной точки”. Достигнув строки, где находится курсор, программа остановится, как если бы там находилась простая контрольная точка. 7.3 Команда Pause Выполняющуюся в IDE программу можно приостановить, выбрав в главном меню Run>Program Pause или нажав кнопку Pause на инструментальной панели. Это более или менее эквивалентно остановке в контрольной точке. Если адресу, на котором остановилось выполнение, соответствует доступный исходный код, он будет показан в редакторе. В противном случае будет открыто окно CPU, отображающее машинные инструкции компилированной программы. 8 Наблюдение за переменными Итак, вы остановили программу в контрольной точке. Обычно затем смотрят, каковы значения тех или иных переменных. Это называется наблюдением переменных (watching variables). 101 В IDE имеется специальное окно списка наблюдаемых переменных (рис. 5.8). Его можно открыть командой View>Debug Windows>Watches и ввести в него любое число переменных. Рис. 5.8 Окно Watch List Проще всего добавить переменную в список наблюдения можно, поместив курсор редактора кода на ее имя и выбрать в контекстном меню редактора Add Watch at Cursor. В окне наблюдений будет показано имя переменной и ее текущее значение либо сообщение, показывающее, что переменная в данный момент недоступна или наблюдение отключено (<disabled>). Можно ввести в список и целое выражение, если выделить его в редакторе и вызвать контекстное меню. Альтернативным методом добавления переменных или выражений является выбор в контекстном меню окна наблюдений пункта Add Watch... (пункт Edit Watch... служит для редактирования свойств уже имеющегося в списке наблюдения). Будет открыт диалог Watch Properties (рис. 5.9). Рис. 5.9 Диалог Watch Properties Помимо выражения, которое будет наблюдаться, диалог позволяет задать формат представления его значения. Поле Repeat count определяет число отображаемых элементов, если наблюдаемый объект – массив. 102 8.1 Быстрый просмотр данных Редактор кода имеет встроенный инструмент, позволяющий чрезвычайно быстро узнать текущее значение переменной или выражения. Он называется подсказкой оценки выражения. Достаточно на секунду задержать курсор мыши над именем переменной или выделенным выражением, и под курсором появится окошко инструментальной подсказки с именем переменной и ее текущим значением (рис. 5.10). Причем – в отличие от окна наблюдений – таким способом можно просматривать и переменные, находящиеся за пределами текущей области действия (поскольку здесь не может возникнуть неоднозначности с именами). Рис. 5.10 Подсказка с оценкой элементов массива Выдачей подсказок управляет диалог Tools>Editor Options..., страница Code Insight (рис. 5.11). Чтобы разрешить отображение подсказок с оценками, следует убедиться, что флажок Tooltip expression evaluation помечен. Ползунок Delay задает задержку появления подсказок. Эта страница управляет и другими “подсказочными инструментами” редактора кода. 103 Рис. 5.11 Страница Code Insight диалога Editor Properties 9 Инспектор отладки Инспектор отладки – это самый универсальный инструмент IDE для просмотра и модификации значений объектов данных, прежде всего объектов, принадлежащих классам. В их число входят и визуальные компоненты C++Builder. Они, в сущности, тоже не более чем представители классов, а инспектор отладки в этом случае является “инспектором объектов времени выполнения”. Открыть инспектор отладки можно либо командой Run | Inspect... главного меню, либо из контекстного меню редактора, установив курсор на имени нужного объекта. На рис. 5.12 показан инспектор отладки, отображающий состояние помещенной на форму метки. Инспектор отладки может использоваться только после остановки программы в контрольной точке. Инспектор отладки имеет три страницы: Data, Methods и Properties. Страница Data показывает все элементы данных класса с их значениями; тип выбранного элемента отображается в строке состояния инспектора. Страница Methods показывает методы (элементы-функции) класса. В некоторых случаях эта страница отсутствует, например, при инспекции переменных простых типов. 104 Страница Properties показывает свойства объекта. При инспекции переменных, не являющихся представителями класса, эта страница также отсутствует. Свойства и методы – это понятия, относящиеся к визуальному программированию. В приведенной ниже таблице перечислены пункты контекстного меню инспектора отладки. Рис. 5.12 Инспектор отладки Таблица 5.4. Пункты контекстного меню инспектора отладки Пункт меню Описание Range... Позволяет указать диапазон элементов массива, которые будут показаны. Change... Позволяет присвоить новое значение элементу данных. Если элемент можно изменять, в поле его значения имеется кнопка с многоточием. Ее нажатие эквивалентно команде Change. Show Inherited Помечаемый пункт, влияющий на объем отображаемой инспектором информации. Если помечен, то инспектор показывает не только элементы, объявленные в классе, но и унаследованные от базовых классов. Inspect Открывает новое окно инспекции для выбранного элемента данных. Это полезно при исследовании деталей структур, классов и массивов. Descend То же, что и Inspect, но выбранный элемент отображается в том же самом окне; нового окна инспекции не создается. Чтобы вернуться к инспекции прежнего объекта, выберите его в выпадающем списке в верхней части окна. Type Cast... Позволяет указать новый тип данных для инспектируемого объекта. Полезен в случаях, когда вы исследуете, например, указатель типа void*. Позволяет ввести новое выражение, задающее инспектируемый объект. New Expression... 9.1 Инспекция локальных переменных Командой View>Debug Windows>Local Variables можно открыть окно локальных переменных (рис. 5.13). Оно внешне похоже на окно Watch List, но работает автоматически, отображая все локальные переменные и параметры текущей функции. Кроме того, его контекстное меню имеет пункт Inspect, открывающий окно инспектора для выбранного элемента списка. Выпадающий список наверху позволяет выбрать 105 контекст, для которого нужно отобразить локальные переменные. Список совпадает с содержимым окна Call Stack, которое описано ниже. Рис. 5.13 Окно Local Variables 10 Другие инструменты отладки В IDE имеются и другие инструменты отладки помимо описанных выше. Мы расскажем о них очень коротко, поскольку применять их приходится не слишком часто. 10.1 Диалог Evaluate/Modify Этот диалог (рис. 5.14) служит для оценки выражений и изменения значений переменных. Его можно открыть командой Run>Evaluate/Modify или из контекстного меню редактора, установив курсор на нужной переменной или выделенном выражении. Рис. 5.14 Диалог Evaluate/Modify 106 В поле Expression вводится выражение, которое требуется оценить. При нажатии кнопки Evaluate результат оценки отображается в поле Result. Если вы хотите изменить значение переменной, введите новое значение в поле New value и нажмите кнопку Modify. Диалог Evaluate/Modify можно использовать в качестве простого калькулятора, позволяющего вычислять арифметические выражения и оценивать логические условия. В выражениях можно смешивать десятичные, восьмеричные и шестнадцатеричные значения. Результат вычисления выводится всегда в десятичном виде, поэтому очень просто, например, перевести шестнадцатеричное число в десятичное. Введите численное выражение в поле Expression и нажмите Evaluate. Поле Result покажет результат вычисления. 10.2 Окно CPU Это окно, показанное на рис. 5.15, открывается командой View>Debug Windows>CPU главного или View CPU контекстного меню редактора. Окно имеет пять отдельных панелей. Слева вверху находится панель дизассемблера. Она показывает строки исходного кода (если в контекстном меню панели помечен флажок Mixed) и генерированные для них машинные инструкции. В окне CPU можно устанавливать контрольные точки, как в редакторе, и выполнять отдельные инструкции командами Step Over Рис. 5.15 Окно CPU и Trace Into. На рисунке вы видите фрагмент программы, приведенной в начале главы – заголовок цикла for и начало блока ассемблерных инструкций. 107 Справа вверху находятся две панели, отображающие состояние регистров и флагов процессора. Содержимое регистров, модифицированных в результате последней инструкции, выделяется красным цветом. Под панелью дизассемблера расположена панель дампа памяти. Панель справа внизу показывает “сырой” стек программы. Положение указателя стека соответствует зеленой стрелке. Каждая из панелей окна CPU имеет свое собственное контекстное меню, позволяющее выполнять все необходимые операции. Мы не будем здесь подробно разбирать отладку с помощью окна CPU, поскольку, чтобы им пользоваться, нужно хорошо знать язык ассемблера. Однако, если вы имеете о нем хотя бы смутное представление, вам будет интересно посмотреть на инструкции, которые генерирует компилятор. Разобраться в них не так уж и трудно, и иногда это помогает написать более эффективный исходный код. У отладчика имеется также окно FPU, отображающее состояние процессора плавающей арифметики. 10.3 Стек вызовов Окно стека вызовов (рис. 5.16) открывается командой View Debug Windows 1 Call Stack. Рис. 5.16 Окно Call Stack В окне показан список функций, вызванных к данному моменту и еще не завершившихся. Функция, вызванная последней, находится вверху списка. Для функций, имена которых неизвестны, указывается только адрес и имя модуля, как, например, для третьей сверху функции на рисунке. Если дважды щелкнуть на имени некоторой функции, в редакторе будет показан ее исходный код или, если он недоступен, будет открыто окно CPU на соответствующем адресе. Исследование стека вызовов бывает полезно при возникновении ошибок типа нарушения доступа. На вершине стека будет находиться функция, получившая управление непосредственно перед ошибкой. 10.4 Команда Go to Address Эта команда позволяет отыскать в исходном коде строку, соответствующую некоторому адресу, например, адресу инструкции, вызвавшей ошибку. 108 Если выбрать Goto Address в меню Search или контекстном меню редактора кода (программа должна быть запущена), то появится диалоговая панель, в которой вводится нужный адрес. Отладчик попытается найти соответствующий ему исходный код, который в случае успешного поиска будет показан в редакторе. Если адрес находится за пределами вашего кода, будет выведено сообщение о том, что адрес не может быть найден. 10.5 Команда Program Reset Иногда отлаживаемая программа “зависает” так, что никаким образом нельзя довести ее до сколько-нибудь нормального завершения. В этом случае можно прибегнуть к команде Run>Program Reset, которая аварийно завершает программу приводит ее в исходное состояние. Нужно сказать, что это крайнее средство и не следует им пользоваться просто для того, чтобы побыстрее закончить сеанс отладки. Windows этого не любит, и после команды Program Reset с IDE и системой могут происходить странные вещи. Пошаговое исполнение кода Одной из важнейших и самых очевидных операций при отладке является пошаговое исполнение кода. Когда программа приостановлена в контрольной точке, вы можете наблюдать значения различных переменных. Но чтобы найти конкретный оператор, ответственный за неправильную работу программы, нужно видеть, как эти значения меняются при исполнении отдельных операторов. Таким образом, выполняя операторы программы по одному, можно определить момент, когда значения каких-то переменных оказываются совсем не теми, что ожидались. После этого уже можно подумать, почему это происходит и как нужно изменить исходный код, чтобы устранить обнаруженную ошибку. Эти команды могут выполняться из главного меню Run или с помощью кнопок инструментальной панели. 10.6 Step Over Команда Step Over выполняет оператор, на котором приостановлено выполнение, и останавливается на следующем по порядку операторе. Текущий оператор выделяется в редакторе кода синим фоном и помечается зеленой стрелкой в пробельном поле. Если текущий оператор содержит вызов функции, она выполняется без остановок, и текущим становится первый оператор, следующий за возвратом из функции. Step Over “перешагивает” через вызов функции. 10.7 Trace Into Команда Trace Into эквивалентна Step Over в случае, когда текущий оператор не содержит вызовов функций. Если же оператор вызывает некоторую функцию, то отладчик по команде Trace Into переходит на строку ее заголовка (заголовок при желании тоже можно рассматривать как исполняемый оператор, 109 ответственный за инициализацию локальных переменных-параметров). При следующей команде (все равно – Step Over или Trace Into) текущим станет первый исполняемый оператор тела функции. Trace Into “входит внутрь” функции. При выполнении команд Step Over и Trace Into в окне CPU отладчик исполняет не операторы, а отдельные машинные инструкции. ЗАДАНИЕ НА ЛАБОРАТОРНУЮ РАБОТУ №7 1. Записать алгоритм своего второго задания из лабораторной работы № 5. 2. Написать программу своего второго задания из лабораторной работы № 5. 3. Просмотреть пошаговое исполнение программы. 4. Открыть окно списка наблюдаемых переменных и ввести в список необходимые переменные, которые будут просматриваться при пошаговом выполнении программы. 5. Провести пошаговое выполнение программы с просмотром переменных. 6. Установить 3 простые контрольные точки и провести выполнение программы. 7. Установить контрольную точку со счетчиком проходов в теле цикла и провести выполнение программы. КОНТРОЛЬНЫЕ ВОПРОСЫ К ЛАБОРАТОРНОЙ РАБОТЕ №7 1. Как установить контрольную точку и какие виды контрольных точек Вы знаете ? 2. Какие виды наблюдения за переменными Вы знаете ? Охарактеризуйте их. 3. Если наблюдаемый объект – массив, как его лучше наблюдать ? При ответе на вопрос выполнить самостоятельное исследование способов наблюдения. 4. Как выполняется инспекция локальных переменных ? 5. Чем отличаются команды пошагового выполнения программы Step Over и Trace Into ? 110 Приложение I Таблица I.1. Список ключевых слов C++ asm else new this auto enum operator throw bool explicit private true break export protected try case extern public typedef catch false register typeid char float reinterpret_cast typename class for return union const friend short unsigned const_cast goto signed using continue if sizeof virtual default inline static void delete int static_cast volatile do long struct wchar_t double mutable switch while dynamic_cast namespace template 111 Таблица I.2. Константы в языке C++ Тип константы Формат Примеры Целая Десятичный: последовательность десятич- 8, 0, 199226 ных цифр, начинающаяся не с нуля, если это не число нуль Восьмеричный: нуль, за которым следуют 01, 020, 07155 восьмеричные цифры (0,1,2,3,4,5,6,7) Шестнадцатеричный: 0х или 0Х, за которым следуют шестнадцатеричные цифры 0хА, 0xlB8, (0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F) 0X00FF Вещественная Десятичный: [цифры]. [цифры] Экспоненциальный: ры][.][цифры]{Е||е}[+;-][цифры] [циф- 5.7, .001, 35. 0.2Е6, .11е-3, .5Е10 Символьная Один или два символа, заключенных в апо- 'А', 'ю', '*', 'db', строфы '\0', '\n', '\012', '\0x7\0x7' Строковая Последовательность символов, заключенная "Это строка\n" в кавычки 112 Таблица I.3. Управляющие последовательности в языке C++ Изображение Шестнадцатеричный код Наименование \а 7 Звуковой сигнал \b 8 Возврат на шаг \f С Перевод страницы (формата) \n А Перевод строки \r D Возврат каретки \t 9 Горизонтальная табуляция \v В Вертикальная табуляция \\ 5С Обратная косая черта \’ 27 Апостроф \" 22 Кавычка \? 3F Вопросительный знак \0ddd – Восьмеричный код символа \0xddd ddd Шестнадцатеричный код символа 113 Таблица I.4. Основные операции языка C++ Операция Краткое описание Унарные операции ++ увеличение на 1 -уменьшение на 1 Sizeof Размер ~ поразрядное отрицание ! логическое отрицание - арифметическое отрицание (унарный минус) + унарный плюс & взятие адреса * разадресация new выделение памяти delete освобождение памяти (type) преобразование типа Бинарные и тернарная операции * умножение / деление % остаток от деления + << >> < <= > >= == != & ^ | && || ?: = *= /= %= += сложение вычитание сдвиг влево сдвиг вправо меньше меньше или равно больше больше или равно равно не равно поразрядная конъюнкция (И) поразрядное исключающее ИЛИ поразрядная дизъюнкция (ИЛИ) логическое И логическое ИЛИ условная операция (тернарная) присваивание умножение с присваиванием деление с присваиванием остаток от деления с присваиванием сложение с присваиванием 114 -= <<= >>= &= |= ^= , вычитание с присваиванием сдвиг влево с присваиванием сдвиг вправо с присваиванием поразрядное И с присваиванием поразрядное ИЛИ с присваиванием поразрядное исключающее ИЛИ с присваиванием последовательное вычисление 115 Таблица I.5. Элементы спецификатора преобразования Элемент флаг Символ Аргумент 0 + пробел # поле число точность число размер h l L символ типа d целое i целое о целое Описание Выровнять вывод по левому краю поля. Заполнить свободные позиции нулями вместо пробелов. Всегда выводить знак числа. Вывести пробел на месте знака, если число положительное. Вывести 0 перед восьмеричным или 0х перед шестнадцатеричным значением. Минимальная ширина поля вывода. Для строк – максимальное число выводимых символов; для целых – минимальное число выводимых цифр; для вещественных – число цифр дробной части. Аргумент – короткое целое. Аргумент – длинное целое. Аргумент имеет тип long double. Форматировать как десятичное целое со знаком. То же, что и d. Форматировать как восьмеричное без знака. 116 u целое х целое Х целое f вещественное е вещественное Е вещественное g вещественное G вещественное с символ s строка n указатель р указатель Форматировать как десятичное без знака. Форматировать как шестнадцатеричное в нижнем регистре. Форматировать как шестнадцатеричное в верхнем регистре. Вещественное в форме [-]dddd.dddd. Вещественное в форме [-]d.dddde[+|-]dd. То же, что и е, с заменой е на Е. Использовать форму f или е в зависимости от величины числа и ширины поля. То же, что и g – но форма f или Е. Вывести одиночный символ. Вывести строку. Аргумент – указатель на переменную типа int. В нее записывается количество выведенных к данному моменту символов. Вывести указатель в виде шестнадцатеричного числа ХХХХХХХХ. 117 Приложение II Блоки схем алгоритмов согласно ГОСТ 19.002 – 80 и 19.003 – 80 Перечень, наименование, обозначение и соотношение размеров обязательных символов и отображаемые ими функции в алгоритме и программе обработки данных должны соответствовать требованиям, указанным в таблице II.1. Таблица II.1 Обозначение и соотношение Наименование Функция размеров 1 2 3 1. Процесс Здесь лаконично излагается процесс обработки информации а 1,5a 2. Решение Условие а 1,5а Выражение y1 y2 yn 3. Модификация а а 1,5а 1 2 Выполнение операции или группы операций, в результате которых изменяется значение, форма представления или расположение данных Выбор направления выполнения алгоритма или программы в зависимости от некоторых переменных условий. Блок должен иметь 2 выхода с надписями: «Да» (условие выполняется) и «Нет» ( условие не выполняется) или 3 выхода с надписями >,<,=, или один выход с произвольным количеством разветвлений с надписями, вставленными в разрыв линии. В этом случае условие заменяется выражением, а надписи являются значениями выражения y1,y2..yn. Выполнение операций, меняющих команды или группы команд, изменяющих программу. Используется для циклических алгоритмов со счетчиком циклов. Выход из цикла должен быть сбоку блока, вход на следующую модификацию с другой стороны. 3 118 4. Ввод -вывод а 1,25а 5. Предопределенный процесс 0,25а Название процесса а Преобразование данных в форму, пригодную для обработки (ввод) или отображения результатов обработки (вывод) Использование ранее созданных и отдельно описанных алгоритмов или программ (подпрограмм) 0,15а 1,5а 6. Начало и завершение процесса Начало и конец алгоритма или программы обязательно содержат эти блоки, причем в единственном экземпляре. Начало Конец 0,5а 1,5а 7. Соединитель (Узел) Указание связи между прерванными линиями потока, связывающими символы в пределах одного листа. N – номер связи N 0,5 а 8. Межстраничный соединитель 12 N 0,8а 0,5а 1 2 Указание связи между разъединенными частями схем алгоритмов и программ, расположенных на разных листах. N – номер связи или последнего блока, 12 – номер страницы, с которой имеется связь 3 119 9. Комментарий Связь между элементом схемы и пояснением Допускается ширину блоков 1–6 выбирать равной 2а. Размер а выбирается из ряда 10, 15, 20 мм и может быть увеличен на число, кратное 5. Ряд блоков не приводится в таблице по причине их редкого использования. Для ознакомления с ними следует использовать ГОСТ. Блоки могут быть пронумерованы в разрыве линии в левом верхнем углу блока. 120 Приложение III Оформление алгоритма программы в виде блок-схемы Блок-схемы алгоритмов оформляются на основе блоков приведенных в таблице II.1 (Приложение II). Следует подчеркнуть, что любая блок-схема должна начинаться и заканчиваться блоками начала и завершения процесса. Это обязательно. Остальное выбирается разработчиком. Пример оформления блок-схемы приведен на рис. III.1 1 НАЧАЛО 2 a,b,c,x – аргументы функции F a,b,c,x 3 a<b и b0 Нет a=b и c=0 4 Да 5 F=ax+c Нет Да 6 F=(a-b)/(cx) 7 F=ab+cx 8 F 9 КОНЕЦ Рис.III.1 Блок-схема алгоритма программы вычисления функции F 121 Приложение IV Базовая программа для выполнения лабораторных работ //--------------------------------------------------------------------------#pragma hdrstop //--------------------------------------------------------------------------#include <conio.h> //библиотека подключения функции getch() #include <iostream.h> //библиотека для cin и cout #include <malloc.h> //библиотека для динамического выделения памяти #include <fstream.h> //библиотека для ввода/вывода в файл #include <windows.h> //библиотека используется в функции RUS #include <math.h> //библиотека математических функций #include <ctype.h> // библиотека проверки принадлежности символов #include <stdlib.h> // библиотека некоторых стандартных функций #include <string.h> // библиотека функций работы со строками // char bufRus[256]; //--------------------------------------------------------------------------char* RUS(const char*text) //функция поддержки русского языка { CharToOem(text,bufRus); return bufRus; } //--------------------------------------------------------------------------//Здесь могут вводится функции и классы пользователя //**************************************************** //**************************************************** #pragma argsused int main(int argc, char* argv[]) { cout<<RUS("***Напишите здесь название программы*** \n"); //Здесь введите свою программу //**************************************************** //**************************************************** cout<<RUS("\nНажмите любую клавишу для завершения программы ...\n"); getch(); return 0; } //--------------------------------------------------------------------------- 122 Приложение V Таблица V.1 Флаги форматирования showpos showbase uppercase showpoint left right dec hex oct fixed scientific печатать знак при выводе положительных чисел выводится основание системы счисления (0x для шестнадцатиричных чисел и 0 для восьмиричных чисел) При выводе использовать символы верхнего регистра При выводе вещественных чисел печатать точку и дробную часть Выравнивание по левому краю поля Выравнивание по правому краю поля Десятичная система счисления Шетнадцатиричная система счисления Восьмиричная система счисления Печатать вещественные числа в форме с фиксированной точкой Печатать вещественные числа в форме мантиссы с порядком Приложение VI Таблица VI.1. Функции работы со строками символов Наименование функции strcpy strcat strchr strcmp strcmpi strcspn strdup strerror _strerror stricmp strlen Краткое описание копирует строку2 в строку1 присоединяет строку2 в конец строки1 возвращает позицию первого вхождения символа в строку сравнивает строку1 со строкой2, различая прописные и строчные буквы см. stricmp возвращает позицию первого вхождения символа из заданного набора символов распределяет память и делает копию строки возвращает по заданному номеру системной ошибки указатель на строку текста сообщения об ошибке возвращает указатель на строку, образованную объединением произвольной строки и сообщения об ошибке в библиотечной функции сравнивает строку1 со строкой2, не различая прописные и строчные буквы возвращает длину строки в байтах, не учитывая нулевой терминатор 123 strupr преобразует все символы строки в строчные буквы присоединяет заданное число символов строки2 в конец строки1 сравнивает заданное число символов двух строк, различая прописные и строчные буквы см. strnicmp копирует заданное число символов строки2 в строку1 сравнивает заданное число символов двух строк, не различая прописные и строчные буквы помещает заданный символ в заданное число позиций строки отыскивает место первого вхождения любого символа из заданного набора отыскивает последнее вхождение символа в строке реверс строки помещает символ во все позиции строки возвращает позицию в строке первого символа, который не принадлежит заданному набору символов отыскивает место первого вхождения строки2 в строку1 возвращает указатель на лексему, ограниченную заданным разделителем преобразует все буквы строки в прописные буквы isalnum (с) истина, если символ с является буквой или цифрой isalpha (с) isascii (с) iscntrl (с) isdigit (с) isgraph(c) islower (с) isprint (с) истина, если символ с является буквой истина, если код символа с <= 127 истина, если с - управляющий символ истина, если с - символ десятичной цифры истина, если с - печатаемый символ (код от 33 до 126) истина, если с - строчная буква истина, если с - печатаемый символ (код от 33 до 126) или пробел истина, если с - символ пунктуации истина, если с - символ пустого места или пробела истина, если с - прописная буква истина, если с - символ шестнадцатеричной цифры возвращает код с или 128, если код с больше 127 strlwr strncat strncmp strncmpi strncpy strnicmp strnset strpbrk strrchr strrev strset strspn strstr strtok ispunct (с) isspace(c) isupper (с) isxdigit(c) toascii (c) _tolower (c) преобразует символ прописной буквы в символ строчной. Используется, если точно известно, что с - прописная буква. Возвращает код строчной буквы 124 tolower (c) _toupper(с) toupper(с) преобразует символ прописной буквы в символ строчной, не изменяя все остальные символы. В отличие от tolower ( ) сначала проверяет, является ли с прописной буквой. Не являющиеся прописными буквами символы не преобразуются. Возвращает код строчной буквы. преобразует символ строчной буквы в символ прописной. Используется, если точно известно, что с - строчная буква. Возвращает код прописной буквы. преобразует символ строчной буквы в символ прописной, не изменяя все остальные символы. В отличие от _toupper () сначала проверяет, является ли с строчной буквой. Не являющиеся строчными буквами символы не преобразуются. Возвращает код прописной буквы. 125 СПИСОК ИСПОЛЬЗУЕМЫХ ИСТОЧНИКОВ 1. С/С++. Программирование на языке высокого уровня / Т. А. Павловская. – СПб.: Питер, 2002. – 464 с.: ил. 2. Уинер Р. Язык Турбо Си: Пер. с англ. – М.: Мир, 1991. – 384 с., ил. 3. Шамис В.А. Borland C++Builder. Программирование на С++ без проблем. – М., Нолидж, 1977. – 266 с. 4. ГОСТ 19.002 – 80. ГОСТ 19.003 – 80. Единая система программной документации (ЕСПД). Схемы алгоритмов и программ. Правила выполнения. Обозначения условные графические. 5. ГОСТ 19.701 – 90. Единая система программной документации (ЕСПД). Схемы алгоритмов, программ, данных и систем. Условные обозначения и правила выполнения.