Foreversoft.ru

IT Справочник
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Язык си define

#define

Директива #define определяет идентификатор и последовательность символов, которой будет за­мещаться данный идентификатор при его обнаружении в тексте программы. Идентификатор так­же называется именем макроса, а процесс замещения называется подстановкой макроса. Стандар­тный вид директивы следующий:

#define имя_макроса последовательность_символов

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

Например, если необходимо использовать TRUE для значения 1, a FALSE для 0 то можно объявить следующие два макроса:

#define TRUE 1
#define FALSE 0

В результате, если компилятор обнаружит в тексте программы TRUE или FALSE, то он заменит их на 1 и 0 соответственно. Например, следующая строка выводит на экран «0 1 2»:

printf («%d %d %d», FALSE, TRUE, TRUE + 1);

В случае, если макрос определен, он может использоваться для определения других макросов. Например, следующий код сопоставляет с именами ONE, TWO и THREE их численные значения:

#define ONE 1
#define TWO ONE + ONE
#def ine THREE ONE + TWO

В результате макроподстановки идентификаторы замещаются указанными строками. Если не­обходимо определить стандартное сообщение об ошибке, то можно написать что-то вроде следу­ющего:

#define E_MS «Standart error on input.n»
/*. */
printf(E_MS);

Если компилятор обнаруживает идентификатор E_MS, то он замещает его строкой «Standart error on input.» На самом деле компилятор увидит оператор в виде

printf(«Standart error on input.n»);

Если идентификатор находится в строке, то подстановка не происходит. Например:

#define XYZ this is a test
/*. */
printf(«XYZ»);

выведет не «this is a test», a «XYZ».

Если строка не вмещается в одной строке, то ее можно продолжить на следующей строке, поместив в конце строки обратный слэш, как показано в следующем примере:

#define LONG_STRING «This is a very long»
string that is used as an example.»

Программисты, пишущие на С, часто используют заглавные буквы для определения идентифика­торов. Данное соглашение помогает любому человеку, читающему программу, бросив на нее один взгляд, узнать, что он имеет дело с макросом. Также вce #define лучше помещать в начале файла или вообще в отдельный заголовочный файл.

Очень часто макросы используют для определения «магических чисел», используемых в про­грамме. Например, программа может определять массив и иметь несколько процедур для работы с ним. Вместо того, чтобы жестко кодировать размер массива, лучше определить макрос, соответ­ствующий размеру массива, и использовать его в тех местах, где необходимо использование раз­мера. Таким образом, если необходимо изменить размер массива, единственное, что требуется сделать, — это изменить оператор #define и перекомпилировать программу. Везде, где использо­вался данный макрос, произойдут автоматические изменения. Рассмотрим пример:

#define MAX_SIZE 100
/*. */
float balance[MAX_SIZE];
/*. */
float temp[MAX_SIZE];

Для изменения размеров обоих массивов просто изменим определение MAX_SIZE.

Директива #define имеет еще одну возможность: макрос может иметь аргументы. Каждый раз при встрече такого макроса аргументы макроса будут замещаться реальными аргументами про­граммы. Такой тип макроса называется макрос типа функция. Например:

#include
#define MIN(a,b) ((a)
#define EVEN(a) a%2==0 ? 1 : 0
int main(void)
<
if (EVEN(9+1) ) printf(«is even»);
else printf («is odd»);
return 0;
>

Из-за способа подстановки данная программа работает неправильно. В результате компиляции программы EVEN(9 + 1) расширится до

9 + 1% 2 == 0 ? 1 : 0

Как известно, оператор взятия по модулю имеет более высокий приоритет, чем оператор сло­жения. Это означает, что сначала выполнится взятие по модулю с числом 1, а затем результат прибавится к 9, что, естественно, не может быть равно 0. Для устранения данной проблемы сле­дует заключить а в макросе EVEN в круглые скобки, как показано в следующей правильной вер­сии программы:

#include
#define EVEN(a) (a)%2==0 ? 1 : 0
int main(void)
<
if(EVEN(9 + 1) ) printf(«is even»);
else printf(«is odd»);
return 0;
>

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

Макросы в Си: как, когда и зачем?

  • Переводы, 21 декабря 2017 в 19:10
  • Андрей Селивестров

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

Прим. перев. Макросы в таких языках, как Lisp, Scala и Rust, во многом лишены тех проблем и недостатков, которые описаны в этой статье.

Макросы и функции

При первом знакомстве макросы могут показаться обычными вызовами функций. Конечно, у них немного странный синтаксис, но они «ведут себя» как обычные функции. Тогда в чём разница?

Макрос можно условно назвать функцией обработки и замены программного кода: после сборки программы макросы заменяются макроопределениями. Вот как это выглядит:

Этот код преобразуется в следующий:

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

Но если попытаться проделать такой же трюк с помощью макроса, то во время компиляции будет выброшена ошибка, так как обе переменные в итоге будут находиться в одной функции:

Один из способов решить эту проблему — поместить тело макроса в новую область видимости имён:

Но если просто обернуть его в блок, то может возникнуть такая проблема: блок, в который раскроется использование bar , и стоящая после него точка с запятой будут учтены как две разные команды, и в результате, встретив else , компилятор выдаст ошибку.

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

Ивент переехал в онлайн, есть новые даты ( 14 – 15 июля ) , Москва и онлайн, 10 750–138 000 ₽

Обычно эту проблему решают с помощью такого трюка:

Такой цикл выполнится только один раз, но поскольку конструкция do-while в Си требует точки с запятой после условия, стоящая после макроса точка с запятой будет отнесена к нему, а не воспринята как отдельная команда.

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

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

Прим. перев. Чтобы не переходить к определению каждого макроса, можно попросить компилятор раскрыть макросы — это можно сделать с помощью команды gcc -E source.c . Имейте в виду, что если вы включаете в свой код с помощью #include стандартные заголовочные файлы, после препроцессинга в коде может оказаться много тысяч строк, так что стоит перенаправить вывод компилятора в файл.

Тем не менее, можно выделить одно явное преимущество макросов перед функциями — производительность. Макрос быстрее, чем функция. Как уже упоминалось выше, под функцию выделяются дополнительные ресурсы, которые можно сэкономить, если использовать макросы. Это преимущество может сыграть весомую роль в системах с ограниченными ресурсами (например, в очень старых микроконтроллерах). Но даже в современных системах программисты производят оптимизации, используя макросы для небольших процедур.

Прим. перев. Интересный подход к оптимизации использования ресурсов в программе на Си рассмотрен в другой нашей статье.

В C99 и C++ существует альтернатива макросам — встраиваемые (inline) функции. Если добавить ключевое слово inline перед функцией, компилятору будет дано указание включить тело функции в место её вызова (по аналогии с макросом). При этом встраиваемые функции могут быть отлажены, и у них есть проверка типов.

Однако ключевое слово inline — это просто подсказка для компилятора, а не строгое правило, и компилятор может проигнорировать эту подсказку. Чтобы этого не произошло, в gcc есть атрибут always_inline , который заставляет компилятор встроить функцию.

Встраиваемые функции — отличная штука, которая, как может показаться, делает использование макросов нецелесообразным. Однако это не совсем так.

Когда использовать макросы в Cи

Передача аргументов по умолчанию

В C++ есть весьма удобный инструмент, которого нет в Си, — аргументы по умолчанию:

В Си задача опциональных аргументов может быть решена с помощью макросов:

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

Использование отладочных строк

Некоторые компиляторы предопределяют макросы, которые нельзя использовать в функциях: __FILE__ , __LINE__ , __func__ .

Их можно включать в определения макросов, используемых для отладки:

В итоге получается интересный способ ведения логов.

Модификация синтаксиса

Это очень мощная особенность макросов. Используя её, можно создать свой собственный синтаксис.

Например, в Cи нет конструкции foreach . Но её можно создать через макрос:

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

Эта техника действительно очень эффективна и при правильном использовании может дать довольно хорошие результаты.

Другие типы макросов

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

  • #include — включить содержимое стороннего файла в текущий файл,
  • #ifdef — задать условие для компиляции,
  • #define — определить константу (и, конечно же, макрос).

#ifdef играет ключевую роль при создании заголовочных файлов. Использование этого макроса гарантирует, что заголовочный файл включён только один раз:

Стоит отметить, что основное предназначение #ifdef — условная компиляция блоков кода на основе некоторого условия. Например, вывод отладочной информации только в режиме отладки:

Прим. перев. С помощью #ifdef также довольно часто задаётся условие для компиляции на различных версиях ОС и архитектурах: #ifdef _WIN32 , #ifdef _WIN64 , #ifdef __linux__ и так далее.

Как правило, для определения констант используется #define , но в некоторых проектах его заменяют на const и перечисления ( enum ). Однако при использовании любой из этих альтернатив есть свои преимущества и недостатки.

Использование ключевого слова const , в отличие от макроподстановки, позволяет произвести проверку типов данных. Но в Cи это создаёт не совсем полноценные константы. Например, их нельзя использовать в операторе switch-case и для определения размера массива.

Прим. автора В C++ переменные, определённые ключевым словом const , являются полноценными константами (их можно использовать в приведённых выше случаях), и настоятельно рекомендуется использовать именно их, а не #define .

Перечисления в то же время — полноценные константы. Они могут использоваться в операторах switch-case и для определения размера массива. Однако их недостаток заключается в том, что в перечислениях можно использовать только целые числа. Их нельзя использовать для строковых констант и констант с плавающей запятой.
Вот почему использование #define — оптимальный вариант, если нужно достичь единообразия в определении различного рода констант.

Таким образом, у макросов есть свои преимущества. Но использовать их нужно весьма аккуратно.

Подводные камни при использовании макросов

Отсутствие скобок

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

В этом примере выполняется вычисление MULTIPLY(x + 5) и ожидаемый результат — 50. Но в процессе подстановки произойдёт следующее преобразование:

MULTIPLY(x + 5) -> (x + 5 * 5)

Как несложно подсчитать, данное выражение выдаст не 50, а 30.

А вот как выполнить данную задачу правильно:

Инкремент и декремент

Допустим, есть такой код:

Здесь можно ожидать, что x будет увеличен на единицу и будет равен 6, а результат — 5. Но вот что получится в реальной жизни:

Виновата всё та же макроподстановка: ABS(x++) -> ((x++)

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

Передача вызовов функций

Использованием функции в коде никого не удивишь. Равно как и передачей результата одной функции в виде аргумента для другой. Часто это делается так:

И в этой вставке кода всё в порядке. Но, когда это же производится с помощью макроса, можно столкнуться с серьёзными проблемами производительности. Допустим, есть вот этот код:

Здесь определена рекурсивная функция sum_chars . Она вызывается один раз для первой строки ( str1 ) и другой раз — для второй ( str2 ). Но, если передать вызовы функций, как аргументы для макроса, будет выполнено три рекурсивных вызова вместо двух. Для больших структур данных это станет узким местом производительности. Особенно, если макрос используется внутри рекурсивной функции.

Многострочные макросы

Программисты не всегда используют фигурные скобки вокруг единичных команд в циклах и условных операторах. Но, если произвести макроподстановку внутри этого блока, и этот макрос будет содержать несколько строк, это приведёт к весьма интересным результатам:

Во вставке кода выше нет фигурных скобок в первом цикле и, так как макрос заменяет одну строку несколькими, только первое выражение в макросе выполняется в цикле. Следовательно, это приведёт к бесконечному циклу, так как i никогда не увеличится.

Прим. перев. Эту проблему также можно решить с помощью упомянутого выше трюка с do <> while (0) .

Именно из-за таких особенностей многие стараются избегать использования макросов.

Хорошая практика

Чтобы свести к минимуму проблемы, вызванные использованием макросов, хорошей практикой будет использование единого подхода для определения макросов в вашем коде. Каким будет этот подход, не имеет значения. Есть проекты, в которых все макроопределения объявлены в верхнем регистре. В некоторых проектах в начале имени макроса используют букву «m». Выберите себе любой подход, но этот подход должен быть таким, чтобы и вы, и другой программист, который будет работать с вашим кодом, сразу понимал, что имеет дело с макросами.

Прим. перев. Другие полезные практики оформления кода можно посмотреть в нашей статье.

Вывод

В большинстве случаев программисты предпочитают использовать функции, поскольку макросы скрывают некоторые серьёзные побочные эффекты. Тем не менее, использование макросов иногда более целесообразно. В этом случае нужно соблюдать правила их именования и учитывать специфику их использования, рассмотренную в статье.

Макросы в Си: как, когда и зачем?

  • Переводы, 21 декабря 2017 в 19:10
  • Андрей Селивестров

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

Прим. перев. Макросы в таких языках, как Lisp, Scala и Rust, во многом лишены тех проблем и недостатков, которые описаны в этой статье.

Макросы и функции

При первом знакомстве макросы могут показаться обычными вызовами функций. Конечно, у них немного странный синтаксис, но они «ведут себя» как обычные функции. Тогда в чём разница?

Макрос можно условно назвать функцией обработки и замены программного кода: после сборки программы макросы заменяются макроопределениями. Вот как это выглядит:

Этот код преобразуется в следующий:

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

Но если попытаться проделать такой же трюк с помощью макроса, то во время компиляции будет выброшена ошибка, так как обе переменные в итоге будут находиться в одной функции:

Один из способов решить эту проблему — поместить тело макроса в новую область видимости имён:

Но если просто обернуть его в блок, то может возникнуть такая проблема: блок, в который раскроется использование bar , и стоящая после него точка с запятой будут учтены как две разные команды, и в результате, встретив else , компилятор выдаст ошибку.

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

Ивент переехал в онлайн, есть новые даты ( 14 – 15 июля ) , Москва и онлайн, 10 750–138 000 ₽

Обычно эту проблему решают с помощью такого трюка:

Такой цикл выполнится только один раз, но поскольку конструкция do-while в Си требует точки с запятой после условия, стоящая после макроса точка с запятой будет отнесена к нему, а не воспринята как отдельная команда.

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

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

Прим. перев. Чтобы не переходить к определению каждого макроса, можно попросить компилятор раскрыть макросы — это можно сделать с помощью команды gcc -E source.c . Имейте в виду, что если вы включаете в свой код с помощью #include стандартные заголовочные файлы, после препроцессинга в коде может оказаться много тысяч строк, так что стоит перенаправить вывод компилятора в файл.

Тем не менее, можно выделить одно явное преимущество макросов перед функциями — производительность. Макрос быстрее, чем функция. Как уже упоминалось выше, под функцию выделяются дополнительные ресурсы, которые можно сэкономить, если использовать макросы. Это преимущество может сыграть весомую роль в системах с ограниченными ресурсами (например, в очень старых микроконтроллерах). Но даже в современных системах программисты производят оптимизации, используя макросы для небольших процедур.

Прим. перев. Интересный подход к оптимизации использования ресурсов в программе на Си рассмотрен в другой нашей статье.

В C99 и C++ существует альтернатива макросам — встраиваемые (inline) функции. Если добавить ключевое слово inline перед функцией, компилятору будет дано указание включить тело функции в место её вызова (по аналогии с макросом). При этом встраиваемые функции могут быть отлажены, и у них есть проверка типов.

Однако ключевое слово inline — это просто подсказка для компилятора, а не строгое правило, и компилятор может проигнорировать эту подсказку. Чтобы этого не произошло, в gcc есть атрибут always_inline , который заставляет компилятор встроить функцию.

Встраиваемые функции — отличная штука, которая, как может показаться, делает использование макросов нецелесообразным. Однако это не совсем так.

Когда использовать макросы в Cи

Передача аргументов по умолчанию

В C++ есть весьма удобный инструмент, которого нет в Си, — аргументы по умолчанию:

В Си задача опциональных аргументов может быть решена с помощью макросов:

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

Использование отладочных строк

Некоторые компиляторы предопределяют макросы, которые нельзя использовать в функциях: __FILE__ , __LINE__ , __func__ .

Их можно включать в определения макросов, используемых для отладки:

В итоге получается интересный способ ведения логов.

Модификация синтаксиса

Это очень мощная особенность макросов. Используя её, можно создать свой собственный синтаксис.

Например, в Cи нет конструкции foreach . Но её можно создать через макрос:

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

Эта техника действительно очень эффективна и при правильном использовании может дать довольно хорошие результаты.

Другие типы макросов

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

  • #include — включить содержимое стороннего файла в текущий файл,
  • #ifdef — задать условие для компиляции,
  • #define — определить константу (и, конечно же, макрос).

#ifdef играет ключевую роль при создании заголовочных файлов. Использование этого макроса гарантирует, что заголовочный файл включён только один раз:

Стоит отметить, что основное предназначение #ifdef — условная компиляция блоков кода на основе некоторого условия. Например, вывод отладочной информации только в режиме отладки:

Прим. перев. С помощью #ifdef также довольно часто задаётся условие для компиляции на различных версиях ОС и архитектурах: #ifdef _WIN32 , #ifdef _WIN64 , #ifdef __linux__ и так далее.

Как правило, для определения констант используется #define , но в некоторых проектах его заменяют на const и перечисления ( enum ). Однако при использовании любой из этих альтернатив есть свои преимущества и недостатки.

Использование ключевого слова const , в отличие от макроподстановки, позволяет произвести проверку типов данных. Но в Cи это создаёт не совсем полноценные константы. Например, их нельзя использовать в операторе switch-case и для определения размера массива.

Прим. автора В C++ переменные, определённые ключевым словом const , являются полноценными константами (их можно использовать в приведённых выше случаях), и настоятельно рекомендуется использовать именно их, а не #define .

Перечисления в то же время — полноценные константы. Они могут использоваться в операторах switch-case и для определения размера массива. Однако их недостаток заключается в том, что в перечислениях можно использовать только целые числа. Их нельзя использовать для строковых констант и констант с плавающей запятой.
Вот почему использование #define — оптимальный вариант, если нужно достичь единообразия в определении различного рода констант.

Таким образом, у макросов есть свои преимущества. Но использовать их нужно весьма аккуратно.

Подводные камни при использовании макросов

Отсутствие скобок

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

В этом примере выполняется вычисление MULTIPLY(x + 5) и ожидаемый результат — 50. Но в процессе подстановки произойдёт следующее преобразование:

MULTIPLY(x + 5) -> (x + 5 * 5)

Как несложно подсчитать, данное выражение выдаст не 50, а 30.

А вот как выполнить данную задачу правильно:

Инкремент и декремент

Допустим, есть такой код:

Здесь можно ожидать, что x будет увеличен на единицу и будет равен 6, а результат — 5. Но вот что получится в реальной жизни:

Виновата всё та же макроподстановка: ABS(x++) -> ((x++)

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

Передача вызовов функций

Использованием функции в коде никого не удивишь. Равно как и передачей результата одной функции в виде аргумента для другой. Часто это делается так:

И в этой вставке кода всё в порядке. Но, когда это же производится с помощью макроса, можно столкнуться с серьёзными проблемами производительности. Допустим, есть вот этот код:

Здесь определена рекурсивная функция sum_chars . Она вызывается один раз для первой строки ( str1 ) и другой раз — для второй ( str2 ). Но, если передать вызовы функций, как аргументы для макроса, будет выполнено три рекурсивных вызова вместо двух. Для больших структур данных это станет узким местом производительности. Особенно, если макрос используется внутри рекурсивной функции.

Многострочные макросы

Программисты не всегда используют фигурные скобки вокруг единичных команд в циклах и условных операторах. Но, если произвести макроподстановку внутри этого блока, и этот макрос будет содержать несколько строк, это приведёт к весьма интересным результатам:

Во вставке кода выше нет фигурных скобок в первом цикле и, так как макрос заменяет одну строку несколькими, только первое выражение в макросе выполняется в цикле. Следовательно, это приведёт к бесконечному циклу, так как i никогда не увеличится.

Прим. перев. Эту проблему также можно решить с помощью упомянутого выше трюка с do <> while (0) .

Именно из-за таких особенностей многие стараются избегать использования макросов.

Хорошая практика

Чтобы свести к минимуму проблемы, вызванные использованием макросов, хорошей практикой будет использование единого подхода для определения макросов в вашем коде. Каким будет этот подход, не имеет значения. Есть проекты, в которых все макроопределения объявлены в верхнем регистре. В некоторых проектах в начале имени макроса используют букву «m». Выберите себе любой подход, но этот подход должен быть таким, чтобы и вы, и другой программист, который будет работать с вашим кодом, сразу понимал, что имеет дело с макросами.

Прим. перев. Другие полезные практики оформления кода можно посмотреть в нашей статье.

Вывод

В большинстве случаев программисты предпочитают использовать функции, поскольку макросы скрывают некоторые серьёзные побочные эффекты. Тем не менее, использование макросов иногда более целесообразно. В этом случае нужно соблюдать правила их именования и учитывать специфику их использования, рассмотренную в статье.

Данные, символьные строки, директива #define

Основные типы данных

Чтобы реализовать алгоритм , программам необходимо работать с данными — числами, символами, т.е. объектами, которые несут в себе информацию, предназначенную для использования. Некоторые данные устанавливаются равными определенным значениям еще до того, как программа начинает выполняться, а после ее запуска такие значения сохраняются неизменными на всем протяжении работы программ. Эти данные называются константами . Данные , которые могут изменяться, или же им могут быть присвоены значения во время выполнения программы, называются переменными . Различие между переменной и константой очевидно: во время выполнения программы значение переменной может быть изменено (например, с помощью присваивания), а значение константы изменить нельзя.

Кроме различия между переменными и константами существует еще различие между типами данных . Некоторые данные в программе являются числами, некоторые — символами. Компилятор должен уметь идентифицировать и обрабатывать данные любого типа. В языке Си предусмотрено использование нескольких основных типов данных . Если величина есть константа , то компилятор может распознать ее тип только по тому виду, в котором она присутствует в программе. В случае переменной необходимо, чтобы ее тип был объявлен в операторе описания. В стандарте языка Си используется семь ключевых слов, указывающих на различные типы данных :

Первые четыре ключевых слова используются для представления целых, т.е. целых чисел без десятичной, дробной части. Если мы хотим подчеркнуть, что целое не может быть отрицательным, то нужно к целому подписывать ключевое слово unsigned , например, unsigned short . char предназначено для указания на буквы и другие символы. float , double используются для представления чисел с десятичной точкой. Типы, обозначаемые этими ключевыми словами, можно разделить на два класса по принципу размещения в памяти машины. Первые пять ключевых слов определяют целые типы данных , последние два — типы данных с плавающей точкой. Дадим краткое объяснение их смысла. Термины бит , байт , слово — используются для описания как элементов данных , которые обрабатывает компьютер , так и элементов памяти. Рассмотрим эти понятия относительно памяти. Наименьшая единица памяти называется бит . Она может принимать одно из двух значений: 0 или 1. Байт в большинстве машин состоит из 8 бит . Всего в байтовом формате можно представить 256 (два в восьмой степени) различных комбинаций из нулей и единиц. Эти комбинации можно использовать для представления целых чисел в диапазоне от 0 до 255 или для кодирования набора символов. Слово является естественным элементом памяти. Есть ЭВМ, у которых слово равно 8 битам , 16 битам , 32 битам или 64 битам .

Описание различных типов, переменные и константы

Целые числа. У целого числа никогда не бывает дробной части. Представив целое число в двоичном виде, его нетрудно разместить в машине. Например, число 3 в двоичном виде выглядит как 11 . Если его поместить в слово 32-разрядной машины, необходимо первые 30 бит установить в 0 , а последние 2 бита — в 1 .

Числа с плавающей точкой. Числа с плавающей точкой соответствуют тому, что математики называют вещественными числами. Способ кодирования, используемый для помещения в память числа с плавающей точкой, полностью отличается от размещения целого числа. Числа с плавающей точкой представляют в виде дробной части и порядка числа, а затем обе части размещают в памяти.

Все данные типов int , short , long являются числами со знаками, т.е. значениями этих типов могут быть только целые числа — положительные, отрицательные и нуль. Один бит используется для указания знака числа, поэтому максимальное число со знаком, которое можно представить в слове , меньше, чем максимальное число без знака.

Описание данных целого типа. При описании данных необходимо написать только тип, за которым должен следовать список имен переменных . Например, int dog , rad , nina . В качестве разделителя между именами переменных необходимо использовать запятую.

Целые константы . Согласно правилам языка Си , число без десятичной точки и без показателя степени рассматривается как целое. Поэтому компилятор по записи константы определяет, целая она или вещественная. Если нужно ввести константу типа long , то нужно указать признак L или l в конце числа. Если при записи константы целое начинается с цифры 0 , то эта константа интерпретируется как восьмеричное число, если же целое начинается с символа 0x или 0X — как шестнадцатеричное число.

Инициализация переменных целого типа. Константы применяются при инициализации переменных . Это означает присваивание переменной некоторого значения перед началом обработки. Можно инициализировать переменную в операторе описания.

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

Описание данных типа unsigned. Этот тип является модификатором типов: int , short , long . Мы можем использовать комбинацию ключевых слов unsigned int , unsigned short , unsigned long , т.е. переменная не может принимать отрицательного значения. Для указания типа unsigned int достаточно написать unsigned . Целые беззнаковые константы записываются так же, как и обычные константы , запрещено только использование знака минус.

Например, unsigned age;

Описание данных типа char. Этот тип определяет целые числа без знака в диапазоне от 0 до 255 (это зависит от реализации, char может совпадать как с signed char так и c unsigned char ). Такое целое обычно размещается в одном байте памяти. Для описания символьной переменной применяется ключевое слово char . Правила описания более чем одной переменной и инициализации переменных остаются теми же, что и для других основных типов.

Например, char dog, cat;

Символьные константы . Символы в языке Си заключаются в апострофы.

Например, char dog; dog=’b’;

Если апострофы опущены, то компилятор считает, что используется неописанная переменная b . В стандартном языке Си значением переменной или константы типа char могут быть только одиночные символы.

Примеры символьных констант : ‘ A ‘, ‘ a ‘, ‘ 7 ‘, ‘ $ ‘.

Специальные ( управляющие ) символьные константы .

Новая строка (перевод строки)‘n’
Горизонтальная табуляция‘t’
Вертикальная табуляция‘v’
Возврат на шаг‘b’
Возврат каретки‘r’
Перевод формата‘f’
Обратная косая‘\’
Апостроф»’
Кавычки‘»‘
Нулевой символ (пусто)‘’

Кроме того, любой символ может быть представлен последовательностью трех восьмеричных цифр: ‘ddd’ . Символьные константы считаются данными типа int .

Читать еще:  Функции языка паскаль
Ссылка на основную публикацию
Adblock
detector