Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Алгоритмизация и программирование на языке С++. Ч.1. Учебник
.pdf
Приложение 1 Директивы препроцессора
191
всех консольных приложениях С++. Отметим, что в языке
нет инструкций (синтаксических конструкций) для организации ввода-вывода. Эти операции реализуются библиотечными функциями. Для их работы требуются специальные
описания, что и реализуется строкой
#include <iostream>
Следует отметить, что сам этот файл может не содержать
непосредственных описаний. В нем можно встретить несколько строк с такой же директивой –
include
(вложенное включе-
ние). В версии 5.9 Dev C++ есть такие строки
#include <ostream>
#include <istream>
Они содержат необходимые описания отдельно для вывода и для ввода соответственно.
Обычно включаемые директивой
*.h
расширение
. Здесь буква h обозначает слово head, поэто-
include
файлы имеют
му такие файлы называются заголовочными. Например, использование средств ввода-вывода в программе, написанной
на С, требует заголовочного файла
stdio.h
.
Следующая директива препроцессора, которую мы рассмо-
#dene
трим –
. Она также имеет две формы записи. Формат пер-
вой такой:
#dene Идентификатор Строка
При каждом вхождении в тексте программы
Строка
заменяется на
строковые данные или случаи, когда
. Исключение составляют комментарии,
Идентификатор
Идентификатор
является
частью более длинного идентификатора. Приведем примеры.
#include <iostream>
#include <math.h>
using namespace std;
#dene Id1 "Hello"
#dene Id2 "Id2_Hello"
#dene Id3 "Hello"
#dene Pi2 6.28

192
АЛГОРИТМИЗАЦИЯ ИПРОГРАММИРОВАНИЕ НАЯЗЫКЕ С++
char Id3_char[] = "Char";
double Pi2double = 3.14;
int main()
{
cout << "\n";
cout << Id1 << endl;
cout << Id2 << endl;
cout << Id3_char << endl;
cout << Pi2 << endl;
cout << Pi2double << endl;
cout << Pi2char << endl;
}
Дадим некоторые пояснения. Вне исходного кода функции
main()
определены несколько директив препроцессора. Первая
Id1
из них определяет, что
будет заменяться на
"Hello"
. Обратим внимание на то, что изменять значения, определенные в
рассматриваемой директиве, нельзя. Например, если в текст
поместить такую инструкцию
Id1=Id1 + ", world";
то выведется такое сообщение о том, что к константным значениям (
char [6]
и
char [8]
) нельзя применять бинарную опе-
рацию сложения:
[Error] invalid operands of types 'const char [6]' and 'const
char [8]' to binary 'operator+'
Впрочем, если бы они не были константными, такая операция все равно не допустима. Например, если в программе
встретится такой текст
char Char1[] = "Hello";
char Char2[] = ", world!";
cout << (Char1+Char2) << endl;
то также выведется сообщение о недопустимости применения
операции + к таким объектам.
#dene
Продолжим рассматривать пример с директивой
Id2
строке с
Идентификатор
няет
показано, что строка (
, может включать его в качестве фрагмента.
"Id2_Hello"
), которая заме-
. В

Приложение 1 Директивы препроцессора
193
Строка с
Id3
демонстрирует, что имя в рассматриваемой
директиве может быть частью (фрагментом) другой символь-
#dene
ной строки. Значение, определяемое
Pi2
и числовым значением (
).
, также может быть
В литературе [37] приведены операторы С++, описывающие семь типов целых чисел, определенных с помощью суффиксов числовых констант:
#dene A 280U // unsigned int
#dene B 280LU // unsigned long int
#dene C 280 // int (long int)
#dene D 280L // long int
#dene K 28.0 // double
#dene L 28.0F // oat
#dene M 28.0L // long double
Вторая форма
#dene
предполагает запись подобных функций. При этом могут быть заданы параметры, заключенные
в скобки (макрос). Они являются формальными. После определения макроса каждое вхождение параметров заменяется
фактическими значениями. В приведенном примере как макрос определена математическая функция косинус с одним
параметром. Заметим, что директивы препроцессора можно
разместить и в теле функций исходного кода.
// макрос в теле функции main()
#dene PI 3.14159265
#dene Cos(x) cos(PI*x/180)
double c;
c = 60;
cout << "cos= " << c << " " << Cos(c) << endl;
Результаты выполнения фрагментов программы с исполь-
#dene
зованием директивы
Отменить определения
показаны на рис. П1.1.
#dene
можно директивой
#undef
.
Большая группа директив препроцессора связана с так
называемой условной компиляцией. Таких директив шесть.
Первые четыре записываются в следующей форме [37]:

194
АЛГОРИТМИЗАЦИЯ ИПРОГРАММИРОВАНИЕ НАЯЗЫКЕ С++
Рис. П1.1. Примеры использования директивы
#dene
#if константное выражение
группа операций
#elif константное выражение
группа операций
#else
группа операций
#endif
При формировании этой конструкции должны соблюдаться
#if
следующие правила [37]: у каждой директивы
в исходном
файле должна быть соответствующая закрывающая директи-
#endif
ва
. Между директивами
любое количество директив
#else
одной директивы
. Директива
должна быть последней перед директивой
Еще две директивы (
стантное выражение было определено директивой
#if
#elif
#ifdef
и
и
#endif
может располагаться
, однако допускается не более
#else
, если присутствует,
#endif
.
#ifndef
) требуют, чтобы кон-
#dene
.
Приведем пример цепочки проверок имени SYSTEM, позволяющей выбрать нужный файл для включения:
#if SYSTEM == SYSV
#dene HDR "sysv.h"
#elif SYSTEM == BSD
#dene HDR "bsd.h"
#elif SYSTEM == MSDOS
#dene HDR "msdos.h"

Приложение 1 Директивы препроцессора
195
#else
#dene HDR "default.h"
#endif
#include HDR
Директива
#error
выдает указанное пользователем сообщение об ошибке во время компиляции, а затем завершает компиляцию [38]:
#include<stdio.h>
#ifndef __MATH_H
#error First include then compile
#else
int main(){
oat a,b=25;
a=sqrt(b);
printf("%f",a);
return 0;
}
#endif
Директива
#import
используется для включения сведений
из библиотеки типов. Содержимое библиотеки типов преобразуется в классы C++, в основном описывающие интерфейсы модели COM [39].
#line
Директива
позволяет компилятору выводить номер
строки с заданного номера, а также и имя файла при указании
на ошибки. Она фактически меняет значение по умолчанию
двух констант: __LINE__ и __FILE__. Приведем фрагмент, в
котором используется эта директива. (Вторая константа здесь
не обязательна и в примере не используется.)
#include <stdio.h>
#line 100
int main()
{
printf("Hello\n");
printf("%d\n ",__LINE__ );

196
АЛГОРИТМИЗАЦИЯ ИПРОГРАММИРОВАНИЕ НАЯЗЫКЕ С++
printf("%d\n",__FILE__);
}
Результат применения директивы
рис. П1.2.
#line
приведен на
Рис. П1.2. Использование директивы
А без строки
Рис. П1.3. Вывод без использования директивы
Еще одна директива (
#line 100
будет такой вывод (рис. П1.3).
#pragma
) препроцессора применяется
#line
#line
для придания компилятору специфических свойств при выполнении. Ими могут быть, например, такие: отключение
сообщения по номеру, включение заголовочного файла только один раз, независимо от того, сколько раз он встречается,
и др.
Исходный текст программы можно разбить на отдельные
связанные строки. Они называются пространство имен. В
пространстве имен собраны описания, связанные со стандарт-

Приложение 1 Директивы препроцессора
197
ными вводом и выводом на консоль. Директива
#using
обеспечивает доступ к указанному в ее строке пространству имен.
Как было сказано выше (см. п. 2.1), наличие строки
using namespace std;
позволяет при использовании консоли указывать короткие
имена
std::cout
че пришлось бы писать так
(вывод в консоль) и
std::cout
cin
(ввод с консоли). (Ина-
и
std::cin
.)

При выполнении программы передаваемые ей исходные
данные преобразуются в результат. Подаваемая на вход информация может быть разного типа и объема. И процесс
получения результата для разных задач может кардинально отличаться по разным показателям, в том числе по используемым инструментам, а значит, и возможностям выполнять те или иные действия над информацией. Разные
языки программирования имеют разные возможности по обработке хранимой в памяти информации. Это реализуется
через определенные типы данных и множество операций
над ними.
В основе классификации языков программирования лежат разные признаки. Один из них связан с основными объектами, обрабатываемыми в программе, – переменными. По
свойствам переменных языки программирования принято
делить прежде всего на две группы: типизированные и нетипизированные. Последние допускают любые операции
над участками памяти, выделенными под переменную. Такими возможностями обычно наделены языки, близкие к ассемблеру. Они выполняют действия с двоичным представлением
данных в памяти. Надо отметить, что сегодня это встречается
редко.
В противоположность такому подходу, типизированные
языки программирования предполагают наличие некоторого списка типов, каждый из которых имеет определенные
характеристики, например, какие действия можно выполнять для каждого элемента списка. Классический пример –
над символьными данными нельзя выполнять операцию деления, которую, конечно, можно совершать над числовыми
данными.
Класс типизированных языков программирования можно, в свою очередь, поделить на два подкласса по моменту
и строгости проверки. Первый из двух подклассов предпола-

Приложение 2 Типизация языков программирования
199
гает, что тип определяется до этапа выполнения программы
(при компиляции – статическая типизация) или на этапе выполнения (динамическая типизация).
Языки со статической типизацией, в свою очередь, могут быть явно типизированными (manifestly typed) или типовыводящими (type-inferred). Для первых требуется явное
указание, к какому типу отнесена переменная. А для вторых – типы вводятся, как правило, только в неоднозначных
и сложных случаях. Отметим, что и некоторые явно типизированные языки в процессе своего развития приобрели возможность вводить типы в лишь в некоторых случаях.
Языки программирования также подразделяются на классы строгой и слабой типизации. В случае строгой типизации
недопустимо использование переменных разных типов в одном выражении. Для слабо типизированных языков это возможно. При этом автоматически выполняется преобразование всех переменных выражения к одному типу.
Есть и еще одна классификация строго типизированных
языков: явная и неявная типизация. В первом случае переменные вводятся специальным оператором в исходном коде.
Во втором случае свойства переменной определяются, например, на этапе компиляции или интерпретации.
Приведенное описание классов языков программирования по типам приведено в следующей схеме (рис. П2.1).
Заметим, что часто языки программирования сложно строго отнести к определенному классу. Например, язык программирования С++ относят к классу с явной и статической типизацией, но, с другой стороны, С++ после стандарта C++11
получил поддержку неявной типизации с помощью ключевых слов auto и decltype. Он поддерживает динамическую типизацию, при использовании библиотеки Boost
boost::variant
) [40].
(boost::any,
Еще добавим, что в языке С++ определен оператор void,
часто применяемый для придания функции свойства не возвращать значений. Это позволяет отнести его к классу не типизированных языков.

200
АЛГОРИТМИЗАЦИЯ ИПРОГРАММИРОВАНИЕ НАЯЗЫКЕ С++
Типизация языков
программирования
Нетипизированные
Явная
Неявная
Динамиче ские
Статические
Типизированные
(manifestly typed)
Типизированные
Строгая
Нестрогая
Типовыводящие
(type-inferred)
Рис. П2.1. Классификация языков программирования
на основе типизации
Далее проиллюстрируем описанные свойства языков программирования конкретными примерами. Сначала приведем
примеры кода на С++, объясняющие статическую типизацию.
int a=1;
cout << " a=1 " << a << " " << typeid(a).name() << endl;
a=a+1.0;
cout << " a=1+1 " << a << " " << typeid(a).name() << endl;
a=1/2;
cout << " a=1/2 " << a << " " << typeid(a).name() << endl;
a=1 + '2';
cout << " a=1+'2' " << a << " " << typeid(a).name() << endl;
cout << " int('2') " << int('2') <<endl;
Здесь определена переменная а, принимающая целочисленное значение. Далее ей присваиваются значения несколь-
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
