Язык программирования C Пятое издание
Скачать 1.85 Mb.
|
1. Каково его имя? 2. Где он определен? 3. Что он делает? Предположим, что класс для решения проблемы книжного магазина имеет имя Sales_item, а определен он в заголовке Sales_item.h. Как уже было продемонстрировано на примере использования таких библиотечных средств, как объекты ввода и вывода, в код необходимо включить соответствующий заголовок. Точно так же заголовки используются для доступа к классам, определенным для наших собственных приложений. Традиционно имена файлов заголовка совпадают с именами определенных в них классов. У написанных нами файлов заголовка, как правило, будет суффикс .h, но некоторые программисты используют расширение .H, .hpp или .hxx. У заголовков стандартной библиотеки обычно нет никакого суффикса вообще. Компиляторы, как правило, не заботятся о форме имен файлов заголовка, но интегрированные среды разработки иногда это делают. 1.5.1. Класс Sales_item Класс Sales_item предназначен для хранения ISBN, а также для отслеживания количества проданных экземпляров, полученной суммы и средней цены проданных книг. Не будем пока рассматривать, как эти данные сохраняются и вычисляются. Чтобы применить класс, необходимо знать, что он делает, а не как . Каждый класс является определением типа. Имя типа совпадает с именем класса. Page 29/1103 Следовательно, класс Sales_item определен как тип Sales_item. Подобно встроенным типам данных, вполне можно создать переменную типа класса. Рассмотрим пример. Sales_item item; Этот код создает объект item типа Sales_item. Как правило, об этом говорят так: создан "объект типа Sales_item", или "объект класса Sales_item", или даже "экземпляр класса Sales_item". Кроме создания переменных типа Sales_item, с его объектами можно выполнять следующие операции. • Вызывать функцию isbn(), чтобы извлечь ISBN из объекта класса Sales_item. • Использовать операторы ввода (>>) и вывода (<<), чтобы читать и отображать объекты класса Sales_item. • Использовать оператор присвоения (=), чтобы присвоить один объект класса Sales_item другому. • Использовать оператор суммы (+), чтобы сложить два объекта класса Sales_item. ISBN этих двух объектов должен совпадать. Результатом будет новый объект Sales_item с тем же ISBN, а количество проданных экземпляров и суммарный доход будут суммой соответствующих значений его операндов. • Использовать составной оператор присвоения (+=), чтобы добавить один объект класса Sales_item к другому. Ключевая концепция. Определение поведения класса Читая эти программы, очень важно иметь в виду, что все действия, которые могут быть осуществлены с объектами класса Sales_item, определяет его автор. Таким образом, класс Sales_item определяет то, что происходит при создании объекта класса Sales_item, а также то, что происходит при его присвоении, сложении или выполнении операторов ввода и вывода. Автор класса вообще определяет все операции, применимые к объектам типа класса. На настоящий момент с объектами класса Sales_item можно выполнять только те операции, которые перечислены в этом разделе. Чтение и запись объектов класса Sales_item Теперь, когда известны операции, которые можно осуществлять, используя объекты класса Sales_item, можно написать несколько простых программ, использующих его. Программа, приведенная ниже, читает данные со стандартного устройства ввода в объект Sales_item, а затем отображает его на стандартном устройстве вывода. #include <iostream> #include "Sales_item.h" int main() { Sales_item book; // прочитать ISBN, количество проданных экземпляров и цену std::cin >> book; Page 30/1103 // вывести ISBN, количество проданных экземпляров, // общую сумму и среднюю цену std::cout << book << std::endl; return 0; } Если ввести значения 0-201-70353-X 4 24.99, то будет получен результат 0-201-70353-X 4 99.96 24.99. Во вводе было указано, что продано четыре экземпляра книги по 24,99 доллара каждый, а вывод свидетельствует, что всего продано четыре экземпляра, общий доход составлял 99,96 доллара, а средняя цена на книгу получилась 24,99 доллара. Код программы начинается двумя директивами #include, одна из которых имеет новую форму. Заголовки стандартной библиотеки заключают в угловые скобки (<>), а те, которые не являются частью библиотеки, — в двойные кавычки (""). В функции main() определяется объект book, используемый для хранения данных, читаемых со стандартного устройства ввода. Следующий оператор осуществляет чтение в этот объект, а третий оператор выводит его на стандартное устройство вывода, сопровождая манипулятором endl. Суммирование объектов класса Sales_item Немного интересней пример суммирования двух объектов класса Sales_item. #include <iostream> #include "Sales_item.h" int main() { Sales_item item1, item2; std::cin >> item1 >> item2; // прочитать две транзакции std::cout << item1 + item2 << std::endl; // отобразить их сумму return 0; } Если ввести следующие данные: 0-201-78345-X 3 20.00 0-201-78345-X 2 25.00 то вывод будет таким: Page 31/1103 0-201-78345-X 5 110 22 Программа начинается с включения заголовков Sales_item и iostream. Затем создаются два объекта (item1 и item2) класса Sales_item, предназначенные для хранения транзакций. В эти объекты читаются данные со стандартного устройства ввода. Выражение вывода суммирует их и отображает результат. Обратите внимание: эта программа очень похожа на программу, приведенную в разд 1.2: она читает два элемента данных и отображает их сумму. Отличаются они лишь тем, что в первом случае суммируются два целых числа, а во втором — два объекта класса Sales_item. Кроме того, сама концепция "суммы" здесь различна. В случае с типом int получается обычная сумма — результат сложения двух числовых значений. В случае с объектами класса Sales_item используется концептуально новое понятие суммы — результат сложения соответствующих компонентов двух объектов класса Sales_item. Использование перенаправления файлов Неоднократный ввод этих транзакций при проверке программы может оказаться утомительным. Большинство операционных систем поддерживает перенаправление файлов, позволяющее ассоциировать именованный файл со стандартным устройством ввода и стандартным устройством вывода: $ addItems <infile >outfile Здесь подразумевается, что $ — это системное приглашение к вводу, а наша программа суммирования была откомпилирована в исполняемый файл addItems.exe (или addItems на системе UNIX). Эта команда будет читать транзакции из файла infile и записывать ее вывод в файл outfile в текущем каталоге. Упражнения раздела 1.5.1 Упражнение 1.20. По адресу http://www.informit.com/title/032174113 в каталоге кода первой главы содержится копия файла Sales_item.h. Скопируйте этот файл в свой рабочий каталог и используйте при написании программы, которая читает набор транзакций проданных книг и отображает их на стандартном устройстве вывода. Упражнение 1.21. Напишите программу, которая читает два объекта класса Sales_item с одинаковыми ISBN и вычисляет их сумму. Упражнение 1.22. Напишите программу, читающую несколько транзакций с одинаковым ISBN и отображающую сумму всех прочитанных транзакций. 1.5.2. Первый взгляд на функции-члены Программа суммирования объектов класса Sales_item должна проверять наличие у этих объектов одинаковых ISBN. Сделаем это так: #include <iostream> #include "Sales_item.h" int main() { Sales_item item1, item2; std::cin >> item1 >> item2; Page 32/1103 // сначала проверить, представляют ли объекты item1 и item2 // одну и ту же книгу if (item1.isbn() == item2.isbn()) { std::cout << item1 + item2 << std::endl; return 0; // свидетельство успеха } else { std::cerr << "Data must refer to same ISBN" << std::endl; return -1; // свидетельство отказа } } Различие между этой программой и предыдущей версией в операторе if и его ветви else. Даже не понимая смысла условия оператора if, вполне можно понять, что делает эта программа. Если условие истинно, вывод будет, как прежде, и возвратится значение 0, означающее успех. Если условие ложно, выполняется блок ветви else, который выводит сообщение об ошибке и возвращает значение -1. Что такое функция-член? Условие оператора if вызывает функцию-член (member function) isbn(). item1.isbn() == item2.isbn() Функция-член — это функция, определенная в составе класса. Функции-члены называют также методами (method) класса. Вызов функции-члена обычно происходит от имени объекта класса. Например, первый, левый, операнд оператора равенства использует оператор точка (dot operator) ( оператор .) для указания на то, что имеется в виду "член isbn() объекта по имени item1". item1.isbn Точечный оператор применим только к объектам типа класса. Левый операнд должен быть объектом типа класса, а правый операнд — именем члена этого класса. Результатом точечного оператора является член класса, заданный правым операндом. Page 33/1103 Точечный оператор обычно используется для доступа к функциям-членам при их вызове. Для вызова функции используется оператор вызова (call operator) ( оператор ()). Оператор обращения — это пара круглых скобок, заключающих список аргументов (argument), который может быть пуст. Функция- член isbn() не получает аргументов. item1.isbn() Таким образом, это вызов функции isbn(), являющейся членом объекта item1 класса Sales_item. Эта функция возвращает ISBN, хранящийся в объекте item1. Правый операнд оператора равенства выполняется тем же способом: он возвращает ISBN, хранящийся в объекте item2. Если ISBN совпадают, условие истинно, а в противном случае оно ложно. Упражнения раздела 1.5.2 Упражнение 1.23. Напишите программу, которая читает несколько транзакций и подсчитывает количество транзакций для каждого ISBN. Упражнение 1.24. Проверьте предыдущую программу, введя несколько транзакций, представляющих несколько ISBN. Записи для каждого ISBN должны быть сгруппированы. 1.6. Программа для книжного магазина Теперь все готово для решения проблемы книжного магазина: следует прочитать файл транзакций и создать отчет, где для каждой книги будет подсчитана общая выручка, средняя цена и количество проданных экземпляров. При этом подразумевается, что все транзакции для каждого ISBN вводятся группами. Программа объединяет данные по каждому ISBN в переменной total (всего). Каждая прочитанная транзакция будем сохранена во второй переменной, trans. В противном случае значение объекта total выводится на экран, а затем заменяется только что считанной транзакцией. #include <iostream> #include "Sales_item.h" int main() { Sales_item total; // переменная для хранения данных следующей // транзакции // прочитать первую транзакцию и удостовериться в наличии данных Page 34/1103 // для обработки if (std::cin >> total) { Sales_item trans; // переменная для хранения текущей транзакции // читать и обработать остальные транзакции while (std::cin >> trans) { // если все еще обрабатывается та же книга if (total.isbn() == trans.isbn()) total += trans; // пополнение текущей суммы else { // отобразить результаты по предыдущей книге std::cout << total << std::endl; total = trans; // теперь total относится к следующей // книге } } std::cout << total << std::endl; // отобразить последнюю запись } else { // нет ввода! Предупредить пользователя std::cerr << "No data?!" << std::endl; return -1; // Page 35/1103 свидетельство отказа } return 0; } Это наиболее сложная программа из рассмотренных на настоящий момент, однако все ее элементы читателю уже знакомы. Как обычно, код начинается с подключения используемых заголовков: iostream (из библиотеки) и Sales_item.h (собственного). В функции main() определен объект по имени total (для суммирования данных по текущему ISBN). Начнем с чтения первой транзакции в переменную total и проверки успешности чтения. Если чтение терпит неудачу, то никаких записей нет и управление переходит к наиболее удаленному оператору else, код которого отображает сообщение, предупреждающее пользователя об отсутствии данных. Если запись введена успешно, управление переходит к блоку после наиболее удаленного оператора if. Этот блок начинается с определения объекта trans, предназначенного для хранения считываемых транзакций. Оператор while читает все остальные записи. Как и в прежних программах, условие цикла while читает значения со стандартного устройства ввода. В данном случае данные читаются в объект trans класса Sales_item. Пока чтение успешно, выполняется тело цикла while. Тело цикла while представляет собой один оператор if, который проверяет равенство ISBN. Если они равны, используется составной оператор присвоения для суммирования объектов trans и total. Если ISBN не равны, отображается значение, хранящееся в переменной total, которой затем присваивается значение переменной trans. После выполнения кода оператора if управление возвращается к условию цикла while, читающему следующую транзакцию, и так далее, до тех пор, пока записи не исчерпаются. После выхода из цикла while переменная total содержит данные для последнего ISBN в файле. В последнем операторе блока наиболее удаленного оператора if отображаются данные последнего ISBN. Упражнения раздела 1.6 Упражнение 1.25. Используя загруженный с веб-сайта заголовок Sales_item.h, откомпилируйте и запустите программу для книжного магазина, представленную в этом разделе. Резюме Эта глава содержит достаточно информации о языке С++, чтобы позволить писать, компилировать и запускать простые программы. Здесь было описано, как определить функцию main(), которую вызывает операционная система при запуске программы. Также было продемонстрировано, как определить переменные, организовать ввод и вывод данных, использовать операторы if, for и while. Глава завершается описанием наиболее фундаментального элемента языка С++ — класса. Здесь было продемонстрировано создание и применение объектов классов, которые были созданы кем-то другим. Определение собственных классов будет описано в следующих главах. Термины Page 36/1103 Аргумент (argument). Значение, передаваемое функции. Библиотечный тип (library type). Тип, определенный в стандартной библиотеке (например, istream). Блок (block). Последовательность операторов, заключенных в фигурные скобки. Буфер (buffer). Область памяти, используемая для хранения данных. Средства ввода (или вывода) зачастую хранят вводимые и выводимые данные в буфере, работа которого никак не зависит от действий программы. Буферы вывода могут быть сброшены явно, чтобы принудительно осуществить запись на диск. По умолчанию буфер объекта cin сбрасывается при обращении к объекту cout, а буфер объекта cout сбрасывается на диск по завершении программы. Встроенный тип (built-in type). Тип данных, определенный в самом языке (например, int). Выражение (expression). Наименьшая единица вычислений. Выражение состоит из одного или нескольких операндов и оператора. Вычисление выражения определяет результат. Например, сложение целочисленных значений (i + j) — это арифметическое выражение, результатом которого является сумма двух значений. Директива #include. Делает код в указанном заголовке доступным в программе. Заголовок (header). Механизм, позволяющий сделать определения классов или других имен доступными в нескольких программах. Заголовок включается в код программы при помощи директивы #include. Заголовок iostream. Заголовок, предоставляющий библиотечные типы для потокового ввода и вывода. Имя функции (function name). Имя, под которым функция известна и может быть вызвана. Инициализация (initialize). Присвоение значения объекту в момент его создания. Класс (class). Средство определения собственной структуры данных, а также связанных с ними действий. Класс — одно из фундаментальных средств языка С++. Классами являются такие библиотечные типы, как istream и ostream. Комментарий (comment). Игнорируемый компилятором текст в исходном коде. Язык С++ поддерживает два вида комментариев: однострочные и парные. Однострочные комментарии начинается символом // и продолжается до конца строки. Парные комментарии начинаются символом /* и включают весь текст до заключительного символа */. Конец файла (end-of-file). Специфический для каждой операционной системы маркер, указывающий на завершение последовательности данных файла. Манипулятор (manipulator). Объект, непосредственно манипулирующий потоком ввода или вывода (такой, как std::endl). Метод (method). Синоним термина функция-член . Неинициализированная переменная (uninitialized variable). Переменная, которая не имеет исходного значения. Переменные типа класса, для которых не определено никакого Page 37/1103 исходного значения, инициализируются согласно определению класса. Переменные встроенного типа, определенные в функции, являются неинициализированными, если они не были инициализированы явно. Использование значения неинициализированной переменной является ошибкой. Неинициализированные переменные являются распространенной причиной ошибок . Объект cerr. Объект типа ostream, связанный с потоком стандартного устройства отображения сообщений об ошибке, который зачастую совпадает с потоком стандартного устройства вывода. По умолчанию запись в объект cerr не буферизируется. Обычно используется для вывода сообщений об ошибках и других данных, не являющихся частью нормальной логики программы. Объект cin. Объект типа istream, обычно используемый для чтения данных со стандартного устройства ввода. Объект clog. Объект типа ostream, связанный с потоком стандартного устройства отображения сообщений об ошибке. По умолчанию запись в объект clog буферизируется. Обычно используется для записи информации о ходе выполнения программы в файл журнала. Объект cout. Объект типа ostream, используемый для записи на стандартное устройство вывода. Обычно используется для вывода данных программы. Оператор !=. Не равно. Проверяет неравенство левого и правого операндов. Оператор (). Оператор вызова. Пара круглых скобок () после имени функции. Приводит к вызову функции. Передаваемые при вызове аргументы функции указывают в круглых скобках. Оператор (statement). Часть программы, определяющая действие, предпринимаемое при выполнении программы. Выражение, завершающееся точкой с запятой, является оператором. Такие операторы, как if, for и while, имеют блоки, способные содержать другие операторы. Оператор --. Оператор декремента. Вычитает единицу из операнда. Например, выражение --i эквивалентно выражению i = i - 1. Оператор .. Точечный оператор. Получает два операнда: левый операнд — объект, правый — имя члена класса этого объекта. Оператор обеспечивает доступ к члену класса именованного объекта. Оператор ::. Оператор области видимости. Кроме всего прочего, оператор области видимости используется для доступа к элементам по именам в пространстве имен. Например, запись std::cout указывает, что используемое имя cout определено в пространстве имен std. Оператор ++. Оператор инкремента. Добавляет к операнду единицу. Например, выражение ++i эквивалентно выражению i = i + 1. Оператор +=. Составной оператор присвоения. Добавляет правый операнд к левому, а результат сохраняет в левом операнде. Например, выражение а += b эквивалентно выражению a = a + b. Оператор <. Меньше, чем. Проверяет, меньше ли левый операнд, чем правый. Оператор <<. Оператор вывода. Записывает правый операнд в поток вывода, указанный левым операндом. Например, выражение cout << "hi" передаст слово "hi" на стандартное Page 38/1103 устройство вывода. Несколько операций вывода вполне можно объединить: выражение cout << "hi" << "bye" выведет слово "hibye". Оператор <=. Меньше или равно. Проверяет, меньше или равен левый операнд правому. Оператор =. Присваивает значение правого операнда левому. Оператор ==. Равно. Проверяет, равен ли левый операнд правому. Оператор >. Больше, чем. Проверяет, больше ли левый операнд, чем правый. Оператор >=. Больше или равно. Проверяет, больше или равен левый операнд правому. Оператор >>. Оператор ввода. Считывает в правый операнд данные из потока ввода, определенного левым операндом. Например, выражение cin >> i считывает следующее значение со стандартного устройства ввода в переменную i. Несколько операций ввода вполне можно объединить: выражение cin >> i >> j считывает данные сначала в переменную i, а затем в переменную j. Оператор for. Оператор цикла, обеспечивающий итерационное выполнение. Зачастую используется для повторения вычислений определенное количество раз. Оператор if. Управляющий оператор, обеспечивающий выполнение на основании значения определенного условия. Если условие истинно (значение true), выполняется тело оператора if. В противном случае (значение false) управление переходит к оператору else. Оператор while. Оператор цикла, обеспечивающий итерационное выполнение кода тела цикла, пока его условие остается истинным. Переменная (variable). Именованный объект. Присвоение (assignment). Удаляет текущее значение объекта и заменяет его новым. Пространство имен (namespace). Механизм применения имен, определенных в библиотеках. Применение пространств имен позволяет избежать случайных конфликтов имени. Имена, определенные в стандартной библиотеке языка С++, находятся в пространстве имен std. Пространство имен std. Пространство имен, используемое стандартной библиотекой. Запись std::cout указывает, что используемое имя cout определено в пространстве имен std. Редактирование, компиляция, отладка (edit-compile-debug). Процесс, обеспечивающий правильное выполнение программы. Символьный строковый литерал (character string literal). Синоним термина строковый литерал . Список параметров (parameter list). Часть определения функции. Список параметров определяет аргументы, применяемые при вызове функции. Список параметров может быть пуст. Стандартная библиотека (standard library). Коллекция типов и функций, которой должен обладать каждый компилятор языка С++. Библиотека предоставляет типы для работы с потоками ввода и вывода. Под библиотекой программисты С++ подразумевают либо всю стандартную библиотеку, либо ее часть, библиотеку типов. Например, когда программисты говорят о библиотеке iostream, они подразумевают ту часть стандартной библиотеки, в которой определены классы ввода и вывода. Page 39/1103 Стандартная ошибка (standard error). Поток вывода, предназначенный для передачи сообщения об ошибке. Обычно потоки стандартного вывода и стандартной ошибки ассоциируются с окном, в котором выполняется программа. Стандартный ввод (standard input). Поток ввода, обычно ассоциируемый с окном, в котором выполняется программа. Стандартный вывод (standard output). Поток вывода, обычно ассоциируемый с окном, в котором выполняется программа. Строковый литерал (string literal). Последовательность символов, заключенных в двойные кавычки (например, "а string literal"). Структура данных (data structure). Логическое объединение типов данных и возможных для них операций. Тело функции (function body). Блок операторов, определяющий выполняемые функцией действия. Тип istream. Библиотечный тип, обеспечивающий потоковый ввод. Тип ostream. Библиотечный тип, обеспечивающий потоковый вывод. Тип возвращаемого значения (return type). Тип возвращенного функцией значения. Тип класса (class type). Тип, определенный классом. Имя типа совпадает с именем класса. Условие (condition). Выражение, результатом которого является логическое значение true (истина) или false (ложь). Нуль соответствует значению false, а любой другой — значению true. Файл исходного кода (source file). Термин, используемый для описания файла, который содержит текст программы на языке С++. Фигурная скобка (curly brace). Фигурные скобки разграничивают блоки кода. Открывающая фигурная скобка ({) начинает блок, а закрывающая (}) завершает его. Функция (function). Именованный блок операторов. Функция main(). Функция, вызываемая операционной системой при запуске программы С++. У каждой программы должна быть одна и только одна функция по имени main(). Функция-член (member function). Операция, определенная классом. Как правило, функции-члены применяются для работы с определенным объектом. Часть I Основы Все широко распространенные языки программирования предоставляют единый набор средств, отличающийся лишь специфическими подробностями конкретного языка. Понимание подробностей того, как язык предоставляет эти средства, является первым шагом к овладению данным языком. К наиболее фундаментальным из этих общих средств относятся приведенные ниже. Page 40/1103 • Встроенные типы данных (например, целые числа, символы и т.д.). • Переменные, позволяющие присваивать имена используемым объектам. • Выражения и операторы, позволяющие манипулировать значениями этих типов. • Управляющие структуры, такие как if или while, обеспечивающие условное и циклическое выполнение наборов действий. • Функции, позволяющие обратиться к именованным блокам действий. Большинство языков программирования дополняет эти основные средства двумя способами: они позволяют программистам дополнять язык, определяя собственные типы, а также использовать библиотеки, в которых определены полезные функции и типы, отсутствующие в базовом языке. В языке С++, как и в большинстве языков программирования, допустимые для объекта операции определяет его тип. То есть оператор будет допустимым или недопустимым в зависимости от типа используемого объекта. Некоторые языки, например Smalltalk и Python, проверяют используемые в выражениях типы во время выполнения программы. В отличие от них, язык С++ осуществляет контроль типов данных статически, т.е. соответствие типов проверяется во время компиляции. Как следствие, компилятор требует сообщить ему тип каждого используемого в программе имени, прежде чем оно будет применено. Язык С++ предоставляет набор встроенных типов данных, операторы для манипулирования ими и небольшой набор операторов для управления процессом выполнения программы. Эти элементы формируют алфавит, при помощи которого можно написать (и было написано) множество больших и сложных реальных систем. На этом базовом уровне язык С++ довольно прост. Его потрясающая мощь является результатом поддержки механизмов, которые позволяют программисту самостоятельно определять новые структуры данных. Используя эти средства, программисты могут приспособить язык для собственных целей без участия его разработчиков и необходимости ожидать, пока они удовлетворят появившиеся потребности. Возможно, важнейшим компонентом языка С++ является класс, который позволяет программистам определять собственные типы данных. В языке С++ такие типы иногда называют "типами класса", чтобы отличить их от базовых типов, встроенных в сам язык. Некоторые языки программирования позволяют определять типы, способные содержать только данные. Другие, подобно языку С++, позволяют определять типы, в состав которых можно включить операции, выполняемые с этими данными. Одна из главных задач проекта С++ заключалась в предоставлении программистам возможности самостоятельно определять типы данных, которые будут так же удобны, как и встроенные. Стандартная библиотека языка С++ использует эту возможность для реализации обширного набора классов и связанных с ними функций. Первым шагом по овладению языком С++ является изучение его основ и библиотеки — такова тема части I, "Основы". В главе 2 рассматриваются встроенные типы данных, а также обсуждается механизм определения новых, собственных типов. В главе 3 описаны два фундаментальных библиотечных типа: string (строка) и vector (вектор). В этой же главе рассматриваются массивы, представляющие собой низкоуровневую структуру данных, встроенную в язык С++, и множество других языков. Главы 4-6 посвящены выражениям, операторам и функциям. Завершается часть главой 7 демонстрирующей основы построения собственных типов классов. Как мы увидим, в определении собственных типов примиряется все, что мы изучили до сих пор, поскольку написание класса подразумевает использование всех средств, частично раскрытых в части I. Page 41/1103 Глава 2 Переменные и базовые типы Типы данных — это основа любой программы: они указывают, что именно означают эти данные и какие операции с ними можно выполнять. У языка С++ обширная поддержка таких типов. В нем определено несколько базовых типов: символы, целые числа, числа с плавающей запятой и т.д. Язык предоставляет также механизмы, позволяющие программисту определять собственные типы данных. В библиотеке эти механизмы использованы для определения более сложных типов, таких как символьные строки переменной длины, векторы и т.д. В этой главе рассматриваются встроенные типы данных и основы применения более сложных типов. Тип определяет назначение данных и операции, которые с ними можно выполнять. Например, назначение простого оператора i = i + j; полностью зависит от типов переменных i и j. Если это целые числа, данный оператор представляет собой обычное арифметическое сложение. Но если это объекты класса Sales_item, то данный оператор суммирует их компоненты (см раздел 1.5.1). 2.1. Простые встроенные типы В языке С++ определен набор базовых типов, включая арифметические типы (arithmetic type), и специальный тип void. Арифметические типы представляют символы, целые числа, логические значения и числа с плавающей запятой. С типом void не связано значений, и применяется он только при некоторых обстоятельствах, чаще всего как тип возвращаемого значения функций, которые не возвращают ничего. 2.1.1. Арифметические типы Есть две разновидности арифметических типов: целочисленные типы (включая символьные и логические типы) и типы с плавающей запятой . Размер (т.е. количество битов) арифметических типов зависит от конкретного компьютера. Стандарт гарантирует минимальные размеры, перечисленные в табл. 2.1. Однако компиляторы позволяют использовать для этих типов большие размеры. Поскольку количество битов не постоянно, значение одного типа также может занимать в памяти больше или меньше места. Таблица 2.1. Арифметические типы языка С++ Тип Значение Минимальный размер bool Логический тип Не определен char Символ 8 битов wchar_t Широкий символ 16 битов char16_t Символ Unicode 16 битов char32_t Символ Unicode 32 бита short Короткое целое число 16 Page 42/1103 битов int Целое число 16 битов long Длинное целое число 32 бита long long Длинное целое число 64 бита float Число с плавающей запятой одинарной точности 6 значащих цифр double Число с плавающей запятой двойной точности 10 значащих цифр long double Число с плавающей запятой повышенной точности 10 значащих цифр Тип bool представляет только значения true (истина) и false (ложь). Существует несколько символьных типов, большинство из которых предназначено для поддержки национальных наборов символов. Базовый символьный тип, char, гарантировано велик, чтобы содержать числовые значения, соответствующие символам базового набора символов машины. Таким образом, тип char имеет тот же размер, что и один байт на данной машине. Остальные символьные типы, wchar_t, char16_t и char32_t, используются для расширенных наборов символов. Тип wchar_t будет достаточно большим, чтобы содержать любой символ в наибольшем расширенном наборе символов машины. Типы char16_t и char32_t предназначены для символов Unicode. (Unicode — это стандарт для представления символов, используемых, по существу, в любом языке.) Остальные целочисленные типы представляют целочисленные значения разных размеров. Язык С++ гарантирует, что тип int будет по крайней мере не меньше типа short, а тип long long — не меньше типа long. Тип long long введен новым стандартом. Машинный уровень представления встроенных типов Компьютеры хранят данные как последовательность битов, каждый из которых содержит 0 или 1: 00011011011100010110010000111011 ... Большинство компьютеров оперируют с памятью, разделенной на порции, размер которых в битах кратен степеням числа 2. Наименьшая порция адресуемой памяти называется байтом (byte). Основная единица хранения, обычно в несколько байтов, называется словом (word). В языке С++ байт содержит столько битов, сколько необходимо для содержания символа в базовом наборе символов машины. На большинстве компьютеров байт содержит 8 битов, а слово — 32 или 64 бита, т.е. 4 или 8 байтов. У большинства компьютеров каждый байт памяти имеет номер, называемый адресом (address). На машине с 8-битовыми байтами и 32-битовыми словами слова в памяти можно было бы представить следующим образом:736424 0 0 1 1 1 0 1 1 736425 0 0 0 1 1 0 1 1 736426 0 1 1 1 0 0 0 1 736427 0 1 1 0 0 1 0 0 Слева представлен адрес байта, а 8 битов его значения — справа. При помощи адреса можно обратиться к любому из байтов, а также к набору из нескольких байтов, начинающемуся с этого адреса. В этом случае говорят о доступе к байту по адресу 736424 или о байте, хранящемуся по адресу 736426. Чтобы получить представление о значении в области памяти по данному адресу, следует знать тип хранимого в ней значения. Именно тип определяет количество используемых битов и то, как эти биты интерпретировать. Если известно, что объект в области по адресу 736424 имеет тип float, и если тип float на этой машине хранится в 32 битах, то известно и то, что объект по этому адресу охватывает все слово. Значение этого числа зависит от того, как именно машина хранит числа с плавающей запятой. Но если объект в области по адресу 736424 имеет тип unsigned char, то на машине, Page 43/1103 использующей набор символов ISO-Latin-1, этот байт представляет точку с запятой. Типы с плавающей точкой представляют значения с одиночной, двойной и расширенной точностью. Стандарт определяет минимальное количество значащих цифр. Большинство компиляторов обеспечивает большую точность, чем минимально определено стандартом. Как правило, тип float представляется одним словом (32 бита), тип double — двумя словами (64 бита), а тип long double — тремя или четырьмя словами (96 или 128 битов). Типы float и double обычно имеют примерно по 7 и 16 значащих цифр соответственно. Тип long double зачастую используется для адаптации чисел с плавающей запятой аппаратных средств специального назначения; его точность, вероятно, также зависит от конкретной реализации этих средств. Знаковые и беззнаковые типы За исключением типа bool и расширенных символьных типов целочисленные типы могут быть знаковыми (signed) или беззнаковыми (unsigned). Знаковый тип способен представлять отрицательные и положительные числа (включая нуль); а беззнаковый тип — только положительные числа и нуль. Типы int, short, long и long long являются знаковыми. Соответствующий беззнаковый тип получают добавлением части unsigned к названию такого типа, например unsigned long. Тип unsigned int может быть сокращен до unsigned. В отличие от других целочисленных типов, существуют три разновидности базового типа char: char, signed char и unsigned char. В частности, тип char отличается от типа signed char. На три символьных типа есть только два представления: знаковый и беззнаковый. Простой тип char использует одно из этих представлений. Какое именно, зависит от компилятора. В беззнаковом типе все биты представляют значение. Например, 8-битовый тип unsigned char может содержать значения от 0 до 255 включительно. Стандарт не определяет представление знаковых типов, но он указывает, что диапазон должен быть поровну разделен между положительными и отрицательными значениями. Следовательно, 8-битовый тип signed char гарантированно будет в состоянии содержать значения от -127 до 127; большинство современных машин использует представления, позволяющие содержать значения от -128 до 127. Совет. Какой тип использовать Подобно языку С, язык С++ был разработан так, чтобы по необходимости программа могла обращаться непосредственно к аппаратным средствам. Поэтому арифметические типы определены так, чтобы соответствовать особенностям различных аппаратных средств. В результате количество возможных арифметических типов в языке С++ огромно. Большинство программистов, желая избежать этих сложностей, ограничивают количество фактически используемых ими типов. Ниже приведено несколько эмпирических правил, способных помочь при выборе используемого типа. • Используйте беззнаковый тип, когда точно знаете, что значения не могут быть отрицательными. • Используйте тип int для целочисленной арифметики. Тип short обычно слишком мал, а тип long на практике зачастую имеет тот же размер, что и тип int. Если ваши значения больше, чем минимально гарантирует тип int, то используйте тип long long. • Не используйте базовый тип char и тип bool в арифметических выражениях. Используйте их только для хранения символов и логических значений. Вычисления с использованием типа Page 44/1103 char особенно проблематичны, поскольку на одних машинах он знаковый, а на других беззнаковый. Если необходимо маленькое целое число, явно определите тип как signed char или unsigned char. • Используйте тип double для вычислений с плавающей точкой. У типа float обычно недостаточно точности, а различие в затратах на вычисления с двойной и одиночной точностью незначительны. Фактически на некоторых машинах операции с двойной точностью осуществляются быстрее, чем с одинарной. Точность, предоставляемая типом long double, обычно чрезмерна и не нужна, а зачастую влечет значительное увеличение продолжительности выполнения. Упражнения раздела 2.1.1 Упражнение 2.1. Каковы различия между типами int, long, long long и short? Между знаковыми и беззнаковыми типами? Между типами float и double? Упражнение 2.2. Какие типы вы использовали бы для коэффициента, основной суммы и платежей при вычислении выплат по закладной? Объясните, почему вы выбрали каждый из типов? 2.1.2. Преобразование типов Тип объекта определяет данные, которые он может содержать, и операции, которые с ним можно выполнять. Среди операций, поддерживаемых множеством типов, есть возможность преобразовать (convert) объект данного типа в другой, связанный тип. Преобразование типов происходит автоматически, когда объект одного типа используется там, где ожидается объект другого типа. Более подробная информация о преобразованиях приведена в разделе 4.11, а пока имеет смысл понять, что происходит при присвоении значения одного типа объекту другого. Когда значение одного арифметического типа присваивается другому bool b = 42; // b содержит true int i = b; // i содержит значение 1 i = 3.14; // i содержит значение 3 double pi = i; // pi содержит значение 3.0 unsigned char с = -1; // при 8-битовом char содержит значение 255 signed char c2 = 256; // Page 45/1103 при 8-битовом char значение c2 не определено происходящее зависит от диапазона значении, поддерживаемых типом. • Когда значение одного из не логических арифметических типов присваивается объекту типа bool, результат будет false, если значением является 0, а в противном случае — true. • Когда значение типа bool присваивается одному из других арифметических типов, будет получено значение 1, если логическим значением было true, и 0, если это было false. • Когда значение с плавающей точкой присваивается объекту целочисленного типа, оно усекается до части перед десятичной точкой. • Когда целочисленное (интегральное) значение присваивается объекту типа с плавающей точкой, дробная часть равна нулю. Если у целого числа больше битов, чем может вместить объект с плавающей точкой, то точность может быть потеряна. • Если объекту беззнакового типа присваивается значение не из его диапазона, результатом будет остаток от деления по модулю значения, которые способен содержать тип назначения. Например, 8-битовый тип unsigned char способен содержать значения от 0 до 255 включительно. Если присвоить ему значение вне этого диапазона, то компилятор присвоит ему остаток от деления по модулю 256. Поэтому в результате присвоения значения -1 переменной 8-битового типа unsigned char будет получено значение 255. • Если объекту знакового типа присваивается значение не из его диапазона, результат оказывается не определен . В результате программа может сработать нормально, а может и отказать или задействовать неверное значение.Совет. Избегайте неопределенного и машинно-зависимого поведения Результатом неопределенного поведения являются такие ошибки, которые компилятор не обязан (а иногда и не в состоянии) обнаруживать. Даже если код компилируется, то программа с неопределенным выражением все равно ошибочна. К сожалению, программы, характеризующиеся неопределенным поведением на некоторых компиляторах и при некоторых обстоятельствах, могут работать вполне нормально, не проявляя проблему. Но нет никаких гарантий, что та же программа, откомпилированная на другом компиляторе или даже на следующей версии данного компилятора, продолжит работать правильно. Нет даже гарантий того, что, нормально работая с одним набором данных, она будет нормально работать с другим. Аналогично в программах нельзя полагаться на машинно-зависимое поведение. Не стоит, например, надеяться на то, что переменная типа int имеет фиксированный, заранее известный размер. Такие программы называют непереносимыми (nonportable). При переносе такой программы на другую машину любой полагающийся на машинно-зависимое поведение код, вероятней всего, сработает неправильно, поэтому его придется искать и исправлять. Поиск подобных проблем в ранее нормально работавшей программе, мягко говоря, не самая приятная работа. Компилятор применяет эти преобразования типов при использовании значений одного арифметического типа там, где ожидается значение другого арифметического типа. Например, при использовании значения, отличного от логического в условии, арифметическое значение преобразуется в тип bool таким же образом, как при присвоении арифметического значения переменной типа bool: Page 46/1103 int i = 42; if (i) // условие рассматривается как истинное i = 0; При значении 0 условие будет ложным, а при всех остальных (отличных от нуля) — истинным. К тому же при использовании значения типа bool в арифметическом выражении оно всегда преобразуется в 0 или 1. В результате применение логического значения в арифметическом выражении является неправильным. Выражения, задействующие беззнаковые типы Хотя мы сами вряд ли преднамеренно присвоим отрицательное значение объекту беззнакового типа, мы можем (причем слишком легко) написать код, который сделает это неявно. Например, если использовать значения типа unsigned и int в арифметическом выражении, значения типа int обычно преобразуются в тип unsigned. Преобразование значения типа int в unsigned выполняется таким же способом, как и при присвоении: unsigned u = 10; int i = -42; std::cout << i + i << std::endl; // выводит -84 std::cout << u + i << std::endl; // при 32-битовом int, // выводит 4294967264 Во втором выражении, прежде чем будет осуществлено сложение, значение -42 типа int преобразуется в значение типа unsigned. Преобразование отрицательного числа в тип unsigned происходит точно так же, как и при попытке присвоить это отрицательное значение объекту типа unsigned. Произойдет "обращение значения" (wrap around), как было описано выше. При вычитании значения из беззнакового объекта, независимо от того, один или оба операнда являются беззнаковыми, следует быть уверенным том, что результат не окажется отрицательным: unsigned u1 = 42, u2 = 10; std::cout << u1 - u2 << std::endl; // ok: результат 32 std::cout << u2 - u1 << std::endl; // ok: но с обращением значения Тот факт, что беззнаковый объект не может быть меньше нуля, влияет на способы написания циклов. Например, в упражнениях раздела 1.4.1 (стр. 39) следовало написать цикл, который использовал оператор декремента для вывода чисел от 10 до 0. Написанный вами цикл, вероятно, выглядел примерно так: Page 47/1103 for (int i = 10; i >= 0; --i) std::cout << i << std::endl; Казалось бы, этот цикл можно переписать, используя тип unsigned. В конце концов, мы не планируем выводить отрицательные числа. Однако это простое изменение типа приведет к тому, что цикл никогда не закончится: // ОШИБКА: u никогда не сможет стать меньше 0; условие // навсегда останется истинным for (unsigned u = 10; u >= 0; --u) std::cout << u << std::endl; Рассмотрим, что будет, когда u станет равно 0. На этой итерации отображается значение 0, а затем выполняется выражение цикла for. Это выражение, --u, вычитает 1 из u. Результат, -1, недопустим для беззнаковой переменной. Как и любое другое значение, не попадающее в диапазон допустимых, это будет преобразовано в беззнаковое значение. При 32-разрядном типе int результат выражения --u при u равном 0 составит 4294967295. Исправить этот код можно, заменив цикл for циклом while, поскольку последний осуществляет декремент прежде (а не после) отображения значения: unsigned u = 11; // начать цикл с элемента на один больше // первого, подлежащего отображению while (u > 0) { --u; // сначала декремент, чтобы последняя итерация отобразила 0 std::cout << u << std::endl; } Цикл начинается с декремента значения управляющей переменной цикла. В начале последней итерации переменная u будет иметь значение 1, а после декремента мы отобразим значение 0. При последующей проверке условия цикла while значением переменной u будет 0, и цикл завершится. Поскольку декремент осуществляется сначала, переменную u следует инициализировать значением на единицу больше первого подлежащего отображению значения. Следовательно, чтобы первым отображаемым значением было 10, переменную u инициализируем значением 11. Внимание! Не смешивайте знаковые и беззнаковые типы Выражения, в которых смешаны знаковые и беззнаковые типы, могут приводить к удивительным результатам, когда знаковое значение оказывается негативным. Важно не Page 48/1103 забывать, что знаковые значения автоматически преобразовываются в беззнаковые. Например, в таком выражении, как a * b, если а содержит значение -1, a b значение 1 и обе переменные имеют тип int, ожидается результат -1. Но если переменная а имеет тип int, а переменная b — тип unsigned, то значение этого выражения будет зависеть от количества битов, занимаемых типом int на данной машине. На нашей машине результатом этого выражения оказалось 4294967295. Упражнения раздела 2.1.2 Упражнение 2.3. Каков будет вывод следующего кода? unsigned u = 10, u2 = 42; std::cout << u2 - u << std::endl; std::cout << u - u2 << std::endl; int i = 10, i2 = 42; std::cout << i2 - i << std::endl; std::cout << i - i2 << std::endl; std::cout << i - u << std::endl; std::cout << u - i << std::endl; Упражнение 2.4. Напишите программу для проверки правильности ответов. При неправильных ответах изучите этот раздел еще раз. 2.1.3. Литералы Такое значение, как 42, в коде программы называется литералом (literal), поскольку его значение самоочевидно. У каждого литерала есть тип, определяемый его формой и значением.Целочисленные литералы и литералы с плавающей запятой Целочисленный литерал может быть в десятичной, восьмеричной или шестнадцатеричной форме. Целочисленные литералы, начинающиеся с нуля (0), интерпретируются как восьмеричные, а начинающиеся с 0x или 0X — как шестнадцатеричные. Например, значение 20 можно записать любым из трех следующих способов. 20 // десятичная форма 024 // восьмеричная форма 0x14 // шестнадцатеричная форма Тип целочисленного литерала зависит от его значения и формы. По умолчанию десятичные литералы считаются знаковыми, а восьмеричные и шестнадцатеричные литералы могут быть знаковыми или беззнаковыми. Для десятичного литерала принимается наименьший тип, int, long, или long long, подходящий для его значения (т.е. первый подходящий в этом списке). Для восьмеричных и шестнадцатеричных литералов принимается наименьший тип, int, unsigned int, long, unsigned long, long long или unsigned long long, подходящий для значения литерала. Не следует использовать литерал, значение которого слишком велико для Page 49/1103 наибольшего соответствующего типа. Нет литералов типа short. Как можно заметить в табл. 2.2, значения по умолчанию можно переопределить при помощи суффикса. Хотя целочисленные литералы могут иметь знаковый тип, с технической точки зрения значение десятичного литерала никогда не бывает отрицательным числом. Если написать нечто, выглядящее как отрицательный десятичный литерал, например -42, то знак "минус" не будет частью литерала. Знак "минус" — это оператор, который инвертирует знак своего операнда (литерала). Литералы с плавающей запятой включают либо десятичную точку, либо экспоненту, определенную при помощи экспоненциального представления. Экспонента в экспоненциальном представлении обозначается символом E или е: 3.14159 3.14159Е0 0. 0e0 .001 По умолчанию литералы с плавающей запятой имеют тип double. Используя представленные в табл. 2.2 суффиксы, тип умолчанию можно переопределить. Символьные и строковые литералы Символ, заключенный в одинарные кавычки, является литералом типа char. Несколько символов, заключенных в парные кавычки, являются строковым литералом: 'a' // символьный литерал "Hello World!" // строковый литерал Типом строкового литерала является массив константных символов. Этот тип обсуждается в разделе 3.5.4. К каждому строковому литералу компилятор добавляет нулевой символ (null character) ('\0'). Таким образом, реальная величина строкового литерала на единицу больше его видимого размера. Например, литерал 'A' представляет один символ А, тогда как строковый литерал "А" представляет массив из двух символов, символа А и нулевого символа. Два строковых литерала, разделенных пробелами, табуляцией или символом новой строки, конкатенируются в единый литерал. Такую форму литерала используют, если необходимо написать слишком длинный текст, который неудобно располагать в одной строке. // многострочный литерал std::cout << "a really, really long string literal " "that spans two lines" << std::endl; Управляющие последовательности У некоторых символов, таких как возврат на один символ или управляющий символ, нет видимого изображения. Такие символы называют непечатаемыми (nonprintable character). Другие символы (одиночные и парные кавычки, вопросительный знак и наклонная черта влево) имеют в языке специальное назначение. В программах нельзя использовать ни один из этих символов непосредственно. Для их представления как символов используется Page 50/1103 управляющая последовательность (escape sequence), начинающаяся с символа наклонной черты влево. В языке С++ определены следующие управляющие последовательности. Новая строка (newline) \n Горизонтальная табуляция (horizontal tab) \t Оповещение, звонок (alert) \a Вертикальная табуляция (vertical tab) \v Возврат на один символ (backspace) \b Двойная кавычка (double quote) \" Наклонная черта влево (backslash) \\ Вопросительный знак (question mark) \? Одинарная кавычка (single quote) \' Возврат каретки (carriage return) \r Прогон страницы (formfeed) \f Управляющую последовательность используют как единый символ: std::cout << '\n'; // отобразить новую строку std::cout << "\tHi!\n"; // отобразить табуляцию, // текст "Hi!" и новую строка Можно также написать обобщенную управляющую последовательность, где за \x следует одна или несколько шестнадцатеричных цифр или за \ следует одна, две или три восьмеричные цифры. Так можно отобразить символ по его числовому значению. Вот несколько примеров (подразумевается использование набора символов Latin-1): \7 (оповещение) \12 (новая строка) \40 (пробел) \0 (нулевой символ) \115 (символ 'M') \x4d (символ 'M') Как и управляющие последовательности, определенные языком, такой синтаксис можно использовать вместо любого другого символа: std::cout << "Hi \x4dO\115!\n"; // выводит Hi MOM! и новую строку std::cout << '\115' << '\n'; // выводит M и новую строку Обратите внимание: если символ \ сопровождается более чем тремя восьмеричными цифрами, то ассоциируются с ним только первые три. Например, литерал "\1234" представляет два символа: символ, представленный восьмеричным значением 123, и символ 4. Форма \x, напротив, использует все последующие шестнадцатеричные цифры; литерал "\x1234" представляет один 16-разрядный символ, состоящий из битов, соответствующих этим четырем шестнадцатеричным цифрам. Поскольку большинство машин использует 8-битовые символы, подобные значения вряд ли будут полезны. Обычно шестнадцатеричные символы с более чем 8 битами используются для расширенных наборов символов с применением одного из префиксов, приведенных в табл. 2.2. Определение типа литерала При помощи суффикса или префикса, представленного в табл. 2.2, можно переопределить заданный по умолчанию тип целого числа, числа с плавающей запятой или символьного литерала. L'a' // литерал типа wchar_t (широкий символ) Page 51/1103 u8"hi!" // строковый литерал utf-8 (8-битовая кодировка Unicode) 42ULL // целочисленный беззнаковый литерал, тип unsigned long long 1E-3F // литерал с плавающей точкой и одинарной точностью, тип float 3.14159L // литерал с плавающей точкой и расширенной точностью, // тип long double При обозначении литерала как имеющего тип long используйте букву L в верхнем регистре; строчная буква l слишком похожа на цифру 1. Таблица 2.2. Определение типа литерала Символьные и строковые литералы Префикс Значение Тип U Символ Unicode 16 char16_t U Символ Unicode 32 char32_t L Широкий символ wchar_t U8 utf-8 (только строковые литералы) char Целочисленные литералы Литералы с плавающей точкой Суффикс Минимальный тип Суффикс Тип u или U unsigned f или F float l или L long l или L long double Ll или LL long long Можно непосредственно определить знак и размер целочисленного литерала. Если суффикс содержит символ U, то у литерала беззнаковый тип. Таким образом, у десятичного, восьмеричного или шестнадцатеричного литерала с суффиксом U будет наименьший тип unsigned int, unsigned long или unsigned long long, в соответствии со значением литерала. Если суффикс будет содержать символ L, то типом литерала будет по крайней мере long; если суффикс будет содержать символы LL, то типом литерала будет long long или unsigned long long. Можно объединить символ U с символом L или символами LL. Литерал с суффиксом UL, например, задаст тип unsigned long или unsigned long long, в зависимости от того, помещается ли его значение в тип unsigned long. Логические литералы и литеральные указатели Слова true и false — это логические литералы (литералы типа bool) bool test = false; Слово nullptr является литеральным указателем. Более подробная информация об указателях и литерале nullptr приведена в разделе 2.3.2. Упражнения раздела 2.1.3 Упражнение 2.5. Определите тип каждого из следующих литералов. Объясните различия между ними: (a) 'a', L'a', "a", L"a" (b) 10, 10u, 10L, 10uL, 012, 0xC (c) 3.14, 3.14f, 3.14L (d) 10, 10u, 10., 10e-2 Упражнение 2.6. Имеются ли различия между следующими определениями: int month = 9, day = 7; int month = 09, day = 07; Упражнение 2.7. Какие значения представляют эти литералы? Какой тип имеет каждый из Page 52/1103 них? (a) "Who goes with F\145rgus?\012" (b) 3.14e1L (c) 1024f (d) 3.14L Упражнение 2.8. Напишите программу, использующую управляющие последовательности для вывода значения 2M, сопровождаемого новой строкой. Модифицируйте программу так, чтобы вывести 2, затем табуляцию, потом M и наконец символ новой строки. 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 — библиотечный тип, представляющий последовательность // символов переменной длины std::string book("0-201-78345-X"); // book инициализирована строковым Page 53/1103 // литералом В определении переменной book использован библиотечный тип std::string. Подобно классу iostream (см. раздел 1.2), класс string определен в пространстве имен std. Более подробная информация о классе string приведена в главе 3, а пока достаточно знать то, что тип string представляет последовательность символов переменной длины. Библиотечный тип string предоставляет несколько способов инициализации строковых объектов. Один из них — копирование строкового литерала (см. раздел 2.1.3). Таким образом, переменная book инициализируется символами 0-201-78345-X. Терминология. Что такое объект? Программисты языка С++ используют термин объект (object) часто, и не всегда по делу. В самом общем определении объект — это область памяти, способная содержать данный и обладающая типом. Одни программисты используют термин объект лишь для переменных и экземпляров классов. Другие используют его, чтобы различать именованные и неименованные объекты, причем для именованных объектов используют термин переменная (variable). Третьи различают объекты и значения, используя термин объект для тех данных, которые могут быть изменены программой, и термин значение (value) — для тех данных, которые предназначены только для чтения. В этой книге используется наиболее общий смысл термина объект , т.е. область памяти, для которой указан тип. Здесь под объектом подразумеваются практически все используемые в программе данные, независимо от того, имеют ли они встроенный тип или тип класса, являются ли они именованными или нет, предназначены только для чтения или допускают изменение.Инициализаторы Инициализация (initialization) присваивает объекту определенное значение в момент его создания. Используемые для инициализации переменных значения могут быть насколько угодно сложными выражениями. Когда определяется несколько переменных, имена всех объектов следуют непосредственно друг за другом. Таким образом, вполне возможно инициализировать переменную значением одной из переменных, определенных ранее в том же определении. // ok: переменная price определяется и инициализируется прежде, // чем она будет использована для инициализации переменной discount double price = 109.99, discount = price * 0.16; // ok: Вызов функции applyDiscount() и использование ее возвращаемого // Page 54/1103 значения для инициализации переменной salePrice salePrice = applyDiscount(price, discount); Инициализация в С++ — на удивление сложная тема, и мы еще не раз вернемся к ней. Многих программистов вводит в заблуждение использование символа = при инициализации переменной. Они полагают, что инициализация — это такая форма присвоения, но в С++ инициализация и присвоение — совершенно разные операции. Эта концепция особенно важна, поскольку во многих языках это различие несущественно и может быть проигнорировано. Тем не менее даже в языке С++ это различие зачастую не имеет значения. Однако данная концепция крайне важна, и мы будем повторять это еще не раз. Инициализация — это не присвоение. Инициализация переменной происходит при ее создании. Присвоение удаляет текущее значение объекта и заменяет его новым. Списочная инициализация Тема инициализации настолько сложна потому, что язык поддерживает ее в нескольких разных формах. Например, для определения переменной units_sold типа int и ее инициализации значением 0 можно использовать любой из следующих четырех способов: int units_sold = 0; int units_sold = {0}; int units_sold{0}; int units_sold(0); Использование фигурных скобок для инициализации было введено новым стандартом. Ранее эта форма инициализации допускалась лишь в некоторых случаях. По причинам, описанным в разделе 3.3.1, эта форма инициализации известна как списочная инициализация (list initialization). Списки инициализаторов в скобках можно теперь использовать всегда, когда инициализируется объект, и в некоторых случаях, когда объекту присваивается новое значение. При использовании с переменными встроенного типа эта форма инициализации обладает важным преимуществом: компилятор не позволит инициализировать переменные встроенного типа, если инициализатор может привести к потере информации: long double ld = 3.1415926536; int a{ld}, b = {ld}; // ошибка: преобразование с потерей int с(ld), d = ld; // ok: но значение будет усечено Компилятор откажет в инициализации переменных а и b, поскольку использование значения типа long double для инициализации переменной типа int может привести к потере данных. Как минимум, дробная часть значения переменной ld будет усечена. Кроме того, целочисленная часть значения переменной ld может быть слишком большой, чтобы поместиться в переменную типа int. То, что здесь представлено, может показаться тривиальным, в конце концов, вряд ли кто Page 55/1103 инициализирует переменную типа int значением типа long double непосредственно. Однако, как представлено в главе 16, такая инициализация может произойти непреднамеренно. Более подробная информация об этих формах инициализации приведена в разделах 3.2.1 и 3.3.1. Инициализация по умолчанию При определении переменной без инициализатора происходит ее инициализация по умолчанию (default initialization). Таким переменным присваивается значение по умолчанию (default value). Это значение зависит от типа переменной и может также зависеть от того, где определяется переменная. Значение объекта встроенного типа, не инициализированного явно, зависит от того, где именно он определяется. Переменные, определенные вне тела функции, инициализируются значением 0. За одним рассматриваемым вскоре исключением, определенные в функции переменные встроенного типа остаются неинициализированными (uninitialized). Значение неинициализированной переменной встроенного типа неопределенно (см. раздел 2.1.2). Попытка копирования или получения доступа к значению неинициализированной переменной является ошибкой. Инициализацию объекта типа класса контролирует сам класс. В частности, класс позволяет определить, могут ли быть созданы его объекты без инициализатора. Если это возможно, класс определяет значение, которое будет иметь его объект в таком случае. Большинство классов позволяет определять объекты без явных инициализаторов. Такие классы самостоятельно предоставляют соответствующее значение по умолчанию. Например, новый объект библиотечного класса string без инициализатора является пустой строкой. std::string empty; // неявно инициализируется пустой строкой Sales_item item; // объект Sales_item инициализируется // значением по умолчанию Однако некоторые классы требуют, чтобы каждый объект был инициализирован явно. При попытке создать объект такого класса без инициализатора компилятор пожалуется на это. Значение неинициализированных объектов встроенного типа, определенных в теле функции, неопределенно. Значение не инициализируемых явно объектов типа класса определяется классом. Упражнения раздела 2.2.1 Упражнение 2.9. Объясните следующие определения. Если среди них есть некорректные, объясните, что не так и как это исправить. (а) std::cin >> int input_value; (b) int i = { 3.14 }; (с) double salary = wage = 9999.99; (d) int i = 3.14; |