Всё для Учёбы — студенческий файлообменник
1 монета
pdf

Студенческий документ № 030623 из НОУ МТИ

А.А. Богуславский, С.М. Соколов

Основы программирования на языке Си++

Часть 1. Введение в программирование на языке Си++

(для студентов физико-математических факультетов педагогических институтов)

Коломна, 2002

ББК 32.97я73 Рекомендовано к изданию УДК 681.142.2(075.8) редакционно-издательским советом

Б 73 Коломенского государственного

педагогического института

Богуславский А.А., Соколов С.М.

Б73 Основы программирования на языке Си++: Для студентов физикоматематических факультетов педагогических институтов. - Коломна: КГПИ, 2002. - 490 с.

Пособие предназначено для обучения студентов, обладающих навыками пользовательской работы на персональном компьютере, основным понятиям и методам современного практического программирования. Предметом изучения курса является объектно-ориентированное программирование на языке Си++ в среде современных 32-х разрядных операционных систем семейства Windows. Программа курса разбита на 4 части: (1) Введение в программирование на языке Си++; (2) Основы программирования трехмерной графики; (3) Объектно-ориентированное программирование на языке Си++ и (4) Программирование для Microsoft Windows с использованием Visual C++ и библиотеки классов MFC.

После изучения курса студент получает достаточно полное представление о содержании современного объектно-ориентированного программирования, об устройстве современных операционных систем Win32 и о событийно-управляемом программировании. На практических занятиях вырабатываются навыки программирования на Си++ в интегрированной среде разработки Microsoft Visual C++ 5.0.

Рецензенты:

И.П. Гиривенко - к.т.н., доцент, зав. кафедрой информатики и вычислительной техники Рязанского государственного педагогического университета им. С.А. Есенина.

А.А. Шамов - к.х.н., доцент кафедры теоретической физики Коломенского государственного педагогического института.

СОДЕРЖАНИЕ

КРАТКОЕ ОПИСАНИЕ УЧЕБНОГО КУРСА "ОСНОВЫ ПРОГРАММИРОВАНИЯ

НА ЯЗЫКЕ СИ++" ..........................................................................................................................5

ЛЕКЦИЯ 1. ОСНОВЫ СИ++.........................................................................................................7

1. НЕСКОЛЬКО ЗАМЕЧАНИЙ О НАЗНАЧЕНИИ ПРОГРАММИРОВАНИЯ................................................7

2. ПРОИСХОЖДЕНИЕ ЯЗЫКА СИ++...................................................................................................9

3. СТАНДАРТ ANSI СИ++................................................................................................................9

4. СРЕДА РАЗРАБОТКИ MICROSOFT DEVELOPER STUDIO VISUAL С++...........................................10

5. ПРИМЕР ПРОГРАММЫ НА СИ++ .................................................................................................10

6. ВЫПОЛНЕНИЕ ВВОДА/ВЫВОДА ДАННЫХ И ПРИСВАИВАНИЕ ЗНАЧЕНИЙ....................................12

7. УПРАВЛЕНИЕ ПОРЯДКОМ ВЫПОЛНЕНИЯ КОМАНД С ПОМОЩЬЮ ОПЕРАТОРА IF ........................13

8. ОФОРМЛЕНИЕ ИСХОДНОГО ТЕКСТА ...........................................................................................15

9. СВОДКА РЕЗУЛЬТАТОВ ...............................................................................................................15

10. УПРАЖНЕНИЯ...........................................................................................................................15

ЛЕКЦИЯ 2. ПЕРЕМЕННЫЕ, ТИПЫ ДАННЫХ И ВЫРАЖЕНИЯ....................................18

1. ИДЕНТИФИКАТОРЫ ....................................................................................................................18 2. ТИПЫ ДАННЫХ...........................................................................................................................18

3. ВЫВОД ВЕЩЕСТВЕННЫХ ЧИСЕЛ НА ЭКРАН................................................................................22

4. ОПИСАНИЯ, КОНСТАНТЫ И ПЕРЕЧИСЛЕНИЯ ..............................................................................24

5. ПРИСВАИВАНИЕ И ВЫРАЖЕНИЯ.................................................................................................26

6. СВОДКА РЕЗУЛЬТАТОВ ...............................................................................................................28 7. УПРАЖНЕНИЯ.............................................................................................................................28

8. ПРИЛОЖЕНИЯ.............................................................................................................................29

ЛЕКЦИЯ 3. ФУНКЦИИ И ПРОЦЕДУРНАЯ АБСТРАКЦИЯ .............................................31

1. НАЗНАЧЕНИЕ ПОДПРОГРАММ.....................................................................................................31 2. ОПРЕДЕЛЕНИЕ НОВЫХ ФУНКЦИЙ...............................................................................................31

3. СПОСОБЫ ПЕРЕДАЧИ ПАРАМЕТРОВ ВНУТРЬ ФУНКЦИЙ..............................................................33

4. ПОЛИМОРФИЗМ И ПЕРЕГРУЗКА ФУНКЦИЙ..................................................................................35

5. ПРОЦЕДУРНАЯ АБСТРАКЦИЯ И "ХОРОШИЙ" СТИЛЬ ПРОГРАММИРОВАНИЯ ...............................36

6. МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ ...........................................................................................36 7. СВОДКА РЕЗУЛЬТАТОВ ...............................................................................................................38

8. УПРАЖНЕНИЯ.............................................................................................................................39

ЛЕКЦИЯ 4. ТЕКСТОВЫЕ ФАЙЛЫ И ПОТОКИ ВВОДА/ВЫВОДА................................41

1. НАЗНАЧЕНИЕ ФАЙЛОВ................................................................................................................41

2. ПОТОКИ ВВОДА/ВЫВОДА ...........................................................................................................41

3. ПРОВЕРКА ОШИБОК ВЫПОЛНЕНИЯ ФАЙЛОВЫХ ОПЕРАЦИЙ.......................................................43 4. СИМВОЛЬНЫЙ ВВОД/ВЫВОД ......................................................................................................44

5. ПРОВЕРКА ДОСТИЖЕНИЯ КОНЦА ФАЙЛА ПРИ ОПЕРАЦИЯХ ВВОДА............................................45

6. ПЕРЕДАЧА ПОТОКОВ ФУНКЦИЯМ В КАЧЕСТВЕ ПАРАМЕТРОВ.....................................................47

7. ОПЕРАТОРЫ ВВОДА/ВЫВОДА ">>" И " int main()

{ int x; cout > x;

for (int z=2; z

int main() {

int year_now, age_now, another_year, another_age; cout > year_now;

cout > age_now;

cout > another_year;

another_age = another_year - (year_now - age_now); if (another_age >= 0)

{

cout Этот оператор называется "директивой include". До компилятора исходный текст обрабатывается препроцессором - специальной программой, которая модифицирует текст программы по специальным командам - директивам. Директивы препроцессора начинаются с символа "#". Директива include предназначена для включения в исходный текст содержимого другого файла. Например, в программу 5.1 включается файл iostream.h, содержащий описания функций стандартной библиотеки ввода/вывода для работы с клавиатурой и экраном. (Стандартные библиотеки языка Си++ будут рассматриваться позже).

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

int main()

{

Первый оператор;

... ...

Последний оператор; return 0;

}

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

return 0; Эта строка значит "вернуть операционной системе в качестве сигнала об успешном завершении программы значение 0". Оператор возврата return применяется не только при завершении программы, но и при завершении отдельных подпрограмм. В любом случае этот оператор возвращает определенное значение на более высокий уровень управления.

В программе-примере используются четыре переменные:

year_now, age_now, another_year и another_age

Переменные в программировании отличаются от математических переменных. Они используются как символические имена "фрагментов оперативной памяти компьютера". При выполнении программы в различные моменты времени переменные могут хранить различные значения. В программе 5.1 первое упоминание четырех переменных содержится в строке с оператором описания переменных:

int year_now, age_now, another_year, another_age;

Этот оператор уведомляет компилятор, что для хранения четырех переменных типа "целое число" (integer - int) требуется выделить необходимое количество памяти. Эта область памяти будет зарезервирована в течение выполнения оставшейся части программы. Переменные всегда должны быть описаны до первого использования. В программировании хорошим стилем считается описание всех переменных, используемых в подпрограмме, в начале этой подпрограммы. В Си++ есть несколько различных типов переменных, и они будут обсуждаться немного позже.

6. Выполнение ввода/вывода данных и присваивание значений

После компиляции программы ее можно запустить на выполнение. Результат выполнения на экране будет выглядеть примерно так:

Введите текущий год и нажмите ENTER.

2000 Введите свой возраст (в годах).

21 Введите год, для которого вы хотите узнать свой возраст.

2017 В 2017 году вам будет 38

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

cout > year_now;

приводит к тому, что переменной year_now присваивается значение 2000. Это происходит после того, как пользователь напечатает "2000" и нажмет клавишу Enter. В программе есть еще места, где переменным присваиваются значения, в том числе оператор присваивания:

another_age = another_year - (year_now - age_now);

Операция "=" означает "присвоить переменной, стоящей слева от знака равенства, значение, указанное справа". Проверка на равенство в Си++ обозначается двойным символом: "==".

7. Управление порядком выполнения команд с помощью оператора if

В нескольких последних строках программы (до строки "return 0") записано:

if (another_age >= 0)

{ cout int main() { int year_now, age_now, another_year, another_age; cout > year_now; cout > age_now; cout > another_year; another_age = another_year - (year_now - age_now); if (another_age >= 0) { cout

Перед функцией main() создайте новую функцию с именем rus_str(), которая будет выполнять необходимое преобразование с помощью специальной функции Windows:

char* rus_str( char* str )

{ CharToOem( str, str ); return str; }

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

cout > wait_char;

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

Упражнение 2

Модифицируйте программу 5.1, чтобы при превышении переменной

"another_age" значения 150 на экран выводилось сообщение:

Извините, но вы вряд ли доживете до [year] года! Проверьте работу своей программы для нескольких разных лет.

Упражнение 3

Измените программу из упр.2 так, чтобы в ней учитывались и годы, и месяцы. На экран программа должна выводить следующие сообщения:

Введите текущий год и нажмите ENTER. 2000

Введите текущий месяц (число от 1 до 12).

10 Введите свой возраст (в годах).

25 Введите месяц своего рождения (число от 1 до 12).

5 Введите год, для которого вы хотите узнать свой возраст.

2006 Введите месяц этого года.

6 Ваш возраст в 6/2006: 31 год и 1 месяц.

Программа должна выдавать корректные сообщения для единственного и множественного числа лет и месяцев, т.е. должна выводить на экран "25 лет и 1 месяц", но "24 года и 2 месяца".

Подсказка: В программе вам потребуются дополнительные переменные. Обязательно добавьте их имена в оператор описания переменных. При вычислениях могут пригодиться некоторые стандартные операции Си++:

Символ Операция Пример Значение + Сложение 3 + 5 8 - Вычитание 43 - 25 18 * Умножение 4 * 7 28 / Деление 9/2 4 % Остаток при делении нацело 20 % 6 2 (Обратите внимание, что в приведенной таблице операция деления "/" применялась к двум целым числам, поэтому результат - тоже целое число.)

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

Символ Операция Пример Значение больше, чем 4 > 7 FALSE >= больше или равно 9 >= 2 TRUE == равно 20 == 6 FALSE != не равно 20 != 6 TRUE && Логическое И 5 > 2 && 6 > 10 FALSE || Логическое ИЛИ 5 > 2 || 6 > 10 TRUE

ЛЕКЦИЯ 2. Переменные, типы данных и выражения

1. Идентификаторы

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

1) Служебные слова языка. Например, это слова if, int и else. Назначение этих слов предопределено и его нельзя изменить. Ниже приведен более полный список служебных слов:

asm continue float new signed try auto default for operator sizeof typedef break delete friend private static union case do goto protected struct unsigned catch double if public switch virtual char else inline register template void class enum int return this volatile const extern long short throw while

По назначению эти слова можно разбить на отдельные группы (прил. 8.1).

2) Библиотечные идентификаторы. Назначение этих слов зависит от среды программирования. В случае серьезной необходимости программист может изменить их смысл. Примеры таких слов: cin, cout и sqrt (имя функции извлечения квадратного корня).

3) Идентификаторы, введенные программистом. Эти слова "создаются" программистом - например, имена переменных (такие, как year_now и another_age в программе 1.5.1).

Идентификатором не может быть произвольная последовательность символов. По правилам Си++, идентификатор начинается с буквы или символа подчеркивания

("_") и состоит только из английских букв, цифр и символов подчеркивания.

2. Типы данных

2.1 Целые числа

Правила Си++ требуют, чтобы в программе у всех переменных был задан тип данных. Тип данных int встречался нам уже неоднократно. Переменные этого типа применяются для хранения целых чисел (integer). Описание переменной, как имеющей тип int, сообщает компилятору, что он должен связать с идентификатором (именем) переменной количество памяти, достаточное для хранения целого числа во время выполнения программы.

Границы диапазона целых чисел, которые можно хранить в переменных типа int, зависят от конкретного компьютера. В Си++ есть еще два целочисленных типа - short int и long int. Они представляют, соответственно, более узкий и более широкий диапазон целых чисел, чем тип int. Добавление к любому из этих типов префикса unsigned означает, что в переменной будут хранится только неотрицательные числа. Например, описание:

unsigned short int year_now, age_now, another_year, another_age;

резервирует память для хранения четырех относительно небольших неотрицательных чисел.

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

1) Нельзя пользоваться десятичной точкой. Значения 26 и 26.0 одинаковы, но "26.0" не является значением типа "int".

2) Нельзя пользоваться запятыми в качестве разделителей тысяч. Например, число 23,897 следует записывать как "23897".

3) Целые значения не должны начинаться с незначащего нуля. Он применяется для обозначения устаревших восьмеричных чисел, так что компилятор будет рассматривать значение "011" как число 9 в восьмеричной форме.

2.2 Вещественные числа

Для хранения вещественных чисел применяются типы данных float и double. Смысл знаков "+" и "-" для вещественных типов совпадает с целыми. Последние незначащие нули справа от десятичной точки игнорируются. Поэтому варианты записи "+523.5", "523.5" и "523.500" представляют одно и то же значение. В Си++ также допускается запись в формате с плавающей запятой (в экспоненциальном формате) в виде мантиссы и порядка. Например, 523.5 можно записать в виде

"5.235e+02" (т.е. 5.235*10*10), а -0.0034 в виде "-3.4e-03".

В большинстве случаев используется тип double, он обеспечивает более высокую точность, чем float. Максимальную точность и наибольший диапазон чисел достигается с помощью типа long double, но он требует больше памяти (в Visual C++ 10 байт на число), чем double (8 байт).

2.3 Преобразование типов в выражениях

При выполнении вычислений иногда бывает нужно гарантировать, что определенное значение будет рассматриваться как вещественное число, даже если на самом деле это целое. Чаще всего это нужно при делении в арифметических выражениях. Применительно к двум значениям типа int операция деления "/" означает деление нацело, например, 7/2 равно 3. В данном случае, если необходимо получить результат 3.5, то можно просто добавить десятичную точку в записи одного или обоих чисел:

"7.0/2", "7/2.0" или "7.0/2.0". Но если и в числителе, и в знаменателе стоят переменные, а не константы, то указанный способ не подходит. Вместо него можно применить явное преобразование типа. Например, значение "7" преобразуется в значение типа double с помощью выражения "double(7)". Поэтому в выражении

answer = double(numerator) / denominator

операция "/" всегда будет рассматриваться компилятором как вещественное деление, даже если "numerator" и "denumerator" являются целыми числами. Для явного преобразования типов можно пользоваться и другими именами типов данных. Например,

"int(14.35)" приведет к получению целого числа 14.

2.4 Символьный тип

Для хранения символьных данных в Си++ предназначен тип "char". Переменная типа "char" рассчитана на хранение только одного символа (например, буквы или пробела). В памяти компьютера символы хранятся в виде целых чисел. Соответствие между символами и их кодами определяется таблицей кодировки, которая зависит от компьютера и операционной системы. Почти во всех таблицах кодировки есть прописные и строчные буквы английского алфавита, цифры 0,...,9, и некоторые специальные символы, например, #, ?, !, +, - и др. Самой распространенной таблицей кодировки, скорее всего, является таблица символов ASCII.

В тексте программ символьные константы типа "char" надо заключать в одиночные кавычки, иначе компилятор поймет их неправильно и это может привести к ошибке компиляции, или, что еще хуже, к ошибкам времени выполнения. Например, "'A'" является символьной константой, но "A" будет рассматриваться компилятором в качестве имени переменной. Аналогично, "'9'" является символом, а "9" - целочисленной константой.

Т.к. в памяти компьютера символы хранятся в виде целых чисел, то тип "char" на самом деле является подмножеством типа "int". На Си++ разрешается использовать символы в арифметических выражениях. Например, на любом компьютере с таблицей ASCII следующее выражение даст истинное значение (TRUE, или 1):

'9'-'0' == 57-48 == 9

В таблице ASCII кодом символа '9' является десятичное число 57 (в шестнадцатеричной записи 0x39), а ASCII-код символа '0' равен десятичному числу 48 (шестнадцатеричное значение 0x30). Приведенное выражение можно переписать в виде:

57-48 == 0x39-0x30 == 9

Кодами ASCII удобнее пользоваться в шестнадцатеричной форме. При записи шестнадцатеричных чисел в Си++ применяется двухсимвольный префикс "0x".

Переменные типа "char" существенно отличаются от "int" при выполнении

ввода данных с клавиатуры и вывода на экран. Рассмотрим следующую программу.

#include

int main() {

int number; char character;

cout > character; number = character;

cout

int main() { int number;

char character;

for (number = 32; number #include int main()

{ float number;

cout > number;

cout #include

#include int main()

{ int number;

cout const float PI = 3.1416;

const float SCREEN_WIDTH = 317.24;

int drawCircle(float diameter); /* Это "прототип функции" */

int main() {

float radius = 0;

cout > radius;

drawCircle(radius*2);

cout.setf(ios::fixed); cout.precision(2);

cout SCREEN_WIDTH) radius = SCREEN_WIDTH/2.0; else

radius = diameter/2.0;

... ...

}

После определения функции "main()" в этой программе содержится определение функции рисования окружности "drawCircle(...)". Детали реализации этой функции сейчас не существенны (будем считать, что функция drawCircle(...)" реализована корректно и ею можно пользоваться так же, как, например, функцией "sqrt(...)"). Обратите внимание, что, хотя переменная "radius" используется в обеих функциях "main()" и "drawCircle(...)", это не одна и та же переменная, а две разных.

Если бы переменная "radius" была описана до функции "main", то в таком слу-

чае она была бы глобальной переменной (общедоступной). Тогда, предполагая, что внутри функции "drawCircle(...)" описания переменной уже нет, если "drawCircle(...)" присвоит глобальной переменной значение "SCREEN_WIDTH/2.0", то это значение чуть позже функция "main()" использует для вычисления длины окружности и получится неверный результат.

В приведенной программе глобальной переменной нет, а есть две локальных переменных "radius". Например, первая переменная "radius" является локальной переменной функции "main()", или, говорят, что функция "main()" является областью видимости этой переменной.

Константы общего назначения, такие, как "PI" и "SCREEN_WIDTH", принято опи-

сывать глобально, чтобы они были доступны внутри любой функции.

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

5. Присваивание и выражения

5.1 Краткая форма записи операторов присваивания

В программах часто встречаются операторы присваивания, в которых справа стоит выражение, модифицирующее текущее значение переменной, например:

number = number + 1;

Переменным часто присваиваются значения, вычисленные на основе их старых значений. Поэтому в Си++ была введена краткая форма записи для подобных операторов присваивания. Любую из операций "+" (сложение), "-" (вычитание), "*" (умножение), "/" (деление) и "%" (остаток от деления нацело) можно указать в качестве префикса оператора присваивания ("=") (cм. следующую таблицу).

Пример: number += 1; total -= discount; bonus *= 2; time /= rush_factor; change %= 100; amount *= count1 + count2; Эквивалентное выражение: number = number + 1; total = total - discount; bonus = bonus * 2; time = time / rush_factor; change = change % 100; amount = amount * (count1 + count2);

Первый пример допускает еще более краткую запись с помощью оператора инкремента "++":

number++;

Оператор "++" существует и в префиксной форме:

++number; Постфиксная и префиксная форма записи имеют важное различие, которое необходимо помнить. Префиксный оператор применяется ДО вычисления остальной части выражения, а постфиксный - ПОСЛЕ. Например, посоле выполнения операторов

x = 4; y = x++;

переменная "x" получит значение 5, а "y" - значение 4. В случае операторов

x = 4; y = ++x;

обе переменные получат значение 5. Это объясняется тем, что "++x" выполняется до того, как значение "x" будет использовано в выражении, а "x++" - после. В Си++ существует аналогичный оператор декремента "--", уменьшающий значение переменной на 1, и у него тоже есть префиксная и постфиксная форма.

Вообще, выражение с оператором присваивания имеет значение, равное значению левой части после выполнения присваивания. Ниже приведено выражение, соответствующее правилам Си++, которое можно использовать для проверки условия:

(y = ++x) == 5 Это выражение означает следующее: "после присвоения переменной y инкрементированного значения x проверить, не равно ли значение y числу 5".

5.2 Логические выражения и операторы

Интуитивно логические выражения наподобие "2=9" воспринимаются человеком как утверждения, которые могут быть "истинными (true)" или "ложными (false)" (операция "!=" означает "не равно"). Допускается объединение нескольких подобных выражений в более сложное выражение с помощью логических операций "&&" ("И"), "||" ("ИЛИ") и "!" ("НЕ") (см. таблицу).

Выражение:

(6 = 50 && total_test_score

... ... for ( number=1 ; number

int area(int length, int width); /* Описание функции */

// ГЛАВНАЯ ФУНКЦИЯ:

int main() { int this_length, this_width;

cout > this_length; cout > this_width; /* = 0) return number; else

return -number;

} 3. Способы передачи параметров внутрь функций

Во всех рассмотренных до сих пор примерах параметры функций передавались по значению. При вызове из функции "main()" вызываемой функции передаются копии указанных переменных. Например, в программе 2.1 функции "area(...)" передаются текущие значения переменных "this_length" и "this_width". Затем функция "area(...)" сохраняет переданные значения в собственных локальных переменных, и именно эти переменные участвуют в последующих вычислениях внутри функции.

3.1 Передача параметров по значению

Функции, принимающие параметры по значению, "безопасны" в том смысле, что они не могут случайно изменить переменные вызывающей функции (т.е. у функций нет скрытых побочных эффектов). Большинство функций проектируются именно таким образом.

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

Введите положительное число:

4 Факториал 4! равен 24, а квадратный корень из 4 равен 2.

Для извлечения квадратного корня применяется библиотечная функция "sqrt(...)". Библиотечной функции для вычисления факториала нет, поэтому придется написать собственную функцию (вычисляющую для любого положительного целого числа n значение n!=(1*2*3*...*n)).

#include #include int factorial(int number);

// ГЛАВНАЯ ФУНКЦИЯ:

int main() {

int whole_number;

cout > whole_number;

cout 0 ; number--) product *= number;

return product;

} // КОНЕЦ ФУНКЦИИ

Программа 3.1.

Если бы функция "factorial(...)" изменяла переменную вызывающей функции, то программа 3.1 выдавала бы следующий ответ (формально правильный, но по смыслу задачи некорректный):

Введите положительное число:

4 Факториал 4! равен 24, а квадратный корень из 0 равен 0.

3.2 Передача параметров по ссылке

Все-таки иногда бывает необходимо, чтобы функция изменила значение переданного ей параметра. Рассмотрим программу 2.1. С 10-й по 14-ю строку в ней выполняется запрос размеров прямоугольника, а затем вычисляется его площадь.

При структурном программировании независимые по смыслу части программы принято оформлять в виде отдельных функций. Для получения данных от пользователя создадим функцию "get_dimensions". В данном случае необходимо, чтобы эта функция изменяла значения переменных "this_length" и "this_width" (переданных ей в качестве параметров) - помещала в них значения, введенные пользователем с клавиатуры. Изменение параметров функции возможно при передаче параметров по ссылке. У таких параметров в заголовке функции после имени типа указывается символ "&".

#include int area( int length, int width );

void get_dimensions( int& length, int& width );

// ГЛАВНАЯ ФУНКЦИЯ:

int main() { int this_length, this_width;

get_dimensions( this_length, this_width ); cout > length; cout > width; cout

int average( int first_number, int second_number, int third_number );

int average( int first_number, int second_number );

// ГЛАВНАЯ ФУНКЦИЯ:

int main() { int number_A = 5, number_B = 3, number_C = 10;

cout ", а не явное объявление этой функции.

• Файл реализации (для пользовательских функций это файлы с исходным текстом на Си++, а библиотечные функции обычно хранятся в скомпилированном виде в специальных библиотечных файлах, например, "libcmtd.lib"). Файлы реализации пользовательских функций (обычно с расширением ".cpp") содержат определения этих функций.

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

В главном файле содержится следующий текст:

#include

#include "averages.h"

int main() {

int number_A = 5, number_B = 3, number_C = 10;

cout "). Файлы с именами в угловых скобках препроцессор ищет в библиотечных каталогах, указанных в настройках компилятора. Имена пользовательских заголовочных файлов обычно заключаются в двойные кавычки, и препроцессор ищет их в текущем каталоге программы.

Далее приведено содержимое файла "averages.h". В нем есть идентификатор препроцессора "AVERAGES_H" и служебные слова препроцессора "ifndef" ("если не определено"), "define" ("определить") и "endif" ("конец директивы if"). Идентификатор "AVERAGES_H" является глобальным символическим именем заголовочного файла. Первые две строки файла служат защитой от повторной обработки текста заголовочного файла препроцессором, на случай, если в исходном тексте программы строка

"#include "averages.h"" встречается несколько раз.

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

#ifndef AVERAGES_H

# define AVERAGES_H

// (Определения констант и пользовательских типов)

// ПРОТОТИП ФУНКЦИИ ДЛЯ ВЫЧИСЛЕНИЯ ЦЕЛОЧИСЛЕННОГО СРЕДНЕГО // ЗНАЧЕНИЯ 3-Х ЦЕЛЫХ ЧИСЕЛ:

int average( int first_number, int second_number, int third_number );

// ПРОТОТИП ФУНКЦИИ ДЛЯ ВЫЧИСЛЕНИЯ ЦЕЛОЧИСЛЕННОГО СРЕДНЕГО // ЗНАЧЕНИЯ 2-Х ЦЕЛЫХ ЧИСЕЛ:

int average( int first_number, int second_number );

#endif Заголовочный файл averages.h.

Ниже показано содержимое файла "averages.cpp" с исходным текстом поль-

зовательских функций:

#include

#include "averages.h"

// ФУНКЦИЯ ДЛЯ ВЫЧИСЛЕНИЯ ЦЕЛОЧИСЛЕННОГО СРЕДНЕГО // ЗНАЧЕНИЯ 3-Х ЦЕЛЫХ ЧИСЕЛ:

int average( int first_number, int second_number, int third_number )

{ return ((first_number + second_number + third_number)/3);

} // ФУНКЦИЯ ДЛЯ ВЫЧИСЛЕНИЯ ЦЕЛОЧИСЛЕННОГО СРЕДНЕГО // ЗНАЧЕНИЯ 2-Х ЦЕЛЫХ ЧИСЕЛ:

int average( int first_number, int second_number )

{ return ((first_number + second_number)/2); }

Файл реализации averages.cpp.

Программа 6.1 демонстрирует основное достоинство модульного подхода: при изменении деталей реализации в файле "averages.cpp" не обязательно вносить изменения в файл "averages.h" или в главный файл программы.

7. Сводка результатов

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

8. Упражнения

Упражнение 1

В программе из упражнения 2 лекции 2 (файл ex2_2.cpp) выделите 6 функций, имена и назначение которых перечислены ниже:

fahrenheit_of Возвращает значение температуры по шкале Фаренгейта для переданного значения по шкале Цельсия. absolute_value_of

Возвращает значение температуры в абсолютной шкале для переданного значения по шкале Цельсия. print_preliminary_message

Печать сообщения, поясняющего назначение программы. input_table_specifications

Запрос параметров таблицы с клавиатуры.

print_message_echoing_input

Повторное отображение параметров, введенных пользователем.

print_table Печать таблицы температур.

Проверьте свою программу для различных исходных данных. В качестве контрольного примера можете использовать следующие выходные данные:

Эта программа печатает значения температур в разных шкалах.

Введите минимальное (целое) значение температуры по Цельсию, которое будет в первой строке таблицы: 0

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

Введите разницу температур между соседними строками таблицы: 20

Преобразование значений температуры от 0 градусов Цельсия до 100 градусов Цельсия, с шагом 20 градусов:

Цельсий Фаренгейт Абсолютная температура

0 32.00 273.15

20 68.00 293.15

40 104.00 313.15 ... ... ...

... ... ...

100 212.00 485.15

Упражнение 2

Разделите программу из упражнения 1 на три файла:

1) главный файл программы;

2) заголовочный файл "conversions.h" с прототипами функций

"fahrenheit_of(...)" и "absolute_value_of(...)"; 3) файл реализации с определением этих двух функций.

Снова проверьте свою программу для различных исходных данных.

Упражнение 3

(а) Создайте заголовочный файл "statistics.h" и соответствующий файл реализа-

ции "statistics.cpp" с функциями "average(...)" и "standard_deviation(...)". Эти функции должны вычислять среднее значение и среднеквадратическое отклонение для последовательности из 1, 2, 3 или 4 вещественных чисел. Среднеквадратическое отклонение чисел r1, ..., rN определяется как корень из среднего значения квадратов отклонений чисел от своего среднего:

?= 1 ?N (ri ? m)2 , где m= N1 ?iN=1 ri N i=1

Подсказки: (1) Примените средства перегрузки функций Си++. (2) Функции можно вызывать изнутри друг друга. (3) Максимально используйте возможности текстового редактора по копированию фрагментов исходного текста.

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

Эта программа предназначена для тестирования функций из заголовочного файла "statistics.h".

Сколько чисел будет в тестовой последовательности - 1, 2, 3 или 4? (для завершения работы введите 0): 3

Введите первое число: 5 Введите второе число: 7

Введите третье число: 9

Среднее значение: 7. Среднеквадратическое отклонение: 1.63299.

Сколько чисел будет в тестовой последовательности - 1, 2, 3 или 4? (для завершения работы введите 0): 1

Введите первое число: 5.8

Среднее значение: 5.8. Среднеквадратическое отклонение: 0.

Сколько чисел будет в тестовой последовательности - 1, 2, 3 или 4? (для завершения работы введите 0): 8

Извините, но эта программа может работать только с 1, 2, 3 или 4-мя числами.

Сколько чисел будет в тестовой последовательности - 1, 2, 3 или 4? (для завершения работы введите 0): 0

Программа тестирования функций из заголовочного файла "statistics.h" завершила работу.

Подсказки: (1) Разрабатывайте свою программу методом "сверху вниз ". Начните с написания короткой главной функции, в которой вызываются функциизаглушки, например, "test_three_values()". Детали этих функций вы уточните позже, после отладки функции "main()". (2) В качестве высокоуровневой структуры программы вы можете использовать цикл for с пустым разделом инициализации и пустым оператором изменения значений (эквивалент цикла while, который будет рассматриваться в следующих лекциях).

ЛЕКЦИЯ 4. Текстовые файлы и потоки ввода/вывода

1. Назначение файлов

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

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

На дисках данные хранятся в виде структур данных, обслуживаемых операционной системой, - в виде файлов. Файл проще всего представить как линейную последовательность символов. Текст этой лекции (если не учитывать специальные символы форматирования) можно сохранить в файле с именем "Lecture_4.txt" в виде

(рис. 1):

Рис. 1. Начало файла "lecture_4.txt".

2. Потоки ввода/вывода

Перед началом изучения файловых операций в Си++, необходимо ознакомиться с понятием потока ввода/вывода. Поток напоминает "канал" или "трубу", через которую данные поступают от передатчика к приемнику. Исключительно важная особенность потоковой обработки данных состоит в том, что элементы данных можно посылать или считывать из потока только по одному за раз, т.е. последовательно.

В данном курсе рассматриваются только однонаправленные потоки, в которых данные всегда передаются в одном направлении. Из программы данные можно отправить (записать) в поток вывода, а получить (прочитать) их в программе из потока ввода. Например, сразу после запуска программы, поток стандартного ввода "cin" подключается к клавиатуре, а поток стандартного вывода "cout" - к экрану.

Потоки ввода/вывода, вроде "cin" и "cout", являются примерами объектов класса "поток". Поэтому изучение потоков полезно и по той причине, что позволяет ознакомиться с элементами синтаксиса и некоторыми объектно-ориентированными понятиями Си++.

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

#include

2.1 Создание потоков

В программе перед первым обращением к потоку ввода или вывода необходимо "создать" поток. Операторы для создания потоков похожи на описания переменных, и они обычно размещаются в начале программы или функции рядом с описаниями переменных. Например, операторы

ifstream in_stream; ofstream out_stream; создают поток с именем "in_stream", являющийся объектом класса (как типа данных) "ifstream" (input-file-stream, файловый поток ввода), и поток с именем "out_stream", являющийся объектом класса "ofstream" (output-file-stream, файловый поток вывода). Аналогию между потоками и обычными переменными (типа "int", "char" и т.д.) не следует понимать слишком буквально. Например, к потокам нельзя применять оператор присваивания (например, нельзя записать "in_stream1 = in_stream2").

2.2 Подключение и отключение потоков от файлов

После создания потока его можно подключить к файлу (открыть файл) с помощью функции-члена "open(...)". (В предыдущих лекциях уже использовались несколько функций-членов потоков вывода. Например, во 2-й лекции применялись "precision(...)" и "width(...)".) Функция "open(...)" у потоков ifstream и ofstream работает по-разному (т.е. это полиморфная функция).

Для подключения потока ifstream с именем "in_stream" к файлу с именем "Lecture_4.txt" надо применить следующий вызов:

in_stream.open("Lecture_4.txt");

Этот оператор подключит поток "in_stream" к началу файла "Lecture_4.txt"

(графически состояние программы после выполнения оператора показано на рис. 2).

Рис. 2. Состояние программы после подключения потока ввода к файлу.

Чтобы к файлу "Lecture_4.txt" подключить поток вывода ofstream с именем "out_stream", надо выполнить аналогичный оператор:

out_stream.open("Lecture_4.txt");

Этот оператор подключит поток "out_stream" к файлу "Lecture_4.txt", но при этом прежнее содержимое файла будет удалено. Файл будет подготовлен к приему новых данных (рис. 3).

Рис. 3. Состояние программы после подключения потока вывода к файлу.

Для отключения потока "in_stream" от файла, к которому он подключен (для закрытия файла), надо вызвать функцию-член "close()":

in_stream.close();

После этого состояние программы по отношению к файлу будет таким, как на рис. 4.

Рис. 4. Состояние программы после отключения потока ввода от файла.

Функция-член отключения от файла у потока вывода:

out_stream.close();

выполняет аналогичные действия, но, дополнительно, в конец файла добавляется служебный символ "end-of-file (маркер конца файла)". Т.о., даже если в поток вывода не записывались никакие данных, то после отключения потока "out_stream" в файле "Lecture_4.txt" будет один служебный символ (рис. 5). В таком случае файл "Lecture_4.txt" останется на диске, но он будет пустым.

Рис. 5. Состояние программы после отключения потока вывода от файла, в который не было записано ни одного символа..

3. Проверка ошибок выполнения файловых операций

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

Простейший способ проверки ошибок файловых операций заключается в вызо-

ве функции-члена "fail()". Вызов

in_stream.fail(); возвращает истинное значение (True), если последняя операция потока "in_stream" привела к ошибке (может быть, была попытка открытия несуществующего файла). После ошибки поток "in_stream" может быть поврежден, поэтому лучше не продолжать работу с ним.

В приведенном ниже фрагменте программы в случае ошибки при открытии файла на экран выдается сообщение и программа завершает работу с помощью библиотечной функции "exit()" (она описана в файле "stdlib.h"):

#include #include

#include

int main() {

ifstream in_stream;

in_stream.open( "Lecture_4.txt" ); if ( in_stream.fail() )

{

cout #include

int main() {

char character; ifstream in_stream; ofstream out_stream; in_stream.open( "Lecture_4.txt" ); out_stream.open( "Copy_of_4.txt" );

in_stream.get( character ); while ( !in_stream.eof() )

{ cout

#include void copy_to( ifstream& in, ofstream& out );

// Главная функция int main() {

ifstream in_stream; ofstream out_stream;

in_stream.open( "Lecture_4.txt" ); out_stream.open( "Copy_of_4.txt" ); copy_to( in_stream, out_stream ); out_stream.close(); in_stream.close();

return 0; }

// Конец главной функции

// Функция для копирования файла в другой файл и на экран void copy_to( ifstream& in, ofstream& out )

{ char character;

in.get( character ); while ( !in.eof() )

{ cout >" и ">" и ">" и ">". Например, в состоянии, показанном на рис. 15:

Рис. 15. Состояние программы в некоторый момент времени, когда текущая позиция потока ввода установлена на пробел.

если "n" является переменной типа "int" и имеет значение 10, то выполнение оператора

in_stream >> n;

приведет к состоянию, показанному на рис. 16.

Рис. 16. Состояние программы после чтения из потока ввода целого значения "437".

Обратите внимание, что оператор ">>" перед числом 437 пропустил пробел ' '. Этот оператор всегда пропускает пробелы, независимо от типа считываемых данных (даже при чтении символов).

Работа с операторами ">>" и "

#include int main() {

char character; int number = 51; int count = 0; ofstream out_stream; ifstream in_stream1; // Поток для подсчета целых чисел. ifstream in_stream2; // Поток для подсчета символов.

// Создание файла out_stream.open( "Integers.txt" ); for ( count = 1; count > number; while ( !in_stream1.eof() )

{

count++; in_stream1 >> number;

} in_stream1.close();

cout > character; while ( !in_stream2.eof() )

{ count++;

in_stream2 >> character;

} in_stream2.close();

cout >" игнорирует в файле пробелы (которые разделяют пять целых чисел).

8. Сводка результатов

В лекции рассмотрены способы работы с текстовыми файлами с помощью потоков ввода/вывода. "Низкоуровневый" ввод/вывод выполняется с помощью функций

"get(...)" и "put(...)", а "высокоуровневый" ввод/вывод значений разных типов - с помощью потоковых операторов ">>" и "

#include int main() {

char character; int integer; ofstream out_stream; ifstream in_stream;

// Создание текстового файла и запись в него двух целых чисел out_stream.open( "Integers.txt" ); out_stream > character >> integer; cout > character >> integer; cout

bool acceptable( int age, int score );

int main() {

int candidate_age, candidate_score;

cout > candidate_age;

cout > candidate_score;

if ( acceptable( candidate_age, candidate_score ) ) cout = 50 ) return true;

else if ( age = 55 ) return true; else if ( score >= 60 ) return true; else

return false; }

Программа 1.1.

2. Циклы "for", "while" и "do...while"

Циклы "for" впервые встречались во 2-й лекции, цикл "while" упоминался в

4-й лекции. Любой цикл "for" можно переписать в виде цикла "while" и наоборот. Рассмотрим программу 2.1 (она похожа на программу 2.2 из 2-й лекции).

#include

int main() { int number;

char character;

for ( number = 32; number

int main()

{ int number; char character;

number = 32;

while ( number > candidate_score;

if ( candidate_score > 100 || candidate_score 100 || candidate_score > candidate_score;

while ( candidate_score > 100 || candidate_score > candidate_score;

} ... ... Фрагмент программы 2.2b.

3. Множественное ветвление и оператор "switch"

Вложенные операторы "if", предназначенные для выполнения "множественного ветвления", уже встречались в 1-й лекции. Упрощенная версия этого примера приведена ниже:

... ... if ( total_test_score break;

... ... ... case меткаN : break; default :

} Сделаем несколько важных замечаний относительно оператора "switch":

• Внутри "switch" выполняются операторы, содержащиеся между меткой, совпадающей с текущим значением селектора, и первым встретившимся после этой метки оператором "break".

• Операторы "break" необязательны, но они улучшают читабельность про-

грамм. С ними сразу видно, где заканчивается каждый вариант множественного ветвления. Как только при выполнении операторов внутри "switch" встречается "break", то сразу выполняется переход на первый оператор программы, расположенный после оператора "switch". Иначе продолжается последовательное выполнение операторов внутри "switch".

• Селектор (переменная или выражение) может быть целочисленного (например, "int" или "char") или любого перечислимого типа, но не вещественного типа.

• Вариант "default" ("по умолчанию") необязателен, но для безопасности лучше его предусмотреть.

4. Блоки и область видимости переменных

В Си++ фигурные скобки "{}" позволяют оформить составной оператор, который содержит несколько операторов, но во всех конструкциях языка может подставляться как один оператор. На описания переменных фигурные скобки также оказывают важное влияние.

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

Если одно и то же имя используется для переменной внутри и снаружи блока, то это две разных, независимых переменных. При выполнении внутри блока программа по умолчанию полагает, что имя относится к внутренней переменной. Обращение к внешней переменной происходит только в том случае, если переменная с таким именем не описана внутри блока. Действие этого правила продемонстрировано в программе 4.1.

#include

int integer1 = 1; int integer2 = 2; int integer3 = 3;

int main() {

int integer1 = -1; int integer2 = -2;

{

int integer1 = 10;

cout int main() { int number;

for ( number = 1; number

void print_times_table( int value, int lower, int upper );

int main() { int number;

for ( number = 1; number

void print_tables( int smallest, int largest ); void print_times_table( int value, int lower, int upper );

int main() {

print_tables( 1, 10 ); return 0;

} void print_tables( int smallest, int largest )

{ int number;

for ( number = smallest; number 1000.

Упражнение 2

Напишите функцию "print_pyramid(...)", которая получает целочисленный параметр "height (высота)" и отображает на экране "пирамиду" заданной высоты из символов "*". Проверьте функцию с помощью простой тестовой программы, которая должна воспроизводить следующий примерный диалог с пользователем:

Эта программа печатает на экране "пирамиду" заданной высоты.

Введите высоту пирамиды: 37

Введите другое значение (из диапазона от 1 до 30): 6

**

****

******

********

**********

************

Упражнение 3

Цикл "for" всегда можно переписать в форме цикла "while", и наоборот. Являются ли две показанных ниже программы эквивалентными? Какие сообщения они печатают на экране? Объясните свой ответ и проверьте его опытным путем.

Программа 3а:

#include int main() { int count = 1; for ( ; count int main() { int count = 1; while ( count

enum Day { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };

int closing_time( Day day_of_the_week );

// Главная функция int main() {

int count;

// Печать заголовка таблицы cout.width(17); cout [];

Допустим, в программе требуется обрабатывать данные о количестве часов, отработанных в течении недели группой из 6-ти сотрудников. Для хранения этих данных можно объявить массив:

int hours[6]; или, лучше, задать численность группы с помощью специальной константы:

const int NO_OF_EMPLOYEES = 6; int hours[NO_OF_EMPLOYEES];

Если подобные массивы будут часто встречаться в программе, то целесообразно определить новый тип:

const int NO_OF_EMPLOYEES = 6;

typedef int Hours_array[NO_OF_EMPLOYEES];

Hours_array hours;

Hours_array hours_week_two;

В любом из трех перечисленных вариантов, в программе будет объявлен массив из 6 элементов типа "int", к которым можно обращаться с помощью имен:

hours[0] hours[1] hours[2] hours[3] hours[4] hours[5]

Каждое из этих имен является именем элемента массива. Числа 0, ..., 5 называются индексами элементов. Отличительная особенность массива заключается в том, что его элементы - однотипные переменные - занимают в памяти компьютера последовательные ячейки памяти (рис. 1).

Рис. 1.. Расположение элементов массива в оперативной памяти (направление сверху вниз соответствует возрастанию адресов ячеек памяти).

1.2 Использование элементов массивов в выражениях

С элементами объявленного массива можно выполнять все действия, допустимые для обычных переменных этого типа (выше был приведен пример целочисленного массива, т.е. типа "int"). Например, возможны операторы присваивания наподобие:

hours[4] = 34; hours[5] = hours[4]/2;

или логические выражения с участием элементов массива:

if (number = 40) { ...

Присвоить значения набору элементов массива часто бывает удобно с помощью циклов "for" или "while". В программе 1.1 в цикле у оператора запрашивается количество часов, отработанных каждым сотрудником группы за неделю. Хотя более естественной может показаться нумерация сотрудников от 1 до 6, а не от 0 до 5, но необходимо помнить, что индексация массивов в Си++ начинается с 0. Поэтому программа 1.1 вычитает 1 из порядкового номера сотрудника, чтобы вычислить индекс соответствующего элемента массива.

#include

const int NO_OF_EMPLOYEES = 6;

typedef int Hours_array[NO_OF_EMPLOYEES];

int main() {

Hours_array hours; int count;

for ( count = 1; count > hours[count - 1];

} return 0; }

Программа 1.1.

В типичном сеансе работы программа 1.1 выведет на экран подобные сообщения:

Сколько часов отработал сотрудник номер 1?: 38

Сколько часов отработал сотрудник номер 2?: 42

Сколько часов отработал сотрудник номер 3?: 29

Сколько часов отработал сотрудник номер 4?: 35

Сколько часов отработал сотрудник номер 5?: 38

Сколько часов отработал сотрудник номер 6?: 37

На рис. 2. показано состояние целочисленного массива после ввода этих данных.

Рис. 2.. Состояние массива после присвоения значений его элементам.

Полезно разобраться, что произошло бы, если бы в программе 1.1 внутри цикла

"for" в операторе "cin ..." отсутствовало бы вычитание 1 из переменной "count". Компилятор Си++ (в отличие, например, от Паскаля) не обнаруживает ошибки выхода за пределы массива, поэтому участок памяти компьютера с массивом и сразу за ним оказался бы в состоянии, показанном на рис. 3.

Рис. 3.. Ошибка выхода за пределы массива.

Другими словами, значение "37" было бы размещено в ячейке памяти, достаточной для хранения целого числа, которая расположена сразу после массива "hours". Это чрезвычайно нежелательная ситуация, потому что компилятор может зарезервировать эту ячейку памяти для другой переменной (например, для переменной

"count"). Массивы могут быть любого типа, не обязательно типа "int". Ниже приведена программа 1.2, в которой символьный ("char") массив применяется для печати собственного исходного файла на экране в обратном порядке.

#include #include

const int MAX_LEN = 1000; typedef char File_array[MAX_LEN];

int main() {

char character; File_array file; int count; ifstream in_stream;

in_stream.open("prg6_1_2.cpp"); in_stream.get(character);

for ( count = 0; !in_stream.eof() && count 0) cout

Если в программе есть строковая переменная "a_string", то скопировать в нее содержимое другой строки можно с помощью функции "strcpy(...)":

strcpy( a_string, "Вы напечатали строку:" );

Этот оператор скопирует в массив a_string символы константной строки ""Вы напечатали строку:"" и добавит в конец массива (т.е. присвоит 21-му элементу) завершающий нуль-символ "'\0'". Вызов оператора

strcpy( a_string, another_string );

приведет к копированию строки, хранящейся в массиве "another_string", в массив

"a_string". При копировании строк важно, чтобы длина массива-приемника

("a_string") была, как минимум, (L+1), где L - длина копируемой строки

("another_string"). В противном случае вызов функции приведет к ошибке выхода за пределы массива, которая не обнаруживается компилятором, но может привести к серьезным сбоям во время выполнения программы.

Для вычисления длины строки предназначена функция "strlen(...)". Вызов

"strlen(another_string)" возвращает длину строки "another_string" (без учета нульсимвола).

Функция "strcmp(...)" выполняет сравнение двух переданных ей строк. Если строки одинаковы, то эта функция возвратит 0 (т.е. "false"), а если строки различаются, то функция возвратит ненулевое значение.

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

Работа с библиотечными строковыми функциями продемонстрирована в программе 5.1.

5.4 Чтение символьных строк из потока ввода с помощью функции "getline(...)"

Для ввода строк (например, с клавиатуры) пригоден оператор ">>", но его применение ограничено, поскольку этот оператор считает пробелы разделителями. Допустим, в программе содержатся операторы:

...

... cout > a_string; ...

... После сеанса работы со следующими экранными сообщениями:

... ... Введите имя: Вася Иванов

... ...

Переменной "a_string" будет присвоено значение ""Вася"", т.к. оператор ">>"

считает пробел разделителем, который сигнализирует о завершении ввода значения.

Для ввода символьных строк часто более удобной оказывается функция "getline(...)", имеющая 2 параметра. Например, оператор:

cin.getline(a_string, 80); позволяет получить от пользователя строку с пробелами длиной до 79 символов (последний, 80-й символ строки - служебный нуль-символ).

В программе 5.1 демонстрируется действие функций "getline(...)",

"strcmp(...)", "strcpy(...)" и "strcat(...)".

#include #include

const int MAXIMUM_LENGTH = 80;

int main() {

char first_string[MAXIMUM_LENGTH]; char second_string[MAXIMUM_LENGTH];

cout typedef int* IntPtrType;

int main() {

IntPtrType ptr_a, ptr_b; int num_c = 4, num_d = 7;

ptr_a = &num_c; /* СТРОКА 10 */ ptr_b = ptr_a; /* СТРОКА 11 */ cout

typedef int* IntPtrType;

int main() {

IntPtrType ptr_a, ptr_b; /* СТРОКА 7 */

ptr_a = new int; /* СТРОКА 9 */ *ptr_a = 4; /* СТРОКА 10 */ ptr_b = ptr_a; /* СТРОКА 11 */

cout #include

... ... delete ptr_a; ptr_a = NULL; delete ptr_b; ptr_b = NULL;

... ... if ( ptr_a != NULL )

{ *ptr_a = ...

... ...

Фрагмент программы 1.3.

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

#include #include /* "exit()" описана в файле stdlib.h */

#include

... ... ptr_a = new int; if ( ptr_a == NULL )

{ cout = 'a' && character > no_of_integers;

number_ptr = new int[no_of_integers]; if ( number_ptr == NULL )

{

cout > number_ptr[count]; cout "

После определения структуры "node (узел)" в программе можно объявлять переменные этого типа:

node my_node, my_next_node;

Для доступа к полям (внутренним переменным) структуры "my_node" надо пользоваться оператором "." (точка):

cin >> my_node.word;

my_node.ptr_to_next_node = &my_next_node;

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

node_ptr my_node_ptr, another_node_ptr; my_node_ptr = new node; another_node_ptr = new node;

В таком случае для доступа к полям узлов можно пользоваться совместно операторами "звездочка" и "точка":

cin >> (*my_node_ptr).word;

(*my_node_ptr).ptr_to_next_node = another_node_ptr;

Более кратко эти выражения записываются с помощью специального оператора "->", который предназначен для доступа к полям структуры через указатель:

cin >> my_node_ptr->word;

my_node_ptr->ptr_to_next_node = &my_next_node;

Другими словами, имена "my_node_ptr->word" и "(*my_node_ptr).word" обозначают одно и то же - поле "word" структуры типа "node", на которую ссылается указа-

тель "my_node_ptr ".

5.2 Создание связного списка

Ниже приведена функция создания связного списка для хранения символьных строк, которые пользователь вводит с клавиатуры. Указатель "a_list" ссылается на первый узел нового списка (на "голову" списка). Для завершения ввода данных пользователь должен ввести специальный завершающий символ (точку).

void assign_list( node_ptr& a_list )

{ node_ptr current_node, last_node;

assign_new_node( a_list ); cout > a_list->word;

if ( !strcmp( ".", a_list->word ) )

{ delete a_list; a_list = NULL;

} current_node = a_list; /* СТРОКА 13 */

while ( current_node != NULL )

{ assign_new_node( last_node ); cout > last_node->word;

if ( !strcmp( ".", last_node->word ) )

{ delete last_node; last_node = NULL;

} current_node->ptr_to_next_node = last_node; current_node = last_node;

} } Фрагмент программы 5.1.

Функция "assign_new_node(...)" для создания нового узла аналогична функции "assign_new_int(...)" из программы 1.5.

Порядок действий функции "assign_list(...)" поясняется на рис. 10-16. На

рис. 10 показано состояние программы после выполнения вызова:

assign_new_node( a_list );

Рис. 10.. Состояние программы 5.1 после создания нового списка.

Предположим, что пользователь напечатал слово "мой". Тогда после 13-й строки программа окажется в состоянии, показанном на рис. 11.

Рис. 11.. Состояние программы после заполнения данными первого узла.

После первой строки в теле цикла "while" программа перейдет в состояние, показанное на рис. 12.

Рис. 12.. В хвост списка был добавлен новый узел.

Далее, если пользователь напечатал слово "список", то после итерации цикла "while" программа будет в состоянии, как на рис. 13.

Рис. 13. Последний узел списка заполнен данными и в предыдущий узел помещен соответствующий указатель.

После выполнения первой строки во второй итерации цикла "while" состояние программы см. рис. 14.

Рис. 14.. В хвост списка был добавлен новый узел.

Допустим, в ответ на следующий запрос пользователь напечатает ".". Тогда после завершения цикла "while" программа будет в состоянии, как на рис. 15.

Рис. 15.. Был удален последний узел списка.

Символ "." сигнализирует о том, что пользователь захотел прекратить ввод данных для списка. Поэтому функция "assign_list(...)" завершается и при выходе из нее автоматически уничтожаются локальные переменные-указатели "current_node" и "last_node" (которые были объявлены в теле функции). После возврата из функции состояние программы будет таким, как на рис. 16.

Рис. 16.. Состояние программы 5.1 после выхода из функции ввода списка с клавиатуры.

5.3 Печать связного списка

Напечатать содержимое связного списка на экране несложно. Ниже приведена функция для печати строк, хранящихся в узлах списка:

void print_list( node_ptr a_list )

{ while ( a_list != NULL )

{

cout word ptr_to_next_node;

} } Фрагмент программы 5.1.

6. Сводка результатов

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

7. Упражнения

Упражнение 1

Не запуская приведенную далее программу, определите, какие сообщения она выводит на экран. Для проверки своего ответа запустите программу.

#include #include

typedef int* IntPtrType;

int main() {

IntPtrType ptr_a, ptr_b, *ptr_c;

ptr_a = new int; *ptr_a = 3; ptr_b = ptr_a;

cout 0, то n! = n*(n-1)!

В этом определении факториала "!" есть базовое утверждение (определение 0!) и рекурсивная часть. Согласно определению, факториал 6 вычисляется следующим образом:

6! = 6*5! = 6*5*4! = 6*5*4*3! = 6*5*4*3*2! = 6*5*4*3*2*1! = 6*5*4*3*2*1*1 = 720

2. Простой пример рекурсии

Рассмотрим рекурсивную функцию "print_backwards()" из программы 2.1. Эта функция предназначена для ввода с клавиатуры последовательности символов. Для прекращения ввода пользователь должен напечатать специальный символ (точку). После этого функция печатает введенные символы в обратном порядке.

#include

void print_backwards();

int main() {

print_backwards(); cout > character; if ( character != '.' )

{

print_backwards(); cout MAXIMUM_NO_OF_ELEMENTS ) {

cout > chars[no_of_chars_input]; no_of_chars_input++;

} while ( chars[no_of_chars_input - 1] != '.'

&& no_of_chars_input = 0; count-- ) cout 0; number-- ) product *= number; return product;

} Фрагмент программы 4.1b.

Конечно, вопрос о том, какая реализация конкретной функции более понятна, довольно спорный. Обычно в итерационную функцию приходится включать больше локальных переменных, например, в первом примере был добавлен массив "chars[MAX_ARRAY_LENGTH]", а во втором примере - целочисленная переменная "product". Другими словами, в итеративных функциях память тратится на локальные переменные, а рекурсивных функциях - на организацию вызовов.

6. Рекурсия в структурах данных

На практике рекурсия чаще встречается в структурных типах данных, а не в функциях. Рекурсивная структура данных уже использовалась в предыдущей лекции для определения узла связного списка. В структуре "узел" хранится указатель структуру такого же типа:

struct node

{ char word[MAX_WORD_LENGTH]; node *ptr_to_next_node;

}; В последующих лекциях будут более подробно рассматриваться другие примеры рекурсивных структур данных и особенности их описания на Си++.

7. Рекурсивная реализация алгоритма быстрой сортировки

В завершающей части этой лекции кратко рассмотрим известный пример рекурсивного алгоритма - алгоритм быстрой сортировки QuickSort. Этот рекурсивно определенный алгоритм предназначен для упорядочения значений, хранящихся в массиве, по возрастанию или по убыванию.

Предположим, что 11 элементов массива имеют следующие значения (рис. 6).

Рис. 6.. Начальное состояние массива.

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

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

Рис. 7.. Массив после разделения на две части и переупорядочения элементов в них.

Процесс разбиения и переупорядочения частей массива можно реализовать рекурсивно. Индекс граничного элемента вычисляется как индекс среднего элемента:

(first + last)/2

где "first" и "last" являются индексами первого и последнего элементов обрабатываемой части массива.

Далее подробно рассматривается процедура переупорядочения элементов массива.

Индексы "left_arrow" и "right_arrow" определим как индексы крайних элемен-

тов обрабатываемой части массива (которая подвергается разбиению, см. рис. 8).

Рис. 8.. Значения индексов "left_arrow" и "right_arrow" перед началом процедуры переупорядочения.

В процессе переупорядочения индексы "left_arrow" и "right_arrow" смещают-

ся в направлении граничного элемента. Так, "right_arrow" перемещается влево, пока значение элемента не окажется меньше или равно граничному значению (рис. 9).

Рис. 9.. Обнаружение левого и правого элементов для обмена.

Аналогично, "left_arrow" смещается вправо, пока не встретится элемент,

больший или равный граничному. В нашем примере этот элемент расположен в начале массива (рис. 9). Теперь значения двух найденных элементов массива меняются местами (рис. 10).

Рис. 10.. Состояние массива после обмена двух элементов.

После обмена двух элементов "right_arrow" продолжает смещаться влево. Смещение прекращается, когда находится очередной элемент, меньший или равный граничному (рис. 11).

Рис. 11.. Справа обнаружен элемент для обмена.

Индекс "left_arrow" смещается вправо, пока не встретится элемент, требую-

щий перестановки (рис. 12).

Рис. 12.. Слева обнаружен элемент для обмена.

Значения найденных элементов меняются местами (рис. 13).

Рис. 13.. Состояние массива после обмена второй пары элементов.

Переупорядочение частей массива прекращается после выполнения условия

"left_arrow > right_arrow". В состоянии на рис. 13 это условия ложно, поэтому "right_arrow" продолжает смещаться влево, пока не окажется в положении, показанном на рис. 14.

Рис. 14.. Справа обнаружен элемент для обмена.

Перемещение "left_arrow" приведет к состоянию, показанному на рис. 15. Поскольку при перемещении вправо надо найти элемент, больший или равный "pivot", то "left_arrow" прекращает перемещение после достижения граничного элемента.

Рис. 15.. Левый элемент для обмена совпадает с граничным.

Теперь выполняется обмен, включающий граничный элемент (такой обмен допустим), и массив переходит в состояние, показанное на рис. 16.

Рис. 16.. Состояние массива после обмена третьей пары элементов.

После обмена элементов "right_arrow" перемещается влево, а "left_arrow" -

вправо (рис. 17).

Рис. 17.. Переупорядочение прекращается после прохождения индексами середины массива.

В состоянии, показанном на рис. 17, становится истинным условие завершения процедуры переупорядочения "left_arrow > right_arrow". Поэтому первую процедуру разбиения и переупорядочения массива можно считать выполненной.

Ниже приведена функция на Си++, в которой рекурсивно реализован алгоритм

сортировки QuickSort:

void quick_sort( int list[], int left, int right )

{ int pivot, left_arrow, right_arrow;

left_arrow = left; right_arrow = right;

pivot = list[(left + right)/2];

do { while ( list[right_arrow] > pivot ) right_arrow--;

while ( list[left_arrow] = left_arrow );

if ( left 2 a(n) = a(n-1) + a(n-2)

Это определение позволяет сгенерировать такую последовательность:

1, 1, 2, 3, 5, 8, 13, 21, ...

Напишите на Си++ функцию "fibonacci(...)", которая вычисляет числа Фибоначчи по их порядковому номеру, например, fibonacci(7)==13.

Упражнение 2

Для выполнения этого упражнения используйте программу 5.1 из 7-й лекции.

а) Напишите две рекурсивных функции "print_list_forwards(...)" и

"print_list_backwards(...)", которые должны печатать на экране содержимое связного списка, рассмотренного в 7-й лекции, соответственного в прямом и обратном порядке. (Функция "print_list_forwards(...)" должна вести себя точно так же, как и функция "print_list(...)" из 7-й лекции). Измените программу 5.1 из 7-й лекции так, чтобы проверить свои функции. Ваша программа должна выдавать на экран следующие сообщения:

Введите первое слово (или '.' для завершения списка): это

Введите следующее слово (или '.' для завершения списка): короткое

Введите следующее слово (или '.' для завершения списка): тестовое Введите следующее слово (или '.' для завершения списка): сообщение Введите следующее слово (или '.' для завершения списка): .

ПЕЧАТЬ СОДЕРЖИМОГО СПИСКА В ПРЯМОМ ПОРЯДКЕ:

это короткое тестовое сообщение

ПЕЧАТЬ СОДЕРЖИМОГО СПИСКА В ОБРАТНОМ ПОРЯДКЕ:

сообщение тестовое короткое это

Подсказка: при разработке функции "print_list_backwards()" еще раз посмотрите на программу 2.1.

б) Напишите и протестируйте итерационный (т.е. нерекурсивный) вариант функции "print_list_backwards(...)". (Какой вариант функции вам показался проще в разработке?)

Упражнение 3

Даны два положительных целых числа m и n таких, что m". Например:

void print_person( Person* p )

{ cout name birthdate.day birthdate.month birthdate.year salary " компонентам "s1" присваиваются начальные значения. Затем указателю "sp" присваивается адрес "s2", и компоненты этой структуры также инициализируются. Возможность динамического перенаправления на различные объекты является одним из важнейших свойств указателей, которые, в частности, полезны для реализации динамических структур данных (например, связного списка или стека).

struct ExStruct {

char c; int i; float f; double d;

}; void main()

{ ExStruct s1, s2; ExStruct* sp = &s1; sp->c = 'a'; sp->i = 1; sp->f = 3.14f; sp->d = 0.00093; sp = &s2; sp->c = 'b'; sp->i = 2; sp->f = 6.28f; sp->d = 2.5; }

Программа 3.1.

В 7-й лекции (для реализации связного списка) и в 8-й лекции уже рассматривалось понятие рекурсивных структур данных. Для создания в структуре ссылки на структуру такого же типа необходимо пользоваться указателем. В программе 3.2 создаются две структуры, содержащие ссылки друг на друга.

struct SelfReferential { int i;

SelfReferential* sr;

};

void main() {

SelfReferential sr1, sr2; sr1.sr = &sr2; sr2.sr = &sr1; sr1.i = 47; sr2.i = 1024; }

Программа 3.2.

Описание без указателя является недопустимым, т.к. в нем при описании компоненты структуры тип структуры используется в тот момент, когда этот тип еще не определен полностью:

struct SelfReferential {

int i; SelfReferential sr; // Недопустимое описание компоненты

}; 4. Массивы и структуры

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

Массивы и структуры могут комбинироваться различными способами. Например, i-я компонента массива "a", который является компонентой структуры "r", обозначается как

r.a[i]

Напротив, компонента с именем "s", входящая в i-ю компоненту-структуру массива структур "a" обозначается как

a[i].s В качестве примера далее приведено описание переменной "screen", которая является двумерным массивом структур типа "Cell". Этот массив предназначен для хранения содержимого текстового экрана размером 80х25 знакомест:

struct Cell { unsigned char character; // Символ int foreground; // Цвет символа int background; // Цвет фона bool blink; // Мигание включено/выключено

};

void main() {

Cell screen[25][80];

... } 5. Перегрузка операторов

В 3-й лекции (п. 4) были описаны средства перегрузки функций. Си++ допускает перегрузку не только функций, но и операторов, таких, как "+", "-", "*" (и большинство других - "+=", "->" и даже "()"). Средства перегрузки операторов полезны тогда, когда желательно, чтобы пользовательские типы данных в исходном тексте программ выглядели в выражениях подобно встроенным типам Си++. Поясним это на нескольких примерах.

Для сложения комплексных чисел (описание структуры см. п.2) можно применить функцию:

Complex C_add( const Complex& x, const Complex& y )

{ Complex t;

t.re = x.re + y.re;

t.im = x.im + y.im; return t;

}

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

Допустим, в программе реализована также функция "C_mult(...)" для умножения двух комплексных чисел. Ниже приведен пример использования этих функций:

Complex u, v, w, z, t;

...

t = C_add( u, v );

w = C_mult( z, t );

Конечно, более естественной является запись:

Complex u, v, w, z;

... w = z * ( u + v );

Тип "Complex" является типом, введенным пользователем, и, естественно, в языке Си++ арифметические операторы для этого типа не определены. Поэтому для естественной записи выражений с числами в виде структур "Complex" надо перегрузить операторы сложения и умножения:

Complex operator +( const Complex& x, const Complex& y )

{ Complex t;

t.re = x.re + y.re;

t.im = x.im + y.im; return t;

} Complex operator *( const Complex& x, const Complex& y )

{ Complex t;

t.re = x.re*y.re - x.im*y.im;

t.im = x.re*y.im + x.im*y.re; return t;

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

Аналогично арифметическим операторам, в Си++ допускается перегружать операторы потокового ввода/вывода ">>" и "

struct Stack { double v[20]; int top;

}; void push( Stack& s, double val )

{ s.v[s.top] = val;

s.top++; }

double pop( Stack& s )

{ return s.v[--(s.top)];

} void init( Stack& s )

{ s.top = 0;

} bool full( Stack& s )

{ return s.top >= 20;

} void main()

{ Stack s; init( s ); // Инициализация стека push( s, 2.31); // Помещение в стек первого элемента push( s, 1.19 ); // Помещение в стек второго элемента cout " (если обращение выполняется через указатель на структуру).

Внутрь функций структуры чаще передаются по ссылке. Если параметрыструктуры внутри функции заведомо не изменяются, то такие параметры обычно описываются как константные параметры.

Можно создавать и использовать массивы структур, или использовать массивы в качестве компонент структур. Для более краткой и естественной записи выражений со структурами допускается перегрузка операторов.

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

8. Упражнения

Упражнение 1

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

Упражнение 2

Разработайте статический стек для хранения целых чисел и для хранения символьных строк длиной до 100 символов. Для каждого из стеков сделайте отдельные заголовочные файлы ("stack_int.h" и "stack_string.h") и файлы реализации

("stack_int.cpp" и "stack_string.cpp"). Проверьте работу стеков с помощью соответствующей тестовой программы.

Упражнение 3

Согласно описанию из п. 6.3 реализуйте стек с использованием динамической памяти (примените операторы "new" и "delete").

Упражнение 4

Напишите функции для записи содержимого массива структур "Person" (см. п.2) в файл, для чтения структур "Person" из файла и для печати этих структур на экране. Для ввода/вывода из файла и для вывода на экран примените перегруженные операторы ">>" и " int main()

{ cout

} Чтобы сохранить набранный текст на диске, выберите команду меню File?Save (Файл?Сохранить).

Рис. 4. Окно текстового редактора с открытым файлом hello.cpp расположено в правой части окна Developer Studio.

3. Сборка проекта

Результатом сборки проекта является исполняемый файл программы, который

может работать независимо от Developer Studio.

Для сборки проекта выберите команду меню Build?Build hello_world.exe (рис. 5). В нашем примере проект содержит только один исходный файл

(hello.cpp). В процессе сборки он будет скомпилирован и скомпонован со стандартной библиотекой ввода/вывода.

Рис. 5. Выбор команды Build?Build hello_world.exe (Сборка?Сборка приложения hello_world.exe).

Информация о выполнении сборки отображается в окне Output window (рис. 6). В нем выводятся сообщения, выдаваемые программами, работающими при сборке проекта: препроцессором, компилятором и компоновщиком. Среди этих сообщений могут встретиться сообщения об ошибках (errors) и предупреждения о возможных ошибках (warnings). Если таких сообщений не возникло, значит, сборка успешно завершена (рис. 6).

Рис. 6. Окно Output window (Окно вывода) расположено в нижней части

окна Developer Studio.

Если есть ошибки, их надо устранить (в нашем случае просто внимательно сверьте исходный текст с образцом) и снова попытаться собрать проект.

4. Запуск нового приложения

В результате сборки было создано консольное приложение. Такие приложения широко использовались до появления систем Windows. Они удобны для учебных целей, т.к. простая структура консольных программ позволяет на начальном этапе изучения языка программирования не отвлекаться на системные особенности программ для Windows.

Для запуска приложения выберите команду меню Build?Execute hello_world.exe (рис. 7). Для удобства Developer Studio помещает имя исполняемого файла в название команды меню.

Рис. 7. Выбор команды Build?Execute hello_world.exe (Сборка?Запуск приложения hello_world.exe).

После выбора команды запуска Developer Studio создаст консольное окно - окно, напоминающее экран компьютера, работающего под управлением MS-DOS, а не Windows. Консольное приложение осуществляет ввод/вывод данных в пределах этого окна (рис. 8).

Рис. 8. Окно консольного приложения hello_world.exe.

Более подробные сведения об использовании среды разработки содержатся в справочной системе Developer Studio. В Приложении 2 описаны некоторые способы отладки программ и служебные клавиши отладчика Developer Studio.

Литература

1) Miller R., Clark D, White B., Knottenbelt W. An Introduction to the Imperative Part of C++. Imperial College of Science, Technology and Medicine, London. 1996-1999. (Вводное описание программирования на Си++, послужившее основой данной части учебного курса.)

2) Savitch W., Problem Solving with C++: The Object of Programming, 2nd Edition, Addison Wesley Publishing Company, Inc., 1999. (Учебник начального уровня по программированию, языку Си++ и объектно-ориентированному программированию.)

3) Вирт Н. Алгоритмы+структуры данных=программы. М.:Мир, 1985. (В этой монографии подробно рассматриваются алгоритмы сортировки, рекурсивные алгоритмы и динамические типы данных. Изложение базируется на языке Паскаль, но излагаемый материал во многом применим и к процедурному программированию на Си++.)

4) Страуструп Б. Язык программирования С++. Вторая редакция. К.:"ДиаСофт", 1993. ("Классическое" справочное руководство по языку Си++, написанное автором языка. Эта книга пригодится тем, кто собирается в будущем серьезно заниматься программировать на Си++.)

5) Уэйт М., Прата С., Мартин Д. Язык Си. Руководство для начинающих. М.:Мир, 1988. (Учебник начального уровня по языку Си без объектно-ориентированных возможностей. В отличие от данных лекций, в этой книге используются библиотечные функции ввода-вывода языка Си, а не потоковые объекты Си++.)

Учебно-методическое издание

А.А. Богуславский, С.М. Соколов

Основы программирования на языке Си++ В 4-х частях.

(для студентов физико-математических факультетов педагогических институтов)

Компьютерная верстка Богуславский А.А.

Технический редактор Пономарева В.В.

Сдано в набор 12.04.2002 Подписано в печать 16.04.2002

Формат 60х84х1/16 Бумага офсетная

Печ. л. 20,5 Учетно-изд.л. ____ Тираж 100

Лицензия ИД №06076 от 19.10.2001

140410 г.Коломна, Моск.обл., ул.Зеленая, 30. Коломенский государственный педагогический институт.

2

4

Показать полностью… https://vk.com/doc365514677_437570798
2 Мб, 10 июня 2016 в 18:20 - Россия, Москва, НОУ МТИ, 2016 г., pdf
Рекомендуемые документы в приложении