close

Вход

Забыли?

вход по аккаунту

?

Bukunov Osn progr

код для вставкиСкачать
С. В. БУКУНОВ
ОСНОВЫ ПРОГРАММИРОВАНИЯ
НА ЯЗЫКЕ С++
0
Министерство образования и науки
Российской Федерации
Санкт-Петербургский государственный
архитектурно-строительный университет
С. В. БУКУНОВ
ОСНОВЫ ПРОГРАММИРОВАНИЯ
НА ЯЗЫКЕ С++
Учебное пособие
Санкт-Петербург
2015
1
УДК 681.3
Рецензенты: д-р физ.-мат. наук, профессор Б. Г. Вагер (СПбГАСУ);
д-р физ.-мат. наук, профессор Е. Л. Генихович (Главная
геофизическая обсерватория им. А. И. Воейкова)
Букунов, С. В.
Основы программирования на языке С++: учеб. пособие /
С. В. Букунов; СПбГАСУ. – СПб., 2015. – 199 с.
ISBN 978-5-9227-0619-3
Содержит основные сведения о языке программирования С++. Отдельное внимание уделяется различным способам организации программ и тем
конструкциям языка, которые необходимы для освоения следующего этапа −
объектно-ориентированного программирования.
Пособие состоит из 8 глав, в которых содержатся необходимые теоретические сведения, упражнения и примеры программ с подробными комментариями, а также задания для самостоятельной работы.
Предназначено для студентов, обучающихся по специальностям «Прикладная математика» и «Прикладная математика и информатика».
Табл. 2. Ил. 243. Библиогр.: 4 назв.
Рекомендовано Редакционно-издательским советом СПбГАСУ в качестве учебного пособия.
ISBN 978-5-9227-0619-3
© С. В. Букунов, 2015
© Санкт-Петербургский государственный
архитектурно-строительный университет, 2015
2
ВВЕДЕНИЕ
Настоящее пособие предназначено для студентов третьего
курса специальностей «Прикладная математика» и «Прикладная
математика и информатика», изучающих предмет «Объектноориентированное программирование». В пособии излагается первая
часть курса, посвященная изучению языка программирования С++
и его использованию для написания процедурно-ориентированных
программ.
Задача пособия заключается в ознакомлении студентов с основными конструкциями языка С++, выработке навыков написания
правильно структурированных программ и создании соответствующей теоретической и практической базы для изучения второй
части курса, посвященной непосредственно объектно-ориентированному программированию.
Для написания всех программ использовалась интегрированная среда разработки Microsoft Visual Studio.
3
Глава 1. ОСНОВЫ ЯЗЫКА С++
1.1. Структура программы на С++
Любая программа на языке С++ состоит из одной или более
функций, задающих действия, которые нужно выполнить. Выполнение любой программы начинается с функции main(). Далее идет
текст программы, заключенный в фигурные скобки. Таким образом, структура программы имеет вид:
main ( )
{ Тело программы }
Тело программы может состоять из множества операторов,
функций, классов и прочих элементов, но при ее запуске управление всегда передается функции main(). Как правило, в теле программы должны присутствовать следующие компоненты:
• объявления переменных программы (переменных, массивов, констант, структур и т. д.);
• операторы задания значений исходных данных (операторы
присваивания и ввода);
• операторы преобразования исходных данных (операторы
присваивания, условные операторы, операторы циклов, обращения
к функциям и т. д.) для вычисления результатов;
• операторы и функции для вывода полученных результатов
на экран дисплея, файл на диске или другое устройство вывода.
Если в программе не содержится функции с именем main(),
то при попытке запустить такую программу выведется сообщение
об ошибке.
Перед именем функции main() должен находиться спецификатор типа, определяющий тип возвращаемого функцией значения
(например, int – для случая целочисленного значения, void – если
функция не возвращает никакого значения).
Замечание. В последних версиях С++ для описания типа
функции main(), не возвращающей никакого значения, рекомендуется использовать спецификатор типа int.
Круглые скобки, идущие вслед за именем main, являются отличительной чертой функции: если бы их не было, то компилятор
4
не смог бы отличить имя переменной или другого элемента программы от имени функции. В скобках может находиться список аргументов функции. В случае их отсутствия этот список пуст.
Тело любой функции должно быть заключено в фигурные
скобки (расположение скобок может быть произвольным).
Упражнение 1.1. Самая короткая программа на языке С++.
Составьте следующую программу:
int main ( ) { }
Результатом ее работы должно стать появление в консольном окне
текста «Для продолжения нажмите любую клавишу…». То есть
эта программа состоит из единственной функции с названием main(),
она ничего не делает, но, тем не менее, является правильной.
1.2. Операторы
Оператор является структурной единицей программы на С++.
Большинство операторов являются указаниями компьютеру совершить какое-либо действие. Окончание любого оператора обозначается знаком «точка с запятой» (;).
Упражнение 1.2. Вывод на экран текстового сообщения.
Результатом работы программы должно стать появление в консольном окне текста «Hello, World !!!» (рис. 1.1).
Рис. 1.1
Первый оператор программы cout задает действие по выводу
на экран фразы, заключенной в кавычки. Имя оператора cout (дословно – console output (вывод на консоль)) является именем объекта, который обеспечивает вставку выводимой информации в стандартный поток вывода, который обычно направлен на экран дисплея. Поток в С++ – это некая абстракция, отражающая перемеще5
ние данных от источника к приемнику. Информация для вывода
передается объекту cout с помощью операции << , которая называется операцией вставки и обозначает «поместить в поток», т. е. она
копирует содержимое переменной, стоящей в правой ее части,
в объект, содержащийся в левой ее части. Механизм вывода информации на экран проиллюстрирован на рис. 1.2.
Рис. 1.2
Для считывания информации с экрана дисплея предназначен
объект cin (дословно – console input (ввод с экрана)), который используется для извлечения данных из стандартного потока ввода,
направленного от клавиатуры. Информация передается переменной
в программе с помощью операции >>, которая называется операцией извлечения и обозначает «извлечь из потока», т. е. она извлекает
данные из потокового объекта, стоящего в левой части (например,
введенного с клавиатуры символа), и присваивает эти данные переменной, стоящей в правой части. Механизм ввода информации
с клавиатуры проиллюстрирован на рис. 1.3.
Рис. 1.3
Оператор endl (end line – конец строки) – это манипулятор
языка С++, который осуществляет перевод курсора на новую строку.
Оператор return 0; является указанием функции main() возвратить значение 0 вызывающему окружению (в данном случае компи6
лятору или операционной системе) и не является обязательным (по
крайней мере, в данной программе его можно было опустить).
1.3. Директивы
Вторая и третья строки программы являются директивами.
Вторая строка представляет собой директиву препроцессора, а третья – директиву using. Директивы нельзя считать частью языка
C++, однако их использование является необходимым. В языке C++
используется много директив препроцессора (#include – одна из
них), каждая из которых предваряется символом #, не входит в тело
какой-либо из функций и не заканчивается точкой с запятой (;).
Директива препроцессора предписывает компилятору совершить
какое-либо действие (в частности, директива #include указывает
препроцессору включить в компилируемый файл содержимое другого файла (в данном случае содержимое файла iostream)). Препроцессором называется специальная часть компилятора, обрабатывающая подобные директивы перед началом процесса компиляции программы. Использование директив препроцессора позволяет
использовать в программе множество уже созданных файлов (чтобы каждый раз «не изобретать велосипед»).
Директива using namespace std; означает, что все определенные ниже имена в программе будут относиться к пространству
имен с именем std (т. е. к стандартному пространству имен).
Пространством имен называется область программы, в которой
распознается определенная совокупность имен. За пределами данного пространства эти имена неизвестны (т. е. каждую программу
на языке C++ можно разбить на несколько пространств имен,
в каждом из которых будут видны только те элементы программы,
которые относятся к данному пространству). В пространстве имен
std описаны различные элементы программы, например объект
cout и манипулятор endl.
1.4. Разделяющие знаки
Компилятор языка С++ игнорирует практически все разделяющие знаки. К ним относятся пробелы, символ перехода на другую
строку, вертикальная и горизонтальная табуляции, перевод страни7
цы и др. В языке C++ можно записать несколько операторов на одной строке, разделить их любым количеством пробелов, табуляций,
пустых строк, и компилятор всегда обработает их правильно.
Однако использование такого нестандартного стиля написания программ является дурным тоном в программировании. Кроме
того, программы, написанные таким образом, очень неудобны для
чтения, хотя и компилируются правильно.
1.5. Строковые константы
Фразы "Hello, World ! \n" или "\nВведите два числа a и b:"
являются примерами строковых констант. В отличие от выражения,
этим константам нельзя придавать новое значение в процессе выполнения программы. Значение константы задается один раз и сохраняется на протяжении всего процесса выполнения программы.
Символ '\n' в приведенных выше фразах является примером
управляющей или escape-последовательности. В данном случае такая последовательность означает, что следующий вывод текста
начнется с новой строки. Это нужно для того, чтобы фраза «Для
продолжения нажмите любую клавишу…», вставляемая компилятором после завершения программы, выводилась на новой строке. Аналогичную функцию выполняет и манипулятор endl.
1.6. Заголовочные файлы
Файл, подключаемый с помощью директивы #include, обычно
называют заголовочным файлом. Например, в программе из упражнения 1.2 (см. рис. 1.1) директива #include дает указание препроцессору включить в исходный текст содержимое файла iostream
перед компиляцией кода. Файл iostream содержит описания, необходимые для работы с объектом cout и операцией <<. Без этих описаний компилятору будет неизвестно, что значит имя cout, а использование операции << будет воспринято как некорректное.
В языке С++ помимо библиотечных заголовочных файлов
можно использовать собственные заголовочные файлы, в которых
могут находиться описания созданных при написании программы
собственных переменных, функций, структур, объектов и других
элементов. Все созданные собственные заголовочные файлы долж8
ны располагаться в папке Header Files (Заголовочные файлы) Обозревателя решений (Solution Explorer), а при их подключении в
тексте программы необходимо вместо символов < > использовать
двойные кавычки " ".
Упражнение 1.3. Использование собственного заголовочного файла (рис. 1.4).
Рис. 1.4
Программа выводит на экран текстовое сообщение «Hello,
World !!!». В данном случае в отличие от предыдущих примеров
реализована многофайловая программа, включающая три файла.
9
В первом из них (файл hello_world.cpp) содержится основная функция main(), в которой при помощи директивы #include осуществляется подключение заголовочного файла hello_world.h и происходит
обращение к функции print(), которая, собственно, и производит
вывод на экран текстового сообщения. При этом описание (или
прототип) функции print() находится в заголовочном файле hello_world.h, а сама функция располагается в отдельном файле
hello_world_f.cpp.
Результат работы программы – вывод на экран компьютера
текстового сообщения «Hello, World !!!».
Замечание. Обратите внимание на отличия при подключении
библиотечного заголовочного файла iostream в файле
hello_world_f.cpp и собственного заголовочного файла hello_world.h
в файле hello_world.cpp.
1.7. Использование функций
Отдельные части программы можно выделять в функции
и в рамках одного файла. Для этого вначале программы (до строки
с функцией main()) необходимо описать прототип функции, в котором будут содержаться только описания типов используемых
данных. Тело самой функции располагается в конце программы после закрывающей фигурной скобки функции main().
Замечание. Строка с прототипом функции заканчивается знаком «точка с запятой», в объявлении функции этот знак отсутствует.
Упражнение 1.4. Пример однофайловой программы с использованием функции без аргументов (рис. 1.5).
Рис. 1.5
10
В функцию можно передавать аргументы, а сама функция может возвращать значение (как правило, − результат вычислений).
В этом случае в прототипе функции описываются только типы используемых данных, а в описании самой функции используются
формальные параметры. Возвращаемое функцией значение содержится в операторе return.
Упражнение 1.5. Пример программы с использованием
функции с аргументами и возвращаемым значением (рис. 1.6).
Рис. 1.6
Программа реализует сложение двух целых чисел (рис. 1.7).
Рис. 1.7
При совместном использовании одних и тех же данных несколькими функциями возможен и другой вариант обмена данными
11
между ними. Для этого данные должны быть объявлены как внешние, или глобальные. Тогда любая функция будет иметь доступ
к этим данным.
Упражнение 1.6. Пример многофайловой программы с использованием функции и глобальных переменных (рис. 1.8).
Рис. 1.8
Программа состоит из трех файлов. В файле prim_7.cpp находится функция main(), в файле prim_7_f.cpp – функция summa().
Переменные a и b описаны в заголовочном файле prim_7.h как глобальные, поэтому нет необходимости в списке аргументов для
12
функции summa() (так как эти переменные «видны» теперь в любой функции).
Замечание. Использование глобальных данных при составлении программ нежелательно, так как это существенно усложняет
процесс отладки программы в случае изменения этих данных
и увеличивает вероятность возникновения ошибок (особенно при
наличии многочисленных связей между функциями и глобальными
данными).
1.8. Комментарии
Первая строка в предыдущих программах, начинающаяся
с двойной косой черты //, представляет собой комментарий. Комментарии являются важной частью программы. Они помогают
разобраться в действиях программы как самому разработчику, так
и любому другому человеку. Компилятор игнорирует все, что помечено в программе как комментарии, поэтому они не включаются
в содержимое исполняемого файла и не влияют на ход выполнения
программы.
Комментарии начинаются с двойной косой черты и заканчиваются концом строки. Комментарий может начинаться в произвольном месте строки (как в начале строки, так и после оператора).
Используйте комментарии так, чтобы человек, читающий
текст программы, понимал, что делает эта программа. Детали алгоритма можно понять из самих операторов, поэтому задача комментариев – дать общие пояснения функционирования отдельных блоков программы.
1.9. Управляющие последовательности
В качестве примера управляющей последовательности можно
привести последовательность символов \n, уже использовавшуюся
в предыдущих примерах (например, строковая константа "\nHello,
World !!!"). Название «управляющая последовательность» означает, что символ \ «управляет» интерпретацией следующих за ним
символов. Так, в данном случае, использование управляющей последовательности \n означает, что текст «Hello, World !!!» необходимо вывести на экране с новой строки.
13
Управляющие последовательности можно использовать как
в качестве отдельных констант, так и в составе строковых констант.
Список управляющих последовательностей представлен на рис. 1.9.
Рис. 1.9
Задание для самостоятельной работы
1. Составьте многофайловую программу для вычисления площади пятиугольника. Исходные данные для расчета – координаты
(x, y) его вершин:
1: (2.5, 1.5);
2: (1.3, 2.1);
3: (3.1, 2.9);
4: (5.4, 2.5);
5: (4.9, 1.8).
Для решения задачи используйте метод декомпозиции: сначала получите площади всех треугольников, а затем сложите их. Расчет сторон и площади треугольника и вывод результата выделите
в отдельные функции, расположив их в отдельных файлах. Для передачи результата расчетов в функцию вывода на экран используйте глобальную переменную, для расчета площади треугольника −
формулу Герона.
14
Глава 2. ТИПЫ ДАННЫХ В С++
2.1. Типы данных для представления целых чисел
В языке С++ существует несколько типов данных, имеющих
различную длину. Самым распространенным является целый тип.
Для представления целых чисел в языке С++ существует несколько типов: char, short, int и long (полное название типов: short
int, long int, unsigned long int и т. д.). Они отличаются друг от друга диапазоном возможных значений:
• int (целый) – задает значения, к которым относятся все целые числа; диапазон возможных значений: от –2 147 483 648
до +2 147 483 647; переменная типа int занимает четыре байта;
• short (короткий целый) – соответствующие объекты не могут быть больше, чем int; диапазон возможных целых значений лежит в пределах от –32 768 до +32 767; переменные этого типа занимают два байта;
• long (длинный целый) – соответствующие объекты не могут
быть меньше, чем int; переменные этого типа занимают четыре
байта и позволяют представлять целые числа в диапазоне от
–2 147 483 648 до +2 147 483 647;
• char (символьный) – задает значения, представляющие различные символы, т. е. каждому символу в языке С++ соответствует
некоторое целое число; переменные этого типа занимают один байт.
Каждый из типов данных может быть знаковым или беззнаковым. Переменные беззнакового типа не имеют отрицательных значений, поэтому они могут принимать в два раза большие положительные значения, чем переменные знаковых типов. Целые числа
лишены дробной части.
Основные особенности использования целых чисел:
• по умолчанию тип целых величин – знаковый;
• если перед определением типа стоит ключевое слово
unsigned, то тип целого числа – беззнаковый;
• по умолчанию целые константы принадлежат к типу int.
Если необходимо указать, что целое число – это константа типа
long, то можно добавить символ L или l после числа. Если константа беззнаковая, т. е. относится к типу unsigned long или unsigned
15
int, после числа записывается символ U или u, например: 34U,
700034L, 7654ul.
Для целых чисел определены следующие стандартные арифметические операции, представленные в табл. 2.1.1.
Таблица 2.1.1
Знак операции
=
+
–
*
/
%
Выполнение действия
Присваивание
Сложение
Вычитание
Умножение
Деление
Деление по модулю
Результатом этих операций также является целое число. При
делении целых чисел остаток отбрасывается. Например, при выполнении операции 1/5 результат будет равен нулю. Для того чтобы
сохранить дробную часть, хотя бы один из операндов должен быть
вещественным (это условие будет выполнено при вычислении 1.0 / 5).
Упражнение 2.1. Потеря точности при делении целых чисел (рис. 2.1).
Рис. 2.1
Результатом работы программы будет целое число 2 (см. рис. 2.2),
что неправильно (правильный результат: 5 / 2 = 2.5).
16
Рис. 2.2
Упражнение 2.2. Остаток от деления целых чисел (рис. 2.3).
Рис. 2.3
Результат работы программы представлен на рис. 2.4.
Рис. 2.4
17
2.2. Тип данных для представления символов
Для работы с символьными константами и переменными
в языке С++ используется тип данных char. Как отмечалось выше,
этот тип данных относится к целочисленному, т. е. любому символу в языке С++ соответствует некоторое целое число.
Упражнение 2.3. Соответствие символов и целых чисел
(рис. 2.5).
Рис. 2.5
Результат работы программы представлен на рис. 2.6.
Рис. 2.6
18
Символьные константы и переменные записываются в одиночных кавычках, например 'a', 'A' и т. д. Когда компилятор встречает символьную константу, он заменяет ее соответствующим целым числом. Например, константа 'A' будет заменена целым числом 65, как это видно из предыдущего примера. Символьным переменным можно присваивать значения символьных констант. В
упражнении 2.4 приведен пример работы с символьными переменными и константами.
Упражнение 2.4. Использование символьного типа данных
(рис. 2.7).
Рис. 2.7
Результатом работы программы будет вывод на экран символов A и B, отделенных друг от друга на восемь позиций (см. рис.
2.8).
Рис. 2.8
Для работы с набором символов можно использовать символьные строки и массивы символов. Объявление символьной
строки отличается от объявления символьной переменной наличием символа '*' после слова char (см. упражнение 2.5).
19
Упражнение 2.5. Использование символьной строки (рис. 2.9).
Рис. 2.9
Результатом работы программы будет вывод на экран уже
знакомой надписи «Hello, World !!!».
Для обозначения одномерного массива в языке С++ используются две квадратные скобки [ ], в которых указывается размер
массива, например [20] для массива из двадцати элементов. Если
размер массива заранее неизвестен, скобки могут быть пустыми.
В программе из упражнения 2.6 приведен пример объявления строки как массива символов.
Упражнение 2.6. Использование массива символов для
строки (рис. 2.10).
Рис. 2.10
Результатом работы программы будет вывод на экран все той
же надписи «Hello, World !!!».
Более подробно работа с массивами и строками будет рассмотрена в следующих главах.
20
2.3. Операции с целыми числами
В табл. 2.3.1 представлены операции сравнения для целых чисел в языке С++.
Таблица 2.3.1
Знак операции
==
!=
>
<
>=
<=
Выполнение действия
Равенство
Неравенство
Больше
Меньше
Больше или равно
Меньше или равно
Некоторые особенности проведения операций с целыми числами:
• допустимо смешивать в выражении различные целые типы;
например, операция x + y , где x – переменная типа short, а y – переменная типа long, будет корректной; при выполнении такой операции значение переменной x преобразуется к типу long; такое
преобразование можно произвести всегда без потери в точности;
• общее правило преобразования целых типов состоит в том,
что более короткий тип при вычислениях преобразуется в более
длинный;
• только при выполнении операции присваивания длинный
тип может преобразовываться в более короткий.
Замечание. Не путайте операцию присваивания (=) и операцию равенства (==).
Упражнение 2.7. Неправильное использование операции равенства (рис. 2.11).
21
Рис. 2.11
На рис. 2.12 представлены результаты работы программы для
двух вариантов введенных чисел.
Рис. 2.12
Из приведенных результатов понятно, что какие бы два числа
не вводили с экрана, результат будет один – вывод сообщения
о том, что эти два числа равны, потому что вместо проверки на равенство вводимых чисел в программе происходит присваивание переменной var1 значения переменной var2.
Для получения правильного результата необходимо заменить
в программе знак = на знак == (рис. 2.13).
22
Рис. 2.13
При работе с данными следует учитывать возможность переполнения содержимого памяти, когда результат может оказаться
больше допустимого значения, определяемого используемым типом данных.
Упражнение 2.8. Пример переполнения (рис. 2.14).
Рис. 2.14
На рис. 2.15 представлены результаты работы программы.
Рис. 2.15
23
Переполнение стало следствием того, что реальный результат
умножения (равный 40 000) превысил максимально допустимый
размер для целых чисел типа short (равный 32 767).
Выход за рамки допустимых для данного типа значений приводит, как правило, к трудно обнаруживаемым ошибкам. Подобные
ошибки с беззнаковыми типами происходят гораздо реже.
Следующая программа хранит одно и то же значение, равное
1 500 000 000, в переменных разного типа, производит над ними
одни и те же действия, но результаты этих действий различаются.
Упражнение 2.9. Использование беззнаковой переменной
(рис. 2.16).
Рис. 2.16
Результаты работы программы представлены на рис. 2.17.
Рис. 2.17
24
Программа умножает обе переменные на число 2, а затем делит на число 3. Однако правильный результат получается только во
втором случае, поскольку для случая знаковой переменной результат умножения, равный 3 000 000 000, выходит за допустимый
верхний предел для переменной signedVar, равный 2 147 483 647.
Упражнение 2.10. Пример эффекта «заворачивания» (wrapping) (рис. 2.18).
Рис. 2.18
Результат работы программы представлен на рис. 2.19.
Рис. 2.19
Из примера видно, что после увеличения числа 32 767 на единицу вместо ожидаемого результата 32 768 мы получаем значение
переменной x, равное нижней границе диапазона изменения для
целых чисел типа short. То есть в данном случае значение переменной как бы «заворачивается» к нижней границе.
Ниже представлены диапазоны изменения стандартных типов
данных языка С++ (рис. 2.20 – для знаковых типов данных, рис.
2.21 – для беззнаковых целых типов данных).
25
Рис. 2.20
Рис. 2.21
2.4. Типы данных для представления вещественных чисел
Для представления вещественных чисел в языке С++ существует три типа данных: float – с одинарной точностью, double –
с двойной точностью и long double – с расширенной точностью.
Точность представления чисел составляет семь десятичных значащих цифр для типа float, пятнадцать значащих цифр для типа
double и девятнадцать значащих цифр для типа long double.
Вещественные числа записываются в виде десятичных дробей
(1.3; 3.14159; 0.0005) или в виде мантиссы и экспоненты (1.2Е0,
0.12е1).
Замечание. По умолчанию вещественная константа принадлежит к типу double.
Чтобы обозначить, что константа на самом деле принадлежит
к типу float, необходимо добавить суффикс f или F после числа,
например 2.7F. Суффикс l или L означает, что записанное число
относится к типу long double. Различные способы записи вещественного числа приведены ниже:
26
const float PI = 3.14F;
double pi = 3.14159;
long double pi = 3.14159L.
Для вещественных чисел определены все стандартные
арифметические операции, кроме операции нахождения остатка от
деления.
Если арифметическая операция применяется к двум вещественным числам разных типов, то тип менее точного числа преобразуется к типу более точного числа, например, float преобразуется
к double. Очевидно, что такое преобразование всегда можно выполнить без потери точности.
Если вторым операндом в операции с вещественным числом
является целое число, то целое число преобразуется в вещественное
число.
Наряду с округлением по умолчанию (т. е. когда дробная
часть вещественного числа просто отбрасывается) для округления
вещественных чисел в языке С++ используются функции floor()
(для округления с недостатком) и ceil() (для округления с избытком), подключаемые с помощью заголовочного файла math.h.
Упражнение 2.11. Пример округления вещественных чисел
(рис. 2.22).
Рис. 2.22
Результат работы программы представлен на рис. 2.23.
27
Рис. 2.23
2.5. Тип данных для представления логических значений
В языке С++ существует специальный тип данных bool для
представления логических значений. Для величин этого типа существует только два возможных значения: true (истина) и false (ложь).
Пример объявления логической переменной:
bool condition;
condition = (3 < 8); // примет значение true
bool p = (a == b); // true если a и b равны, false в противном
// случае
Логические переменные используются для выражения результатов логических операций. Для типа bool определены следующие
стандартные логические операции:
логические И (&&), ИЛИ (||) и НЕ (!).
Примеры использования логических операций:
cond1 && cond2; // истинно, если обе переменные истинны
cond1 || cond2; // истинно, если хотя бы одна из переменных
// истинна
! cond1; // результат противоположен значению cond1
Логические значения получаются в результате операций сравнения. В языке С++ принято следующее правило преобразования
чисел в логические значения: ноль соответствует значению false,
а любое отличное от нуля число преобразуется в значение true.
2.6. Перечисления
В том случае, когда какая-нибудь переменная может принимать ограниченный набор константных значений, в языке С++ мо28
жет быть использован перечисляемый тип данных. Перечисления
позволяют создавать новые типы данных, а затем определять переменные этих типов. Для создания перечисления используется ключевое слово enum, за которым следуют имя типа, открывающая фигурная скобка, список константных значений, разделенных запятыми, закрывающая фигурная скобка и точка с запятой.
Пример.
enum COLOR {RED, BLUE, GREEN, WHITE, BLACK};
Это выражение выполняет две задачи:
1. Создается перечисление с именем COLOR, являющееся
новым типом данных.
2. Определяются символьные константы: RED co значением
0; BLUE co значением 1; GREEN co значением 2 и т. д.
Пример.
В программе используется переменная, хранящая величину,
отражающую время суток (ночь, утро, день и вечер). Тогда можно
ввести перечисляемый тип данных:
enum DayTime { morning, day, evening, night};
а потом определить переменную current типа DayTime:
DayTime current;
которая хранит текущее время дня, а затем присваивать ей одно из
допустимых значений типа DayTime, например
current = day;
Для наборов определены операции сравнения на равенство
(==) и неравенство (!=) с атрибутами этого же типа. Например,
if (current != night)
{//выполнить работу }
Внутреннее представление значений набора – это целые числа, и по умолчанию элементам набора соответствуют последовательные числа, начиная с 0. То есть в последнем примере morning = 0,
day = 1, evening = 2, night = 3. Возможны варианты произвольного
соответствия, например
enum DayTime { morning, day=5, evening, night=7};
В этом случае morning = 0, day = 5, evening = 2, night = 7.
29
2.7. Преобразование типов данных
Язык С++ свободнее, чем многие другие языки программирования, обращается с выражениями, содержащими данные разных типов.
Упражнение 2.12. Использование смешанных типов данных
(рис. 2.24).
Рис. 2.24
Результат работы программы представлен на рис. 2.25.
Рис. 2.25
В программе переменная типа int умножается на переменную
типа float, а результат присваивается переменной типа double.
Компиляция программы происходит без ошибок, поскольку компиляторы допускают возможность умножения (и выполнения других
арифметических операций) операндов разных типов. В данном случае компилятор сам сделал все необходимые преобразования типов
данных. Такие преобразования называются неявными.
Правила преобразования разнотипных операндов в случае
арифметической операции следующие:
30
1) char и short преобразуются в int, float – в double;
2) enum преобразуется в int;
3) если один из операндов двойной точности (double), то второй преобразуется к double;
4) если один из операндов имеет тип long, то второй преобразуется к long;
5) если один из операторов имеет тип unsigned, то второй
преобразуется к типу unsigned.
При вычислении выражения операнды преобразуются к типу
того операнда, который имеет наибольший размер.
Иерархия типов данных в С++ приведена на рис. 2.26.
.
Рис. 2.26
Упражнение 2.13. Неявное преобразование типов данных
(рис. 2.27).
Рис. 2.27
31
Результат работы программы представлен на рис. 2.28.
Рис. 2.28
Замечание. Стандартная функция sizeof(arg) вычисляет размер переменной arg в байтах.
В программе при выполнении оператора присваивания
х = f / (i + l*r),
преобразования типов данных осуществляются следующим образом:
• операнд l преобразуется к типу unsigned int (правило 1),
а затем к типу unsigned long (правило 2);
• операнд i преобразуется к типу unsigned long (правила 4, 5);
• результат вычисления выражения в круглых скобках будет
иметь тип unsigned long;
• затем он преобразуется к типу double (правило 2);
• результат вычисления всего выражения будет иметь тип
double, перед выполнением операции присваивания он преобразуется к типу float, который имеет переменная x, стоящая в левой части оператора.
Явные преобразования типов данных, в отличие от неявных,
совершаются самим программистом. Явные преобразования необходимы в тех случаях, когда компилятор не может безошибочно
преобразовать типы автоматически. Для явного преобразования
значения переменной одного типа в другой перед именем переменной в скобках указывается присваиваемый ей новый тип.
Упражнение 2.14. Явное преобразование типов данных
(рис. 2.29).
32
Рис. 2.29
Результат работы программы представлен на рис. 2.30.
Рис. 2.30
При вычислении переменной z2 значение переменной a было
явно преобразовано к типу float.
Задание для самостоятельной работы
1. Разработайте алгоритм и составьте программу расчета доходности инвестиций инвестора для случая, когда инвестор приобрел акций на сумму 10 000 рублей и через 6 месяцев продал их на
сумму 14 000 рублей. Доходность рассчитайте в % годовых. Для
значений денежных сумм и доходности используйте данные типа
float, а для значения интервала времени – данные типа int. Используя стандартную функцию sizeof(), проследите за процессом преобразования типов данных в процессе вычислений.
2. Составьте программу, которая будет выводить на экран два
разных сообщения (например, «Hello, World!» и «Hello, my
darling!»), используя для этого две символьные строки и одну
функцию для вывода сообщений на экран. Решите задачу тремя
разными способами:
33
• однофайловая программа;
• многофайловая программа с использованием локальных
переменных;
• многофайловая программа с использованием глобальных
переменных.
3. Составьте программу, реализующую сложение двух дробей.
Результат выведите на экран двумя способами: в виде дроби и в виде вещественного числа. Во втором случае используйте явное преобразование типов int во float. Результат работы программы может
выглядеть следующим образом:
При написании программы используйте правило сложения
дробей и тот факт, что операция извлечения >> может считывать
более одного значения за один раз, например:
cin >> a >> ch >> b >> oper >> с >> ch >> d;
34
Глава 3. ОПЕРАЦИИ. ФОРМАТИРОВАНИЕ ДАННЫХ
3.1. Арифметические операции с присваиванием
Язык С++ располагает средствами для того, чтобы сократить
размер кода и сделать его более наглядным. Одним из таких
средств являются арифметические операции с присваиванием. Они
позволяют придать программному коду характерный вид в стиле
С++.
В большинстве языков программирования типичным является
следующий оператор:
total = total + item; // сложение total и item
В данном случае производится сложение с замещением существующего значения одного из слагаемых. Такая форма оператора
не отличается лаконичностью, поскольку в данном случае приходится дважды использовать имя total. Применение арифметической
операции с присваиванием в С++ позволяет исключить подобное
дублирование:
total += item; // сложение total и item
Такие операции комбинируют арифметическую операцию
с операцией присваивания. На рис. 3.1 показана эквивалентность
обоих операторов.
Рис. 3.1
С присваиванием комбинируется не только операция сложения, но и другие арифметические операции: -=, *=, /=, %= и т. д.
35
Упражнение 3.1. Арифметические операции с присваиванием (рис. 3.2).
Рис. 3.2
Результат работы программы представлен на рис. 3.3.
Рис. 3.3
Замечание. Использование арифметических операций с присваиванием при программировании не является обязательным,
но в С++ они применяются довольно часто.
3.2. Инкремент
При программировании достаточно часто приходится иметь
дело с увеличением какой-либо величины на единицу. Это можно
сделать явным образом, используя оператор
count = count + 1; // увеличение переменной count на 1
или с помощью операции сложения с присваиванием:
count += 1; // увеличение переменной count на 1
В языке С++ существует еще один, более компактный, способ
записи такой операции:
36
++count;
Операция ++ увеличивает на единицу, или инкрементирует,
свой операнд.
Знак инкремента может быть записан двумя способами: в
префиксной форме, когда он расположен перед своим операндом,
и в постфиксной форме, когда операнд записан перед знаком инкремента, например
totalWeight = avgWeight * ++count;
totalWeight = avgWeight * count++;
В первом случае сначала будет выполнена операция инкрементирования, т. е. переменная count будет увеличена на единицу,
а затем будет выполнена операция умножения переменной
avgWeight на измененную переменную count. Во втором же случае
сначала произойдет перемножение переменных avgWeight и count,
уже после этого значение переменной count будет увеличено на
единицу. Рис. 3.4 иллюстрирует эти две формы записи для случая,
когда avgWeight = 155.5 и count = 7.
Рис. 3.4
Упражнение 3.2. Применение операции инкрементирования (рис. 3.5).
37
Рис. 3.5
Результат работы программы представлен на рис. 3.6.
Рис. 3.6
При первом инкрементировании переменной count, для того
чтобы вывести на экран значение переменной, увеличенное на единицу, использовалась префиксная форма. Поэтому второй оператор
выводит на экран значение 11, а не 10. Постфиксное инкрементирование действует после вывода на экран значения 11, и только следующий оператор получит значение count = 12.
3.3. Декремент
Операция декрементирования, обозначаемая --, уменьшает,
а не увеличивает на единицу свой операнд. Декремент также допускает префиксную и постфиксную формы записи.
3.4. Тернарная условная операция
В языке С++ используется один вид не совсем обычной операции, которая возвращает свой второй или третий операнд в зави38
симости от значения логического выражения, заданного первым
операндом. Такие операции называются тернарными условными
операциями. В любой тернарной операции принимают участие три
операнда.
Пример. Оператор
y = х?а : b;
вернет значение y = a для x ≠ 0 и значение y = b в случае, если x = 0.
Пример. Оператор
y = (а > b)?а : b;
находит максимальное число из двух чисел a и b, т. е. y = max(а,b).
Упражнение 3.3. Использование тернарной операции
(рис. 3.7).
Рис. 3.7
Результат работы программы представлен на рис. 3.8.
Рис. 3.8
39
Упражнение 3.4. Вычисление максимума из двух целых чисел с помощью тернарной операции (рис. 3.9).
Рис. 3.9
Пример результата работы программы представлен на рис. 3.10.
Рис. 3.10
3.5. Каскадирование операций вставки в поток
и извлечения из потока
В программе из упражнения 3.2 используется только один
оператор с объектом cout, несмотря на то что он расположен на нескольких строках. Такая запись этого оператора возможна благодаря свойству компилятора игнорировать разделительные символы.
Таким образом, происходит каскадирование операции вставки данных в поток, т. е. запись в одном операторе того, что можно бы было реализовать с помощью пяти последовательных операторов,
начинающихся с cout.
Аналогичным образом можно каскадировать и операцию извлечения данных из потока с использованием объекта cin (см.
упражнение 3.4).
40
3.6. Операции отношения
Операция отношения сравнивает между собой два значения.
Значения могут быть как стандартных типов, например, char, int
или float, так и типов, определяемых пользователем. Сравнение
устанавливает одно из трех возможных отношений между переменными: равенство, больше и меньше. Результатом сравнения является значение истина или ложь.
Упражнение 3.5. Пример использования операций сравнения (рис. 3.11).
Рис. 3.11
Возможный
на рис. 3.12.
результат
работы
программы
представлен
Рис. 3.12
Компилятор С++ присваивает истинному выражению значение 1
(целочисленный аналог булевой переменной true), а ложному – значение 0 (целочисленный аналог булевой переменной false).
41
3.7. Логические операции
В языке С++ используются три логические операции:
&& – логическое И;
|| – логическое ИЛИ;
! – логическое НЕ.
Пример. Выражение
x < 5 || x > 15;
является истинным как для x < 5 , так и в случае, если x > 15 .
Замечание. Логическая операция || имеет более низкий приоритет, чем операции отношения < и >, поэтому скобки в приведенном выше выражении не требуются.
Операция логического НЕ является унарной (также как и операции инкремента-декремента), т. е. имеет один операнд (почти все
операции в С++ являются бинарными, т. е. имеют два операнда;
условная операция служит примером тернарной операции, поскольку имеет три операнда). Действие операции ! заключается в
том, что она меняет значение своего операнда на противоположное:
если операнд имел истинное значение, то после применения операции ! он становится ложным, и наоборот.
Пример. Выражение (x == 7) является истинным для x, равного семи, а выражение !(x == 7) является истинным для всех значений x, которые не равны семи (последнее выражение эквивалентно
записи x!= 7 ).
3.8. Приоритеты операций С++
На рис. 3.13 представлены приоритеты основных операций
языка С++. Приоритет операций в таблице убывает сверху вниз.
Операции, находящиеся в одной строке, имеют одинаковый приоритет.
42
Рис. 3.13
Замечание. Чтобы повысить приоритет операции, можно заключать ее в круглые скобки. Если при составлении относительного выражения с несколькими операциями возникают ошибки, то
рекомендуется использовать круглые скобки даже там, где это не
является обязательным. Скобки не оказывают нежелательного воздействия на выражение и гарантируют правильный порядок его
вычисления даже в том случае, если программист не знает приоритетов операций. Кроме того, наличие скобок делает смысл выражения более ясным.
3.9. Форматирование данных с помощью манипуляторов
и флагов
Манипуляторы языка С++ – это особые операции, используемые совместно с операцией вставки <<, для того чтобы видоизменять вывод данных, который делает программа. Существует два
вида манипуляторов – манипуляторы без аргументов и с аргументами. К первым относится уже использовавшийся в предыдущих
программах манипулятор endl, осуществляющий разделение строк,
объявление которого происходит в заголовочном файле
<iostream>. Объявления большинства других популярных манипуляторов находятся в заголовочном файле <iomanip>. Поэтому
в случае необходимости их использования в программе, необходимо подключить этот файл с помощью директивы #include.
43
Упражнение 3.6. Форматирование данных с помощью манипуляторов (рис. 3.14).
Рис. 3.14
Результатом работы программы является таблица, содержащая
названия городов и численность их населения (см. рис. 3.15).
Рис. 3.15
Очевидно, что такая форма вывода информации неудобна для
визуального восприятия. Намного удобнее было бы воспринимать
таблицу, если бы второй столбец был выровнен по правому краю.
Для решения этой задачи можно вставить в каждой строке необходимое количество пробелов, что неудобно, а можно использовать
манипулятор setw(), который будет задавать длину полей имен городов и численности населения (см. рис. 3.16, 3.17).
44
Упражнение
(рис. 3.16).
3.7.
Использование
манипулятора
setw()
Рис. 3.16
Результаты работы программы представлены на рис. 3.17.
Рис. 3.17
Манипулятор setw(n) печатает число или строку, следующую
за ним в потоке, в поле фиксированной длины n, где n – это аргумент манипулятора. В данном примере под название города было
отведено девять позиций, а под численность населения – двенадцать позиций.
Таким образом, манипуляторы – это специальные инструкции
форматирования, которые вставляются прямо в поток.
Замечание. Манипуляторы действуют только на те данные,
которые следуют за ними в потоке, а не на те данные, которые
находятся перед ними (на то он и поток).
Когда размер величины, выводимой на экран с помощью манипулятора setw(), оказывается меньше размера зарезервированного поля, по умолчанию незаполненные поля заполняются пробелами.
45
Для их заполнения любым другим символом (например, символом
«точка») можно использовать манипулятор setfill(), в качестве аргумента которого необходимо указать требуемый символ заполнения.
По умолчанию значение переменной, помещаемой в поле вывода, выравнивается по правому краю, что мы и наблюдаем в результатах работы программы. Для изменения параметров выравнивания выводимой информации можно использовать флаги форматирования: ios::left – для выравнивания по левому краю и ios::right –
для выравнивания по правому краю. Флаги форматирования работают переключателями, определяющими различные форматы
и способы ввода/вывода информации. Для установки соответствующего флага форматирования используется стандартная функция (или метод) setiosflags() с именем флага в качестве аргумента, а
для его отмены (если необходимо убрать флаги в более поздней
точке программы) – функция resetiosflags().
Упражнение 3.8. Применение манипулятора setw() совместно с флагами форматирования (рис. 3.18).
Рис. 3.18
Результатом работы программы является таблица, в которой
названия городов выровнены по левому краю, а численность населения – по правому (рис. 3.19).
Рис. 3.19
46
Основные
на рис. 3.20.
манипуляторы
с
аргументами
представлены
Рис. 3.20
Основные
на рис. 3.21.
флаги
форматирования
языка
представлены
Рис. 3.21
Для форматирования десятичных чисел в языке С++ используются свои манипуляторы и флаги.
47
Упражнение 3.9. Применение манипуляторов флагов для
форматирования десятичных чисел (рис. 3.22).
Рис. 3.22
Результатом работы программы является отформатированное
в соответствии с заданными параметрами число (см. рис. 3.23).
Рис. 3.23
Задание для самостоятельной работы
1. Составьте программу, генерирующую следующую последовательность целых чисел: 10, 5, 4, 5.
Для вывода числа 5 воспользуйтесь одной из арифметических
операций с присваиванием, для вывода числа 4 – операцией декремента, а для повторного вывода числа 5 – операцией инкремента.
Для вывода информации используйте каскадирование операции
вставки в поток.
2. Составьте программу вычисления модуля произвольного
целого числа и минимального из двух целых чисел с использовани48
ем условной тернарной операции. Ввод данных, вывод результатов
и сами вычисления оформите в виде отдельных функций.
3. Составьте программу, которая будет отображать на экране
структуру инвестиционного портфеля клиента брокерской фирмы.
Таблица должна содержать следующие поля:
• наименование ценной бумаги;
• количество ценных бумаг, шт.;
• цена приобретения одной ценной бумаги, руб.;
• суммарная стоимость инвестиций в ценную бумагу, руб.;
• доля ценной бумаги в инвестиционном портфеле, %.
В инвестиционном портфеле клиента находятся акции следующих эмитентов:
• «Транснефть» – 6 шт. по цене 79 357 руб./шт.;
• «Банк ВТБ» – 10,5 млн шт. по цене 0,04657 руб./шт.;
• «Газпром» – 3 350 шт. по цене 148,47 руб./шт.
При составлении программы используйте соответствующие
типы и форматы вывода данных. Вид таблицы – произвольный.
49
Глава 4. ОРГАНИЗАЦИЯ ВЫЧИСЛЕНИЙ. ЦИКЛЫ.
ВЕТВЛЕНИЯ
4.1. Циклы
Действие циклов заключается в последовательном повторении
определенной части программы некоторое количество раз. Повторение продолжается до тех пор, пока выполняется условие выполнения цикла. Когда значение выражения, задающего условие, становится ложным, выполнение цикла прекращается, и управление
программой передается оператору, следующему непосредственно
за циклом.
В языке С++ существует три типа циклов: for, while и do.
4.1.1. Цикл for (цикл с параметром)
Цикл for реализует выполнение фрагмента программы фиксированное число раз. Как правило, этот тип цикла используется тогда, когда число повторений цикла известно заранее.
Упражнение 4.1. Пример простейшего цикла for (рис. 4.1).
Рис. 4.1
Результат работы программы представлен на рис. 4.2.
Рис. 4.2
50
В теле оператора цикла for содержатся три выражения, разделенные точками с запятой. Первое из них называют инициализирующим, второе – условием проверки, а третье – инкрементирующим
(рис. 4.3).
Рис. 4.3
Все три выражения в операторе for, как правило, содержат одну переменную, которую называют счетчиком цикла (переменная j).
Она определяется либо до того, как начнет исполняться тело цикла,
либо одновременно с ее инициализацией в самом операторе for.
Инициализирующее выражение вычисляется один раз – в
начале цикла (в нашем примере переменная j получает значение 0).
Под телом цикла понимается та часть программы, которая периодически исполняется в цикле. В примере из упражнения 4.1 тело цикла состоит из единственного оператора
cout << j * j << " ";
печатающего значение квадрата переменной j. Во время исполнения цикла счетчик цикла j принимает значения 0, 1, 2, 3, …, 14,
а выводимые значения соответственно 0, 1, 4, 9, …, 196.
Инкрементирующее выражение предназначено для изменения
значения счетчика цикла. Как правило, такое изменение сводится к
инкрементированию счетчика. Модификация счетчика происходит
после того, как тело цикла полностью выполнилось. В примере
увеличение j на единицу происходит каждый раз после завершения
тела цикла.
Блок-схема работы цикла for приведена на рис. 4.4.
51
Рис. 4.4
Если в теле цикла выполняется не один, а несколько операторов, то их необходимо заключить в фигурные скобки, причем после
закрывающей фигурной скобки ставить точку с запятой не следует.
Упражнение 4.2. Использования нескольких операторов
в теле цикла (рис. 4.5).
Рис. 4.5
Результат работы программы представлен на рис. 4.6.
Рис. 4.6
52
Вместо одного инициализирующего выражения в операторе
цикла for можно использовать несколько выражений, разделяемых
запятыми. Также можно использовать более одного инкрементирующего выражения. Только условие продолжения цикла всегда
должно быть одно.
Пример.
for (j = 0, alpha = 100; j < 50; j++, beta--)
У данного цикла есть обычный счетчик в виде переменной j,
но в операторе цикла помимо j также инициализируется переменная alpha и декрементируется переменная beta. Переменные alpha
и beta никак не связаны ни друг с другом, ни с переменной j.
На самом деле ни одно из трех выражений, используемых при
задании цикла, не является обязательным. Так, оператор цикла
for ( ; ; )
является корректным и позволяет организовать бесконечный цикл
(аналог цикла while с условием продолжения true).
Упражнение 4.3. Пример бесконечного цикла (рис. 4.7).
Рис. 4.7
Результат работы программы − бесконечно продолжающийся
вывод на экран текста «Это бесконечный цикл…» (рис. 4.8).
Рис. 4.8
53
Замечание. Стандартная функция Sleep() обеспечивает задержку вывода на экран каждой последующей записи на 400 миллисекунд для того, чтобы процесс работы программы был более
воспринимаемым (иначе текст будет перемещаться по экрану
со скоростью работы процессора).
4.1.2. Цикл while (цикл с предусловием)
Оператор цикла while используется в том случае, когда количество шагов цикла заранее неизвестно. Внешне оператор цикла
while напоминает упрощенный вариант цикла for. Он содержит
условие для продолжения цикла, но не содержит ни инициализирующих, ни инкрементирующих выражений. Синтаксис оператора
цикла while показан на рис. 4.9.
Рис. 4.9
До тех пор пока условие выполнения цикла выполняется, исполнение тела цикла продолжается.
Упражнение 4.4. Использование оператора цикла while
(рис. 4.10).
Рис. 4.10
54
Пользователю предлагается ввести серию значений. Выход из
цикла происходит после ввода цифры ноль. Очевидно, что в этой
ситуации невозможно узнать заранее, сколько ненулевых значений
введет пользователь.
Возможный результат работы программы представлен
на рис. 4.11.
Рис. 4.11
На рис. 4.12 показан механизм работы цикла while.
Рис. 4.12
Механизм работы цикла while не так прост, как может показаться вначале. Несмотря на отсутствие инициализирующего оператора, нужно инициализировать переменную цикла до начала исполнения тела цикла. Тело цикла должно содержать оператор, изменяющий значение переменной цикла, иначе цикл будет бесконечным (таким оператором в примере является оператор cin >> n;).
55
Упражнение 4.5. Пример использования нескольких операторов в цикле while (рис. 4.13).
Рис. 4.13
В программе последовательные целые числа возводятся в четвертую степень. Условием выхода из цикла служит превышение
числа 9999.
Результат работы программы представлен на рис. 4.14.
Рис. 4.14
Следующее значение (10 000) будет велико для вывода в четыре позиции, но к этому времени уже будет произведен выход
из программы.
Замечание. Для организации бесконечного цикла в случае
оператора цикла while достаточно отсутствия в теле цикла оператора, изменяющего значение переменной цикла.
56
Упражнение 4.6. Пример бесконечного цикла с использованием оператора цикла while (рис. 4.15).
Рис. 4.15
В данном примере условие выполнения цикла истинно по
определению, поэтому цикл будет продолжаться бесконечно.
4.1.3. Цикл do … while (цикл с постусловием)
В цикле while условие продолжения цикла помещается в
начало цикла. Это означает, что в случае невыполнения условия
при первой проверке тело цикла вообще не будет исполняться. В
некоторых случаях это целесообразно, но возможны и ситуации,
когда необходимо выполнить тело цикла хотя бы один раз вне зависимости от истинности проверяемого условия. В таких случаях
следует использовать цикл do, в котором условие продолжения
цикла располагается не перед телом цикла, а после него. Синтаксис
цикла do показан на рис. 4.16.
Большая часть программы находится в составе тела цикла do.
Ключевое слово do обозначает начало цикла. Затем следует тело
цикла, обрамленное фигурными скобками. Завершает цикл условие
продолжения цикла, описываемое с помощью ключевого слова
while. Это условие похоже на условие цикла while, но у него есть
два отличия: оно располагается в конце цикла и завершается точкой
с запятой (;).
Схема работы цикла с постусловием представлена
на рис. 4.17.
57
Рис. 4.16
Рис. 4.17
Упражнение 4.7. Использование оператора цикла do …
while (рис. 4.18).
Рис. 4.18
В данном примере, перед тем как производить очередное вычисление, программа спрашивает пользователя, хочет ли он произвести это вычисление. Если в ответ программа получает от пользователя любой символ, кроме символа n (например, символ y),
то выражение
ch != 'n'
58
сохраняет значение true. Когда пользователь вводит символ n,
условие продолжения цикла не выполняется и происходит выход
из цикла.
Возможный результат работы программы представлен
на рис. 4.19.
Рис. 4.19
Выбор типа цикла в каждом конкретном случае – это прерогатива программиста и определяется стилем программирования,
нежели строго определенными правилами. В принципе каждый
из циклов можно применить практически в любой ситуации.
4.2. Ветвления
Ветвление сводится к переходу в другую часть программы
в зависимости от выполнения или невыполнения каких-либо условий. Условие – это логическое выражение, результатом которого
является логическое значение true (истина) или false (ложь).
4.2.1. Условный оператор if
Условный оператор if является наиболее простым из операторов ветвления. Он выбирает один из двух вариантов последовательности вычислений. Синтаксис оператора if показан на рис. 4.20.
Синтаксис оператора if напоминает синтаксис оператора цикла while. Разница заключается в том, что при условии истинности
проверяемого условия операторы, следующие за if, будут выполнены только один раз; операторы, следующие за while, исполняются
до тех пор, пока проверяемое условие не станет ложным.
59
Рис. 4.20
Схема работы условного оператора if показана на рис. 4.21.
Рис. 4.21
Упражнение 4.8. Использование условного оператора if
(рис. 4.22).
Рис. 4.22
60
Возможный
на рис. 4.23.
результат
работы
программы
представлен
Рис. 4.23
Если вводимое число окажется меньше 100, то программа завершится, не напечатав вторую строку.
Замечание. Тело оператора if может состоять как из одного
оператора, так и из нескольких операторов, заключенных в фигурные скобки.
Замечание. Циклы и ветвления можно использовать совместно. Можно помещать ветвления внутрь цикла и наоборот, использовать вложенные ветвления и вложенные циклы.
Упражнение 4.9. Пример совместного использования оператора цикла for и оператора if (рис. 4.24).
Рис. 4.24
Программа определяет, является ли вводимое число простым
или нет (простым называется число, которое делится только
на число 1 и на само себя: 2, 3, 5, 7, 11, 13, 17 и т. д.).
61
Варианты работы программы представлены на рис. 4.25.
Рис. 4.25
Замечание. В последнем примере используется библиотечная
функция exit(), подключаемая с помощью заголовочного файла
<process.h>. Эта функция производит немедленный выход из программы независимо от того, в каком месте она находится. В данном
случае, когда программа получает число, не являющееся простым,
она завершается, поскольку нет необходимости несколько раз проверять, является число простым или нет.
4.2.2. Ветвление if … else
Оператор if позволяет совершать действие в том случае,
если выполняется некоторое условие. Если
же условие не выполняется, никакого действия не выполняется.
В случае же когда
необходимо в случае
выполнения условия
совершить одно дейРис. 4.26
ствие, а в случае невыполнения условия –
другое действие, можно использовать ветвление if … else. Синтаксис ветвления показан на рис. 4.26.
62
Схема работы конструкции if … else представлена на рис. 4.27.
Рис. 4.27
Упражнение 4.10. Пример ветвления if .. else (рис. 4.28).
Рис. 4.28
В отличие от программы из упражнения 4.8, данная программа выводит на экран разные сообщения в зависимости от истинности или ложности условия ветвления (рис. 4.29).
Рис. 4.29
63
Упражнение 4.11. Ветвление if .. else внутри цикла while
(рис. 4.30).
Рис. 4.30
Программа подсчитывает количество слов и символов в строке, введенной пользователем с клавиатуры.
Возможный результат работы программы представлен
на рис. 4.31.
Рис. 4.31
Замечание. До сих пор для ввода информации использовался
только объект cin и операция >>. Такой способ ввода предполагает,
что после ввода значения или символа пользователь нажмет клавишу Enter. В данной программе необходимо обрабатывать каждый введенный символ сразу после его появления, не дожидаясь
нажатия клавиши Enter. Такую возможность обеспечивает библиотечная функция _getche(), для использования которой необходимо
64
подключить стандартный заголовочный файл <conio.h>. Значение,
возвращаемое функцией _getche(), присваивается переменной ch
и печатается на экране; такой ввод называется вводом с эхо, что и
отражено в названии функции буквой e на конце. Функция _getch()
похожа на функцию _getche(), но, в отличие от нее, не отображает
вводимый символ на экране.
В данной программе условие цикла while проверяет, не является ли нажатая клавиша клавишей Enter, что соответствует выдаче функцией _getche() символа, соответствующего управляющей
последовательности '\r'. При нажатии клавиши Enter цикл и программа завершаются.
4.2.3. Вложенное ветвление if … else
Условный оператор можно расширить для проверки нескольких условий. Такие операторы называют вложенными условными
операторами. Все вложенные ветвления работают по общему алгоритму: если первое условие не выполняется, то проверяется второе
условие и т. д. до тех пор, пока не будут проверены все условия.
Если какое-либо из условий выполняется, то производятся необходимые действия, после чего программа выходит из всех вложенных
ветвлений. Подобные вложенные группы ветвлений называются
деревом ветвлений.
Упражнение 4.12. Вложенное ветвление if .. else (рис. 4.32).
Рис. 4.32
65
Программа проверяет, является введенное пользователем число положительным, отрицательным или нулем.
Возможный результат работы программы представлен на рис. 4.33.
Рис. 4.33
Замечание. В языке С++ глубина вложенности может быть
любой.
Замечание. Поскольку в роли вложенных операторов могут
быть операторы и в полной, и в сокращенной форме (так как конструкция else не всегда является обязательной), то иногда в программе могут возникать ошибки из-за неправильной трактовки, к
какому оператору if относится ветвь else. Для таких ситуаций существует следующее правило: ветвь else всегда относится к ближайшему предшествующему оператору if.
При большой глубине вложенности ветвления if…else могут
представлять значительные трудности для восприятия текста программы. Для реализации более трех уровней вложенности удобнее
использовать конструкцию if-else-if.
Упражнение 4.13. Конструкция if … else … if (рис. 4.34).
Рис. 4.34
66
Замечание. Для простых выражений вместо конструкции if …
else можно использовать условную тернарную операцию.
4.3. Оператор switch
Если в программе присутствует большое дерево ветвлений
и все ветвления зависят от значения какой-либо одной переменной,
то вместо ступенчатой последовательности конструкций if … else
или if … else … if обычно используют оператор switch (переключатель). Синтаксис оператора switch показан на рис. 4.35.
Рис. 4.35
Схема работы оператора switch выглядит следующим образом:
• вычисляется выражение в скобках за ключевым словом
switch;
• просматривается список константных выражений, до тех
пор пока не находится константное выражение, соответствующее
значению вычисленного выражения;
• происходит выполнение соответствующей последовательности операторов, следующей за двоеточием, и выход из оператора
67
с помощью оператора break; после этого управление передается
первому оператору, следующему за конструкцией switch;
• если значение выражения не соответствует ни одному из
значений константных выражений, то выполняется последовательность операторов, следующих за ключевым словом default (конструкция default не является обязательной и может отсутствовать).
Схема работы оператора switch представлена на рис. 4.36.
Рис. 4.36
68
Упражнение 4.14. Пример использования оператора switch
(рис. 4.37).
Рис. 4.37
Программа выводит на экран цену выбранной модели телефона.
Результаты работы программы представлены на рис. 4.38.
Рис. 4.38
69
4.4. Принудительное окончание цикла
Иногда при выполнении тела цикла возникает необходимость
закончить выполнение цикла не в начале цикла или в его конце,
а в произвольный момент в зависимости от выполнения или невыполнения некоторых условий. Для реализации данной ситуации используют операторы передачи управления.
К таким операторам относятся следующие операторы:
• оператор безусловной передачи управления goto;
• оператор выхода из цикла или переключателя break;
• оператор перехода к следующей итерации цикла continue.
Оператор безусловной передачи управления имеет вид:
goto <идентификатор>;
где идентификатор – это имя метки перед тем оператором (расположенным в этой же функции), на который необходимо сделать переход. После метки всегда ставится двоеточие.
Пример.
goto System Crash;
// операторы
System Crash:
// сюда передается управление оператором goto
Передача управления разрешена на любой помеченный оператор в теле функции. Существует одно ограничение: запрещается
«перескакивать» через объявления, содержащие инициализацию
объектов.
Замечание. Использование оператора goto может легко запутать логику программы и сделать ее трудной для понимания и исправления ошибок. На практике сложно представить ситуацию,
в которой использование оператора goto является необходимостью.
Поэтому профессиональные программисты не используют этот
оператор в своих программах.
Оператор break производит выход из цикла так же, как он
действует в конструкции switch. Следующим оператором, исполняемым после оператора break, будет первый оператор, находящийся
вне данного цикла.
Схема работы оператора break в цикле показана на рис. 4.39.
70
Рис. 4.39
Замечание. В случае вложенных циклов или переключателей
оператор break производит выход только из одного цикла с максимальной глубиной вложения.
Пример. Если внутри цикла for находится ветвление switch,
то оператор break внутри оператора switch выведет программу за
пределы конструкции switch, но оставит ее внутри цикла for.
Замечание. Оператор break нельзя использовать вне циклов
или переключателей.
Оператор передачи управления continue, в отличие от оператора break, не производит выход из цикла, а обеспечивает досрочное возвращение в его начало. Действие оператора continue проиллюстрировано на рис. 4.40.
Рис. 4.40
Замечание. Оператор continue употребляется только в операторах цикла. С его помощью завершается текущая итерация и
начинается проверка выполнения условия дальнейшего продолжения цикла, т. е. условий начала следующей итерации.
71
Упражнение 4.15. Использование оператора continue (рис. 4.41).
Рис. 4.41
Возможный результат работы программы представлен на рис. 4.42.
Рис. 4.42
В данной программе производится вычисление суммы всех
целых чисел в диапазоне между двумя числами, введенными пользователем с экрана, которые не делятся на число 7.
72
Задание для самостоятельной работы
1. Составьте программу, выводящую в одной строке на экране
все простые числа в диапазоне от 2 до 101. Используйте оператор
цикла for и условный оператор if. Для форматирования вывода используйте манипулятор setw(). Анализ числа на простоту выделите
в отдельную функцию.
Результат работы программы может выглядеть следующим
образом
2. Составьте программу для вычисления кубического корня из
произвольного числа arg, вводимого пользователем с экрана. Вычисление осуществите двумя способами. В первом случае используйте стандартную функцию pow(arg, 1.0/3), а во втором – итерационную формулу:

1  arg
+ 2 ⋅ xi −1  .
xi = ⋅ 
3  xi −1 ⋅ xi −1

При организации итерационного процесса используйте оператор
цикла while. Выведите на экран оба результата.
3. Составьте программу, имитирующую на экране календарь
на один месяц. Исходными данными для программы служат номер
(1, 2, …, 12) месяца и номер (1, 2, …, 7) первого дня недели, вводимые пользователем с экрана. При составлении программы используйте оператор цикла for, операторы ветвления switch и if , а также
манипулятор setw(). Результат работы программы может выглядеть
следующим образом.
73
Глава 5. СТРУКТУРЫ И ПЕРЕЧИСЛЕНИЯ
5.1. Структуры
Для реализации объединения разнородных данных в языке
С++ используются специальные типы данных, называемые структурами. Структура является объединением простых переменных.
Эти переменные могут иметь различные типы: int, float, char и т. д.
Переменные, входящие в состав структуры, называются полями
структуры. Полями структуры могут быть как переменные любого
допустимого типа, так и функции.
Замечание. Разнородностью типов данных структуры отличаются от массивов, все элементы которых должны быть однотипны.
Упражнение 5.1. Пример простой структуры (рис. 5.1).
Рис. 5.1
В программе создана структура, содержащая три поля. Каждое
из них хранит данные разного типа – символьные, целые и вещественные. Структура предназначена для хранения информации о
моделях телефонов. Первое поле структуры хранит информацию
74
о наименовании марки телефона, второе поле – номер модели
и третье поле – цену телефона.
Результат работы программы представлен на рис. 5.2.
Рис. 5.2
В программе присутствуют три основных аспекта работы
со структурами: объявление структуры, определение переменной
типа этой структуры и доступ к полям структуры.
5.1.1. Объявление структуры
Объявление структуры задает ее внутреннюю организацию,
описывая поля, входящие в состав структуры. Синтаксис объявления структуры следующий:
• объявление структуры начинается с ключевого слова struct;
затем следует имя структуры (в данном случае этим именем является phone);
• затем в фигурных скобках через точку с запятой перечисляются поля структуры с указанием типа данных для каждого поля;
• завершается объявление структуры точкой с запятой.
Идентификатор phone в программе из упражнения 5.1 выполняет функцию шаблона, на основе которого можно создавать конкретные переменные – структуры типа phone, например
phone phone1, phone2, phone3;
Очевидна аналогия с переменными стандартных типов, например
int a1, a2, a3;
Объявление структуры не создает никаких переменных, т. е.
при этом не происходит ни выделения памяти, ни объявления переменной. При объявлении же обычной переменной память под нее
выделяется. Таким образом, объявление структуры задает внутреннюю организацию структурных переменных, после того как они
будут определены.
75
5.1.2. Объявление структурной переменной
Первый оператор функции main() в программе из упражнения 5.1
представляет собой объявление переменной phone1 типа phone.
Определение переменной означает, что под нее выделяется память.
Под структурную переменную всегда отводится столько памяти,
сколько необходимо для хранения всех ее полей.
Замечание. Структуру phone можно считать новым типом
данных.
Объявление структуры и переменных, связанных с этой структурой, можно совместить. В этом случае имя переменной (или имена переменных, перечисленных через запятую) располагается
в описании структуры после закрывающей фигурной скобки
(см. упражнение 5.2).
Упражнение 5.2. Пример одновременного объявления
структуры и структурной переменной (рис. 5.3).
Рис. 5.3
Результат работы программы представлен на рис. 5.4.
76
Рис. 5.4
5.1.3. Доступ к полям структуры
Когда структурная переменная определена, доступ к ее полям
возможен с применением операции точки. В результате поле
структуры идентифицируется с помощью трех составляющих: имени структурной переменной (phone2), операции точки (.) и имени
поля (model_number): phone2.model_number. Подобную запись
следует понимать, как «поле model_number структурной переменной phone2».
Операция точки в соответствии с общепринятой терминологией называется операцией доступа к полю структуры, но, как правило, такое длинное название не употребляется.
С полями структурных переменных можно обращаться так же,
как с обычными простыми переменными. Так в программе из
упражнения 5.2 в результате выполнения оператора
phone1.model_number = 601;
полю model_number переменной phone1 присваивается значение
601, а в результате выполнения оператора
phone2.model_name = phone1.model_name;
полю model_name переменной phone2 присваивается значение поля model_name переменной phone1.
В программе также продемонстрирован вывод полей структурных переменных на экран с помощью объекта cout.
С полями структурных переменных можно производить
и другие, рассмотренные в предыдущих работах, операции (арифметические, сравнения и др.).
77
Упражнение 5.3. Использование арифметической операции
сложения для полей структурных переменных (рис. 5.5).
Рис. 5.5
В данной программе подсчитывается общая стоимость заказа
путем сложения соответствующих полей структурных переменных.
Результат работы программы представлен на рис. 5.6.
Рис. 5.6
5.1.4. Инициализация полей структуры
В предыдущих программах для инициализации полей структурных переменных использовалась арифметическая операция присваивания для каждого поля (например, phone1.model_number = 601;
или phone2.model_name = phone1.model_name). В языке С++ возможен еще один способ инициализации структурной переменной:
78
значения полей заключены в фигурные скобки и разделены запятыми.
Упражнение 5.4. Разные способы инициализации структурных переменных (рис. 5.7).
Рис. 5.7
5.1.5. Присваивание структурных переменных
В языке С++ можно присваивать не только значения полей структурных переменных (phone2.model_name = phone1.model_name),
но и значения одних переменных другим переменным.
Упражнение 5.5. Присваивание структурных переменных.
В программе переменной phone2 присваивается значение переменной phone1. При этом значение каждого поля переменной
phone1 присваивается соответствующему полю переменной phone2
(рис. 5.8).
79
Рис. 5.8
Результат работы программы представлен на рис. 5.9.
Рис. 5.9
Обе выводимые строки идентичны, поскольку значению второй переменной было присвоено значение первой переменной.
Замечание. Операция присваивания может быть выполнена
только над переменными одного и того же типа (в данном случае
это тип phone). При попытке выполнить операцию присваивания
над переменными разных типов компилятор выдаст сообщение об
ошибке.
80
Упражнение 5.6. Ошибочное присваивание структурных
переменных (рис. 5.10).
Рис. 5.10
Замечание. Применять к структурным переменным (даже однотипным) другие арифметические операции (сложения, вычитания
и др.) или операции сравнения нельзя. Например, операция сравнения
if (phone2 == phone1)
некорректна, потому что операции == ничего не известно о переменных типа phone (во второй части курса будет показано, что
данная проблема разрешима с помощью перегрузки операций).
5.1.6. Передача структур в качестве параметров
Переменные структурного типа и элементы структуры можно
передавать в функции в качестве параметров. Существует несколько способов передачи, но в данном уроке рассматривается только один
из них, а именно передача структурной переменной по значению.
81
Упражнение 5.7.
в функцию (рис. 5.11).
Передача
структурной
переменной
Рис. 5.11
Как видно из примера, передача в функцию структурных переменных очень похожа на передачу в функцию переменных стандартных типов (int, char, float и др.).
5.1.7. Вложенные структуры
Структуры, как и циклы или условные операторы, допускают
вложенность, т. е. использование структурной переменной
в качестве поля какой-либо другой структуры.
Упражнение 5.8. Использование вложенных структур
(рис. 5.12).
В данной программе структура Distance представляет длину
произвольного объекта в виде двух чисел, одно из которых равняется количеству метров, а другое – количеству сантиметров. Структура Room хранит данные о размерах комнаты, т. е. ее длину и ширину. В программе используется единственная переменная dining
типа Room.
82
Рис. 5.12
Результат работы программы представлен на рис. 5.13.
Рис. 5.13
В данной программе структура Distance является вложенной
по отношению к структуре Room. Если одна структура вложена
в другую, то для доступа к полям внутренней структуры необходимо дважды применить операцию точки (.), например
dining.length.meters = 13;
В этом операторе dining – это имя структурной переменной; length –
имя поля внешней структуры Room; meters – имя поля внутренней
структуры Distance. Таким образом, данный оператор берет поле
83
meters поля length переменной dining и присваивает этому полю
значение, равное 13.
5.1.8. Инициализация вложенных структур
Для инициализации структурной переменной, которая содержит внутри себя поле, также являющееся структурной переменной,
можно использовать и другой способ. Применительно к предыдущей программе он выглядит следующим образом:
Room dining = { {13, 6.5}, {10, 0.0} };
В данном случае каждая структура Distance, входящая в состав типа Room, инициализируется отдельно.
Инициализировав по отдельности каждое из полей типа
Distance, мы инициализировали переменную типа Room, заключив
значения типа Distance в фигурные скобки и разделив их запятыми.
5.2. Перечисления
Помимо структур, в языке С++ существует еще один способ
создания пользовательских типов данных, называемых перечислениями. Перечисления используются в тех случаях, когда переменные создаваемого типа могут принимать заранее известное конечное (и, как правило, небольшое) множество значений.
Упражнение 5.9. Использование перечислений для создания
типа данных, хранящего дни недели (рис. 5.14).
Рис. 5.14
84
Результат работы программы представлен на рис. 5.15.
Рис. 5.15
Объявление типа начинается со слова enum и содержит перечисление всех возможных значений переменных создаваемого типа. Эти значения называются константами перечисляемого типа.
В данной программе перечисляемый тип days_of_week включает
семь констант перечисляемого типа: Sun, Mon, …, Sat. На рис. 5.16
представлен синтаксис спецификатора enum.
Рис. 5.16
Когда тип days_of_week определен, можно определять и переменные этого типа. В данной программе используются две переменные типа days_of_week day1 и day2. Переменным перечисляемого типа можно присваивать любое из значений, указанных при
объявлении типа. Так в программе переменным day1 и day2 присваиваются значения Mon и Thu.
Замечание. Присваивание значений, не указанных при перечислении, например,
day1 = halloween;
не допускается.
Перечисляемые типы данных, в отличие от структур, допускают применение основных арифметических операций, а также
операций сравнения. В частности, в представленной программе
85
производится вычитание двух переменных, а также их сравнение.
Это связано с тем, что внутренне перечисляемые типы данных
представляют собой целые числа, т. е. фактически первое значение
в списке рассматривается как число 0, второе – как число 1 и т. д.
Таким образом, значения от Sun до Sat представляют собой числа
от 0 до 6. Для изменения значения, с которого начинается нумерация, можно с помощью операции присваивания задать это значение
первой из перечисляемых констант, например
enum days_of_week {Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat};
В этом случае следующим по списку константам будут соответствовать числа 2, 3, … , 7. Величину целого числа, соответствующего любому элементу списка, можно изменить через операцию
присваивания.
Замечание. Использование арифметических операций и операций отношения с перечисляемыми типами данных обычно не
несет большой смысловой нагрузки. Например, если определить
тип данных pets, хранящий названия домашних животных, следующим образом:
enum pets {cat, dog, hamster, canary, ocelot};
то смысл выражений dog + cat; или (cat <= canary) и т. п. не ясен.
Замечание. Важным недостатком перечисляемых типов данных является то, что они не распознаются средствами ввода/вывода
С++. Например, результатом работы оператора
cout << day2;
в предыдущей программе будет вывод на экран целого числа 4 (являющегося внутренним представлением переменной day2), а не
слова Thu, формально являющегося значением переменной day2.
Пример. Наиболее используемые перечисляемые типы
данных:
enum months {Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep,
Oct, Nov, Dec}; // месяцы года
enum direction {north, south, east, west}; // стороны света
enum switch {off, on}; // переключатель
enum meridian {am, pm}; // меридиан (до полудня, после полудня)
enum answer {no, yes}; // варианты ответов
86
Задание для самостоятельной работы
1. Любая ценная бумага может быть описана двумя параметрами: названием эмитента ценной бумаги («Газпром», «Сбербанк»
и т. д.) и наименованием типа ценной бумаги (акция, облигация,
опцион и т. д.).
Составьте программу с использованием структуры, позволяющей хранить информацию о ценных бумагах, входящих в состав
инвестиционного
портфеля
клиента
брокерской
фирмы,
а именно: наименование эмитента ценной бумаги, тип ценной бумаги, количество ценных бумаг и цену одной ценной бумаги. Создайте структуру stock, содержащую четыре переменные типов
char*, int и float. Создайте три структурные переменные типа
stock. Инициализируйте поля этих переменных следующими значениями:
• Газпром, акция, 50 шт., 141.56 руб./шт.;
• Сбербанк, акция, 135 шт., 101.87 руб./шт.;
• Газпром, облигация, 214 шт., 1001.26 руб./шт.
Реализуйте в программе расчет стоимости каждой позиции
и общей стоимости инвестиционного портфеля. При организации
вывода информации на экран используйте необходимые манипуляторы и флаги форматирования. Результат работы программы может
выглядеть следующим образом.
2. Расположение точки на плоскости можно задать с помощью
двух координат: x и y. Предположим, что сумма двух точек определяется как точка, имеющая координаты, равные сумме соответствующих координат слагаемых.
Составьте программу, использующую для интерпретации точки на плоскости структуру с названием point. Определите три переменные типа point и инициализируйте две из них с помощью
87
следующих значений: (3, 4) и (-2, 2). Присвойте третьей переменной значение, равное сумме первых двух переменных.
Определите самую дальнюю от начала координат точку. Сложение точек и вывод результата на экран выполните в функции main(),
а определение дальней точки выделите в отдельную функцию. Результат работы программы может выглядеть следующим образом.
3. Как отмечалось выше, стандартные средства ввода/вывода
С++ вместо значений перечисляемых типов данных выводят их
внутреннее представление в виде целых чисел. Для того чтобы преодолеть это ограничение, можно использовать оператор switch,
с помощью которого устанавливается соответствие между значением
переменной перечисляемого типа и ее внутренним представлением.
Составьте программу, которая будет по первой букве, введенной пользователем, выводить на экран полное название должности
сотрудника из имеющегося списка.
Определите в программе перечисляемый тип данных etype,
отражающий названия всех должностей:
enum etype {secretary, engineer, accountant, manager};
По введенному символу определите соответствующее значение переменной, поместите это значение в переменную типа etype,
а затем выведите на экран полное название на русском языке должности, начальную букву которой ввел пользователь.
Возможно, для решения задачи понадобится два ветвления
switch, одно – для ввода символа, другое – для вывода названия
должности.
Для корректного считывания с экрана букв русского алфавита
включите в программу стандартную функцию SetConsoleCP(1251).
Пример взаимодействия программы с пользователем:
88
Глава 6. ФУНКЦИИ
Деление программы на функции − базовый принцип структурного программирования. Функция представляет собой именованное объединение группы операторов. Это объединение может
быть вызвано из других частей программы. Одна из основных причин
создания концепции функций − стремление сократить размер программного кода. Любая последовательность операторов, встречающаяся в программе более одного раза, при вынесении ее в отдельную
функцию сокращает размер программы. Несмотря на то что функция
в процессе выполнения программы исполняется несколько раз, ее код
хранится только в одной области программы. На рис. 6.1 показана
схема вызова функции из разных участков программы.
Рис. 6.1
6.1. Простые функции
Несложные функции уже использовались в предыдущих программах. Рассмотрим технологию работы с функциями на еще одном простом примере.
Упражнение 6.1. Пример простой функции (рис. 6.2).
В данной программе используется две функции: main()
и line(). Функция main() – это основная функция, а функция line() –
это дополнительная функция, которая печатает на экране строку из
45 символов '_', улучшающую зрительное восприятие выводимой
информации.
89
Рис. 6.2
Результат работы программы представлен на рис. 6.3.
Рис. 6.3
Для использования в программе дополнительных (кроме
функции main()) функций необходимо включить в нее три обязательных компонента: объявление функции, ее определение и вызовы.
6.1.1. Объявление функции
Подобно тому, как нельзя использовать переменную, не сообщив компилятору информацию о ней, нельзя и обратиться к функции, не указав в программе ее необходимые атрибуты. Для этого
90
функцию необходимо объявить. В программе из упражнения 6.1
объявление функции line() выглядит следующим образом:
void line();
Объявление функции означает, что где-то ниже в листинге будет содержаться код этой функции. Ключевое слово void указывает
на то, что функция не возвращает никакого значения, а пустые
скобки говорят об отсутствии у функции аргументов.
Замечание. Объявление функции заканчивается точкой с запятой (;) и на самом деле является обычным оператором.
Объявление функции также называют прототипом функции,
поскольку оно является общим представлением или описанием
функции. Прототип говорит компилятору о том, что «функция,
имеющая данные атрибуты, будет написана позже, и можно вызывать эту функцию до того, как будет обнаружен ее код».
6.1.2. Вызов функции
Функция line() трижды вызывается из функции main(). Все
три вызова выглядят одинаково:
line();
Для того чтобы вызвать функцию, понадобилось только ее имя
и круглые скобки. Вызов функции внешне очень похож на прототип, разница заключается лишь в том, что при вызове не указывается тип возвращаемого значения. Вызов функции завершается точкой с запятой (;). Выполнение оператора вызова функции инициирует выполнение самой функции. Это означает, что управление передается операторам функции, которые после своего выполнения
передают управление оператору, следующему за вызовом функции.
6.1.3. Определение функции
Определение функции содержит код функции и состоит из заголовка и тела функции. Тело функции состоит из последовательности операторов, заключенной в фигурные скобки. Заголовок
функции должен соответствовать ее прототипу; имя функции и тип
возвращаемого значения должны совпадать с именем функции
и типом возвращаемого значения, указанным в прототипе. Аргу91
менты функции должны иметь те же типы и следовать в том же порядке, в каком они указаны в прототипе.
Для функции line() определение выглядит следующим образом:
void line() // заголовок функции
{
for(int j = 0; j < 45; j++) // тело функции
cout << '_';
cout << endl;
}
Замечание. Заголовок функции не заканчивается точкой с запятой (;).
При вызове функции программа передает управление первому
оператору тела функции. Затем исполняются операторы, находящиеся в теле функции, после чего управление снова передается вызывающей программе.
Синтаксис функции представлен на рис. 6.4.
Рис. 6.4
6.1.4. Отсутствие определения функции
Определить функцию можно и до ее первого вызова. В этом
случае прототип функции не используется (упражнение 6.2).
Упражнение 6.2. Использование функции без ее объявления
(рис. 6.5).
92
Рис. 6.5
Такой подход удобен для применения в коротких программах,
поскольку не требует наличия прототипа функции. Однако при
этом теряется гибкость работы с функциями. При большом количестве используемых функций необходимо следить за тем, чтобы тело каждой из функций располагалось раньше, чем любой ее вызов
из других функций. Иногда эта задача бывает практически невыполнимой. Кроме того, функцию main(), как правило, располагают
в начале программы, поскольку она всегда выполняется первой.
Поэтому обычно при работе с функциями используется первый способ вставки функций в программу (с использованием прототипов).
6.1.5. Подключение функции, расположенной в отдельном файле
При наличии прототипа вызываемые функции не обязаны размещаться в одном файле с вызывающей функцией, а могут оформляться в виде отдельных файлов. В этом случае прототип функции
помещают в отдельный заголовочный файл, для подключения которого к программе используют препроцессорную директиву
#include. Пример такой организации программы уже рассматривался в упражнении 1.6. В упражнении 6.3 (рис. 6.6) такой подход реа93
лизован для программы из упражнения 6.1. Для этого определение
функции line() выведено в файл prim_3_f.cpp, объявление функции –
в заголовочный файл prim_3.h, а функция main() – в файл
prim_3.cpp. Подключение заголовочного файла производится
в файле prim_3.cpp с помощью директивы #include.
Замечание. В отличие от подключения стандартных библиотечных файлов при подключении заголовочного файла, содержащего прототип функции, созданной пользователем, используются
двойные кавычки (" "), а не угловые скобки (< >).
Упражнение 6.3. Пример многофайловой организации программы с использованием функции (рис. 6.6).
Рис. 6.6
6.1.6. Пользовательские и библиотечные функции
В предыдущих программах уже встречались примеры вызова
библиотечных функций, например
_getche();
Объявления библиотечных функций содержатся в заголовочных файлах, подключаемых к программе с помощью директивы
#include (в случае функции _getche() таким файлом является
94
conio.h). Определение библиотечной функции, скомпилированное
в исполняемый код, находится в библиотечном файле, содержимое
которого автоматически прикомпоновывается к исполняемому коду
программы в процессе его компоновки. Поэтому при использовании библиотечной функции не нужно создавать ее объявление
и определение. При разработке же собственной функции и объявление, и определение функции должны присутствовать в исходном
тексте программы.
6.2. Передача аргументов в функцию
Аргументом называется единица данных (например, переменная типа int), передаваемая программой в функцию. Аргументы
позволяют функции оперировать различными значениями или выполнять различные действия в зависимости от переданных значений.
6.2.1. Передача констант в функцию
Функцию line() можно сделать более гибкой, а именно такой,
чтобы она выводила не фиксированное (45 в предыдущих программах), а любое заданное количество символов, а также не фиксированный символ (символ '_' в предыдущих программах), а любой
произвольный символ. В этом случае символ, предназначенный для
вывода на экран, и число его повторений необходимо использовать
в качестве аргументов функции. Этот способ передачи данных
в функцию реализован в упражнении 6.4.
В прототипе функции в скобках указываются типы данных, которые будут иметь передаваемые в функцию аргументы, т. е. char
и int. При вызове функции вместо аргументов в скобках указываются их конкретные значения (константы '_' и 45 или '=' и 23).
95
Упражнение 6.4. Пример передачи константных значений
аргументов в функцию (рис. 6.7).
Рис. 6.7
Результат работы программы представлен на рис. 6.8.
Рис. 6.8
Переменные, используемые внутри функции для хранения
значений аргументов, называются формальными параметрами.
В функции intchar() в качестве таких параметров выступают переменные ch и n. В отличие от прототипа функции (где, как правило,
указываются только типы данных для аргументов функции), при
определении функции в ее заголовке необходимо указать и типы,
и имена параметров:
void intchar (char ch, int n)
96
Формальные параметры используются внутри функции так
же, как обычные переменные. Когда производится вызов функции,
формальные параметры принимают значения фактических аргументов, заданных при вызове, и функция выполняется.
Соответствие между аргументами и формальными параметрами устанавливается по их взаимному расположению в списках.
По умолчанию аргументы передаются в функцию по значению.
6.2.2. Передача переменных в функцию
В предыдущей программе роль аргументов функции intchar()
выполняли константы: '_', 43 и т. д. В следующей программе
(упражнение 6.5) в функцию передаются значения переменных.
Программа включает в себя ту же функцию intchar(), но теперь
символ заполнения и число его повторений задаются пользователем
в функции main(), а затем передаются в качестве аргументов
в функцию intchar().
Упражнение 6.5. Пример передачи переменных в функцию
(рис. 6.9).
Рис. 6.9
97
Пример работы программы представлен на рис. 6.10.
Рис. 6.10
Замечание. Как и в случае констант, типы переменных, используемых в качестве аргументов функции, должны совпадать
с типами, указанными в объявлении и определении функции.
В данном случае это требование сводится к тому, чтобы в функции
main() переменная chin имела тип char, а переменная nin имела
тип int.
6.2.3. Передача аргументов по значению
В программе из упражнения 6.5 в момент вызова функции
intchar() значения переменных chin и nin будут переданы функции.
Функция имеет две переменные для хранения переданных значений. Типы и имена этих переменных указаны в прототипе и определении функции: char ch и int n. При вызове функции переменные
ch и n инициализируются переданными в функцию значениями переменных chin и nin.
При передаче аргумента в функцию С++ по умолчанию создает копию значения аргумента и помещает эту копию во временный
участок памяти, называемый стеком. Затем функция использует
копию значения аргумента для выполнения своих операций. Когда
функция завершается, содержимое стека и все изменения, сделанные функцией в копиях значения аргумента, сбрасываются.
Замечание. Аргументы функции локальны по отношению
к самой функции, т. е. у функции нет доступа к исходным значениям аргументов, и, следовательно, она не может изменять их значения. Поэтому любые изменения, внесенные в аргументы функции во время ее выполнения, никак не влияют на значения передаваемых переменных. Это демонстрирует программа из упражнения 6.6.
98
Упражнение 6.6. Передача аргументов в функцию по значению (рис. 6.11).
Рис. 6.11
Результат работы программы представлен на рис. 6.12.
Рис. 6.12
В программе переменные x и y передаются из функции main()
в функцию swap() по значению, при этом в функции swap() создаются копии значений переменных x и y, которые являются локальными по отношению к этой функции.
Обмен значениями был реализован в функции swap() на этих
локальных переменных, но это никак не повлияло на переменные
x и y в функции main().
99
6.2.4. Использование функций в качестве аргументов функций
В С++ функция может принимать в качестве аргумента вторую функцию, которая возвращает некоторое значение.
Пример. Есть функция int max2(int, int), определяющая максимальное значение из двух переменных. Для вычисления максимума из трех переменных a, b и с можно использовать следующий
оператор:
int max3 = max2(max2(a, b), c);
Замечание. Такой стиль программирования затрудняет чтение
программы и ее отладку.
Пример. Есть функции double(), triple(), square() и cube().
Можно записать следующий оператор:
answer = (double (triple (square (cube (my_value) ) ) ) );
В качестве альтернативного варианта можно было бы каждый
промежуточный результат вычисления присваивать промежуточной переменной:
unsigned long my_value = 2;
unsigned long cubed = cube(my_value); // 2 в степени 3 = 8
unsigned long squared = square(cubed); // 8 в степени 2 = 64
unsigned long tripled = triple(squared); // 64 в степени 3 = 192
unsigned long answer = double(tripled); // 192 в степени 2 = 384
6.2.5. Структурные переменные в качестве аргументов функций
В качестве аргументов функций могут использоваться структурные переменные (передача в функцию структурных переменных
phone1 и phone2 в программе из упражнения 5.7). В программе аргументом функции print() служит переменная b типа phone. Как
и в случаях с переменными стандартных типов, аргументу b присваивается значение передаваемой функцией main() структурной
переменной типа phone (переменные phone1 и phone2).
Как и в случае со стандартными переменными, переменная b
является лишь копией переменных phone1 и phone2, передаваемых
в функцию. Поэтому если бы функция print() изменяла значение
переменной b, например
b.model_number = 350;
b.model_cost = 12 356.0;
100
то сделанные изменения никак не отразились бы на значениях переменных phone1 и phone2.
6.2.6. Имена переменных внутри прототипа функций
В предыдущих примерах при описании прототипов функций
в списке аргументов указывались только типы данных, с которыми
оперирует данная функция. Однако в этом списке также могут присутствовать и имена переменных.
Пример. Пусть функция отображает точку на экране. Прототип такой функции можно было бы записать обычным образом:
void display_point (int, int);
однако более наглядным было бы следующее представление:
void display_point (int horiz, int vert);
Эти два объявления функции абсолютно одинаково воспринимаются компилятором, но в первом случае прототип не несет
информации о том, какой из аргументов будет играть роль абсциссы, а какой – ординаты. Во втором случае эта неопределенность
снята, что снижает вероятность ошибки при вызове функции.
Указывать или не указывать при объявлении функции имена
переменных – это выбор программиста.
Замечание. Имена, указываемые в прототипе функций, никак
не связаны с именами переменных, передаваемых в качестве аргументов при вызове функции, например, вызов функции point() может выглядеть следующим образом:
display_point (x, y);
6.2.7. Значение, возвращаемое функцией
Когда выполнение функции завершается, она может возвратить значение программе, которая ее вызвала. Как правило, возвращаемое функцией значение имеет отношение к решению задачи, возложенной на эту функцию. Следующая программа демонстрирует применение функции, получающей значение веса в фунтах и возвращающей эквивалентное значение этого веса в килограммах.
101
Упражнение 6.7. Пример функции, возвращающей значение
(рис. 6.13).
Рис. 6.13
Результат работы программы представлен на рис. 6.14.
Рис. 6.14
В случае, когда функция возвращает значение, тип этого значения должен быть определен. Тип возвращаемого значения указывается перед именем функции при ее объявлении и определении.
Например, функция lbstoks() возвращает значение типа float, что
и отражено в ее объявлении:
float lbstoks(float);
Первое слово float означает, что функция lbstoks() возвращает
значение типа float, а слово float, заключенное в скобках, указывает
на наличие у функции lbstoks() одного аргумента типа float.
102
В предыдущих примерах функции не возвращали никаких
значений, поэтому перед их именами вместо названия типа находилось ключевое слово void (пустой).
Замечание. Тип значения, возвращаемого функцией, следует
указывать всегда. Если не указать возвращаемый тип данных при
объявлении функции, то по умолчанию возвращаемым типом будет
int. Например, запись прототипа функции в виде
some func();
будет означать возврат функцией some func() значения типа int.
Однако на практике использовать тип, возвращаемый по умолчанию, не следует. Лучше явно указывать возвращаемый тип даже
в том случае, если этим типом является int. Это делает код более
понятным и легко читаемым.
Если функция возвращает значение, то вызов функции рассматривается как выражение, значение которого равно величине,
возвращаемой функцией. Это выражение можно использовать
обычным образом, например, присвоить его значение переменной:
kgs = lbstoks(lbs);
Переменной kgs присвоено возвращаемое функцией lbstoks() значение.
Для возврата функцией значения используется оператор return. В последнем примере функция lbstoks() получает в качестве
аргумента значение веса, выраженное в фунтах, которое хранится
в параметре pounds. Эквивалентный вес в килограммах вычисляется путем умножения переменной pounds на константу и записывается в переменную kilograms, а значение переменной kilograms
возвращается в функцию main() с помощью оператора return
kilograms;.
Замечание. Значение веса в килограммах хранится как
в функции lbstoks() (в переменной kilograms), так и в функции
main() (в переменной kgs). При возвращении функцией значения
происходит копирование значения переменной kilograms в переменную kgs. Функция main() может получить значение переменной
kilograms только через механизм возврата значения; доступ к самой переменной kilograms из функции main() невозможен. Механизм возврата функцией значения показан на рис. 6.15.
103
Рис. 6.15
Замечание. Предыдущая программа содержит несколько необязательных переменных, употребление которых обусловлено исключительно соображениями наглядности и простоты кода.
На практике вместо таких переменных можно использовать сами
функции (упражнение 6.8).
Упражнение 6.8. Пример более компактной программы,
возвращающей значение функции (рис. 6.16).
Рис. 6.16
В теле функции main() теперь отсутствует переменная kgs.
Вместо этого вызов функции lbstokgs(lbs) помещен внутрь оператора cout. В функции lbstokgs() не используется переменная
104
kilograms, а выражение 0.453592 * pounds помещено непосредственно в оператор return.
Из соображений удобочитаемости выражение, входящее в состав оператора return, как правило, заключают в скобки:
return (0.453592 * pounds);
Даже в том случае, когда компилятор не требует наличия скобок
в выражении, они не влияют на процесс вычисления, но в то же
время облегчают чтение листинга программы.
Несмотря на то что исключение необязательных переменных
увеличивает быстродействие программы и сокращает объем занимаемой ею памяти, опытные программисты обычно предпочитают
более длинный код программы «экономичному» коду.
Замечание. Количество аргументов у функции может быть
сколь угодно большим, но возвращаемое значение всегда только
одно. Эта особенность функций является препятствием в тех случаях, когда необходимо вернуть в программу несколько значений.
Для возврата функцией нескольких значений существует три
способа:
• передача аргументов по ссылке;
• передача аргументов с помощью указателей;
• возврат структурной переменной, в полях которой будут
располагаться нужные значения.
6.2.8. Структурная переменная в качестве возвращаемого
значения
Структурные переменные могут не только использоваться
в качестве аргументов функций, но и выступать в виде значения,
возвращаемого функцией. Функция из упражнения 6.9 складывает
две структурные переменные типа time и возвращает результат этого же типа.
105
Упражнение 6.9. Возврат функцией структурной переменной (рис. 6.17).
Рис. 6.17
Результат работы программы представлен на рис. 6.18.
Рис. 6.18
106
Программа просит пользователя ввести два значения времени
в виде числа минут и секунд, затем складывает введенные значения
путем вызова функции add_time() и выводит результат на экран
с помощью функции print_time().
Функция add_time() возвращает сумму аргументов t1 и t2
в виде значения типа time, которое в функции main() присваивается структурной переменной t3.
Замечание. В данной программе, в отличие от предыдущих
примеров, используется не одна, а две функции. Функции программы могут располагаться в любом порядке. Единственным условием
является появление прототипа каждой из функций до того, как
производится первое обращение к данной функции.
6.3. Ссылки на аргументы
Ссылка – это другое имя уже существующего объекта, т. е. это
еще одно имя переменной, которое называется псевдонимом, или
альтернативным именем переменной. Как правило, ссылки используются при работе с функциями.
При создании ссылки она инициализируется с помощью имени другого объекта, или адресата. Так, если в программе есть целочисленная переменная с именем someInt, то можно создать ссылку
на эту переменную:
int & rSomeRef = someInt;
где rSomeRef – это ссылка на целочисленное значение, инициализированная адресом переменной someInt. Значением ссылки после
ее объявления и инициализации становится адрес объекта.
Пример.
int L = 100; // объявлена и инициализирована переменная L
int & RL = L; // значением ссылки RL стал адрес переменной L
Для объявления ссылки нужно указать тип объекта, адресата,
за которым следует оператор ссылки (&), а за ним – имя ссылки:
<тип> & <имя ссылки> = <выражение>;
т. е. после объявления и инициализации <имя ссылки> становится
еще одним именем (псевдонимом) существующей переменной.
Функционально ссылка ведет себя как переменная того же, что
и ссылка, типа.
107
6.3.1. Особенности применения ссылок
При работе со ссылками необходимо помнить следующие
правила:
• определяя переменную типа ссылки, ее необходимо инициализировать, указав, на какую переменную она ссылается, например:
int & xref ; // неправильное объявление ссылки;
int & xref = x; // правильное объявление ссылки;
• синтаксически обращение к ссылке аналогично обращению
к переменной;
• нельзя переопределить ссылку, т. е. изменить указание на
какой объект она ссылается. Ссылка подобна константе, т. е. после
инициализации ее значение изменить нельзя. Ни одна из операций
не действует на ссылку, а относится к тому объекту, с которым она
связана. То есть если после определения ссылки xref выполнить
присваивание xref = y, то выполнится присваивание значения
переменной y той переменной, на которую ссылается xref.
Ссылка xref по-прежнему будет ссылаться на переменную x
(упражнение 6.10).
Упражнение 6.10. Пример использования ссылки (рис. 6.19).
Рис. 6.19
108
Результат работы программы представлен на рис. 6.20.
Рис. 6.20
В данной программе после операции присваивания xref = y
переменной x присваивается значение переменной y, после чего
в результате выполнения операции сложения с присваиванием ее
значение увеличивается на два. Значение переменной типа ссылки
(т. е. переменной xref) всегда равно значению той переменной,
на которую она ссылается (в данном случае на переменную x).
6.3.2. Передача в функцию аргументов стандартных типов
по ссылке
В случае передачи аргументов в функцию по значению функция не имеет доступа к переменным-аргументам, а работает со сделанными для нее копиями значений. Такой механизм полезен в тех
случаях, когда у функции нет необходимости изменять значения
аргументов. При этом осуществляется защита аргументов от несанкционированного доступа.
Передача аргументов по ссылке происходит по-другому. Вместо значения переменной функции передается ссылка на эту переменную. При этом в стек помещается не сам объект, а его адрес.
То есть фактически в функцию передается адрес переменнойаргумента.
При использовании ссылок функция приобретает возможность
изменять исходные данные в вызывающей функции, хотя при этом
сам вызов функции ничем не отличается от обычного вызова.
К достоинствам ссылочного механизма также относится возмож109
ность возвращения функцией программе не одного, а множества
значений.
В следующем примере (упражнение 6.11) ссылочный механизм используется для передачи в функцию обычной переменной.
Функция main() запрашивает у пользователя число типа float, передает его в функцию intfrac(), которая выделяет у него целую
и дробную части и возвращает их в функцию main(). Для выделения целой части в функции intfrac() используется механизм явного
преобразования типов с помощью оператора static_cast<type>
(name), в котором type – это тип данных, к которому необходимо
привести переменную с именем name.
Упражнение 6.11. Передача аргументов в функцию по
ссылке (рис. 6.20).
Рис. 6.21
Возможный результат программы представлен на рис. 6.22.
Рис. 6.22
110
Использование ссылочного механизма позволяет вернуть
в функцию main() не одно (как в случае передачи аргумента по
значению и использования оператора return), а два значения (intp
и fracp).
Аргументы, передаваемые по ссылке, обозначаются знаком &
(знак амперсанда), который следует за типом аргумента, например
float& intp. Знак & означает, что intp является псевдонимом для
той переменной, которая будет передана в функцию в качестве аргумента. Другими словами, используя имя intp в функции intfrac(),
фактически мы оперируем значением переменной intpart функции
main(). Знак & символизирует ссылку, поэтому запись
float& intp
означает, что intp является ссылкой на переменную типа float. Точно также fracp является ссылкой на переменную типа float (fracpart).
Если какие-либо аргументы передаются в функцию по ссылке,
то в прототипе функции необходимо с помощью знака & указывать
соответствующий аргумент-ссылку, например
void intfrac(float, float&, float&);
Аналогичным образом знаком & отмечаются ссылочные параметры в определении функции. Однако при вызове функции нет
смысла сообщать, будет ли передан аргумент по значению или по
ссылке, поэтому при вызове функции амперсанды отсутствуют.
Замечание. В то время как переменные intpart и fracpart передаются в функцию по ссылке, переменная number передается по
значению. Это связано с тем, что в данной программе не требуется
изменять значение, хранящееся в переменной number.
6.3.3. Передача структурных переменных по ссылке
Механизм передачи по ссылке можно применять не только
к переменным стандартных типов, но и к структурным переменным. Следующая программа производит масштабирование значений типа Distance. Масштабирование представляет собой умножение нескольких длин на один и тот же коэффициент. Масштабирование можно применить, например, к трем измерениям помещения,
что приведет к изменению размеров помещения с одновременным
сохранением его пропорций.
111
Программа инициализирует две переменные d1 и d2 типа
Distance и выводит их на экран. Затем вызывается функция scale(),
которая производит умножение длины d1 на коэффициент 0.5,
а длины d2 – на коэффициент 0.25. После этого на экран выводятся
новые значения переменных d1 и d2.
Упражнение 6.12. Передача структурной переменной по
ссылке (рис. 6.23).
Рис. 6.23
Результат работы программы представлен на рис. 6.24.
Рис. 6.24
112
Замечание. Значения переменных d1 и d2, передаваемые по
ссылке, изменяются непосредственно в функции, при этом функция
не возвращает никакого значения.
Замечание. Поскольку в функции scale() изменяется только
один аргумент, этот аргумент можно передавать по значению
и возвращать масштабированную величину с помощью оператора
return. В этом случае вызов функции должен выглядеть следующим образом:
d1 = scale(d1, 0.5);
d2 = scale(d2, 0.25);
6.4. Перегрузка функций
Во избежание дублирования функций С++ позволяет определять несколько функций с одним и тем же именем. Возможность
выбора среди нескольких функций называется перегрузкой. Перегруженная функция выполняет различные действия, зависящие от
типов данных, передаваемых ей в качестве аргументов.
Для перегрузки функции необходимо определить две функции
с одним и тем же именем, которые отличаются либо количеством
параметров, либо их типом.
6.4.1. Переменное число аргументов функции
В программе из упражнения 6.1 функция line() выводила
на экран линию из сорока пяти символов '_', а функция intchar()
в программе из упражнения 6.4 выводила заданный символ заданное число раз, используя два аргумента. Можно создать третью
функцию charline(), которая всегда печатает сорок пять символов,
но позволяет задавать печатаемый символ. Все три функции, line(),
intchar() и charline(), выполняют схожие действия, но их имена
различны. Следующая программа демонстрирует возможность использования одного и того же имени для всех трех функций, несмотря на различные наборы аргументов для каждой из них.
113
Упражнение 6.13. Пример перегрузки функции (рис. 6.25).
Рис. 6.25
Результатом работы программы будет вывод на экран трех
разных линий (рис. 6.26).
Рис. 6.26
В программе содержатся три функции с одинаковым именем.
Каждой из функций соответствует свое объявление, определение
и вызов. Какая из функций будет выполнена при вызове, зависит
от количества аргументов, указанных при вызове.
114
6.4.2. Различные типы аргументов
В предыдущем примере несколько функций имеют одно
и то же имя, но различное число аргументов. Аналогично можно
определить несколько функций с одинаковыми именами и одинаковым количеством аргументов, но различными типами аргументов. Следующая программа использует перегруженную функцию
для вывода длины в формате метры/сантиметры. Аргументом
функции может являться как структурная переменная типа
Distance, так и обычная переменная типа float. В зависимости
от типа аргумента, указанного при вызове, будет исполняться одна
из двух функций.
Упражнение 6.14. Еще один пример перегрузки функции
(рис. 6.27).
Рис. 6.27 (начало)
115
Рис. 6.27 (окончание)
Возможный
на рис. 6.28.
результат
работы
программы
представлен
Рис. 6.28
Использование перегруженных функций упрощает процесс
разработки программы, так как в этом случае отпадает необходимость в запоминании большого количества имен функций.
6.5. Рекурсия
Функции дают возможность использовать такое средство программирования, как рекурсия. Рекурсия позволяет функции вызывать саму себя на выполнение. Следующая программа подсчитывает факториал произвольного числа с помощью рекурсии.
116
Упражнение 6.15. Применение рекурсии (рис. 6.29).
Рис. 6.29
Замечание. Рекурсивная функция должна включать в себя
условие окончания рекурсии. Иначе рекурсия будет происходить
бесконечно. В последнем примере роль такого условия играет ветвление if в функции factfunc(). При достижении параметром n значения, равного единице, вместо очередного рекурсивного вызова
произойдет возврат этого значения предыдущему (т. е. четвертому)
вызову и его умножение на значение параметра, хранящегося
в этом вызове (т. е. на два). После чего возврат значений продолжится по цепочке до первого вызова функции.
6.6. Встраиваемые функции
Использование функций экономит размеры занимаемой памяти, однако увеличивает время выполнения программы в результате
генерации и выполнения многочисленных инструкций по взаимодействию функции с процессором компьютера. К этим инструкциям относятся команды переходов, команды, помещающие в стек
и извлекающие из стека аргументы функций, команды перехода из
117
функции обратно в программу, команды, работающие с возвращаемым функцией значением, и проч.
Для того чтобы сократить время выполнения небольших
функций, можно дать указание компилятору при каждом вызове такой функции вместо команды перехода в функцию производить
подстановку операторов, выполняемых функцией, непосредственно в код программы. Различия между обычными и встраиваемыми
функциями показаны на рис. 6.30.
Рис. 6.30
Встраиваемые функции пишутся так же, как и обычные, но
при компиляции их исполняемый код встраивается в исполняемый
код программы. При этом листинг программы сохраняет свою организованность и четкость, поскольку функция остается независимой частью программы.
Чтобы сделать функцию встраиваемой, необходимо лишь указать ключевое слово inline в ее прототипе. В программе из упражнения 6.7 функция lbstokg() сделана встраиваемой.
118
Упражнение
(рис. 6.31).
6.16.
Применение
встроенных
функций
Рис. 6.31
Замечание. Встраиваемыми следует делать только очень короткие, содержащие несколько операторов, функции.
Замечание. Ключевое слово inline является лишь рекомендацией компилятору, которая может быть им проигнорирована.
В этом случае функция будет скомпилирована как обычная. Такое
может произойти, например, в том случае, если компилятор посчитает функцию слишком длинной для того, чтобы делать ее встраиваемой.
6.7. Аргументы по умолчанию
Функцию, имеющую аргументы, можно организовать таким
образом, что ее можно будет вызывать, вообще не указывая при
этом никаких аргументов. Для этого при объявлении функции
необходимо задать значения аргументов по умолчанию.
В программе из упражнения 6.13 для обработки передаваемых
значений различных типов использовались три различные функции
119
с одним именем. Программа из упражнения 6.17 делает то же самое
с помощью одной функции и аргументов по умолчанию.
Упражнение 6.17. Применение аргументов по умолчанию
(рис. 6.32).
Рис. 6.32
Функция repchar() имеет два аргумента. Ее вызов производится из функции main() три раза: первый раз – без аргументов,
второй раз – с одним аргументом, третий раз – с двумя аргументами. Первые два вызова работают потому, что вызываемая функция
имеет значения аргументов по умолчанию, которые используются
в том случае, если при вызове значение аргумента не передается.
Замечание. Значения по умолчанию можно определять
и в том случае, если в прототипе функции фигурируют имена переменных, например:
void repchar(char reptChar = '_', int numberReps = 45);
Функция может иметь несколько аргументов по умолчанию.
При вызове функции можно опускать только аргументы, стоящие
в конце списка. Например, можно не указать три последних аргумента, но нельзя одновременно пропустить предпоследний аргумент и указать последний (компилятор в этом случае не сможет
узнать, какой из аргументов, стоящих в середине списка, пропущен).
Замечание. Задание значений аргументов по умолчанию полезно в тех случаях, когда аргументы функции часто принимают
120
какое-то одно значение (например, возведение числа в квадрат).
Значения по умолчанию можно использовать и в тех случаях, когда
необходимо модифицировать уже написанную функцию путем добавления в нее нового аргумента. В этом случае не нужно будет
изменять вызовы функции, так как значение нового аргумента будет задано по умолчанию.
6.8. Константные аргументы функции
Как отмечалось выше, ссылочный механизм передачи аргументов в функцию позволяет функции изменять значения аргументов в вызывающей программе. Существуют и другие причины для
использования ссылок на аргументы функции, и одной из таких
причин является эффективность. Переменные, используемые в качестве аргументов, могут иметь большой размер, например структурные переменные с большим числом полей. В случае, когда аргумент занимает много места в памяти, его передача по ссылке является гораздо более эффективной, поскольку в этом случае
в функцию передается не значение переменной, а только ее адрес.
В том случае, если ссылочный механизм используется только
из соображений эффективности и при этом функция не должна
иметь свободного доступа к аргументу и изменять его значение,
можно указать модификатор const перед соответствующим аргументом в прототипе функции. В упражнении 6.18 показан пример
использования этого метода.
Упражнение 6.18. Применение константных аргументов
(рис. 6.33).
121
Рис. 6.33
В программе функция aFunc() не должна изменять значение
переменной beta, поэтому для нее используется модификатор const
и в прототипе функции, и в ее определении. Поэтому любая попытка изменения значения переменной beta функцией aFunc() повлечет за собой сообщение компилятора об ошибке.
Замечание. Использование модификатора const в прототипе
функции является единственным способом передачи в функцию
константной переменной с помощью ссылки (с передачей константного аргумента по значению подобной проблемы не возникает, поскольку определение константной переменной гарантирует,
что ни функция, ни любые операторы программы не способны изменить ее значения).
6.9. Область видимости и класс памяти
Существуют два аспекта, касающихся взаимодействия переменных и функций. Область видимости определяет, из каких частей программы возможен доступ к переменной, а класс памяти –
время, в течение которого переменная существует в памяти.
Существуют два типа области видимости переменной: локальная и область видимости файла. Переменные, имеющие локальную
область видимости, доступны внутри того блока, в котором они
определены. Переменные, имеющие область видимости файла, доступны из любого места файла, в котором они определены.
Блок − это часть программы, заключенная в фигурные скобки.
В языке С++ существуют три области, где может быть объявлена переменная:
• вне каких-либо функций (в том числе и функции main()) –
такая переменная называется глобальной и может использоваться
в любом месте программы от места объявления до конца программы;
• внутри блока (в том числе внутри тела функции) – такая
переменная называется локальной и может использоваться только
внутри блока;
• как параметр функции – такая переменная является локальной для тела функции.
В языке С++ существует два класса памяти: automatic (автоматический) и static (статический).
122
Время жизни переменных, имеющих класс памяти automatic,
равно времени жизни функции, внутри которой они определены.
Время жизни переменных, имеющих класс памяти static, равно времени жизни всей программы.
6.9.1. Локальные переменные
Локальные переменные определяются внутри тех блоков или
функций, в которых они используются.
Пример.
void somefunc();
{
int somevar; // переменные определены внутри
float othervar; // функции somefunc()
// другие операторы
}
Замечание. Переменные, определяемые внутри функции
main(), ничем не отличаются от переменных, определяемых внутри
других функций.
Локальные переменные могут определяться внутри блока
в теле функции.
Пример.
void somefunc();
{
for(int i = 0; i < N; i++) // переменная i определена
// внутри внешнего цикла for
{
for(int j = 100; j>0; j--) // переменная j определена
// внутри внутреннего цикла for
{// операторы внутреннего цикла for}
// другие операторы внешнего цикла for
}
}
Локальная переменная может быть объявлена как аргумент
функции.
123
Пример.
void somefunc(int somevar, float othervar);
{
// другие операторы
// переменные somevar и othervar определены как аргументы
// функции somefunc()
// другие операторы
}
6.9.1.1. Классы памяти локальных переменных
Локальная переменная не существует в памяти до тех пор, пока не будет вызвана функция, в которой она определена. Так, переменные somevar и othervar в предыдущем примере не существуют
до тех пор, пока не будет вызвана функция somefunc(). По завершении работы функции управление передается вызывающей программе, переменные уничтожаются, а их значения теряются.
Название автоматический класс памяти как раз указывает на
то, что переменные автоматически создаются при входе в функцию
и автоматически уничтожаются при выходе из нее. Период времени
между созданием переменной и ее уничтожением называется временем жизни переменной. Время жизни автоматической локальной переменной равно времени, проходящему с момента создания
переменной в теле функции (т. е. с момента вызова функции) до завершения исполнения функции.
Ограниченное время жизни переменой позволяет экономить
память. Если функция не вызывается, память под ее локальные переменные не выделяется. Удаление локальных переменных из памяти при завершении работы функции позволяет распределить
освободившуюся память между локальными переменными других
функций.
Время жизни у статической локальной переменной иное: она
существует от момента первого вызова функции и далее на всем
протяжении работы программы.
Статические локальные переменные используются в тех случаях, когда необходимо сохранить значение переменной в памяти
между вызовами функций.
124
6.9.1.2. Область видимости локальных переменных
У автоматических и статических локальных переменных область видимости одинаковая – функция, в которой они определены.
Область видимости переменной определяет участки программы, откуда возможен доступ к этой переменной. Это означает, что
внутри области видимости переменной операторы могут обращаться к ней по имени и использовать ее значение при вычислении выражений. За пределами области видимости попытки обращения
к переменной приведут к выдаче сообщения о том, что переменная
неизвестна.
Переменные, определенные внутри функции, видимы, или доступны, только внутри функции, в которой они определены. Следующий фрагмент программы демонстрирует варианты некорректного использования локальных переменных.
Упражнение 6.19. Область видимости локальных переменных (рис. 6.34).
Рис. 6.34
Переменная nextvar невидима внутри функции somefunc(),
а переменные somevar и othervar − внутри функции otherfunc().
Ограничение области видимости переменной обеспечивает
организованность программы и ее модульность. Программист может быть уверен, что переменные одной функции защищены от несанкционированного доступа других функций, поскольку во всех
остальных функциях эти переменные невидимы.
125
Замечание. Ограничение области видимости является важным
как для процедурного, так и для объектно-ориентированного программирования.
В случае, когда переменная определяется внутри функции,
ее область видимости и время жизни ограничены этой функцией:
переменная существует только во время исполнения функции и видима только внутри функции.
Замечание. Область видимости и время жизни совпадают не
для всех переменных.
6.9.1.3. Инициализация локальных переменных
При создании локальной переменной компилятор не пытается
собственными силами инициализировать ее. В этом случае переменная имеет неопределенное значение, которое может быть как
нулевым, так и ненулевым (как правило, это большое отрицательное число, или «мусор»). Для того чтобы переменная получила
определенное значение, ее необходимо явно инициализировать,
например
int n = 48;
Теперь переменная n начнет существование со значением, равным 48.
6.9.1.4. Статические локальные переменные
В отличие от автоматической локальной переменной область
видимости и время жизни статической локальной переменной не
совпадают: статическая локальная переменная (так же как и автоматическая) видима только внутри функции, в которой она объявлена, однако после инициализации она существует на протяжении
всего времени работы программы.
В следующем примере функция getavg() подсчитывает среднее арифметическое значений, полученных ею, с учетом нового переданного ей значения. Функция запоминает количество переданных ей значений и с каждым новым вызовом увеличивает это значение на единицу. Функция возвращает среднее арифметическое
переданных значений, полученное путем деления суммы всех переданных значений на количество этих значений.
126
Упражнение 6.20. Статические локальные переменные
(рис. 6.35).
Рис. 6.35
Результат работы программы представлен на рис. 6.36.
Рис. 6.36
Статические переменные total и count функции getavg() сохраняют свои значения после завершения этой функции, поэтому
при следующем вызове этими значениями снова можно пользоваться.
127
6.9.1.5. Инициализация статических локальных переменных
Инициализация статических переменных, таких как total
и count в функции getavg(), происходит только один раз – во время
первого вызова функции. При последующих вызовах повторной
инициализации не происходит, как это должно было бы произойти
с обычными локальными переменными.
6.9.2. Глобальные переменные
В отличие от локальных переменных, определяемых внутри
функций, глобальные переменные определяются вне каких-либо
функций. Глобальная переменная видима из всех функций данного
файла и, потенциально, из других файлов. Точнее говоря, глобальная переменная видна из всех функций файла, которые определены
позже, чем сама глобальная переменная. Как правило, необходимо,
чтобы глобальные переменные были видимы из всех функций, поэтому их определения располагают в начале листинга. Иногда глобальные переменные называют внешними, поскольку они являются
внешними по отношению к любой из функций программы.
В программе из упражнения 6.21 глобальная переменная используется тремя функциями. Функция getachar() считывает символы с клавиатуры до тех пор, пока пользователь не нажмет клавишу Enter. Она использует библиотечную функцию _getch(), действие которой напоминает действие функции _getche() с той разницей, что _getch() не отображает вводимые символы на экране.
Функцию по выводу вводимых символов на экран выполняет вторая функция putachar(). В результате совместной работы двух
функций на экране отображаются все вводимые с клавиатуры символы.
128
Упражнение 6.21. Использование глобальной переменной
(рис. 6.37).
Рис. 6.37
Пример работы программы приведен на рис. 6.38.
Рис. 6.38
В данной программе переменная ch, определяемая в начале
файла перед объявлениями функций и не принадлежащая какойлибо из функций, является глобальной, или внешней.
6.9.2.1. Область видимости и время жизни глобальных
переменных
Глобальные переменные видимы от места своего объявления
до конца того файла, в котором они определены. В программе из
упражнения 6.21 любая функция, определенная позднее, чем переменная ch, имеет к ней доступ (в данном случае – это все три функ129
ции: main(), getachar() и putachar()). Таким образом, областью видимости переменной ch является весь исходный файл. Если бы переменная ch была определена между функциями main()
и getachar(), то она была бы видна из функций getachar()
и putachar(), но не была бы видна из функции main().
Глобальные переменные имеют статический класс памяти,
что означает их существование в течение всего времени выполнения программы. Память под эти переменные выделяется в начале
выполнения программы и закрепляется за ними вплоть до ее завершения.
Замечание. Для того чтобы глобальные переменные были статическими, не обязательно указывать в их определении ключевое
слово static: статический класс памяти определен для этих переменных по умолчанию.
6.9.2.2. Инициализация глобальных переменных
Если глобальная переменная инициализируется явно, например
int exvar = 55;
то подобная инициализация происходит в момент загрузки программы. Если же явной инициализации не происходит, например
int exvar ;
то в момент создания этой переменной ей автоматически будет
присвоено значение, равное 0.
Замечание. В этом состоит различие между локальными
и глобальными переменными. Неинициализированные явно локальные переменные после своего объявления содержат случайное
число.
Области видимости и классы памяти переменных в языке С++
представлены на рис. 6.39.
Рис. 6.39
130
6.9.2.3. О конфликте имен глобальных и локальных переменных
При наличии в программе глобальной переменной ее имя может конфликтовать с именем локальной переменной. В таком случае приоритет предоставляется локальной переменной.
Однако возможны ситуации, когда необходимо обратиться
к глобальной переменной, чье имя конфликтует с именем локальной переменной. В таких случаях следует использовать оператор
расширения области видимости (::) (упражнение 6.22).
Упражнение 6.22. Конфликт глобальной и локальной переменных (рис. 6.40).
Рис. 6.40
Результат работы программы представлен на рис. 6.41.
Рис. 6.41
6.9.2.4. Роль глобальных переменных
Глобальные переменные используются в тех случаях, когда их
значения должны быть доступны одновременно нескольким функ131
циям. Обычно такие переменные содержат наиболее важную информацию, используемую программой. Однако использование глобальных и локальных переменных может внести путаницу в программу. Всеобщая доступность глобальных переменных приводит
к их незащищенности от несанкционированного и некорректного
доступа со стороны функций. Поскольку любая функция может изменить значение глобальной переменной, очень часто бывает
сложно отследить все функции, которые потенциально могли бы
изменить данную переменную.
Общая рекомендация: минимизация использования глобальных
переменных при создании программ. Более предпочтительным вариантом является объявление переменных внутри функции main()
с последующей передачей их в качестве аргумента в те функции,
которым она нужна (при этом в стек помещается временная копия
переменной, и оригинал не изменяется).
6.10. Генерация случайных чисел в С++
При составлении программ довольно часто для выполнения
каких-либо действий бывает нужно получать случайные числа. Для
получения случайного числа можно воспользоваться стандартными
библиотечными функциями С++. Как правило, для генерации случайных чисел используют функцию rand(), описанную в библиотеке stdlib.h. Данная функция генерирует случайные целые числа
в диапазоне от 0 до 32 767.
Упражнение 6.23. Простейший генератор случайных чисел
(рис. 6.42).
Рис. 6.42
132
Программа
(рис. 6.43).
генерирует
десять
случайных
целых
чисел
Рис. 6.43
Однако при каждом новом запуске данной программы будет
генерироваться одна и та же последовательность случайных чисел.
Это связано с тем, что случайное число, генерируемое функцией
rand(), зависит от содержащегося внутри нее стартового числа
next, которое в самой функции rand() установлено равным единице. Изменить это стартовое число в процессе обращения к функции rand() не представляется возможным, так как список ее аргументов пуст.
Для того чтобы при каждом новом запуске программы получать разные случайные числа, необходимо использовать функцию
rand() совместно с другой библиотечной функцией srand(). Определение этой библиотечной функции выглядит следующим
образом:
void srand(unsigned int seed)
{
next = seed;
}
Функция srand() изменяет переменную next функции rand(),
присваивая ей значение, получаемое в качестве аргумента. Поэтому, передавая в функцию srand() разные значения переменной
seed, можно каждый раз заново инициализировать генератор случайных чисел (функцию rand()) и получать разные последовательности случайных чисел (упражнение 6.24).
133
Упражнение 6.24. Усовершенствованный генератор случайных чисел (рис. 6.44).
Рис. 6.44
Результатом работы программы будут разные последовательности случайных чисел для различных значений числа n (рис. 6.45).
Рис. 6.45
Как правило, в качестве величины, передаваемой в функцию
srand(), используют системное время в секундах, так как это число
всегда будет разным. Для передачи в функцию srand() текущего
системного времени используется еще одна библиотечная функция
time(), описанная в библиотеке time.h. Для того чтобы эта функция
возвращала текущее время в секундах (секунды отсчитываются от
00:00:00), нужно вызывать ее с параметром NULL.
134
Упражнение 6.25. Усовершенствованный генератор случайных чисел (рис. 6.46).
Рис. 6.46
Результат работы программы для четырех последовательных
запусков программы представлен на рис. 6.47.
Рис. 6.47
Задание для самостоятельной работы
1. Составьте программу, которая будет складывать два значения в формате времени 12:59:59. Для этого создайте структуру
с именем times, содержащую три поля типа int. Назовите их hours,
minutes и seconds. Реализуйте ввод с экрана двух значений времени
в формате 12:59:59, сохраните их в переменных типа times, затем
135
переведите оба значения в секунды, сложите их, переведите результат в исходный формат, сохраните его в переменой типа times
и выведите на экран. Ввод и вывод данных выполните в функции
main(). Для преобразования данных создайте две функции. Первая,
time_to_secs(), принимает в качестве аргументов два значения типа
times и возвращает значение типа long (сумма в секундах). Вторая,
secs_to_time(), в качестве аргумента принимает число секунд, имеющее тип long, и возвращает эквивалентное значение типа times.
2. Используя передачу аргументов по ссылке, измените программу из упражнения 6.6 таким образом, чтобы после изменения
переменных x и y в функции swap() их значения в функции main()
тоже изменились. Результат работы программы представлен ниже.
3. Составьте программу, которая будет отображать изменение
состояния инвестиционного портфеля клиента брокерской фирмы
из задания для самостоятельной работы № 1 предыдущего урока
с учетом совершенных им сделок купли-продажи, а именно:
• акции Газпрома: покупка 50 шт. по цене 136.54 руб./шт.;
• акции Сбербанка: продажа 35 шт. по цене 103.14 руб./шт.;
• облигации Газпрома: продажа 114 шт. по цене 1004.63 руб./шт.
Для решения задачи создайте новую вложенную структуру
sec, содержащую три поля типа stock: stock initial, stock buy
и stock sell для хранения всей информации о каждой из ценных бумаг (начальное состояние, параметры купленных/проданных бумаг). Объявите три структурные переменные типа sec (sec stock_1,
sec stock_2, sec stock_3) для каждой из ценных бумаг. Инициализацию полей каждой переменной произведите в функции main().
Пересчет состояния портфеля с учетом заключенных сделок, а также вывод на экран первоначального портфеля, заключенных сделок
и результирующего портфеля осуществите в отдельных функциях.
Для хранения информации о новом состоянии портфеля используйте соответствующие структурные переменные типа stock для каждой из ценных бумаг (например, stock res1, stock res2, stock res3).
Возможный результат работы программы представлен ниже.
136
4. Реализуйте перегрузку функции swap() из задания № 3
предыдущей работы так, чтобы функция могла принимать в качестве аргументов и значения типа times (см. задание № 1). Возможный результат работы программы представлен ниже.
5. Составьте программу, которая будет в бесконечном цикле
выводить на экран случайные числа в заданном пользователем диапазоне. Для ввода данных и вывода результата создайте отдельные
функции. Для передачи границ диапазона в функцию main() используйте ссылочный механизм. Для вывода результата создайте inline-функцию с передачей аргументов по значению. Возможный результат работы программы представлен ниже.
137
Глава 7. МАССИВЫ И СТРОКИ
7.1. Массивы
7.1.1. Основные понятия
Механизмом, позволяющим группировать вместе однотипные
данные, является массив. Данные, сгруппированные в массиве, могут быть как основных типов, таких как int или float, так и определенных пользователем типов, таких как структуры или объекты.
Массивы, подобно структурам, объединяют некоторое количество переменных в большой блок. Но в структурах обычно
группируются переменные разных типов, а в массивах группируются данные одного типа. Более важное отличие массивов
от структур заключается в том, что доступ к элементам структуры можно получить по имени, а доступ к элементам массива –
по индексу.
Упражнение 7.1. Пример простейшего массива (рис. 7.1).
Рис. 7.1
Результат работы программы − вывод на экран введенных
пользователем данных о возрасте некоторых людей (рис. 7.2).
138
Рис. 7.2
7.1.2. Определение массива
Как и другие переменные в С++, массив должен быть определен перед его использованием. Определение массива включает
в себя тип хранящихся в нем данных, имя и размер массива. Размер
массива определяет количество элементов, которое может содержать массив, следует за его именем и заключается в квадратные
скобки (рис. 7.3).
Рис. 7.3
Размер массива может указываться любым константным выражением целого типа. В случае, когда размерность массива явно
не указана, она автоматически определяется компилятором
по списку значений при инициализации массива.
Члены массива называются элементами. Первый элемент
массива всегда имеет номер 0.
7.1.3. Доступ к элементам массива
Доступ к элементам массива осуществляется по индексу. Индексом массива называется выражение, содержащееся в квадратных скобках после имени массива. В программе из упражнения 7.1
выражение для элемента массива выглядит как age[j], в котором
139
переменная цикла j играет роль индекса массива. Изменяя значение
индекса массива j, можно получить доступ к каждому из его элементов: age[0] ссылается на первый элемент, age[1] – на второй,
age[2] – на третий и age[3] – на четвертый.
Упражнение 7.2. Вычисление средней цены акции (рис. 7.4).
Рис. 7.4
Программа вычисляет среднеарифметическую цену акции
за пять рабочих дней.
Возможный результат работы программы представлен на рис. 7.5.
Рис. 7.5
Новая деталь здесь – использование константной переменной
SIZE в ограничениях цикла и для задания размера массива.
Использование переменной вместо явно задаваемого числа
упрощает процесс изменения размера массива и ограничений цик140
лов при возникновении такой необходимости: для этого будет достаточно модифицировать только одну строку программы.
Замечание. Имя переменной SIZE, написанное заглавными
буквами, напоминает, что данная переменная является константой
и не может быть изменена в программе.
7.1.4. Инициализация массива
Когда массив определен, можно присвоить значения его элементам, т. е. инициализировать его. Следующая программа вычисляет количество дней с начала года до даты, введенной пользователем (программа не работает для високосных лет).
Упражнение 7.3. Вычисление количества дней с начала
года до введенной даты (рис. 7.6).
Рис. 7.6
Возможный пример работы программы представлен на рис. 7.7.
Рис. 7.7
141
Значения, которыми инициализируется массив days_per_month,
заключаются в скобки и разделяются запятыми. Они связаны с
определением массива знаком равенства.
Синтаксис инициализации массива представлен на рис. 7.8.
Рис. 7.8
Размер массива не является обязательным полем при его объявлении и инициализации. В том случае, когда все элементы массива инициализированы, компилятор может сам вычислить размер
массива как отношение размера всего массива, выраженного в байтах, к размеру одного элемента, выраженному в байтах (упражнение 7.4).
Упражнение 7.4. Вычисление размера массива (рис. 7.9).
Рис. 7.9
Результат работы программы представлен на рис. 7.10.
Рис. 7.10
142
Варианты объявления и инициализации массивов:
• int array_of_integer[21]; // объявление массива из 21 переменной типа int
• int integer_array [5] = {10, 20, 30, 40, 50}; // объявление
массива из пяти элементов типа int с одновременной инициализацией
• char array_of_char [3] = {'R', 'Z', ' Y'}; // объявление массива
из трех элементов типа char с одновременной инициализацией
• int integer_array [ ] = {10, 20, 30, 40, 50}; // объявление
массива из пяти элементов типа int с одновременной инициализацией без указания размера массива
Замечание. Некоторые особенности инициализации массивов:
• если ни размер массива, ни список значений инициализации не указаны, то будет создан массив нулевой длины;
• количество элементов массива в списке не должно превышать размер массива:
int integer_array [5] = {10, 20, 30, 40, 50, 60}; // ошибка
int integer_array [5] = {10, 20}; // ошибки нет
• значения тех элементов, которые не были инициализированы при объявлении, не устанавливаются; обычно считается, что
значения неинициализированных элементов массива равны нулю,
на самом деле они могут содержать «мусор» – данные, которые когда-то ранее были занесены в эти ячейки памяти, что, в свою очередь, может оказаться источником ошибки.
Упражнение 7.5. Пример неполной инициализации массива
(рис. 7.11).
Рис. 7.11
143
Результат работы программы представлен на рис. 7.12.
Все рассмотренные выше
массивы имеют только один индекс, т. е. каждый элемент массива идентифицируется с помощью
одной переменной. Такие одномерные массивы часто называют
векторами.
Рис. 7.12
7.1.5. Многомерные массивы
Массивы могут иметь размерность больше единицы. Такие
массивы называются многомерными. Наибольшую популярность из
многомерных массивов приобрели двумерные массивы.
Упражнение 7.6. Пример двумерного массива (рис. 7.13).
Рис. 7.13
144
В программе двумерный массив используется для хранения
данных о продажах нескольких отделов за несколько месяцев. Программа принимает от пользователя данные о продажах и затем выводит их в виде таблицы (рис. 7.14).
Рис. 7.14
7.1.6. Определение многомерного массива
Двумерный массив определяется двумя размерами, которые
заключены в квадратные скобки:
double sales [ DEPARTMENTS] [MONTH] ;
Можно сказать, что двумерный массив − это массив массивов.
В частности двумерный массив double sales − это массив из
DEPARTMENTS элементов, каждый из которых является массивом из MONTH элементов. Общее количество чисел в массиве
равно DEPARTMENTS · MONTH (для последней программы:
3 · 3 = 9).
Массивы могут иметь и большую размерность. Например,
трехмерный массив – это массив массивов, которые состоят из массивов. Например, массив для хранения цен на различные акции за
различные дни может быть определен следующим образом:
double prices [STOCKS] [DAY] [PRICE];
145
7.1.7. Доступ к элементам многомерного массива
Доступ к элементам двумерного массива осуществляется с помощью двух индексов, например
sales [d] [m];
а к элементам трехмерного массива − с помощью трех индексов:
prices [i] [j] [k];
Каждый индекс заключается в отдельные квадратные скобки.
7.1.8. Инициализация многомерных массивов
При инициализации многомерных массивов каждая размерность должна быть заключена в фигурные скобки. На рис. 7.15
представлен вариант предыдущей программы, который использует
инициализацию массива вместо запроса пользователя о вводе.
Упражнение 7.7. Пример инициализации двумерного массива (рис. 7.15).
Рис. 7.15
Замечание. При инициализации многомерного массива можно
задавать размер только последнего его измерения (упражнение 7.8).
146
Упражнение 7.8. Пример пропуска размерности при инициализации двумерного массива (рис. 7.16).
Рис. 7.16
Результат работы программы представлен на рис. 7.17.
Рис. 7.17
Возможные способы инициализации массивов:
• указать значения инициализации в фигурных скобках во
время объявления массива;
• присвоить значения элементам массива в процессе выполнения программы;
• объявить массив как глобальный или статический, инициализируемый по умолчанию (для числовых массивов выполняется
инициализация нулевыми значениями).
Замечание. Как и в случае одномерных массивов, если при
инициализации двумерных массивов количество значений инициализации превышает объявленную размерность массива, компилятор
С++ выдаст сообщение об ошибке. Если количество значений инициализации меньше, чем объявленная размерность массива, то
147
оставшиеся значения автоматически инициализируются нулевыми
значениями.
7.1.9. Передача массивов в функции
Массивы могут быть использованы как аргументы функций.
Примером может служить вариант программы из упражнения 7.7,
в котором массивы объемов продаж передаются в функцию, выводящую данные в виде таблицы.
Упражнение 7.9. Передача массива в виде аргумента функции (рис. 7.18).
Рис. 7.18
В объявлении функции массивы-аргументы представлены типом данных и размером.
Замечание. Как и при объявлении массива в объявлении
функции, содержащей массив в качестве аргумента, необязательно
указывать все размеры массива.
148
Примеры возможных вариантов объявления функций с массивами-аргументами:
void print (double [ ] [MONTH]);
void somefunc (int mass [ ] );
При вызове функции в качестве аргумента используется только
имя массива. Например,
print (sales);
Имя массива в действительности представляет собой адрес
массива в памяти. Использование адресов для массивов-аргументов
похоже на использование аргумента ссылки, т. е. функция работает
с оригиналом массива, а не с его копией, хотя ссылается на него,
используя другое имя. Такая система передачи массивов в функции
используется потому, что они могут быть очень большими, и дублирование массива для каждой функции может отнимать много
времени и пространства в памяти.
Адрес – это не то же самое, что и ссылка. С именем массива
знак амперсанда (&) в объявлении функции не используется.
В определении функции с массивами-аргументами необходимо
указать не только тип его данных и размер, но и имя массива. При
этом имя массива, используемое в определении функции (в нашем
случае sec_sales), может отличаться от имени массива, который затем будет использоваться в функции (в нашем случае sales), но оба
этих имени ссылаются на один и тот же массив.
Обращаясь к элементам массива, функция использует имя
массива, использовавшееся в ее определении: sec_sales [d] [m].
Замечание. При передаче массивов в функцию должны быть
определены все его размерности.
7.1.10. Массивы структур
Массивы могут содержать в себе не только данные основных
типов, но и структуры.
149
Упражнение 7.10. Пример массива структур (рис. 7.19).
Рис. 7.19
Результат работы программы представлен на рис. 7.20.
Рис. 7.20
В данной программе при определении массива структур применен тот же синтаксис, что и для массивов, использующих основные типы данных. Только имя типа phone указывает на то, что этот
массив содержит данные более сложного типа.
Для доступа к данным − членам структуры, которая является
элементом массива, требуется новый синтаксис. Например,
mass[i].model_number ссылается на переменную model_number
структуры, которая является i-м элементом массива mass.
150
Массивы структур – это полезный тип данных, используемый
в различных ситуациях. В таких массивах можно хранить большие
объемы разнотипных данных: данные о сотрудниках (имя, возраст,
зарплата), о запчастях для разных марок автомобилей и др.
7.2. Строки
В С++ используется два вида строк: строка как массив символов типа char и строка как объект класса string. Первый из них
иногда называют char*-строками, так как он может быть представлен и в виде указателя на тип char (* означает указатель,
см. раздел 8). Несмотря на то что в последнее время строки, созданные с помощью класса string, во многих ситуациях вытеснили
строковый тип, последний является более простым для понимания
на начальном этапе обучения.
7.2.1. Строковые переменные
Строки тоже могут быть переменными и константами.
Упражнение 7.11. Простая переменная строка (рис. 7.21).
Рис. 7.21
Программа просит пользователя ввести строку, помещает эту
строку в строковую переменную, а затем выводит строку на экран.
Пример работы программы представлен на рис. 7.22.
151
Рис. 7.22
В программе строковая переменная str определена как массив
типа char. Для считывания строки с клавиатуры и помещения ее
в строковую переменную str в используется операция >>, которая
понимает, что это массив символов.
7.2.2. Границы массива
В С++ отсутствует встроенный механизм, защищающий программу от помещения элементов за пределы массива, т. е. в таких
случаях ни компилятор, ни сама программа не выдадут сообщений
об ошибке. Однако при этом данные могут быть записаны поверх
других данных или поверх самой программы, что может послужить
причиной странных эффектов или даже полного краха системы.
Поэтому необходимо проверять границы массива. Если возникает возможность переполнения массива, то нужно устанавливать
для него заведомо большие размеры или предусматривать возможность выдачи предупреждения о возможном переполнении.
Например,
if (i >= MAX)
{
cout << "Массив полон!";
break;
}
В следующей программе реализован еще один вариант защиты от переполнения массива в случае, если пользователь введет
строку длиннее, чем размер массива.
152
Упражнение 7.12. Защита от переполнения массива
(рис. 7.23).
Рис. 7.23
Возможный результат работы программы представлен на рис. 7.24.
Рис. 7.24
Программа использует метод setw(), определяющий максимальное количество символов, которое может принять буфер.
Пользователь может напечатать больше символов, но операция
вставки >> не вставит их в массив.
Замечание. В действительности операция вставки вставит
в массив на один символ меньше, чем определено его размерностью, поскольку последним элементом любой строки символов всегда является нулевой символ конца строки "\0" (так в результате
работы последней программы на экран было выведено не десять,
а девять символов).
153
7.2.3. Строковые константы
Строку можно инициализировать постоянным значением при
ее определении. В предыдущих программах неоднократно применялся такой способ инициализации с помощью char*-строк.
Например,
char* text = "Hello, World !";
Следующая программа демонстрирует другой способ инициализации строковых констант.
Упражнение 7.13. Пример инициализации строки (рис. 7.25).
Рис. 7.25
Результат работы программы представлен на рис. 7.26.
Рис. 7.26
Замечание. В программе строковая константа записана как
нормальная фраза, ограниченная кавычками, а не в виде последовательности символов. Тем не менее компилятор С++ позволяет инициализировать строки таким образом, при этом массив str[ ] формируется правильно: символы помещаются один за другим в массив, причем последним символом будет нулевой.
Замечание. Для инициализации строки можно было бы использовать и такую последовательность символьных констант:
сhar str[ ] = {'Я', ' ', 'п', 'а', 'м', 'я', 'т', 'н', 'и', 'к', ' ', и т. д. };
К счастью, в С++ можно использовать первый вариант.
154
7.2.4. Чтение внутренних пробелов
Если в программе из упражнения 7.11 ввести строку из нескольких слов, то на экран будет выведено только первое слово,
потому что операция >> считает пробел нулевым символом, т. е.
все напечатанное после пробела не считывается (рис. 7.27).
Рис. 7.27
Для считывания строк с пробелами вместо операции вставки
используется библиотечная функция (или, что более правильно,
метод) get() для объекта cin: cin.get(). Такой синтаксис означает:
использовать метод get() класса stream для его объекта cin.
Упражнение 7.14. Считывание строки с пробелами (рис. 7.28).
Рис. 7.28
Результат работы программы представлен на рис. 7.29.
Рис. 7.29
Первый аргумент метода get() – это адрес массива, куда будет
помещена введенная строка (адрес массива задается его именем).
Второй аргумент определяет максимальный размер массива, автоматически предупреждая таким образом его переполнение.
155
7.2.5. Считывание нескольких строк
Использование метода get() с двумя аргументами не позволяет
считывать более одной строки. Для решения этой задачи необходимо указать третий аргумент для этого метода. Этот аргумент
определяет символ, на котором метод завершает считывание строки. По умолчанию значением этого аргумента является символ новой строки '\n', но если вызвать метод с другим аргументом, то
символ '\n' будет заменен на введенный символ.
В следующей программе реализован вызов метода get() с символом '*' («звездочка») в качестве третьего аргумента.
Упражнение 7.15. Пример использования метода get() для
считывания нескольких строк (рис. 7.30).
Рис. 7.30
На рис. 7.31 представлен результат работы программы.
Рис. 7.31
Символы считываются до тех пор, пока не будет введен символ '*'.
156
7.2.6. Копирование, конкатенация и сравнение строк
Лучший способ познать истинную природу строк − выполнение с ними действий символ за символом. Следующая программа
делает именно это: она копирует строку путем копирования каждого ее символа.
Программа создает строковую константу str1 и строковую переменную str2. Затем в цикле for происходит поэлементное копирование строковой константы в строковую переменную.
Упражнение 7.16. Копирование строки с помощью цикла
for (рис. 7.32).
Рис. 7.32
На рис. 7.33 представлен результат работы программы.
Рис. 7.33
В программе используется еще одна из многочисленных библиотечных функций для работы со строками (к счастью, в С++ их
достаточно много). Это функция strlen(), определяющая длину
строки, которая затем используется для ограничения цикла for.
157
Скопированная версия строки должна заканчиваться нулевым
символом. Однако функция strlen() не включает этот символ в длину возвращаемой cтроки. Поэтому в конце программы происходит
присваивание нулевого значения последнему элементу вновь созданного массива.
Замечание. Для использования библиотечных функций для
строк нужно подключить к программе заголовочный файл string.
Поэлементный способ копирования строк, реализованный
в предыдущей программе, конечно, весьма архаичен. При всем
многообразии библиотечных функций, работающих со строками,
естественно, существует и функция для копирования строк – это
функция strcpy() (упражнение 7.17).
Упражнение 7.17. Копирование строк с помощью функции
strcpy() (рис. 7.34).
Рис. 7.34
Замечание. Первым аргументом функции strcpy() является та
строка, куда будут копироваться данные.
Для работы со строками помимо библиотечных функций
strlen() и strcpy() достаточно часто используются функции strcat()
и strcmp(). Первая из них позволяет соединять строки (еще одно
название этого действия – конкатенация), а вторая – сравнивать их
между собой. Функция strcat() присоединяет вторую строку из
списка аргументов к первой. Функция strcmp() сравнивает две
строки и возвращает целое число: 0 – в случае равенства строк,
158
1 – в случае, когда первая строка больше второй, и -1 − в обратном
случае.
Упражнение 7.18. Пример использования функций strcat()
и strcmp() при работе со строками (рис. 7.35).
Рис. 7.35
Результат работы представлен на рис. 7.36.
Рис. 7.36
159
7.2.7. Массивы строк
Поскольку возможно существование массива массивов,
то возможен и массив строк (ведь строка – это массив символов).
В следующей программе названия дней недели помещаются в массив.
Упражнение 7.19. Пример массива строк (рис. 7.37).
Рис. 7.37
Программа печатает все строки массива (рис. 7.38).
Рис. 7.38
Поскольку строки – это массивы, массив строк star в действительности является двумерным массивом. Его первая размерность,
DAYS, определяет количество строк в массиве, а вторая размерность, MAX, определяет максимальную длину строк (девять символов для строки Wednesday (среда) плюс десятый завершающий
нулевой символ) (рис. 7.39).
160
Рис. 7.39
Замечание. Поскольку двумерный массив – это массив массивов, то для доступа к элементам «внешнего» массива, каждый из
которых в отдельности является массивом (в данном случае – строкой), второй индекс не нужен. Поэтому star[j] – это строка под номером j из массива строк.
Задание для самостоятельной работы
1. Составьте программу, в которой из двух одномерных массивов формируется один двумерный массив, значения элементов
которого вычисляются по следующим правилам:
• значения элементов первой строки равны сумме значений
элементов исходных массивов;
• значения элементов второй строки равны разности значений элементов исходных массивов;
• значения элементов третьей строки равны произведению
значений элементов исходных массивов.
Создание двумерного массива и вывод результатов реализуйте
в отдельных функциях.
Возможный результат работы программы представлен ниже.
161
2. Составьте программу, которая переворачивает введенную
пользователем строку. Протестируйте работу программы на строке
«Аргентина манит негра». Возможный результат работы программы представлен ниже.
Реализуйте два варианта программы:
• для расчета длины введенной строки напишите свою функцию count();
• для расчета длины введенной строки используйте библиотечную функцию strlen().
3. Реализуйте вариант программы из задания № 3 предыдущего урока, в котором вместо структурных переменных initial, buy
и sell типа sec используются соответствующие массивы структур
типа stock: stock initial[3], stock buy [3] и stock sell[3]. Результаты
работы обеих программ должны совпадать.
162
Глава 8. УКАЗАТЕЛИ, АДРЕСА И ВЫДЕЛЕНИЕ ПАМЯТИ
8.1. Адреса и указатели
Указатели – это важная возможность языка С++, отсутствующая, например, в таких языках, как Visual Basic или Java.
Наиболее часто указатели используются в следующих случаях:
• доступ к элементам массива;
• передача аргументов в функцию, от которой требуется изменить эти аргументы;
• передача в функции массивов и строковых переменных;
• выделение памяти;
• создание сложных структур, таких как связный список.
8.1.1. Адрес переменной
У каждой переменной есть свой адрес в памяти. Память состоит из последовательности пронумерованных ячеек. Каждый
байт памяти компьютера имеет адрес. Адреса – это те же числа, которые используются, например, для нумерации домов на улице
(0, 1, 2, 3 и т. д.; так, для памяти в 1 Мбайт наибольшим адресом
будет число 1 048 575).
Загружаясь в память, программа занимает некоторое количество этих адресов. Это означает, что каждая переменная и каждая
функция программы начинается с какого-либо конкретного адреса.
На рис. 8.1 показан пример размещения переменных в памяти компьютера.
163
Рис. 8.1
8.1.2. Получение адреса переменной
Для получения адреса переменной используется операция получения адреса &.
Упражнение 8.1. Пример получения адреса переменной (рис. 8.2).
Рис. 8.2
В этой простой программе определены три целочисленные переменные, которые инициализированы значениями 11, 22 и 33.
На экран же выводятся не значения, а адреса этих переменных
в памяти (рис. 8.3).
164
Рис. 8.3
Адреса переменных – это не то же самое, что их значения.
В последнем примере содержимое трех переменных – это 11, 22
и 33 (рис. 8.4).
Рис. 8.4
Адреса расположены в убывающем порядке, потому что локальные переменные хранятся в стеке, где адреса располагаются по
убыванию.
8.1.3. Переменные указатели
Видеть само значение адреса нужно далеко не всегда, а знание
расположения переменных в памяти может быть полезно. Для этих
целей в языке С++ по аналогии с переменными, хранящими данные
различных типов (целые числа, вещественные числа, знаки и т. д.),
существуют переменные, содержащие в себе значение адреса.
Такие переменные называются переменными-указателями или просто указателями.
В следующем упражнении показан синтаксис указателей.
165
Упражнение 8.2. Пример использования указателей (рис. 8.5).
Рис. 8.5
В данной программе определена переменная ptr как указатель
на целый тип int (символ (*) означает указатель на), т. е. переменная ptr может содержать в себе адрес любой переменной типа int.
Значением указателя ptr является адрес переменной: вначале адрес
переменной var1, затем – адрес переменной var2 (рис. 8.6, 8.7).
Рис. 8.6
Компилятору для правильной работы нужна информация
о типе переменной, на которую указывает указатель, например:
char* cptr; // указатель на символьную переменную
int* iptr; // указатель на целочисленную переменную
float* fptr // указатель на вещественную переменную
Distance* distptr // указатель на структурную переменную типа
// Distance
166
Рис. 8.7
Каждому указателю перед его использованием обязательно
должно быть присвоено некоторое значение.
8.1.4. Доступ к переменной по указателю
Доступ к значению переменной по ее адресу осуществляется
с помощью специальной операции разыменовывания указателя.
Упражнение 8.3. Пример доступа к переменной через указатель (рис. 8.8).
Рис. 8.8
167
Результат работы программы представлен на рис. 8.9.
Рис. 8.9
Программа очень похожа на программу из упражнения 8.2,
за исключением того что вместо адресов переменных var1 и var2,
хранящихся в переменной ptr, на экран выводятся значения этих
переменных, хранящиеся по адресу, на который указывает ptr. Это
делается с помощью выражения *ptr.
Символ * («звездочка»), стоящий перед именем указателя,
называется операцией разыменовывания. Запись *ptr означает:
взять значение переменной, на которую указывает указатель. Таким образом, выражение *ptr представляет собой значение переменной, на которую указывает указатель ptr. Когда ptr указывает
на переменную var1, то значением выражения *ptr будет число 11,
а когда ptr указывает на переменную var2, то значением выражения *ptr будет число 22.
Доступ к значению переменной через указатель с использованием операции разыменовывания называется непрямым (или косвенным) доступом или разыменовыванием указателя.
Указатели можно использовать не только для получения значения переменной, на которую он указывает, но и для выполнения
действий с этой переменной (упражнение 8.4).
Упражнение 8.4. Работа с переменной через указатель
(рис. 8.10).
Рис. 8.10
168
Результат работы программы представлен на рис. 8.11.
Рис. 8.11
Замечание. Символ «звездочка» (*), используемый в операции
разыменовывания, – это не то же самое, что символ «звездочка»,
используемый в объявлении указателя. Операция разыменовывания
предшествует имени указателя и означает значение, находящееся
в переменной, на которую указывает указатель. «Звездочка» же
в объявлении указателя означает указатель на тип данных.
Например,
int* ptr; // объявление: указатель на int
*ptr = 25; // разыменовывание: значение переменной,
// хранящейся по адресу, на который указывает ptr
8.2. Работа с указателями. Адресная арифметика
Указатели можно использовать как операнды в арифметических выражениях − с ними можно выполнять не только операции
присваивания и обращения по адресу, но и некоторые арифметические операции.
8.2.1. Сравнение указателей
Указатели одного типа можно сравнивать с помощью стандартных операций сравнения. При этом сравниваются значения
указателей, а не значения величин, на которые данные указатели
ссылаются.
169
Упражнение 8.5. Пример сравнения указателей (рис. 8.12).
Рис. 8.12
Результат работы программы представлен на рис. 8.13.
Рис. 8.13
8.2.2. Применение операций сложения/вычитания
при работе с указателями
К указателю можно прибавить целое число или вычесть из него целое число. Так, в результате прибавления к указателю единицы получится адрес следующей величины того типа, на которую
ссылается указатель (например, следующее целое число).
Пример. Пусть xptr – указатель на тип long, а cptr – указатель
на тип char. Начиная с адреса 2000 в памяти расположены целые
числа. Тогда адрес второго числа – 2004, третьего – 2008 и т. д., так
как в большинстве версий С++ под тип long выделяется четыре
байта памяти. Начиная с адреса 3000 в памяти расположены объекты типа char. Тогда адрес второго объекта – 3001, третьего – 3002
и т. д., так как под тип char выделяется один байт памяти.
170
Поскольку размер памяти, выделяемый для числа типа long
и объекта типа char, различен, адреса, хранящиеся в xptr и cptr,
при увеличении указателей на единицу будут изменяться поразному. Однако в обоих случаях увеличение указателя на единицу
означает переход к следующей в памяти величине того же типа
(рис. 8.14).
Рис. 8.14
8.2.3. Вычитание указателей
Указатели одного и того же типа можно вычитать друг из друга. Разность указателей показывает, сколько объектов соответствующего типа может поместиться между указанными адресами,
т. е. результатом вычитания указателей является целое число. Выполнять вычитание можно только над указателями одного типа.
8.2.4. Инкрементирование и декрементирование указателей
Если p – указатель, то унарная операция p++ увеличивает его
значение на один адрес, а операция p-- – уменьшает на один адрес.
Также справедливо выражение p+n (n – число элементов). Компилятор в этом случае будет масштабировать приращение адреса
в соответствии с типом данных, который будет определяться из соответствующего объявления (int* p, char* p, float* p и т. д.).
171
Инкрементировать и декрементировать указатели можно как
после операции присваивания (*p++= 22), так и до нее (*++p = 22).
В первом случае сначала выполняется оператор присваивания
(т. е. переменной, на которую указывает указатель p, присваивается
значение 22), а затем значение указателя увеличивается на единицу.
Во втором случае сначала увеличивается значение указателя, а затем выполняется присваивание.
Упражнение 8.6. Пример работы с указателями (рис. 8.15).
Рис. 8.15
Результат работы программы представлен на рис. 8.16.
Рис. 8.16
172
8.2.5. Операция присваивания
Значению указателя может быть присвоено значение другого
указателя. В этом случае следует использовать операцию разыменовывания *. Компилятор С++ отслеживает некорректные присваивания – указателю значения или наоборот, и выдает предупреждение о различных уровнях адресации.
Замечание. Слово NULL позволяет определить указатель, который ничего не адресует.
8.3. Указатель на void
Как отмечалось выше, указатель должен быть одного типа
с переменной, на адрес которой он указывает. Например, нельзя
присвоить указателю на int адрес переменной типа float:
float flovar = 98.6;
int* intptr = &flovar; // так нельзя: типы int* и float*
// несовместимы
Однако есть одно исключение. Существует тип указателя, который может указывать на любой тип данных. Он называется
указателем на void и определяется следующим образом:
void* ptr; // указатель, который может указывать на
// любой тип данных
Такие указатели предназначены для использования в определенных случаях, например при передаче указателей в функции,
работающие независимо от типа данных, на которые указывает
указатель.
173
Упражнение 8.7. Пример указателя на void (рис. 8.17).
Рис. 8.17
Замечание. Если по некоторым причинам необходимо присвоить одному типу указателя другой тип, можно использовать
функцию reinterpret_cast():
ptrint = reinterpret_cast<int*>(&flovar);
ptrflo = reinterpret_cast<float*>(&intvar);
8.4. Указатели и массивы
Указатели и массивы очень похожи – и указатель, и массив
содержат адрес. Поэтому указателю может быть присвоен адрес
первого элемента массива или имя массива (что одно и то же, поскольку имя массива является его адресом). Таким образом, доступ
к элементам массива можно получить как через индекс, так и через
указатель.
174
Упражнение 8.8. Доступ к элементам массива через указатели (рис. 8.18).
Рис. 8.18
В данной программе реализованы четыре различных способа
доступа к элементам массива.
Замечание. Из приведенного примера понятно, почему при
объявлении указателя обязательно необходимо указывать тип переменной, на которую он будет указывать. Компилятору необходимо знать, на переменные какого типа указывает указатель, чтобы
осуществлять правильный переход к элементам массива (в случае
int при переходе к следующему элементу массива компилятор будет увеличивать значение адреса на четыре, в случае double –
на восемь, а в случае char – на единицу).
Результатом работы программы будет последовательный вывод на экран всех элементов массива.
8.5. Константные указатели
При объявлении указателей допускается использование ключевого слова const как перед спецификатором типа, так и после него. В первом случае мы будем иметь дело с указателем на кон175
станту, а во втором – с константным указателем. Главное их отличие заключается в следующем:
• значение указателя на константу можно изменять;
• значение константного указателя является константой
и не подлежит изменению.
Пример.
const int* pOne;
int* const pTwo;
const int* const pThree;
В этом примере:
• pOne – указатель на константу типа int, поэтому значение самого указателя изменять можно (pOne++ корректно),
а значение, на которое он указывает, изменять нельзя
((*pOne)++ некорректно);
• pTwo – константный указатель на тип int; в этом случае
значение, записанное по адресу в указателе, может изменяться
((*pTwo)++ корректно), но сам адрес остается неизменным
(pTwo++ некорректно);
• pThree – константный указатель на константу типа int; он
всегда указывает на одну и ту же область памяти и значение, записанное по этому адресу, не может изменяться (pThree++ некорректно, (*pThree)++ некорректно).
8.6. Указатели на указатели
В языке С++ можно использовать указатели на указатели. При
объявлении указателя на указатель уровнем вложенности указателей служит число «звездочек» перед именем переменной.
Для получения значения по указателю на указатель перед именем указателя надо записать количество «звездочек», равное уровню вложенности указателя.
176
Упражнение 8.10. Использование указателей на указатели
(рис. 8.19).
Рис. 8.19
Результат работы программы представлен на рис. 8.20.
Рис. 8.20
8.7. Указатели и функции
Передача аргументов в функцию может быть реализована
тремя способами: по значению, по ссылке и по указателю. Первые
два способа были рассмотрены в главе 6.
8.7.1. Передача аргументов в функцию по указателю
В следующей программе с помощью указателей упорядочиваются по возрастанию два числа, вводимых пользователем
с экрана.
177
Упражнение 8.11. Использование указателей при передаче
аргументов в функцию (рис. 8.21).
Рис. 8.21
Результат работы программы представлен на рис. 8.22.
Рис. 8.22
Передача аргументов по указателю похожа на передачу аргументов по ссылке. Разница заключается в том, что в первом случае
в функцию передается ссылка на переменную (которая является
псевдонимом переменной), а во втором случае – ее адрес.
8.7.2. Передача массивов в функцию по указателю
При передаче массива в функцию достаточно часто используют указатели. В большинстве случаев С++ интерпретирует имя
178
массива как адрес его первого элемента, поэтому для передачи адреса массива в функцию наряду с указателем можно использовать
имя массива.
Упражнение 8.12. Передача одномерного массива в функцию с помощью указателя (рис. 8.23).
Рис. 8.23
Результат работы программы представлен на рис. 8.24.
Рис. 8.24
179
Замечание. Поскольку указатель можно трактовать как имя
массива, в функциях mass() и print() вместо выражения *p можно
использовать выражение p[i].
Для передачи в функцию двумерного целочисленного массива
в качестве аргумента ей необходимо передать не «указатель на int»,
а «указатель на массив из m элементов типа int», где m – количество элементов массива в каждой строке (т. е. количество столбцов). Синтаксис такого указателя следующий:
int (*ptr) [m];// указатель на массив из m элементов типа int
Для доступа к элементам массива в этом случае используется
синтаксис массива (т. е. указатель трактуется как имя массива).
В следующей программе функция maxmas() увеличивает
на десять элементы двумерного массива, переданные ей по указателю.
Упражнение 8.13. Передача двумерного массива в функцию
с помощью указателя (рис. 8.25).
Рис. 8.25 (начало)
180
Рис. 8.25 (окончание)
Результат работы программы представлен на рис. 8.26.
Рис. 8.26
8.8. Указатели и динамическая память
Указатели позволяют реализовывать выделение динамической
памяти. Динамической называется память, выделяемая программе
в процессе ее работы, а не на этапе ее компиляции.
8.8.1. Выделение динамической памяти
Для выделения динамической памяти в C++ используется
ключевая операция new. Алгоритм выделения динамической памяти:
• операции new сообщается, для какого типа данных запрашивается память;
• new находит блок памяти нужного размера и возвращает
его адрес;
• выделенный адрес присваивается указателю.
181
Общая форма получения и назначения памяти отдельному
объекту данных выглядит следующим образом:
TYPE* pointer = new TYPE; // выделение области памяти для
// переменной типа TYPE и
// присваивание адреса выделенной
// области указателю pointer
TYPE* pointer = new TYPE [n] // выделение области памяти для
// массива из n элементов типа TYPE
// и присваивание адреса выделенной
// области указателю pointer
Упражнение
(рис. 8.27).
8.14.
Выделение
динамической
памяти
Рис. 8.27
Результат работы программы представлен на рис. 8.28.
Рис. 8.28
182
Программа использует операцию new для выделения памяти
под объекты данных типа int и типа double. Это происходит во
время работы программы (а не во время ее компиляции, как для переменной nights). Указатели pt и pd указывают на эти объекты
данных. Без них доступ к выделенным объектам получить было бы
невозможно, так как у них нет имени (в отличие от переменной
nights). С ними же, используя операцию разыменовывания адреса
(*pt и *pd), с вновь выделенными объектами можно работать как
с обычными («именованными») переменными (присваивать им значения, производить арифметические действия, выводить на экран и т. д.).
Замечание. Программа также демонстрирует одну из причин
того, почему необходимо объявлять тип данных, на которые указывает указатель. Сам по себе адрес относится только к началу объекта, он не включает информацию о типе или размере. Как видно из
результатов работы программы, указатель на int имеет тот же размер, что и указатель на double, поскольку оба они являются адресами. Но поскольку в программе объявлены типы указателей, программа знает, что *pd имеет тип double с размером восемь байт,
в то время как *pt представляет собой значение типа int размером
в четыре байта.
Ввиду того что память компьютера является ограниченным
ресурсом, попытка выделения памяти оператором new может оказаться неудачной. В этом случае операция new генерирует исключение (возвращает значение 0).
Замечание. Указатель со значением 0 называется NULLуказателем (или нулевым указателем). Нулевой указатель никогда
не указывает на допустимые данные, поэтому он часто используется
в качестве признака неудачного завершения операций или функций.
8.8.2. Освобождение выделенной динамической памяти
Когда память, выделенная под переменную, больше не нужна,
ее необходимо освободить для осуществления возможности ее повторного использования другими частями программы. Для освобождения выделенной памяти применяется операция delete:
delete pointer; // освобождение области памяти, на которую
// ссылается указатель pointer
delete [ ] pointer; // освобождение области памяти, занятой
// массивом, на который указывает pointer
183
Упражнение 8.15. Пример работы с динамической памятью (рис. 8.29).
Рис. 8.29
Результат работы программы представлен на рис. 8.30.
Рис. 8.30
184
Замечание. При выполнении оператора delete [ ] pi; происходит не удаление указателя pi, а освобождение памяти по адресу, на
который он указывает. После освобождения памяти с самим указателем ничего не происходит и ему можно присвоить другой адрес.
При использовании операций new и delete необходимо придерживаться следующих правил:
• операция delete используется только с памятью, выделенной операцией new;
• нельзя использовать операцию delete дважды для освобождения одного и того же блока памяти;
• нельзя использовать операцию [ ] delete для освобождения
блока памяти, выделенного с помощью операции new для одного
элемента, и операцию delete для освобождения блока памяти, выделенного с помощью операции new [ ] для массива элементов;
• применение операции delete к NULL-указателю безопасно.
Примеры некорректного использования операции delete:
int* ps = new int; // корректно
delete ps; // корректно
delete ps; // некорректно – нельзя пытаться освободить дважды
// одну и ту же область памяти
int var = 5; // корректно
int* pi = &var; // корректно
delete pi; // некорректно – память, на которую указывает pi,
// не была выделена с помощью операции new
int* ps = new int; // корректно
int* pq = ps; // корректно
delete pq; // корректно – pq и ps указывают на один и тот же
// блок памяти, выделенный с помощью операции new
int* pt = new int; // корректно
delete [ ] pt; // некорректно – эффект не определен; память была
// выделена под один элемент типа int
short* ps = new short[500]; // корректно
delete ps; // некорректно – эффект не определен; память была
// выделена под массив элементов типа int
185
8.9. Динамические массивы
Динамическим называется массив, размерность которого становится известной в процессе выполнения программы, а сам процесс создания массива и выбора его размерности в процессе выполнения программы называется динамическим связыванием. Создание массива на этапе компиляции программы называется статическим связыванием, а сам такой массив – статическим.
8.9.1. Реализация одномерного динамического массива
Для создания одномерного динамического массива достаточно
сообщить операции new тип элементов массива и необходимое количество элементов (указывается в квадратных скобках), например:
int* pi = new int [10]; // выделение памяти на десять элементов
// типа int
Операция new возвращает адрес первого элемента в массиве,
значение которого присваивается указателю pi. В результате указатель pi указывает на первый элемент выделенного массива.
По завершении работы с выделенным блоком памяти необходимо сбалансировать вызов new соответствующим вызовом delete:
delete [ ] pi;
Присутствие квадратных скобок сообщает программе, что
необходимо освободить весь массив, а не только один элемент, на
который указывает указатель (обратите внимание, что квадратные
скобки расположены между delete и указателем).
Замечание. Поскольку имя массива, как и указатель, является
адресом его первого элемента, то имя указателя на массив можно
использовать точно так же, как обычное имя массива. Поэтому
для доступа к элементам массива с помощью указателей можно использовать указатель, как если бы он был именем массива, т. е.
можно писать pi[0] вместо *pi для значения первого элемента массива, pi[1] – для значения второго элемента массива и т. д. Такой
легкий доступ к элементам динамического массива объясняется
тем, что С++ внутренне все равно работает с массивами через указатели. Подобная эквивалентность указателей и массивов – одно
из замечательных свойств С++.
186
В следующем упражнении операция new используется для создания динамического одномерного массива. Для доступа к его
элементам используется синтаксис обычного массива. Также отмечается фундаментальное отличие между указателем и именем
массива.
Упражнение 8.16. Пример одномерного динамического массива (рис. 8.31).
Рис. 8.31
Результат работы программы представлен на рис. 8.32.
Рис. 8.32
187
Из текста программы видно, что указатель pd действительно
используется так, как если бы он был именем массива: pd[0] для
первого элемента массива, pd[1] – для второго элемента и т. д.
Фундаментальное отличие между именем массива и указателем проявляется в операциях инкрементирования (pd++) и декрементирования (pd--), которые могут быть применены к указателю
(поскольку указатель – это переменная) и не могут быть применены
к имени массива. В данной программе pd – это указатель на массив,
который всегда указывает на адрес первого элемента массива.
Поэтому после увеличения значения pd на единицу (т. е. при переходе к следующему адресу) указатель указывает на второй элемент
выделенного массива, который теперь становится первым, а третий
элемент массива становится вторым. Поскольку при организации
массива память была выделена под три элемента, содержимое третьего элемента нового массива находится по адресу, содержимое
которого не определено (в данном случае там оказался «мусор»).
Декрементирование указателя возвращает его назад к исходному
значению, поэтому программа может применить операцию delete [
] с корректным адресом (в противном случае можно было удалить
какую-нибудь нужную информацию, находящуюся за пределами
выделенной динамической памяти).
8.9.2. Реализация многомерного динамического массива
Для создания многомерных (например, двумерных) динамических массивов воспользоваться оператором
int** masd = new int [n] [k];
по аналогии с одномерным динамическим массивом нельзя, так как
в этом случае возникает ошибка несоответствия указателей.
Динамический двумерный массив создается следующим образом.
1. Сначала необходимо с помощью операции new выделить
память под массив из m указателей. Эта память будет представлять
собой массив (или вектор), элементами которого будут являться
указатели, располагающиеся в памяти последовательно.
2. Затем необходимо в цикле каждому указателю присвоить
адрес выделенной памяти размером n, равным границе массива.
Созданный таким образом двумерный динамический массив
существенно отличается от обычного двумерного массива, под ко188
торый при объявлении выделяется сплошной участок памяти. Для
динамического массива выделенная память не представляет собой
сплошной участок, поскольку она выделяется с помощью нескольких операций new. На рис. 8.33 показано размещение динамического массива masd размером m · n в памяти компьютера.
masd[0]
masd[1]
…
masd[m-1]
→masd [0] [0]
→masd [1] [0]
→…
→masd [m-1] [0]
masd [0] [1]
masd [1] [1]
…
masd [m-1] [1]
…
…
…
…
masd [0] [n-1]
masd [1] [n-1]
…
masd [m-1] [n-1]
Рис. 8.33
Упражнение 8.17. Реализация двумерного динамического
массива (рис. 8.34).
Рис. 8.34 (начало)
189
Рис. 8.34 (окончание)
Программа реализует двумерный динамический массив в виде
набора одномерных динамических массивов. Возможный результат
работы программы представлен на рис. 8.35.
Рис. 8.35
8.10. Указатели на строки
Поскольку строки – это массивы элементов типа char,
то доступ к элементам строки аналогичен доступу к элементам
массива.
В следующей программе определены две строки: одна – с использованием массива, а другая – с использованием указателя.
190
Упражнение 8.18. Различные способы описания строк
(рис. 8.36).
Рис. 8.36
Как правило, эти два способа определения строк эквивалентны, т. е. их можно передавать в функции в качестве аргументов,
выводить на экран и т. д. Однако существует небольшое различие:
str1 – это адрес, т. е. константный указатель (в данном случае –
адрес первого элемента массива str1), а str2 – это указательпеременная. Поэтому str2 может изменять свое значение, а str1 – не
может. То есть можно, например, увеличить значение str2 на единицу, так как это указатель, но после этой операции он уже не будет показывать на первый элемент строки. В отличие от него указатель str1 (он же «по совместительству» и имя массива) всегда будет
указывать на первый элемент массива, что и отражено в результатах работы программы (рис. 8.37).
Рис. 8.37
191
Таким образом, строка, определенная как указатель, гораздо
более гибка, чем строка, определенная как массив.
Передача строк в функции в качестве аргументов осуществляется аналогично передаче массивов.
Упражнение 8.19. Передача строк в функцию через указатели (рис. 8.38).
Рис. 8.37
В данной программе функция printstr() посимвольно выводит
на экран строку, переданную ей в качестве аргумента.
Задание для самостоятельной работы
1. Измените программу из задания № 2 главы № 6 для случая
передачи аргументов в функцию swap() по указателю. Возможный
результат работы программы представлен ниже.
192
2. Измените программу из задания № 1 главы № 7 для случая
использования передачи исходных одномерных массивов в функцию по указателям. Реализуйте три варианта работы программы
с результирующим двумерным массивом:
• доступ к элементам результирующего двумерного массива
осуществляется по индексам;
• доступ к элементам результирующего двумерного массива
осуществляется по указателям;
• результирующий двумерный массив представляет собой
набор одномерных динамических массивов (для этого используйте
указатель на массив указателей и механизм динамического выделения памяти).
Результаты работы всех программ должны совпадать.
3. Реализуйте программу из задания № 2 главы № 7 по «переворачиванию» строки с помощью указателей. Саму строку «Аргентина манит негра» задайте в функции main() в виде массива типа
char и передайте его в качестве аргумента в функцию revers(),
в которой с помощью указателей реализуйте посимвольный вывод
строки сначала в прямом, а затем в обратном порядке.
193
Рекомендуемая литература
1. Глушаков В. С. Программирование на С++ / В. С. Глушаков,
Т. С. Дуравкина. – М. : АСТ, 2009. – 685 с.
2. Джамса К. Учимся программировать на языке С++ / К. Джамса. –
М. : Мир, 2009. – 320 с.
3. Шилдт Г. Искусство программирования на С++ / Г. Шилдт. – СПб. :
БХВ-Петербург, 2011. – 496 с.
4. Либерти Д. Освой самостоятельно С++ за 21 день / Д. Либерти. –
М. : Диалектика-Вильямс, 2010. – 850 с.
194
Оглавление
Введение……………………………………………………………………..
Глава 1. Основы языка С++ …………………………………………......
1.1. Структура программы на С++……………………………….....
1.2. Операторы…………………………………………………….....
1.3. Директивы……………………………………………………….
1.4. Разделяющие знаки……………………………………………..
1.5. Строковые константы…………………………………………..
1.6. Заголовочные файлы…………………………………………....
1.7. Использование функций………………………………………..
1.8. Комментарии……………………………………………………
1.9. Управляющие последовательности……………………………
Задание для самостоятельной работы…………………………………......
Глава 2. Типы данных в С++ ……………………………………………
2.1. Типы данных для представления целых чисел………………..
2.2. Тип данных для представления символов……………………..
2.3. Операции с целыми числами…………………………………...
2.4. Типы данных для представления вещественных чисел………
2.5. Тип данных для представления логических значений………..
2.6. Перечисления…………………………………………………....
2.7. Преобразование типов данных………………………………....
Задание для самостоятельной работы…………………………………......
Глава 3. Операции. Форматирование данных ………………………..
3.1. Арифметические операции с присваиванием………………....
3.2. Инкремент……………………………………………………….
3.3. Декремент………………………………………………………..
3.4. Тернарная условная операция………………………………….
3.5. Каскадирование операций вставки в поток и извлечения
из потока…...........................................................................................
3.6. Операции отношения…………………………………………...
3.7. Логические операции…………………………………………...
3.8. Приоритеты операций С++……………………………………..
3.9. Форматирование данных с помощью манипуляторов
и флагов…............................................................................................
Задание для самостоятельной работы…………………………………......
Глава 4. Организация вычислений. Циклы. Ветвления …………….
4.1. Циклы………………………………………………………….....
4.1.1. Цикл for (цикл с параметром) …………………………..
4.1.2. Цикл while (цикл с предусловием) ……………………..
4.1.3. Цикл do … while (цикл с постусловием) ………………
195
3
4
4
5
7
7
8
8
10
13
13
14
15
15
18
21
26
28
28
30
33
35
35
36
38
38
40
41
42
42
43
48
50
50
50
54
57
4.2. Ветвления………………………………………………………..
4.2.1. Условный оператор if…………………………………....
4.2.2. Ветвление if…else………………………………………..
4.2.3. Вложенное ветвление if…else………………………......
4.3. Оператор switch………………………………………………....
4.4. Принудительное окончание цикла………………………….....
Задание для самостоятельной работы……………………………………..
Глава 5. Структуры и перечисления……………………………………
5.1. Структуры……………………………………………………......
5.1.1. Объявление структуры………………………………......
5.1.2. Объявление структурной переменной……………….....
5.1.3. Доступ к полям структуры……………………………....
5.1.4. Инициализация полей структуры…………………….....
5.1.5. Присваивание структурных переменных……………....
5.1.6. Передача структур в качестве параметров…………......
5.1.7. Вложенные структуры……………………………….......
5.1.8. Инициализация вложенных структур………………......
5.2. Перечисления…………………………………………………....
Задание для самостоятельной работы……………………………………..
Глава 6. Функции………………………………………………………….
6.1. Простые функции………………………………………….........
6.1.1. Объявление функции ………………………………........
6.1.2. Вызов функции……………………………………….......
6.1.3. Определение функции……………………………….......
6.1.4. Отсутствие определения функции………………….......
6.1.5. Подключение функции, расположенной
в отдельном файле…...................................................................
6.1.6. Пользовательские и библиотечные функции ……….....
6.2. Передача аргументов в функцию………………………………
6.2.1. Передача констант в функцию…………………………..
6.2.2. Передача переменных в функцию………………………
6.2.3. Передача аргументов по значению……………………..
6.2.4. Использование функций в качестве аргументов
функций….....................................................................................
6.2.5. Структурные переменные в качестве аргументов
функций….....................................................................................
6.2.6. Имена переменных внутри прототипа функций……….
6.2.7. Значение, возвращаемое функцией……………………..
6.2.8. Структурная переменная в качестве возвращаемого
значения…....................................................................................
6.3. Ссылки на аргументы…………………………………………...
6.3.1. Особенности применения ссылок………………………
6.3.2. Передача в функцию аргументов стандартных типов
по ссылке…...................................................................................
6.3.3. Передача структурных переменных по ссылке………
196
59
59
62
65
67
70
73
74
74
75
76
77
78
79
81
82
84
84
87
89
89
90
91
91
92
93
94
95
95
97
98
100
100
101
101
105
107
108
109
111
6.4. Перегрузка функций………………………………………...................
6.4.1. Переменное число аргументов функции………….........
6.4.2. Различные типы аргументов…………………………….
6.5. Рекурсия………………………………………………………….
6.6. Встраиваемые функции…………………………………….......
6.7. Аргументы по умолчанию……………………………………...
6.8. Константные аргументы функции……………………………..
6.9. Область видимости и класс памяти……………………………
6.9.1. Локальные переменные………………………………….
6.9.2. Глобальные переменные………………………………...
6.10. Генерация случайных чисел в С++……………………….......
Задание для самостоятельной работы……………………………………..
Глава 7. Массивы и строки………………………………………………
7.1. Массивы……………………………………………………….....
7.1.1. Основные понятия………………………………………..
7.1.2. Определение массива………………………………….....
7.1.3. Доступ к элементам массива…………………………….
7.1.4. Инициализация массива…………………………………
7.1.5. Многомерные массивы…………………………………..
7.1.6. Определение многомерного массива…………………...
7.1.7. Доступ к элементам многомерного массива…………...
7.1.8. Инициализация многомерных массивов………………..
7.1.9. Передача массивов в функции…………………………..
7.1.10. Массивы структур……………………………………....
7.2. Строки…………………………………………………………....
7.2.1. Строковые переменные………………………………….
7.2.2. Границы массива…………………………………………
7.2.3. Строковые константы……………………………………
7.2.4. Чтение внутренних пробелов…………………………....
7.2.5. Считывание нескольких строк…………………………..
7.2.6. Копирование, конкатенация и сравнение строк………..
7.2.7. Массивы строк…………………………………………....
Задание для самостоятельной работы……………………………………..
Глава 8. Указатели, адреса и выделение памяти……………………...
8.1. Адреса и указатели……………………………………………...
8.1.1. Адрес переменной…………………………………….....
8.1.2. Получение адреса переменной………………………….
8.1.3. Переменные указатели…………………………………..
8.1.4. Доступ к переменной по указателю………………….....
8.2. Работа с указателями. Адресная арифметика………………....
8.2.1. Сравнение указателей…………………………………....
8.2.2. Применение операций сложения/вычитания
при работе с указателями………………………………………
8.2.3. Вычитание указателей…………………………………...
197
113
113
115
116
117
119
121
122
123
128
132
135
138
138
138
139
139
141
144
145
146
146
148
149
151
151
152
154
155
156
157
160
161
163
163
163
164
165
167
169
169
170
171
8.2.4. Инкрементирование и декрементирование
указателей………….....................................................................
8.2.5. Операция присваивания………………………………....
8.3. Указатель на void………………………………………………..
8.4. Указатели и массивы…………………………………………....
8.5. Константные указатели…………………………………………
8.6. Указатели на указатели…………………………………………
8.7. Указатели и функции……………………………………………
8.7.1. Передача аргументов в функцию по указателю……….
8.7.2. Передача массивов в функцию по указателю………….
8.8. Указатели и динамическая память……………………………..
8.8.1. Выделение динамической памяти………………………
8.8.2. Освобождение выделенной динамической памяти……
8.9. Динамические массивы…………………………………….......
8.9.1. Реализация одномерного динамического массива…….
8.9.2. Реализация многомерного динамического массива…...
8.10. Указатели на строки……………………………………….......
Задание для самостоятельной работы…………………………………......
Рекомендуемая литература……………………………………………......
198
171
173
173
174
175
176
177
177
178
181
181
183
186
186
188
190
192
194
Учебное издание
Букунов Сергей Витальевич
ОСНОВЫ ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ С++
Учебное пособие
Редактор О. Д. Камнева
Корректор М. А. Молчанова
Компьютерная верстка И. А. Яблоковой
Подписано к печати 28.12.2015. Формат 60×84 1/8. Бумага офсетная.
Усл. печ. л. 11,6. Тираж 100 экз. Заказ 195. «С» 112.
Санкт-Петербургский государственный архитектурно-строительный университет.
190005, Санкт-Петербург, 2-я Красноармейская ул., д. 4.
Отпечатано на ризографе. 190005, Санкт-Петербург, ул. Егорова, д. 5/8, лит. А.
199
ДЛЯ ЗАПИСЕЙ
200
Документ
Категория
Без категории
Просмотров
6
Размер файла
3 500 Кб
Теги
bukunov, prog, osn
1/--страниц
Пожаловаться на содержимое документа