Шилдт c++_базовый_курс издание 3. Герберт Шилдт С базовый курс
Скачать 9.37 Mb.
|
Использование оператора defined Помимо директивы #ifdef существует еще один способ выяснить, определено ли в программе некоторое макроимя. Для этого можно использовать директиву #if в сочетании с оператором времени компиляции defined. Например, чтобы узнать, определено ли макроимя MYFILE, можно использовать одну из следующих команд препроцессорной обработки. #if defined MYFILE или #ifdef MYFILE При необходимости, чтобы реверсировать условие проверки, можно предварить оператор defined символом "!". Например, следующий фрагмент кода скомпилируется только в том случае, если макроимя DEBUG не определено. #if !defined DEBUG cout << "Окончательная версия!\n"; #endif О роли препроцессора Препроцессор C++ — прямой потомок препроцессора языка С, причем без каких-либо усовершенствований. Однако его роль в C++ намного меньше роли, которую играет препроцессор в С. Дело в том, что многие задачи, выполняемые препроцессором в С, реализованы в C++ в виде элементов языка. Страуструп тем самым выразил свое намерение сделать функции препроцессора ненужными, чтобы в конце концов от него можно было бы совсем освободить язык. На данном этапе препроцессор уже частично избыточен. Например, два наиболее употребительных свойства директивы #define были заменены инструкциями C++. В частности, ее способность создавать константное значение и определять макроопределение, действующее подобно функциям, сейчас совершенно избыточна. В C++ есть более эффективные средства для выполнения этих задач. Для создания константы достаточно определить const-переменную. А с созданием встраиваемой (подставляемой) функции вполне справляется спецификатор inline. Оба эти средства лучше работают, чем соответствующие механизмы директивы #define. Приведем еще один пример замены элементов препроцессора элементами языка. Он связан с использованием однострочного комментария. Одна из причин его создания — разрешить "превращение" кода в комментарий. Как вы знаете, комментарий, использующий /*...*/-стиль, не может быть вложенным. Это означает, что фрагменты кода, содержащие /*...*/-комментарии, одним махом "превратить в комментарий" нельзя. Но это можно сделать с //-комментариями, окружив их /*...*/-символами комментария. Возможность "превращения" кода в комментарий делает использование таких директив условной компиляции, как #ifdef, частично избыточным. Директива #line Директива #line изменяет содержимое псевдопеременных _ _LINE_ _ и _ _FILE_ _. Директива #line используется для изменения содержимого псевдопеременных _ _LINE_ _ и _ _FILE_ _, которые являются зарезервированными идентификаторами (макроименами). Псевдопеременная _ _LINE_ _ содержит номер скомпилированной строки, а псевдопеременная _ _FILE_ _— имя компилируемого файла. Базовая форма записи этой команды имеет следующий вид. #line номер "имя_файла" Здесь номер — это любое положительное целое число, а имя_файла — любой допустимый идентификатор файла. Значение элемента номер становится номером текущей исходной строки, а значение элемента имя_файла— именем исходного файла. Имя_файла — элемент необязательный. Директива #line используется, главным образом, в целях отладки и в специальных приложениях. Например, следующая программа обязывает начинать счет строк с числа 200. Инструкция cout отображает номер 202, поскольку это — третья строка в программе после директивной инструкции #line 200. #include using namespace std; #line 200 // Устанавливаем счетчик строк равным 200. int main() // Эта строка сейчас имеет номер 200. {// Номер этой строки равен 201. cout << _ _LINE_ _;// Здесь выводится номер 202. return 0; } Директива #pragma Директива #pragma зависит от конкретной реализации компилятора. Работа директивы #pragma зависит от конкретной реализации компилятора. Она позволяет выдавать компилятору различные инструкции, предусмотренные создателем компилятора. Общий формат его использования таков. #pragma имя Здесь элемент имя представляет имя желаемой #pragma-инструкции. Если указанное имя не распознается компилятором, директива #pragma попросту игнорируется без сообщения об ошибке. Важно! Для получения подробной информации о возможных вариантах использования директивы #pragma стоит обратиться к системной документации по используемому вами компилятору. Вы можете найти для себя очень полезную информацию. Обычно #pragma- инструкции позволяют определить, какие предупреждающие сообщения выдает компилятор, как генерируется код и какие библиотеки компонуются с вашими программами. Операторы препроцессора "#" и "##" В C++ предусмотрена поддержка двух операторов препроцессора: "#" и "##". Эти операторы используются совместно с директивой #define. Оператор "#" преобразует следующий за ним аргумент в строку, заключенную в кавычки. Рассмотрим, например, следующую программу. #include using namespace std; #define mkstr(s) # s int main() { cout << mkstr(Я в восторге от C++); return 0; } Препроцессор C++ преобразует строку cout << mkstr(Я в восторге от C++); в строку cout << "Я в восторге от C++"; Оператор используется для конкатенации двух лексем. Рассмотрим пример. #include using namespace std; #define concat(a, b) a ## b int main() { int xy = 10; cout << concat(x, y); return 0; } Препроцессор преобразует строку cout << concat (x, y); в строку cout << xy; Если эти операторы вам кажутся странными, помните, что они не являются операторами "повседневного спроса" и редко используются в программах. Их основное назначение — позволить препроцессору обрабатывать некоторые специальные ситуации. Зарезервированные макроимена В языке C++ определено шесть встроенных макроимен. _ _LINE_ _ _ _FILE_ _ _ _DATE_ _ _ _TIME_ _ _ _STDC_ _ _ _cplusplus Рассмотрим каждое из них в отдельности. Макросы _ _LINE_ _ и _ _FILE_ _ описаны при рассмотрении директивы #line выше в этой главе. Они содержат номер текущей строки и имя файла компилируемой программы. Макрос _ _DATE_ _ представляет собой строку в формате месяц/день/год, которая означает дату трансляции исходного файла в объектный код. Время трансляции исходного файла в объектный код содержится в виде строки в макросе _ _TIME_ _. Формат этой строки следующий: часы.минуты.секунды. Точное назначение макроса _ _STDC_ _ зависит от конкретной реализации компилятора. Как правило, если макрос _ _STDC_ _ определен, то компилятор примет только стандартный С/С++-код, который не содержит никаких нестандартных расширений. Компилятор, соответствующий ANSI/ISO-стандарту C++, определяет макрос _ _cplusplus как значение, содержащее по крайней мере шесть цифр. "Нестандартные" компиляторы должны использовать значение, содержащее пять (или даже меньше) цифр. Мысли "под занавес" Мы преодолели немалый путь: длиной в целую книгу. Если вы внимательно изучили все приведенные здесь примеры, то можете смело назвать себя программистом на C++. Подобно многим другим наукам, программирование лучше всего осваивать на практике, поэтому теперь вам нужно писать побольше программ. Полезно также разобраться в С++- программах, написанных другими (причем разными) профессиональными программистами. При этом важно обращать внимание на то, как программа оформлена и реализована. Постарайтесь найти в них как достоинства, так и недостатки. Это расширит диапазон ваших представлений о программировании. Подумайте также над тем, как можно улучшить существующий код, применив контейнеры и алгоритмы библиотеки STL. Эти средства, как правило, позволяют улучшить читабельность и поддержку больших программ. Наконец, просто больше экспериментируйте! Дайте волю своей фантазии и вскоре вы почувствуете себя настоящим С++-программистом! Для продолжения теоретического освоения C++ предлагаю обратиться к моей книге Полный справочник по C++, М. : Издательский дом "Вильямс". Она содержит подробное описание элементов языка C++ и библиотек. Приложение А: С-ориентированная система ввода-вывода Это приложение содержит краткое описание С-системы ввода-вывода. Несмотря на то что вы предполагаете использовать С++-систему ввода-вывода, есть ряд причин, по которым вам все-таки следует понимать основы С-ориентированной системы ввода-вывода. Во- первых, если вам придется работать с С-кодом (особенно, если возникнет необходимость его перевода в С++-код), то вам нужно знать, как работает С-система ввода-вывода. Во- вторых, часто в одной и той же программе используются как С-, так и С++-операции ввода- вывода. Это особенно характерно для очень больших программ, отдельные части которых писались разными программистами в течение довольно длительного периода времени. В- третьих, большое количество существующих С-программ продолжают находиться в эксплуатации и нуждаются в поддержке. Наконец, многие книги и периодические издания содержат программы, написанные на С. Чтобы понимать эти С-программы, необходимо понимать основы функционирования С-системы ввода-вывода. Узелок на память. Для С++-программ необходимо использовать объектно- ориентированную С++-систему ввода-вывода. В этом приложении описаны наиболее употребительные С-ориентированные функции ввода-вывода. Однако стандартная С-библиотека содержит такое огромное количество функций ввода-вывода, что мы не в силах рассмотреть их здесь в полном объеме. Если же вам придется серьезно погрузиться в С-программирование, то рекомендую обратиться к справочной литературе. Система ввода-вывода языка С требует включать в программы заголовочный файл stdio.h (ему соответствует заголовок std, а заголовочный файл stdio.h— в глобальное пространство имен, что соответствует С- ориентации. В этом приложении в качестве примеров приведены С-программы, поэтому они используют С-стиль включения заголовочного файла stdio.h и не требуют установки пространства имен. И еще. Как отмечалось в главе 1, стандарт языка С был обновлен в 1999 году и получил название стандарта С99. В то время в С-систему ввода-вывода было внесено несколько усовершенствований. Но поскольку C++ опирается на стандарт С89, то он не поддерживает средств, которые были добавлены в стандарт С99. (Более того, на момент написания этой книги ни один из широко доступных компиляторов C++ не поддерживал стандарт С99. Да и ни одна из широко распространяемых программ не использовала средства стандарта С99.) Поэтому здесь не описываются средства, внесенные в С-систему ввода-вывода стандартом С99. Если же вас интересует язык С, включая полное описание его системы ввода-вывода и средств, добавленных стандартом С99, я рекомендую обратиться к моей книге Полный справочник по С, М.: Издательский дом "Вильямс". Использование потоков в С-системе ввода-вывода Подобно С++-системе ввода-вывода, С-ориентированная система ввода-вывода опирается на понятие потока. В начале работы программы автоматически открываются три заранее определенных текстовых потока: stdin, stdout и stderr. Их называют стандартными потоками ввода данных (входной поток), вывода данных (выходной поток) и ошибок соответственно. (Некоторые компиляторы открывают также и другие потоки, которые зависят от конкретной реализации системы.) Эти потоки представляют собой С-версии потоков cin, cout и cerr соответственно. По умолчанию они связаны с соответствующим системным устройством. Помните, что большинство операционных систем, включая Windows, позволяют перенаправлять потоки ввода-вывода, поэтому функции чтения и записи данных можно перенаправлять на другие устройства. Никогда не пытайтесь явно открывать или закрывать эти потоки. Каждый поток, связанный с файлом, имеет структуру управления файлом типа FILE. Эта структура определена в заголовочном файле stdio.h. Вы не должны модифицировать содержимое этого блока управления файлом. Функции printf() и scanf() Двумя самыми популярными С-функциями ввода-вывода являются printf() и scanf(). Функция printf() записывает данные в стандартное устройство вывода (консоль), а функция scanf(), ее дополнение, считывает данные с клавиатуры. Поскольку язык С не поддерживает перегрузку операторов и не использует операторы "<<" и ">>" в качестве операторов ввода-вывода, то для консольного ввода-вывода используются именно функции printf() и scanf(). Обе они могут обрабатывать данные любых встроенных типов, включая символы, строки и числа. Но поскольку эти функции не являются объектно-ориентированными, их нельзя использовать непосредственно для ввода-вывода объектов классов, создаваемых программистом. Функция printf() Функция printf() имеет такой прототип: int printf(const char *fmt_string, ...); Первый аргумент, fmt_string, определяет способ отображения всех последующих аргументов. Этот аргумент часто называют строкой форматирования. Она состоит из элементов двух типов: текста и спецификаторов формата. К элементам первого типа относятся символы (текст), которые выводятся на экран. Элементы второго типа (спецификаторы формата) содержат команды форматирования, которые определяют способ отображения аргументов. Команда форматирования начинается с символа процента, за которым следует код формата. Спецификаторы формата перечислены в табл. А.1. Количество аргументов должно в точности совпадать с количеством команд форматирования, причем совпадение обязательно и в порядке их следования. Например, при вызове следующей функции printf(). printf ("Привет %с %d %s", 'с', 10, "всем!"); На экране будет отображено: "Привет с 10 всем! ". Функция printf() возвращает число реально выведенных символов. Отрицательное значение возврата свидетельствует об ошибке. Команды формата могут иметь модификаторы, которые задают ширину поля, точность (количество десятичных разрядов) и признак выравнивания по левому краю. Целое значение, расположенное между знаком % и командой форматирования, выполняет роль спецификатора минимальной ширины поля. Наличие этого спецификатора приведет к тому, что результат будет заполнен пробелами или нулями, чтобы гарантированно обеспечить для выводимого значения заданную минимальную длину. Если выводимое значение (строка или число) больше этого минимума, оно будет выведено полностью, несмотря на превышение минимума. По умолчанию в качестве заполнителя используется пробел. Для заполнения нулями нужно поместить 0 перед спецификатором ширины поля. Например, строка форматирования %05d дополнит выводимое число нулями (их будет меньше пяти), чтобы общая длина была равной пяти символам. Точное значение модификатора точности зависит от кода формата, к которому он применяется. Чтобы добавить модификатор точности, поставьте за спецификатором ширины поля десятичную точку, а после нее — значение спецификации точности. Для форматов а, А, е, Е, f и F модификатор точности определяет число выводимых десятичных знаков. Например, строка форматирования %10.4f обеспечит вывод числа, ширина которого составит не меньше десяти символов, с четырьмя десятичными знаками. Применительно к целым или строкам, число, следующее за точкой, задает максимальную длину поля. Например, строка форматирования %5.7s отобразит строку длиной не менее пяти, но не более семи символов. Если выводимая строка окажется длиннее максимальной длины поля, конечные символы будут отсечены. По умолчанию все выводимые значения выравниваются по правому краю: если ширина поля больше выводимого значения, оно будет выровнено по правому краю поля. Чтобы установить выравнивание по левому краю, поставьте знак "минус" сразу после знака %. Например, строка форматирования %-10.2f обеспечит выравнивание вещественного числа (с двумя десятичными знаками в 10-символьном поле) по левому краю. Рассмотрим программу, в которой демонстрируется использование спецификаторов ширины поля и выравнивания по левому краю. #include int main() { printf("|%11.6f|\n", 123.23); printf ("|%-11.6f|\n", 123.23); printf("|%11.6s |\n", "Привет всем"); printf("|%-11.6s |\n", "Привет всем"); return 0; } При выполнении эта программа отображает такие результаты. | 123.230000 | | 123.230000 | | Привет| | Привет | Существуют два модификатора команд форматирования, которые позволяют функции printf() отображать короткие (short) и длинные (long) целые. Эти модификаторы могут применяться к спецификаторам типа d, i, о, и, х и X. Модификатор l уведомляет функцию printf() о длинном формате значения. Например, строка %ld означает, что должно быть выведено длинное целое. Модификатор h указывает на применение короткого формата. Следовательно, строка %hu означает, что выводимое целочисленное значение имеет тип short unsigned. Чтобы обозначить, что соответствующий аргумент указывает на длинное целое, к спецификатору n можно применить модификатор l. Для указания на короткое целое примените к спецификатору n модификатор h. Если вы используете современный компилятор, который поддерживает добавленные в 1995 году средства работы с символами широкого формата (двухбайтовыми символами), то можете задействовать модификатор l применительно к спецификатору с, чтобы уведомить об использовании двухбайтовых символов. Кроме того, модификатор l можно использовать с командой формата s для вывода строки двухбайтовых символов. Модификатор l можно также поставить перед командами форматирования вещественных чисел е, Е, f, F, g и G. В этом случае он уведомит о выводе значения типа long double. |