Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Алгоритмизация и программирование на языке С++. Ч.1. Учебник

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
Приложение 1 Директивы препроцессора
191
всех консольных приложениях С++. Отметим, что в языке нет инструкций (синтаксических конструкций) для органи­зации ввода-вывода. Эти операции реализуются библиотеч­ными функциями. Для их работы требуются специальные описания, что и реализуется строкой
#include <iostream>
Следует отметить, что сам этот файл может не содержать непосредственных описаний. В нем можно встретить несколь­ко строк с такой же директивой –
include
(вложенное включе-
ние). В версии 5.9 Dev C++ есть такие строки
#include <ostream> #include <istream>
Они содержат необходимые описания отдельно для выво­да и для ввода соответственно.
Обычно включаемые директивой
*.h
расширение
. Здесь буква h обозначает слово head, поэто-
include
файлы имеют
му такие файлы называются заголовочными. Например, ис­пользование средств ввода-вывода в программе, написанной на С, требует заголовочного файла
stdio.h
.
Следующая директива препроцессора, которую мы рассмо-
#dene
трим –
. Она также имеет две формы записи. Формат пер-
вой такой:
#dene Идентификатор Строка
При каждом вхождении в тексте программы
Строка
заменяется на строковые данные или случаи, когда
. Исключение составляют комментарии,
Идентификатор
Идентификатор
является
частью более длинного идентификатора. Приведем примеры.
#include <iostream> #include <math.h>
using namespace std;
#dene Id1 "Hello" #dene Id2 "Id2_Hello" #dene Id3 "Hello" #dene 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;
то также выведется сообщение о недопустимости применения операции + к таким объектам.
#dene
Продолжим рассматривать пример с директивой
Id2
строке с
Идентификатор
няет
показано, что строка (
, может включать его в качестве фрагмента.
"Id2_Hello"
), которая заме-
. В
Приложение 1 Директивы препроцессора
193
Строка с
Id3
демонстрирует, что имя в рассматриваемой
директиве может быть частью (фрагментом) другой символь-
#dene
ной строки. Значение, определяемое
Pi2
и числовым значением (
).
, также может быть
В литературе [37] приведены операторы С++, описываю­щие семь типов целых чисел, определенных с помощью суф­фиксов числовых констант:
#dene A 280U // unsigned int #dene B 280LU // unsigned long int #dene C 280 // int (long int) #dene D 280L // long int #dene K 28.0 // double #dene L 28.0F // oat #dene M 28.0L // long double
Вторая форма
#dene
предполагает запись подобных функ­ций. При этом могут быть заданы параметры, заключенные в скобки (макрос). Они являются формальными. После опре­деления макроса каждое вхождение параметров заменяется фактическими значениями. В приведенном примере как ма­крос определена математическая функция косинус с одним параметром. Заметим, что директивы препроцессора можно разместить и в теле функций исходного кода.
// макрос в теле функции main() #dene PI 3.14159265 #dene Cos(x) cos(PI*x/180)
double c;
c = 60; cout << "cos= " << c << " " << Cos(c) << endl;
Результаты выполнения фрагментов программы с исполь-
#dene
зованием директивы
Отменить определения
показаны на рис. П1.1.
#dene
можно директивой
#undef
.
Большая группа директив препроцессора связана с так называемой условной компиляцией. Таких директив шесть. Первые четыре записываются в следующей форме [37]:
194
АЛГОРИТМИЗАЦИЯ ИПРОГРАММИРОВАНИЕ НАЯЗЫКЕ С++
Рис. П1.1. Примеры использования директивы
#dene
#if константное выражение группа операций #elif константное выражение группа операций
#else
группа операций
#endif
При формировании этой конструкции должны соблюдаться
#if
следующие правила [37]: у каждой директивы
в исходном
файле должна быть соответствующая закрывающая директи-
#endif
ва
. Между директивами
любое количество директив
#else
одной директивы
. Директива
должна быть последней перед директивой
Еще две директивы ( стантное выражение было определено директивой
#if
#elif
#ifdef
и
и
#endif
может располагаться
, однако допускается не более
#else
, если присутствует,
#endif
.
#ifndef
) требуют, чтобы кон-
#dene
.
Приведем пример цепочки проверок имени SYSTEM, по­зволяющей выбрать нужный файл для включения:
#if SYSTEM == SYSV #dene HDR "sysv.h" #elif SYSTEM == BSD #dene HDR "bsd.h" #elif SYSTEM == MSDOS #dene HDR "msdos.h"
Приложение 1 Директивы препроцессора
195
#else
#dene 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;
Здесь определена переменная а, принимающая целочис­ленное значение. Далее ей присваиваются значения несколь-
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]