Директива #define
Символические константы
С директивой препроцессора мы также уже знакомы. С ее помощью объявляются и определяются так называемые символические константы. Например:
#define N 100 #define HELLO "Hello. Answer the questions."
Когда перед компиляцией исходный код будет обработан препроцессором, то все символьные константы (в примере это N и HELLO) в тексте исходного кода на языке C будут заменены на соответствующие им числовые или строковые литералы.
Символические константы можно определять в любом месте исходного кода. Однако чтобы переопределить их (изменить значение), следует отменить предыдущее определение. Иначе возникнет предупреждение (но не ошибка). Для удаления символической константы используют директиву :
#include <stdio.h> #define HELLO "Hello. Answer the questions.\n" int main () { printf(HELLO); #undef HELLO #define HELLO "Good day. Tell us about.\n" printf(HELLO); }
Если в этом примере убрать строку , то при компиляции в GNU/Linux появляется предупреждение: «HELLO» переопределён.
Символические константы принято писать заглавными буквами. Это только соглашение для удобства чтения кода.
Макросы как усложненные символьные константы
С помощью директивы можно заменять символьными константами не только числовые и строковые константы, но почти любую часть кода:
#include <stdio.h> #define N 100 #define PN printf("\n") #define SUM for(i=0; i<N; i++) sum += i int main () { int i, sum = ; SUM; printf("%d", sum); PN; }
Здесь в теле функции константа PN заменяется препроцессором на , а SUM на цикл . Такие макроопределения (макросы) в первую очередь удобны, когда в программе часто встречается один и тот же код, но выносить его в отдельную функцию нет смысла.
В примере выше PN и SUM являются макросами без аргументов. Однако препроцессор языка программирования C позволяет определять макросы с аргументами:
#include <stdio.h> #define DIF(a, b) (a)>(b)?(a)-(b):(b)-(a) int main() { int x = 10, y = 30; printf("%d\n", DIF(67, 90)); printf("%d\n", DIF(876-x, 90+y)); }
Вызов макроса в теле приводит к тому, что при обработке программы препроцессором туда подставляется выражение . В этом выражении вычисляется разница между двумя числами с помощью условного выражения (см. урок 4). В данном случае скобки не нужны. Однако при таком разворачивании скобки подчеркивают порядок операций. Если бы вместо сложения и вычитания фигурировали операции умножения или деления, то наличие скобок было бы принципиальным.
Обратите внимание, что после имени идентификатора не должно быть пробела:. Иначе, он бы означал конец символической константы и начало выражения для подстановки
- Напишите программу, содержащую пару макросов: один вычисляет сумму элементов массива, другой выводит элементы массива на экран.
- Напишите программу, содержащую макросы с аргументами, вычисляющие площади различных геометрических фигур (например, квадрата, прямоугольника, окружности).
Константы, определенные препроцессором
Препроцессор самостоятельно определяет пять констант. От обычных (определенных программистом) они отличаются наличием пары символов подчеркивания в начале и конце их имени.
- __DATE__ — дата компиляции;
- __FILE__ — имя компилируемого файла;
- __LINE__ — номер текущей строки исходного текста программы;
- __STDC__ — равна 1, если компилятор работает по стандарту ANSI для языка C;
- __TIME__ — время компиляции.
Если эти константы встречаются в тексте программы, то заменяются на соответствующие строки или числа. Т.к. это происходит до компиляции, то, например, мы видим дату компиляции, а не дату запуска программы на выполнение. Программа ниже выводит значение предопределенных препроцессором имен на экран:
#include <stdio.h> #define NL printf("\n") int main () { printf(__DATE__); NL; printf("%d",__LINE__); NL; printf(__FILE__); NL; printf(__TIME__); NL; printf("%d",__STDC__); NL; }
Результат:
Dec 17 2023 7 e5_const.c 21:48:52 1
Подключение заголовочного файла stdafx.h
Для подключения стандартной библиотеки в C обычно используется директива #include.
Однако в проектах, которые используют предварительное объявление (precompiled headers), вместо включения отдельных заголовочных файлов можно просто подключить заголовочный файл stdafx.h.
Этот файл содержит все необходимые предварительные определения и директивы препроцессора.
Для использования stdafx.h необходимо его сначала создать и настроить в проекте.
Затем его можно подключить с помощью указанной директивы и использовать стандартные функции и классы из стандартной библиотеки.
В программировании на C для использования стандартной библиотеки часто требуется подключение соответствующих заголовочных файлов. Однако, для проектов, использующих предварительное объявление (precompiled headers), можно вместо этого использовать заголовочный файл stdafx.h.
Этот файл содержит все необходимые предварительные определения и директивы препроцессора, что упрощает процесс компиляции и сокращает время сборки программы.
В данном разделе мы рассмотрим, как правильно подключать заголовочный файл stdafx.h и зачем он нужен в проекте на C .
Далее мы расскажем о роли заголовочного файла stdafx.h в проекте на C .
2. Библиотеки и препроцессор
В разработке программ на C широко используются библиотеки, которые содержат набор функций и классов для решения различных задач.
Для использования этих библиотек в программе необходимо подключать соответствующие заголовочные файлы, которые содержат объявления функций и классов из библиотеки.
Препроцессор C отвечает за обработку директив препроцессора, таких как #include, которые указывают компилятору, какие заголовочные файлы подключить к программе.
Однако, для использования стандартной библиотеки C можно вместо подключения отдельных заголовочных файлов использовать заголовочный файл stdafx.h при использовании предварительного объявления.
3. Роль заголовочного файла stdafx.h
Заголовочный файл stdafx.h играет важную роль в проектах на C , использующих предварительное объявление (precompiled headers).
Он содержит все необходимые предварительные определения и директивы препроцессора, которые обеспечивают более быструю и эффективную компиляцию программы.
При использовании stdafx.h, компилятор уже содержит предварительно скомпилированный код из этого файла, что экономит время компиляции.
Благодаря использованию stdafx.h, не нужно подключать отдельные заголовочные файлы каждый раз, когда они требуются.
Все необходимые определения и директивы уже находятся в stdafx.h, поэтому просто подключается этот файл и можно использовать стандартные функции и классы из стандартной библиотеки.
Подключение заголовочного файла stdafx.h является удобным и эффективным способом использования стандартной библиотеки в проектах на C с предварительным объявлением.
Этот файл содержит все необходимые предварительные определения и директивы препроцессора, что упрощает процесс компиляции и сокращает время сборки программы.
Подключение stdafx.h позволяет избежать необходимости включения отдельных заголовочных файлов и повышает эффективность разработки и сопровождения кода.
Использование stdafx.h ⏤ это хорошая практика, которая позволяет упростить разработку программ на C и достичь лучшей производительности.
Подключение заголовочного файла stdafx.h ー важный шаг в процессе разработки на C , который следует учитывать при создании проектов.
Директивы условной компиляции
Так называемая условная компиляция позволяет компилировать или не компилировать части кода в зависимости от наличия символьных констант или их значения.
Условное выражение для препроцессора выглядит в сокращенном варианте так:
#if … … #endif
То, что находится между и выполняется, если выражение при возвращает истину. Находится там могут как директивы препроцессора так и исходный код на языке C.
Условное включение может быть расширено за счет веток и .
Рассмотрим несколько примеров.
Если в программе константа N не равна 0, то цикл выполнится, и массив arr заполнится нулями. Если N определена и равна 0, или не определена вообще, то цикл выполняться не будет:
#include <stdio.h> #define N 10 int main() { int i, arr100; #if N for (i = ; i < N; i++) { arri = ; printf("%d ", arri); } #endif printf("\n"); }
Если нужно выполнить какой-то код в зависимости от наличия символьной константы, а не ее значения, то директива будет выглядеть так:
#if defined(N)
Или сокращенно (что тоже самое):
#ifdef N
Когда нет уверенности, была ли определена ранее символьная константа, то можно использовать такой код:
#if !defined(N) #define N 100 #endif
Таким образом мы определим константу N, если она не была определена ранее. Такие проверки могут встречаться в многофайловых проектах. Выражение препроцессора может быть сокращено так:
#ifndef N
Условную компиляцию иногда используют при отладке программного кода, а также с ее помощью компилируют программы под конкретные операционные системы.
Препроцессор обрабатывает программу до компиляции. В двоичном коде уже отсутствуют какие-либо условные выражения для препроцессора. Поэтому в логическом выражении «препроцессорного if» не должно содержаться переменных, значение которых определяется в момент выполнения программы.
Использование заголовочных файлов стандартной библиотеки
Рассмотрим следующую программу:
Эта программа печатает «Hello, world!» в консоль с помощью . Однако эта программа никогда не предоставляла определение или объявление для , поэтому как компилятор узнает, что такое ?
Ответ заключается в том, что был предварительно объявлен в заголовочном файле «iostream». Когда мы пишем , мы запрашиваем, чтобы препроцессор скопировал всё содержимое (включая предварительные объявления для ) из файла с именем «iostream» в файл, выполняющий .
Ключевой момент
Когда вы включаете файл с помощью , содержимое включаемого файла вставляется в точке включения. Это удобный способ извлечения объявлений из другого файла.
Подумайте, что бы произошло, если бы заголовок не существовал. Каждый раз, когда вы хотели бы использовать , вам приходилось бы вручную вводить или копировать все объявления, связанные с , в начало каждого файла, который использовал бы ! Для этого потребуется много знаний о том, как реализован , и потребуется много работы. Хуже того, если бы прототип функции изменился, нам пришлось бы вручную обновлять все предварительные объявления. Намного проще просто включить iostream с помощью !
Когда дело доходит до функций и переменных, стоит помнить, что заголовочные файлы обычно содержат только объявления функций и переменных, а не их определения (в противном случае может произойти нарушение правила одного определения). объявлен в заголовке iostream, но определен как часть стандартной библиотеки C++, которая автоматически подключается к вашей программе на этапе линкера.
Рисунок 1 – Диаграмма процесса сборки
Лучшая практика
Заголовочные файлы обычно не должны содержать определений функций и переменных, чтобы не нарушать правило одного определения. Исключение сделано для символьных констант (которые мы рассмотрим в уроке «4.14 – const, constexpr и символьные константы»).
Использование заголовка «stdafx.h» в C++ с примерами
meta http-equiv=»Content-Type» content=»text/html;charset=UTF-8″>¤Ð°Ð¹Ð» заголовка ÑодеÑÐ¶Ð¸Ñ Ð½Ð°Ð±Ð¾Ñ Ð¿ÑедопÑеделеннÑÑ ÑÑнкÑий ÑÑандаÑÑной библиоÑеки. ÐаголовоÑнÑй Ñайл Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ Ð²ÐºÐ»ÑÑен в пÑогÑÐ°Ð¼Ð¼Ñ Ñ Ð¿Ð¾Ð¼Ð¾ÑÑÑ Ð´Ð¸ÑекÑÐ¸Ð²Ñ Ð¿ÑедваÑиÑелÑной обÑабоÑки C «#include» . ÐÑе заголовоÑнÑе ÑÐ°Ð¹Ð»Ñ Ð¸Ð¼ÐµÑÑ ÑаÑÑиÑение « .h» .
СинÑакÑиÑ:
#include / "header_file"
#include ÑказÑÐ²Ð°ÐµÑ ÐºÐ¾Ð¼Ð¿Ð¸Ð»ÑÑоÑÑ Ð´Ð¾Ð±Ð°Ð²Ð¸ÑÑ header_file в иÑÑоднÑй код пеÑед вÑполнением опеÑаÑоÑов пÑогÑаммÑ.
ÐаголовоÑнÑй Ñайл «stdafx.h»
ÐÑÐ¾Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²Ð¾ÑнÑй Ñайл ÑвлÑеÑÑÑ Ð¿ÑедваÑиÑелÑно ÑкомпилиÑованнÑм заголовоÑнÑм Ñайлом. ÐÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑÐµÐ»Ñ ÑÑого заголовоÑного Ñайла â ÑокÑаÑиÑÑ Ð²ÑÐµÐ¼Ñ ÐºÐ¾Ð¼Ð¿Ð¸Ð»ÑÑии. ÐÑÐ¾Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²Ð¾ÑнÑй Ñайл обÑÑно иÑполÑзÑеÑÑÑ Ð² Microsoft Visual Studio.
Ðез заголовоÑного Ñайла stdafx.h:
РпÑиведенной ниже пÑогÑамме иÑполÑзÑÑÑÑÑ Ð´Ð²Ð° заголовоÑнÑÑ Ñайла, iostream и cmath. ÐаждÑй Ñаз, когда пÑогÑамма компилиÑÑеÑÑÑ, Ñо Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ компилÑÑии пÑогÑÐ°Ð¼Ð¼Ñ ÐºÐ°Ð¶Ð´Ñй заголовоÑнÑй Ñайл бÑÐ´ÐµÑ ÐºÐ¾Ð¼Ð¿Ð¸Ð»Ð¸ÑоваÑÑÑÑ Ñ Ð½ÑлÑ. Таким обÑазом, вÑÐµÐ¼Ñ ÐºÐ¾Ð¼Ð¿Ð¸Ð»ÑÑии одинаково Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ компилÑÑии.
C ++
 |
ÐÑÑод
GFG!
С заголовоÑнÑм Ñайлом stdafx. h:
«stdafx.h» пÑигодиÑÑÑ, когда Ð¼Ñ ÑоÑим компилиÑоваÑÑ Ð¿ÑогÑÐ°Ð¼Ð¼Ñ Ñнова и Ñнова. Ðн должен бÑÑÑ Ð²ÐºÐ»ÑÑен в наÑало вÑÐµÑ Ñайлов заголовков, и ÑепеÑÑ Ð¿Ñи пеÑвой компилÑÑии пÑогÑÐ°Ð¼Ð¼Ñ ÑкомпилиÑÐ¾Ð²Ð°Ð½Ð½Ð°Ñ Ð²ÐµÑÑÐ¸Ñ Ð²ÑÐµÑ Ñайлов заголовков, пÑиÑÑÑÑÑвÑÑÑÐ¸Ñ Ð² пÑогÑамме, бÑÐ´ÐµÑ ÑоÑÑанена в Ñайле «stdafx.h». ТепеÑÑ ÐºÐ°Ð¶Ð´Ñй Ñаз, когда пÑогÑамма компилиÑÑеÑÑÑ, компилÑÑÐ¾Ñ Ð±ÐµÑÐµÑ ÑкомпилиÑованнÑÑ Ð²ÐµÑÑÐ¸Ñ Ñайлов заголовков из Ñайла «stdafx.h», а не компилиÑÑÐµÑ Ð¾Ð´Ð½Ð¸ и Ñе же ÑÐ°Ð¹Ð»Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ¾Ð² Ñ Ð½ÑÐ»Ñ Ñнова и Ñнова.
ÐÑимеÑ:
Ð ÑÑÑ Ð¿ÑогÑÐ°Ð¼Ð¼Ñ Ð²ÐºÐ»ÑÑен заголовоÑнÑй Ñайл «stdafx.h». ТепеÑÑ, когда пÑогÑамма ÑкомпилиÑована, cmath и iostream бÑдÑÑ ÑкомпилиÑÐ¾Ð²Ð°Ð½Ñ Ñ Ð½ÑлÑ, а ÑкомпилиÑÐ¾Ð²Ð°Ð½Ð½Ð°Ñ Ð²ÐµÑÑÐ¸Ñ cmath и iostream бÑÐ´ÐµÑ ÑоÑÑанена в Ñайле «stadafx.h». ÐÐ»Ñ ÑледÑÑÑей компилÑÑии компилÑÑÐ¾Ñ Ð°Ð²ÑомаÑиÑеÑки вÑбиÑÐ°ÐµÑ ÑкомпилиÑованнÑÑ Ð²ÐµÑÑÐ¸Ñ ÑÑÐ¸Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²Ð¾ÑнÑÑ Ñайлов из заголовоÑного Ñайла «stadafx».
C++
   9004;  |
ÐÑвод:
prog.cpp:5:20: ÑаÑалÑÐ½Ð°Ñ Ð¾Ñибка: stdafx.h: Ð½ÐµÑ Ñакого Ñайла или каÑалога компилÑÑÐ¸Ñ Ð¿ÑекÑаÑена.
Заголовки и их назначение
По мере того, как программы становятся больше (и используют больше файлов), становится всё более утомительным давать предварительные объявления каждой функции, которую вы хотите использовать, и которая определена в другом файле. Было бы неплохо, если бы вы могли поместить все свои предварительные объявления в одно место, а затем импортировать их, когда они вам понадобятся?
Исходные файлы кода C++ (с расширением .cpp) – это не единственные файлы, которые обычно встречаются в программах на C++. Другой тип файлов – это заголовочный файл (иногда просто заголовок). Заголовочные файлы обычно имеют расширение .h, но иногда вы можете встретить их с расширением .hpp или вообще без расширения. Основная цель заголовочного файла – распространять объявления в исходные файлы кода.
Ключевой момент
Заголовочные файлы позволяют нам размещать объявления в одном месте, а затем импортировать их туда, где они нам нужны. Это может сэкономить много времени при наборе текста в проектах из нескольких файлов.
Включение заголовочного файла в соответствующий исходный файл
Позже вы увидите, что большинство исходных файлов включают свой соответствующий заголовочный файл, даже если он им не нужен. Зачем?
Включение заголовочного файла в исходный файл увеличивает прямую совместимость. Очень вероятно, что в будущем вы добавите больше функций или измените существующие таким образом, что им нужно будет знать о существовании друг друга.
Когда мы углубимся в изучение стандартной библиотеки, вы будете включать множество заголовочных файлов библиотек. Если вам потребовалось включение в заголовочном файле, оно, вероятно, понадобилось вам для объявления функции. Это означает, что вам также потребуется такое же включение в исходный файл. Это приведет к тому, что в исходном файле у вас будет копия включений заголовочного файла. Включив заголовочный файл в исходный файл, исходный файл получит доступ ко всему, к чему имел доступ заголовочный файл.
При разработке библиотеки включение заголовочного файла в исходный файл может даже помочь в раннем обнаружении ошибок.
Лучшая практика
При написании исходного файла включите в него соответствующий заголовочный файл (если он существует), даже если он вам пока не нужен.
Директивы условной компиляции
Следующий набор
директив, который мы разберем, — это, так называемые, директивы условной компиляции:
#if,
#endif, #elif, #else, #ifdef, #ifndef, #elifdef, #elifndef
В основном они используются,
чтобы оставить или убрать определенный фрагмент текста программы в зависимости
от какого-либо условия. Например, пишется программа, которую предполагается
компилировать с использованием компилятора Си, а также с использованием
компилятора С++. Но в этих языках программирования имеются некоторые отличия в используемых
конструкциях и часть программного кода должна различаться в зависимости от языка.
Так вот, чтобы написать универсальный текст программы и иметь возможность
компилировать его как Си, так и С++, можно воспользоваться условными
директивами следующим образом:
#define LANG_C #if defined(LANG_C) # include <stdio.h> #else # include <iostream> #endif int main(void) { int x=5; #ifdef LANG_C printf("%d\n", x); #else std::cout << x << std::endl; #endif return ; }
Смотрите,
вначале определено макроимя LANG_C с помощью
директивы #define. Затем,
прописана директива #if, в которой проверяется условие:
определено ли макроимя LANG_C в текущем
модуле. Если это так (как в нашем примере), то макропроцессор оставляет в
программе все, что записано после этой директивы либо до следующей условной
директивы, либо до директивы #endif. В приведенном примере, остается
строчка «# include <stdio.h>» и
удаляется строка «# include <iostream>».
Соответственно, директива #include также, затем, обрабатывается
макропроцессором. В итоге, после обработки, у нас получается следующий текст программы:
# include <stdio.h> int main(void) { int x=5; printf("%d\n", x); return ; }
Разумеется,
директива #include здесь также
впоследствии преобразуется макропроцессором. А директива #ifdef – это
сокращенный вариант записи конструкции #if defined.
По сути,
директивы условной компиляции #if, #else, #endif работают
подобно условным операторам if-else, о которых мы с
вами уже говорили. Но, конечно же, есть и отличия. Первый важный момент: в
условиях директив можно использовать исключительно целочисленные литералы и
макроимена. С этими элементами можно выполнять все булевы операции сравнения:
==, !=, <,
>, <=, >=
логические
связки:
&&, ||,
!
все бинарные арифметические
и битовые операции:
+, -, *, /, %,
&, |, ^
и применять
оператор defined, которые
возвращает 1, если указанное макроимя существует и 0 – в противном случае. Есть
еще несколько экзотических конструкций, вроде условной тернарной операции,
которые допустимо прописывать в условиях директив, но в основном используются
те операции, что перечислены выше
Обратите внимание, никаких переменных,
функций и прочих конструкций, значение которых определяется в процессе работы
программы, здесь применять нельзя
Второй важный
момент. Директивы условной компиляции не образуют своих собственных внутренних
блоков. Поэтому для указания того, что попадает внутрь таких директив, в конце
обязательно следует прописать директиву #endif – метку,
означающую конец текущей директивы условной компиляции.
Третий важный
момент. Директивы препроцессора анализируют программу как текст (на уровне
лексем). Это означает, что они не учитывают области видимости: локальные,
глобальные и т.п. Поэтому все директивы принято записывать с самого начала
строки (с левого края). В частности, именно поэтому символ # у директив include записан на
одном уровне с другими директивами, т.к. никакого реального вложения здесь нет,
и это мы подчеркиваем оформлением. То же самое при записи директив внутри
функции main(). Для всех
этих директив функции не имеют никакого значения – это просто текст. Поэтому
все они прописаны с самого начала строки.
Рекомендации по использованию заголовочных файлов
Вот еще несколько рекомендаций по созданию и использованию заголовочных файлов.
- Всегда включайте защиту заголовков (мы рассмотрим это в следующем уроке).
- Не определяйте переменные и функции в файлах заголовков (глобальные константы являются исключением – мы рассмотрим их позже)
- Давайте файлам заголовков те же имена, что и исходным файлам, с которыми они связаны (например, grades.h идет в паре с grades.cpp).
- Каждый заголовочный файл должен иметь конкретное назначение и быть максимально независимым. Например, вы можете поместить все объявления, относящиеся к функциональности , в A.h, а все объявления, относящиеся к функциональности , в B.h. Таким образом, если позже вам нужен будет только , вы можете просто включить A.h и не получать ничего, связанного с .
- Учитывайте, какие заголовки вам нужно явно включить для функций, которые вы используете в своих файлах исходного кода.
- Каждый заголовок, который вы пишете, должен компилироваться сам по себе (он должен включать с все необходимые зависимости)
- Включайте с только то, что вам нужно (не включайте всё только потому, что вы можете).
- Не включайте с файлы .cpp.