Язык программирования C++. Пятое издание

Липпман Стенли Б.

Лажойе Жози

Му Барбара Э.

Лучшее руководство по программированию и справочник по языку, полностью пересмотренное и обновленное под стандарт С++11!

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

стандарт С++11

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

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

Стенли Б. Липпман

работал старшим консультантом в Jet Propulsion Laboratory, архитектором группы Visual С++ корпорации Microsoft, техническим сотрудником Bell Laboratories и главным инженером- программистом по анимации в кинокомпаниях Disney, DreamWorks, Pixar и PDI.

Жози Лажойе

, работающий ныне в кинокомпании Pixar, был членом канадской группы разработчиков компилятора C/C++ корпорации IBM, а также возглавлял рабочую группу базового языка С++ в составе международной организации по стандартизации ANSI/ISO.

Барбара Э. Му

имеет почти тридцатилетний опыт программирования. На протяжении пятнадцати лет она работала в компании AT&T, сотрудничая с Бьярне Страуструпом, автором языка С++, и несколько лет руководила группой разработчиков С++.

• Узнайте, как использовать новые средства языка С++11 и стандартной библиотеки для быстрого создания надежных программ, а также ознакомьтесь с высокоуровневым программированием

• Учитесь на примерах, в которых показаны передовые стили программирования и методики проектирования

• Изучите принципы и узнайте почему язык С++11 работает именно так

• Воспользуйтесь множеством перекрестных ссылок, способных помочь вам объединить взаимосвязанные концепции и проникнуть в суть

• Ознакомьтесь с современными методиками обучения и извлеките пользу из упражнений, в которых подчеркиваются ключевые моменты, позволяющие избежать проблем

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

Исходный код примеров можно загрузить с веб-страницы книги на сайте издательства по адресу: http://www.williamspublishing.com

Введение

Благодаря предыдущим изданиям книги язык С++ изучило множество программистов. За истекшее время язык С++ претерпел существенные усовершенствования, а основное внимание сообщества программистов переместилось главным образом с эффективности использования аппаратных средств к эффективности программирования.

В 2011 году комитет по стандартам С++ выпустил новую основную версию стандарта ISO С++. Этот пересмотренный стандарт является последним этапом развития языка С++, его основное внимание уделено эффективности программирования. Основные задачи нового стандарта таковы.

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

• Упростить, обезопасить и повысить эффективность использования стандартных библиотек.

• Облегчить написание эффективных абстракций и библиотек.

Глава 1

Первые шаги

Эта глава знакомит с большинством фундаментальных элементов языка С++: типами, переменными, выражениями, операторами и функциями. Кроме того, здесь кратко описано, как откомпилировать программу и запустить ее на выполнение.

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

Лучше всего изучать новый язык программирования в процессе написания программ. В этой главе мы напишем простую программу для книжного магазина.

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

0-201-70353-Х 4 24.99

1.1. Создание простой программы на языке С++

Каждая программа С++ содержит одну или несколько

функций

(function), причем одна из них обязательно имеет имя

main()

. Запуская программу С++, операционная система вызывает именно функцию

main()

. Вот простая версия функции

main()

, которая не делает ничего, кроме возвращения значения 0 операционной системе:

int main() {

 return 0;

}

Определение функции содержит четыре элемента:

тип возвращаемого значения

(return type),

имя функции

(function name),

список параметров

(parameter list), который может быть пустым, и

тело функции

(function body). Хотя функция

main()

является в некоторой степени особенной, мы определяем ее таким же способом, как и любую другую функцию.

1.1.1. Компиляция и запуск программы

Написанную программу необходимо откомпилировать. Способ компиляции программы зависит от используемой операционной системы и компилятора. Более подробную информацию о работе используемого вами компилятора можно получить в его документации или у хорошо осведомленного коллеги.

Большинство PC-ориентированных компиляторов обладают

интегрированной средой разработки

(Integrated Development Environment — IDE), которая объединяет компилятор с соответствующими средствами редактирования и отладки кода. Эти средства весьма удобны при разработке сложных программ, однако ими следует научиться пользоваться. Описание подобных систем выходит за рамки этой книги.

Большинство компиляторов, включая укомплектованные IDE, обладают интерфейсом командной строки. Если читатель не очень хорошо знаком с IDE используемого компилятора, то, возможно, имеет смысл начать с применения более простого интерфейса командной строки. Это позволит избежать необходимости сначала изучать IDE, а затем сам язык. Кроме того, хорошо понимая язык, вам, вероятно, будет проще изучить интегрированную среду разработки.

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

файлами исходного кода

(source file). На большинстве систем имя файла исходного кода заканчивается суффиксом (расширением), где после точки следует один или несколько символов. Суффикс указывает операционной системе, что файл содержит исходный код программы С++. Различные компиляторы используют разные суффиксы; к наиболее распространенным относятся

.cc

,

.cxx

,

.cpp

, .

cp

и

.

1.2. Первый взгляд на ввод-вывод

В самом языке С++ никаких операторов для

ввода и вывода

(Input/Output — IO) нет. Их предоставляет

стандартная библиотека

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

В большинстве примеров этой книги использована

библиотека

iostream

. Ее основу составляют два типа,

istream

и

ostream

, которые представляют потоки ввода и вывода соответственно.

Поток

(stream) — это последовательность символов, записываемая или читаемая из устройства ввода-вывода некоторым способом. Термин "поток" подразумевает, что символы поступают и передаются последовательно на протяжении определенного времени.

В библиотеке определены четыре объекта ввода-вывода. Для осуществления ввода используется объект

cin

(произносится "си-ин") типа

istream

. Этот объект упоминают также как

стандартный ввод

(standard input). Для вывода используется объект

cout

(произносится "си-аут") типа

ostream

. Его зачастую упоминают как

стандартный вывод

(standard output). В библиотеке определены еще два объекта типа

ostream

— это

cerr

и

clog

(произносится "си-ерр" и "си-лог" соответственно). Объект

cerr

, называемый также

стандартной ошибкой

(standard error), как правило, используется в программах для создания предупреждений и сообщений об ошибках, а объект

clog

— для создания информационных сообщений.

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

cin

они считываются из того окна, в котором выполняется программа. Аналогично при выводе данных объектами

cout

,

cerr

или

clog

они отображаются в том же окне.

1.3. Несколько слов о комментариях

Прежде чем перейти к более сложным программам, рассмотрим комментарии языка С++.

Комментарий

(comment) помогает человеку, читающему исходный текст программы, понять ее смысл. Как правило, они используются для кратких заметок об используемом алгоритме, о назначении переменных или для дополнительных разъяснений сложного фрагмента кода. Поскольку компилятор игнорирует комментарии, они никак не влияют ни на размер исполняемой программы, ни на ее поведение или производительность.

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

В языке С++ существуют два вида комментариев: однострочные и парные. Однострочный комментарий начинается символом двойной наклонной черты (

//

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

Второй тип комментария, заключенного в пару символов (

/*

и

*/

), унаследован от языка С. Такие комментарии начинаются символом

/*

и завершаются символом

*/

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

*/

. Все, что находится между символами

/*

и

*/

, компилятор считает комментарием.

1.4. Средства управления

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

1.4.1. Оператор

while

Оператор

while

организует итерационное (циклическое) выполнение фрагмента кода, пока его условие остается истинным. Используя оператор

while

, можно написать следующую программу, суммирующую числа от

1

до

10

включительно:

#include <iostream>

int main() {

 int sum = 0, val = 1;

 // продолжать выполнение цикла, пока значение val

1.4.2. Оператор

for

В рассмотренном ранее цикле

while

для управления количеством итераций использовалась переменная

val

. Мы проверяли ее значение в условии, а затем в теле цикла

while

увеличивали его.

Такая схема, подразумевающая использование переменной в условии и ее инкремент в теле столь популярна, что было разработано второе средство управления —

оператор

for

, существенно сокращающий подобный код. Используя оператор

for

, можно было бы переписать код программы, суммирующей числа от 1 до 10, следующим образом:

#include <iostream>

int main() {

 int sum = 0;

1.4.3. Ввод неизвестного количества данных

В приведенных выше разделах мы писали программы, которые суммировали числа от 1 до 10. Логическое усовершенствование этой программы подразумевало бы запрос суммируемых чисел у пользователя. В таком случае мы не будем знать, сколько чисел суммировать. Поэтому продолжим читать числа, пока будет что читать.

#include <iostream>

int main() {

 int sum = 0, value = 0;

 // читать данные до конца файла, вычислить сумму всех значений

1.4.4. Оператор

if

Подобно большинству языков, С++ предоставляет

оператор

if

, который обеспечивает выполнение операторов по условию. Оператор

if

можно использовать для написания программы подсчета количества последовательных совпадений значений во вводе:

#include <iostream>

int main() {

 // currVal - подсчитываемое число; новые значения будем читать в val

 int currVal = 0, val = 0;

Часть I

Основы

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

• Встроенные типы данных (например, целые числа, символы и т.д.).

• Переменные, позволяющие присваивать имена используемым объектам.

• Выражения и операторы, позволяющие манипулировать значениями этих типов.

• Управляющие структуры, такие как

if

или

while

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

Глава 2

Переменные и базовые типы

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

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

Тип определяет назначение данных и операции, которые с ними можно выполнять. Например, назначение простого оператора

i = i + j

; полностью зависит от типов переменных

i

и

j

. Если это целые числа, данный оператор представляет собой обычное арифметическое сложение. Но если это объекты класса

Sales_item

, то данный оператор суммирует их компоненты (см раздел 1.5.1).

2.1. Простые встроенные типы

В языке С++ определен набор базовых типов, включая

арифметические типы

(arithmetic type), и специальный тип

void

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

void

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

2.1.1. Арифметические типы

Есть две разновидности арифметических типов:

целочисленные типы

(включая символьные и логические типы) и

типы с плавающей запятой

.

Размер (т.е. количество битов) арифметических типов зависит от конкретного компьютера. Стандарт гарантирует минимальные размеры, перечисленные в табл. 2.1. Однако компиляторы позволяют использовать для этих типов большие размеры. Поскольку количество битов не постоянно, значение одного типа также может занимать в памяти больше или меньше места.

Таблица 2.1. Арифметические типы языка С++

2.1.2. Преобразование типов

Тип объекта определяет данные, которые он может содержать, и операции, которые с ним можно выполнять. Среди операций, поддерживаемых множеством типов, есть возможность

преобразовать

(convert) объект данного типа в другой, связанный тип.

Преобразование типов происходит автоматически, когда объект одного типа используется там, где ожидается объект другого типа. Более подробная информация о преобразованиях приведена в разделе 4.11, а пока имеет смысл понять, что происходит при присвоении значения одного типа объекту другого.

Когда значение одного арифметического типа присваивается другому

bool b = 42;          // b содержит true

int i = b;            // i содержит значение 1

2.1.3. Литералы

Такое значение, как

42

, в коде программы называется

литералом

(literal), поскольку его значение самоочевидно. У каждого литерала есть тип, определяемый его формой и значением.

Целочисленный литерал может быть в десятичной, восьмеричной или шестнадцатеричной форме. Целочисленные литералы, начинающиеся с нуля (

0

), интерпретируются как восьмеричные, а начинающиеся с

0x

или

0X

— как шестнадцатеричные. Например, значение

20

можно записать любым из трех следующих способов.

20   // десятичная форма

024  // восьмеричная форма

2.2. Переменные

Переменная

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

2.2.1. Определения переменных

Простое определение переменной состоит из

спецификатора типа

(type specifier), сопровождаемого списком из одного или нескольких имен переменных, отделенных запятыми, и завершающей точки с запятой. Тип каждого имени в списке задан спецификатором типа. Определение может (не обязательно) предоставить исходное значение для одного или нескольких определяемых имен:

int sum = 0, value, // sum, value и units_sold имеют тип int

    units_sold = 0; // sum и units_sold инициализированы значением 0

Sales_item item;    // item имеет тип Sales_item (см. p. 1.5.1)

// string — библиотечный тип, представляющий последовательность

2.2.2. Объявления и определения переменных

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

раздельная компиляция

(separate compilation). Раздельная компиляция позволяет составлять программу из нескольких файлов, каждый из которых может быть откомпилирован независимо.

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

std::cout

и

std::cin

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

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

Для поддержки раздельной компиляции язык С++ различает объявления и определения.

Объявление

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

Определение

(definition) создает соответствующую сущность.

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

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

// определено четыре разных переменных типа int

int somename, someName, SomeName, SOMENAME;

Язык резервирует набор имен, перечисленных в табл. 2.3 и 2.4, для собственных нужд. Эти имена не могут использоваться как идентификаторы.

2.2.4. Область видимости имен

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

Область видимости

(scope) — это часть программы, в которой у имени есть конкретное значение. Как правило, области видимости в языке С++ разграничиваются фигурными скобками.

В разных областях видимости то же имя может относиться к разным сущностям. Имена видимы от момента их объявления и до конца области видимости, в которой они объявлены.

В качестве примера рассмотрим программу из раздела 1.4.2:

#include <iostream>

2.3. Составные типы

Составной тип

(compound type) — это тип, определенный в терминах другого типа. У языка С++ есть несколько составных типов, два из которых, ссылки и указатели, мы рассмотрим в этой главе.

У рассмотренных на настоящий момент объявлений не было ничего, кроме имен переменных. Такие переменные имели простейший, базовый тип объявления. Более сложные операторы объявления позволяют определять переменные с составными типами, которые состоят из объявлений базового типа.

2.3.1. Ссылки

Ссылка (reference) является альтернативным именем объекта. Ссылочный тип "ссылается на" другой тип. В определении ссылочного типа используется оператор объявления в форме

&d

, где

d

— объявляемое имя:

int ival = 1024;

int &refVal = ival; // refVal ссылается на другое имя, ival

int &refVal2;       // ошибка: ссылку следует инициализировать

Обычно при инициализации переменной значение инициализатора копируется в создаваемый объект. При определении ссылки вместо копирования значения инициализатора происходит

связывание

(bind) ссылки с ее инициализатором. После инициализации ссылка остается связанной с исходным объектом. Нет никакого способа изменить привязку ссылки так, чтобы она ссылалась на другой объект, поэтому ссылки

следует

инициализировать.

2.3.2. Указатели

Указатель

(pointer) — это составной тип, переменная которого указывает на объект другого типа. Подобно ссылкам, указатели используются для косвенного доступа к другим объектам. В отличие от ссылок, указатель — это настоящий объект. Указатели могут быть присвоены и скопированы; один указатель за время своего существования может указывать на несколько разных объектов. В отличие от ссылки, указатель можно не инициализировать в момент определения. Подобно объектам других встроенных типов, значение неинициализированного указателя, определенного в области видимости блока, неопределенно.

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

Тип указателя определяется оператором в форме

*d

, где

d

— определяемое имя. Символ

*

следует повторять для каждой переменной указателя.

int *ip1, *ip2;  // ip1 и ip2 — указатели на тип int

double dp, *dp2; // dp2 — указатель на тип double;

2.3.3. Понятие описаний составных типов

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

// i - переменная типа int; p - указатель на тип int;

// r - ссылка на тип int

int i = 1024, *p = &i, &r = i;

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

2.4. Спецификатор

const

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

спецификатор

const

(qualifier const):

const int bufSize = 512; // размер буфера ввода

Это определит переменную

bufSize

как константу. Любая попытка присвоить ей значение будет ошибкой:

bufSize = 512; // ошибка: попытка записи в константный объект

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

2.4.1. Ссылка на константу

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

ссылка на константу

(reference to

const

), т.е. ссылка на объект типа

const

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

const int ci = 1024;

const int &r1 = ci; // ok: и ссылка, и основной объект - константы

r1 = 42;            // ошибка: r1 - ссылка на константу

int &r2 = ci; // ошибка: неконстантная ссылка на константный объект

2.4.2. Указатели и спецификатор

const

Подобно ссылкам, вполне возможно определять указатели, которые указывают на объект константного или неконстантного типа. Как и ссылку на константу (см. раздел 2.4.1),

указатель на константу

(pointer to

const

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

const double pi = 3.14;   // pi - константа; ее значение неизменно

double *ptr = &pi;        // ошибка: ptr - простой указатель

const double *cptr = &pi; // ok: cptr может указывать на тип

                          //  const double

2.4.3. Спецификатор

const

верхнего уровня

Как уже упоминалось, указатель — это объект, способный указывать на другой объект. В результате можно сразу сказать, является ли указатель сам константой и являются ли константой объекты, на которые он может указывать. Термин

спецификатор const верхнего уровня

(top-level

const

) используется для обозначения того ключевого слова

const

, которое объявляет константой сам указатель. Когда указатель способен указывать на константный объект, это называется

спецификатор const нижнего уровня

(low-level

const

). 

В более общем смысле спецификатор

const

верхнего уровня означает, что объект сам константа. Спецификатор

const

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

const

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

const

как верхнего, так и нижнего уровня, независимо друг от друга.

int i = 0;

int *const pi = &i;  // нельзя изменить значение pi;

                     // const верхнего уровня

2.4.4. Переменные

constexpr

и константные выражения

Константное выражение

(constant expression) — это выражение, значение которого не может измениться и вычисляется во время компиляции. Литерал — это константное выражение. Константный объект, инициализируемый константным выражением, также является константным выражением. Вскоре мы увидим, что в языке есть несколько контекстов, требующих константных выражений.

Является ли данный объект (или выражение) константным выражением, зависит от типов и инициализаторов. Например:

const int max_files = 20;  // max_files - константное выражение

const int limit = max_files + 1; // limit - константное выражение

int staff_size = 27;       // staff_size - неконстантное выражение

Глава 3

Типы

string

,

vector

и массивы

Кроме встроенных типов, рассмотренных в главе 2, язык С++ предоставляет богатую библиотеку абстрактных типов данных. Важнейшими библиотечными типами являются тип

string

, поддерживающий символьные строки переменной длины, и тип

vector

, определяющий коллекции переменного размера. С типами

string

и

vector

связаны типы, известные как

итераторы

(iterator). Они используются для доступа к символам строк и элементам векторов.

Типы

string

и

vector

, определенные в библиотеке, являются абстракциями более простого встроенного типа массива. Эта главы посвящена массивам и введению в библиотечные типы

vector

и

string

.

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

В данной главе представлены два важнейших библиотечных типа:

string

и

vector

. Тип

string

— это последовательность символов переменной длины. Тип vector содержит последовательность объектов указанного типа переменной длины. Мы также рассмотрим встроенный тип массива. Как и другие встроенные типы, массивы представляют возможности аппаратных средств. В результате массивы менее удобны в использовании, чем библиотечные типы

string

и

vector

.

Однако, прежде чем начать исследование библиотечных типов, рассмотрим механизм, упрощающий доступ к именам, определенным в библиотеке.

3.1. Пространства имен и объявления

using

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

std

. Например, при чтении со стандартного устройства ввода применялась форма записи

std::cin

. Здесь использован

оператор области видимости

::

(см. раздел 1.2). Он означает, что имя, указанное в правом операнде оператора, следует искать в области видимости, указанной в левом операнде. Таким образом, код

std::cin

означает, что используемое имя

cin

определено в пространстве имен

std

.

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

объявление

using

(

using

declaration). Другие способы, позволяющие упростить использование имен из других пространств, рассматриваются в разделе 18.2.2.

Объявление

using

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

имя_пространства_имен ::

. Объявление

using

имеет следующий формат:

using пространство_имен :: имя ;

После того как объявление

using

было сделано один раз, к указанному в нем имени можно обращаться без указания пространства имен.

3.2. Библиотечный тип

string

Строка

(string) — это последовательность символов переменной длины. Чтобы использовать тип

string

, необходимо включить в код заголовок

string

. Поскольку тип

string

принадлежит библиотеке, он определен в пространстве имен

std

. Наши примеры подразумевают наличие следующего кода:

#include <string>

using std::string;

В этом разделе описаны наиболее распространенные операции со строками; а дополнительные операции рассматриваются в разделе 9.5.

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

3.2.1. Определение и инициализация строк

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

string s1;          // инициализация по умолчанию; s1 - пустая строка

string s2 = s1;     // s2 - копия s1

string s3 = "hiya"; // s3 - копия строкового литерала

string s4(10, 'c'); // s4 - cccccccccc

3.2.2. Операции со строками

Наряду с определением способов создания и инициализации объектов класс определяет также операции, которые можно выполнять с объектами класса. Класс может определить обладающие именем операции, такие как функция

isbn()

класса

Sales_item

(см. раздел 1.5.2). Класс также может определить то, что означают различные символы операторов, такие как

<<

или

+

, когда они применяются к объектам класса. Наиболее распространенные операции класса

string

приведены в табл. 3.2.

Таблица 3.2. Операции класса string

3.2.3. Работа с символами строки

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

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

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

cctype

.

Таблица 3.3. Функции cctype

3.3. Библиотечный тип

vector

Вектор

(vector) — это коллекция объектов одинакового типа, каждому из которых присвоен целочисленный индекс, предоставляющий доступ к этому объекту. Вектор — это

контейнер

(container), поскольку он "содержит" другие объекты. Более подробная информация о контейнерах приведена в части II.

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

using

.

#include <vector>

using std::vector;

Тип

vector

— это

шаблон класса

(class template). Язык С++ поддерживают шаблоны и классов, и функций. Написание шаблона требует довольно глубокого понимания языка С++. До главы 16 мы даже не будем рассматривать создание собственных шаблонов! К счастью, чтобы использовать шаблоны, вовсе не обязательно уметь их создавать.

3.3.1. Определение и инициализация векторов

Подобно любому типу класса, шаблон

vector

контролирует способ определения и инициализации векторов. Наиболее распространенные способы определения векторов приведены в табл. 3.4.

Инициализация вектора по умолчанию (см. раздел 2.2.1) позволяет создать пустой вектор определенного типа:

vector<string> svec; // инициализация по умолчанию;

                     //  у svec нет элементов

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

3.3.2. Добавление элементов в вектор

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

Если необходим вектор со значениями от 0 до 9, то можно легко использовать списочную инициализацию. Но что если необходимы элементы от 0 до 99 или от 0 до 999? Списочная инициализация была бы слишком громоздкой. В таких случаях лучше создать пустой вектор и использовать его функцию-член

push_back()

, чтобы добавить элементы во время выполнения. Функция

push_back()

вставляет переданное ей значение в вектор как новый последний элемент. Рассмотрим пример.

vector<int> v2; // пустой вектор

for (int i = 0; i != 100; ++i)

 v2.push_back(i); // добавить последовательность целых чисел в v2

3.3.3. Другие операции с векторами

Кроме функции

push_back()

, шаблон

vector

предоставляет еще несколько операций, большинство из которых подобно соответствующим операциям класса

string

. Наиболее важные из них приведены в табл. 3.5.

Таблица 3.5. Операции с векторами

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

for

(раздел 3.2.3).

3.4. Знакомство с итераторами

Хотя для доступа к символам строки или элементам вектора можно использовать индексирование, для этого существует и более общий механизм —

итераторы

(iterator). Как будет продемонстрировано в части II, кроме векторов библиотека предоставляет несколько других видов контейнеров. У всех библиотечных контейнеров есть итераторы, но только некоторые из них поддерживают оператор индексирования. С технической точки зрения тип

string

не является контейнерным, но он поддерживает большинство контейнерных операций. Как уже упоминалось, и строки, и векторы предоставляют оператор индексирования. У них также есть итераторы.

Как и указатели (см. раздел 2.3.2), итераторы обеспечивают косвенный доступ к объекту. В случае итератора этим объектом является элемент в контейнере или символ в строке. Итератор позволяет выбрать элемент, а также поддерживает операции перемещения с одного элемента на другой. Подобно указателям, итератор может быть допустим или недопустим. Допустимый итератор указывает либо на элемент, либо на позицию за последним элементом в контейнере. Все другие значения итератора недопустимы.

3.4.1. Использование итераторов

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

begin()

и

end()

. Функция-член

begin()

возвращает итератор, который обозначает первый элемент (или первый символ), если он есть.

// типы b и е определяют компилятор; см. раздел 2.5.2

// b обозначает первый элемент контейнера v, а е - элемент

// после последнего

auto b = v.begin(), е = v.end();

3.4.2. Арифметические действия с итераторами

Инкремент итератора перемещает его на один элемент. Инкремент поддерживают итераторы всех библиотечных контейнеров. Аналогично операторы

==

и

!=

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

Итераторы строк и векторов поддерживают дополнительные операции, позволяющие перемещать итераторы на несколько позиций за раз. Они также поддерживают все операторы сравнения. Эти операторы зачастую называют

арифметическими действиями с итераторами

(iterator arithmetic). Они приведены в табл. 3.7.

Таблица 3.7. Операции с итераторами векторов и строк

Глава 4

Выражения

Язык С++ предоставляет богатый набор операторов, а также определяет их назначение и применение к операндам встроенного типа. Он позволяет также определять назначение большинства операторов, операндами которых являются объекты классов. Эта глава посвящена операторам, определенным в самом языке и применяемым к операндам встроенных типов. Будут описаны также некоторые из операторов, определенных библиотекой. Определение операторов для собственных типов рассматривается в главе 14.

Выражение

(expression) состоит из одного или нескольких

операторов

(operator) и возвращает

результат

(result) вычисления. Самая простая форма выражения — это одиночной литерал или переменная. Более сложные выражения формируются из оператора и одного или нескольких

операндов

(operand).

4.1. Основы

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

4.1.1. Фундаментальные концепции

Существуют

унарные операторы

(unary operator) и

парные операторы

(binary operator). Унарные операторы, такие как обращение к адресу (

&

) и обращение к значению (

*

), воздействуют на один операнд. Парные операторы, такие как равенство (

==

) и умножение (

*

), воздействуют на два операнда. Существует также (всего один)

тройственный оператор

(ternary operator), который использует три операнда, а также

оператор вызова функции

(function call), который получает неограниченное количество операндов.

Некоторые

символы

(symbol), например

*

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

Чтобы лучше понять порядок выполнения выражений с несколькими операторами, следует рассмотреть концепцию

приоритета

(precedence),

порядка

(associativity) и

порядка вычисления

(order of evaluation) операторов. Например, в следующем выражении используются сложение, умножение и деление:

5 + 10 * 20/2;

4.1.2. Приоритет и порядок

Выражения с двумя или несколькими операторами называются составными (compound expression). Результат составного выражения определяет способ группировки операндов в отдельных операторах. Группировку операндов определяют приоритет и порядок. Таким образом, они определяют, какие части выражения будут операндами для каждого из операторов в выражении. При помощи скобок программисты могут изменять эти правила, обеспечивая необходимую группировку.

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

• Благодаря приоритету результатом выражения

3 + 4*5

будет

23

, а не

35

.

• Благодаря порядку результатом выражения

20-15-3

будет

2

, а не

8

.

В более сложном примере при вычислении слева направо следующего выражения получается 20:

4.1.3. Порядок вычисления

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

f1()

и

f2()

будут вызваны перед умножением:

int i = f1() * f2();

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

f1()

вызвана до функции

f2()

, или наоборот.

Для операторов, которые не определяют порядок вычисления, выражение, пытающееся

обратиться

к тому же объекту и

изменить его

, было бы ошибочным. Выражения, которые действительно так поступают, имеют непредсказуемое поведение (см. раздел 2.1.2). Вот простой пример: оператор

<<

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

int i = 0;

4.2. Арифметические операторы

Таблица 4.1. Арифметические операторы

(левосторонний порядок)

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

Если не указано иное, то арифметические операторы могут быть применены к любому арифметическому типу (см. раздел 2.1.1) или любому типу, который может быть преобразован в арифметический тип. Операнды и результаты этих операторов являются r-значениями. Как упоминается в разделе 4.11, в ходе вычисления операторов их операнды малых целочисленных типов преобразуются в больший целочисленный тип и все операнды могут быть преобразованы в общий тип.

4.3. Логические операторы и операторы отношения

Операторам отношения передают операторы арифметического типа или типа указателя, а логическим операторам — операнды любого типа, допускающего преобразование в тип

bool

. Все они возвращают значение типа

bool

. Арифметические операнды и указатели со значением нуль рассматриваются как значение

false

, а все другие как значение

true

. Операнды для этих операторов являются r-значениями, а результат — r-значение.

Таблица 4.2. Логические операторы и операторы отношения

4.4. Операторы присвоения

Левым операндом оператора присвоения должно быть допускающее изменение l-значение. Ниже приведено несколько примеров недопустимых попыток присвоения.

int i = 0, j = 0, k = 0; // инициализация, а не присвоение

const int ci = i;        // инициализация, а не присвоение

1024 = k;  // ошибка: литерал является r-значением

i + j = k; // ошибка: арифметическое выражение - тоже r-значение

Глава 5

Операторы

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

Операторы

(statement) выполняются последовательно. За исключением самых простых программ последовательного выполнения недостаточно. Поэтому язык С++ определяет также набор операторов

управления потоком

(flow of control), обеспечивающих более сложные пути выполнения кода.

5.1. Простые операторы

Большинство операторов в языке С++ заканчиваются точкой с запятой. Выражение типа

ival + 5

становится

оператором выражения

(expression statement), завершающимся точкой с запятой. Операторы выражения составляют вычисляемую часть выражения.

ival + 5;     // оператор выражения (хоть и бесполезный)

cout << ival; // оператор выражения

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

5.2. Операторная область видимости

Переменные можно определять в управляющих структурах операторов

if

,

switch

,

while

и

for

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

while (int i = get_num()) // i создается и инициализируется при

                          // каждой итерации

 cout << i << endl;

i = 0; // ошибка: переменная i недоступна вне цикла

5.3. Условные операторы

Язык С++ предоставляет два оператора, обеспечивающих условное выполнение. Оператор

if

разделяет поток выполнения на основании условия. Оператор

switch

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

5.3.1. Оператор

if

Оператор

if

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

if

: с разделом

else

и без него. Синтаксис простой формы оператора

if

имеет следующий вид:

if ( условие )

 оператор

Оператор

if else

имеет следующую форму:

if ( условие )

5.3.2. Оператор

switch

Оператор

switch

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

• Читать каждый введенный символ.

• Сравнить каждый символ с набором искомых гласных.

• Если символ соответствует одной из гласных букв, добавить

1

к соответствующему счетчику.

• Отобразить результаты.

5.4. Итерационные операторы

Итерационные операторы

(iterative statement), называемые также

циклами

(loop), обеспечивают повторное выполнение кода, пока их условие истинно. Операторы

while

и

for

проверяют условие прежде, чем выполнить тело. Оператор

do while

сначала выполняет тело, а затем проверяет свое условие.

5.4.1. Оператор

while

Оператор

while

многократно выполняет оператор, пока его условие остается истинным. Его синтаксическая форма имеет следующий вид:

while ( условие )

  оператор

Пока

условие

истинно (значение

true

),

оператор

(который зачастую является блоком кода) выполняется. Условие не может быть пустым. Если при первой проверке условие ложно (значение

false

), оператор не выполняется.

Условие может быть выражением или объявлением инициализированной переменной (см. раздел 5.2). Обычно либо само условие, либо тело цикла должно делать нечто изменяющее значение выражения. В противном случае цикл никогда не закончится.

5.4.2. Традиционный оператор

for

Оператор

for

имеет следующий синтаксис:

for ( инициализирующий-оператор условие ; выражение )

  оператор

Слово

for

и часть в круглых скобках зачастую упоминают как

заголовок

for

(

for

header).

Инициализирующий-оператор

должен быть оператором объявления, выражением или пустым оператором. Каждый из этих операторов завершается точкой с запятой, поэтому данную синтаксическую форму можно рассматривать так:

5.4.3. Серийный оператор

for

Новый стандарт ввел упрощенный оператор

for

, который перебирает элементы контейнера или другой последовательности. Синтаксис

серийного оператора

for

(range

for

) таков:

for ( объявление : выражение )

  оператор

выражение

должно представить некую последовательность, такую, как список инициализации (см. раздел 3.3.1), массив (см. раздел 3.5), или объект такого типа, как

vector

или

string

, у которого есть функции-члены

begin()

и

end()

, возвращающие итераторы (см. раздел 3.4).

объявление

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

auto

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

5.4.4. Оператор

do while

Оператор

do while

похож на оператор

while

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

do

  оператор

while (условие);

После заключенного в скобки условия оператор

do while

заканчивается точкой с запятой.