Препроцессорные директивы. Директивы препроцессора в си

Одной из самых распространенных ошибок программистов считают создания велосипедов, колеса, и т.д. С этим невероятно сложно не согласиться, потому что на практике так и есть, но как с этим бороться? Многие программисты скажут вам в один голос: учить STL - стандартную библиотеку, которая содержит наработки множества разработчиков языка программирования, а также неплохо может помочь при разработке новой программы. В данной статье мы подробно разберем, что же такое C #Include, как это работает и для чего создавалось. Статья рекомендуется не только начинающим пользователям операционной системы Windows, но и программистам первого-второго курса.

Что это такое?

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

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

Да, многие скажут, что там нет как на С++. Это так, но можно написать ОС и на функциях, чему доказательством являются Linux и ядро Windows. В данном языке программирования каждая библиотека подключается в начале файла, все благодаря метки препроцессора С #include. После ключевого слова с решеткой должно быть название файла следующим образом: <так, если файл в папке с компилятором> и “так, если он вне папки компилятора”. Пример правильного подключения: #include

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

Библиотеки Visual С Include

Итак. Мы уже разобрались, что такое Include, теперь можем с ним работать, однако вы еще ничего не знаете про IDE - интегрированную среду разработки. Среда разработки - это, по сути, умный блокнот, если слишком уж сократить. Вы пишете свой код, после чего хотите его откомпилировать. Если бы вы работали в Linux, то пришлось бы делать компиляцию через командную строку, подключая вручную или через make-файл все нестандартные библиотеки, но в IDE это все делается автоматически.

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

Одним из лучших IDE по праву считают Microsoft Visual Studio. Более того, в данной среде разработки даже немного видоизменился сам язык, поэтому в данном пункте мы разберем самые востребованные стандартные библиотеки Visual C.

Список библиотек:

  1. Time.h - заголовочный файл для работы с временными интервалами.
  2. Stdlib.h - заголовочный файл с подключенным классом стандартной библиотеки.
  3. Stdio.h - стандартная библиотека ввода\ввыода.
  4. Fsteam.h - библиотека для работы с файлами.

Также имеются другие, намного более специфические библиотеки, но их изучение рекомендуется исключительно тогда, когда нужно работать только с возможностями Visual Studio. К слову, существует несколько вариаций данного IDE, от чего и зависят наборы стандартных библиотек, например, в версии PRO есть инструменты для работы с Android, а в обычной такого нет.

Список всех Include с пояснениями на С

Вы знаете, что такое классы? Если нет, то у вас появились вопросы, почему же библиотеки заканчиваются на «.h». Если быть кратким, то классы - это определенного рода кубики «Лего», которые можно вставить в программу. Чтоб это было легко - они и существуют. По правилу хорошего тона программирования, объявления их параметров надо заполнять в заголовочном файле, а само выполнение в отдельном с расширением «*. с» или «*. cpp».

Перед началом пояснения требуется разобраться в библиотеке С: Include - эта библиотека создана специально для операционной системы «Виндовс», внутри нее находятся все необходимые функции и классы для работы не только с графической составляющей системы, но и с параметрами, точными настройками, командной строкой и многим прочим. Если хотите писать программы под «Виндовс», то она должна быть первой в списке изучения.

Список STL:

  1. Vector.h - работа с динамической памятью, называемой векторами.
  2. Map.h - специальные словари.
  3. Iostream - библиотека для работы с вводом и выводом в консоль.
  4. Fout - работа с файлами. Аналогом является C Include .
  5. Stdlib - является классом наследником от других STL.
  6. Errno.h - заголовочный файл для выведения ошибок в консоль.
  7. Ctype.h - заголовочный файл для работы с аски-кодом.

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

В С++

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

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

Что будет, если на компьютере нет STL?

В С и С++ существует динамическая и статическая компиляция. В зависимости от системы, под которую изготовляется продукт, также и вкладываются соответствующие библиотеки. Например, библиотеки Windows.h нет на Linux, а на Windows нет x11.lib (там у библиотек такое расширение - *.lib). Данный факт требуется учитывать, но благодаря умным IDE можно выбрать между динамической и статической сборкой. При динамической - от системы специфические библиотеки, а в статической проект занимает больше, но гарантированно идет в конкретной системе.

В заключение

Надеемся, что вам понравилось окунуться в столь удивительный и красочный мир программирования. Если вы никогда не писали собственную программу, то рекомендуем начать это делать прямо сейчас, ведь вкус победы над машиной слишком приятен. Также надеемся, что изучение c Include вам далось без особых осложнений. В любом случае, можете навестить MDSN и узнать еще больше.

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

Основные директивы препроцессора

#include - вставляет текст из указанного файла
#define - задаёт макроопределение (макрос) или символическую константу
#undef - отменяет предыдущее определение
#if - осуществляет условную компиляцию при истинности константного выражения
#ifdef - осуществляет условную компиляцию при определённости символической константы
#ifndef - осуществляет условную компиляцию при неопределённости символической константы
#else - ветка условной компиляции при ложности выражения
#elif - ветка условной компиляции, образуемая слиянием else и if
#endif - конец ветки условной компиляции
#line - препроцессор изменяет номер текущей строки и имя компилируемого файла
#error - выдача диагностического сообщения
#pragma - действие, зависящее от конкретной реализации компилятора.

Директива #include

Директива #include позволяет включать в текст программы указанный файл. Если файл является стандартной библиотекой и находится в папке компилятора, он заключается в угловые скобки <> .
Если файл находится в текущем каталоге проекта, он указывается в кавычках "" . Для файла, находящегося в другом каталоге необходимо в кавычках указать полный путь.

#include
#include "func.c"

Директива #define

Директива #define позволяет вводить в текст программы константы и макроопределения.
Общая форма записи

#define Идентификатор Замена


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

1
2
3
4
5
6
7
8

#include
#define A 3
int main()
{
printf("%d + %d = %d" , A, A, A+A); // 3 + 3 = 6
getchar();
return 0;
}

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

  • U или u представляет целую константу в беззнаковой форме (unsigned );
  • F (или f ) позволяет описать вещественную константу типа float ;
  • L (или l ) позволяет выделить целой константе 8 байт (long int );
  • L (или l ) позволяет описать вещественную константу типа long double

#define A 280U // unsigned int
#define B 280LU // unsigned long int
#define C 280 // int (long int)
#define D 280L // long int
#define K 28.0 // double
#define L 28.0F // float
#define M 28.0L // long double

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

идентификатор(аргумент1, ..., агрументn)


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

Пример на Си : Вычисление синуса угла

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#include
#include
#include
#define PI 3.14159265
#define SIN(x) sin(PI*x/180)
int main()
{
int c;
system("chcp 1251" );
system("cls" );
printf("Введите угол в градусах: " );
scanf("%d" , &c);
printf("sin(%d)=%lf" , c, SIN(c));
getchar(); getchar();
return 0;
}

Результат выполнения

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13

#include
#define sum(A,B) A+B
int main()
{
int a, b, c, d;
a = 3; b = 5;


getchar();
return 0;
}


Результат выполнения:


По умолчанию текст макроопределения должен размещаться на одной строке. Если требуется перенести текст макроопределения на новую строку, то в конце текущей строки ставится символ "обратный слеш" — \ .

1
2
3
4
5
6
7
8
9
10
11
12
13
14

#include
#define sum(A,B) A + \
B
int main()
{
int a, b, c, d;
a = 3; b = 5;
c = (a + b) * 2; // c = (a + b)*2
d = sum(a, b) * 2; // d = a + b*2;
printf(" a = %d\n b = %d\n" , a, b);
printf(" c = %d \n d = %d \n" , c, d);
getchar();
return 0;
}


Кроме того, директива #define позволяет замещать часть идентификатора. Для указания замещаемой части используется ## .

1
2
3
4
5
6
7
8
9

#include
#define SUM(x,y) (a##x + a##y)
int main()
{
int a1 = 5, a2 = 3;
printf("%d" , SUM(1, 2)); // (a1 + a2)
getchar();
return 0;
}


Результат выполнения:

Директивы #if или #ifdef/#ifndef вместе с директивами #elif , #else и #endif управляют компиляцией частей исходного файла.
Если указанное выражение после #if имеет ненулевое значение, в записи преобразования сохраняется группа строк, следующая сразу за директивой #if . Синтаксис условной директивы следующий:

1
2
3
4
5
6
7

#if константное выражение
группа операций
#elif константное выражение
группа операций
#else
группа операций
#endif


Отличие директив #ifdef/#ifndef заключается в том, что константное выражение может быть задано только с помощью #define .

У каждой директивы #if в исходном файле должна быть соответствующая закрывающая директива #endif . Между директивами #if и #endif может располагаться любое количество директив #elif , однако допускается не более одной директивы #else . Директива #else , если присутствует, должна быть последней перед директивой #endif .

Пример

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#include
#include
#define P 2
int main()
{
system("chcp 1251" );
system("cls" );
#if P==1
printf("Выполняется ветка 1" );
#elif P==2
printf("Выполняется ветка 2, P=%d" , P);
#else
printf("Выполняется другая ветка, P=%d" , P);
#endif
getchar();
return 0;
}

Директивы препроцессора языка си

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

Определение

Назначение

Определение макроса

Отмена определения макроса

Включение объекта-заголовка

Компиляция, если выражение истинно

Компиляция, если макрос определен

Компиляция, если макрос не определен

Компиляция, если выражение в ifложно

Составная директива else/if

Окончание группы компиляции по условию

Замена новым именем строки или имени исходного файла

Формирование ошибок трансляции

Действие определяется реализацией

Null- директива

Директива # define

Директива # define вводит макроопределение или макрос. Общая форма директивы следующая:

# define ИМЯ_МАКРОСА последовательность_символов

Последовательность символов называют еще строкой замещения. Когда препроцессор находит в исходном файле имя_макроса (просто макрос), он заменяет его на последовательность_символов.

Можно отменить определение макроса директивой # undef:

# undef имя_макроса

Данная строка удаляет любую ранее введенную строку замещения. Определение макроса теряется и имя_макроса становится неопределенным.

К примеру, можно определить МАХ как величину 100:

Это значение будет подставляться каждый раз вместо макроса МАХ в исходном файле, Можно также использовать макрос вместо строковой константы:

#defineNAME“TurboC++”

Если последовательность символов в директиве не помещается в одной строке, то можно поставить в конце строки \ и продолжить последовательность на другой строке. Среди программистов принято соглашение, что для имен макросов используются прописные буквы, так как их легко находить в программе. Также все директивы #defineлучше помещать в начало программы.

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

Пример : #define MIN(a, b) ((9a)<(b)) ? (a) : (b)

printf(“Минимум из x и y “ % d, MIN(x ,y));

printf(“Минимум из a и b “ % d, MIN(n ,m));

Когда программа будет компилироваться, в выражение, определенное MIN(a,b) будут подставлены соответственноxиyилиmиn. Аргументыaиbзаключены в круглые скобки, так как вместо них может подставляться некоторое выражение, а не просто идентификатор.

Например, printf(“Минимум “ %d,MIN(x*x,x));

Директива # error

Имеет вид: # error сообщение_об_ошибке

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

Директивы условной компиляции

К данным директивам относятся: # if , # else , # elif , # endif .

Данные директивы производят выборочную компиляцию программы. Если выражение, следующее за #if, истинно, то коды, заключенные между #ifи #endif, будут компилироваться. В противном случае они при компиляции будут пропущены. Выражение, следующее за #if, проверяется во время компиляции, поэтому оно может содержать только константы и макросы, которые прежде определены. Переменные здесь не могут использоваться.

Директива # else используется так же, как иelseв языке Си.

Пример: Использование условной компиляции.

# include

# define MAX 100

printf(“ MAX равно %d \n”, MAX);

Директива # elif используется для организации вложенной условной компиляции. Форма использования ее следующая:

#if<выражение>

последовательность операторов

#elif<выражение 1>

последовательность операторов

#elif<выражение 2>

последовательность операторов

…………………………………..

Другой метод условной компиляции состоит в использовании директив # ifdef и# ifndef . Основная форма использования этих директив следующая:

# ifdef ИМЯ_МАКРОСА

# endif

и соответственно

# ifndef ИМЯ_МАКРОСА

последовательность операторов

# endif

Если макрос определен, то при использовании # ifdefкомпилируется соответствующая последовательность до операторов #endif. Если же макрос не определен или был отменен директивой #undef, то соответствующая последовательность операторов игнорируется компилятором. Директива #ifndefдействует противоположным образом.

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

Директива #include

Вы уже видели директиву #include в действии. Когда вы #include файл, препроцессор копирует содержимое подключаемого файла в текущий файл сразу после строчки #include. Это очень полезно при использовании определённых данных (например, функций) сразу в нескольких местах.

Директива #include имеет две формы:

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

#include "filename" , которая сообщает препроцессору искать файл в текущей директории проекта. Если его там не окажется, то препроцессор начнёт проверять системные пути и любые другие, которые вы указали в настройках вашей . Эта форма используется для подключения пользовательских заголовочных файлов.

Директива #define

Директиву #define можно использовать для создания макросов. Макрос — это правило, которое определяет конвертацию идентификатора в указанные данные.

Есть два основных типа макросов: макросы-функции и макросы-объекты.

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

Макросы-объекты можно определить одним из двух следующих способов:

#define identifier
#define identifier substitution_text

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

Макросы-объекты с substitution_text

Когда препроцессор встречает макросы-объекты с substitution_text , то любое дальнейшее появление identifier заменяется на substitution_text . Идентификатор обычно пишется заглавными буквами с символами подчёркивания вместо пробелов.

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

#define MY_FAVORITE_NUMBER 9 std::cout << "My favorite number is: " << MY_FAVORITE_NUMBER << std::endl;

#define MY_FAVORITE_NUMBER 9

std :: cout << "My favorite number is: " << MY_FAVORITE_NUMBER << std :: endl ;

Препроцессор преобразует код выше в:

std::cout << "My favorite number is: " << 9 << std::endl;

Любое дальнейшее появление идентификатора USE_YEN удаляется и заменяется «ничем» (пустым местом)!

Это может показаться довольно бесполезным, однако, это не основное предназначение подобных директив. В отличие от макросов-объектов с substitution_text , эта форма макросов считается приемлемой для использования.

Условная компиляция

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

#ifdef;

#ifndef;

#endif.

Директива #ifdef (англ. «if def ined» = «если определено») позволяет препроцессору проверить, было ли значение ранее #define. Если да, то код между #ifdef и #endif скомпилируется. Если нет, то код будет проигнорирован. Например:

#define PRINT_JOE #ifdef PRINT_JOE std::cout << "Joe" << std::endl; #endif #ifdef PRINT_BOB std::cout << "Bob" << std::endl; #endif

#define PRINT_JOE

#ifdef PRINT_JOE

std :: cout << "Joe" << std :: endl ;

#endif

#ifdef PRINT_BOB

std :: cout << "Bob" << std :: endl ;

#endif

Поскольку PRINT_JOE уже был #define, то строчка std::cout << "Joe" << std::endl; скомпилируется и выполнится. А поскольку PRINT_BOB не был #define, то строчка std::cout << "Bob" << std::endl; не скомпилируется и, следовательно, не выполнится.

Директива #ifndef (англ. «if n ot def ined» = «если не определено») - это полная противоположность #ifdef, которая позволяет проверить, не было ли значение ранее определено. Например:

#ifndef PRINT_BOB std::cout << "Bob" << std::endl; #endif

#ifndef PRINT_BOB

std :: cout << "Bob" << std :: endl ;

#endif

Результатом выполнения этого фрагмента кода будет Bob , так как PRINT_BOB ранее никогда не был #define. Условная компиляция очень часто используется в качестве header guards (о них мы поговорим в следующем уроке).

Область видимости директивы #define

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

Рассмотрим следующую программу:

#include void boo() { #define MY_NAME "Alex" } int main() { std::cout << "My name is: " << MY_NAME; return 0; }

#include

void boo ()

#define MY_NAME "Alex"

int main ()

std :: cout << "My name is: " << MY_NAME ;

return 0 ;

Несмотря на то, что директива #define MY_NAME "Alex" определена внутри функции boo, препроцессор этого не заметит, так как он не понимает такие понятия C++, как функции. Следовательно, выполнение этой программы будет идентично той, в которой бы #define MY_NAME "Alex" было определено ДО, либо сразу ПОСЛЕ функции boo. Для лучше читабельности кода определяйте идентификаторы (с помощью #define) вне функций.

После того, как препроцессор завершит своё выполнение, все идентификаторы (определённые с помощью #define) из этого файла отбрасываются. Это означает, что директивы действительны только от точки определения до конца файла, в котором они определены. Директивы, определённые в одном файле кода, не влияют на директивы, определённые внутри других файлов этого же проекта.

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

#include void doSomething() { #ifdef PRINT std::cout << "Printing!"; #endif #ifndef PRINT std::cout << "Not printing!"; #endif }

#include

void doSomething ()

#ifdef PRINT

std :: cout << "Printing!" ;

#endif

#ifndef PRINT

std :: cout << "Not printing!" ;