Археология об основании Рима: Новые раскопки проясняют и такой острый дискуссионный вопрос, как дата самого возникновения Рима...
История развития пистолетов-пулеметов: Предпосылкой для возникновения пистолетов-пулеметов послужила давняя тенденция тяготения винтовок...
Топ:
Методика измерений сопротивления растеканию тока анодного заземления: Анодный заземлитель (анод) – проводник, погруженный в электролитическую среду (грунт, раствор электролита) и подключенный к положительному...
Особенности труда и отдыха в условиях низких температур: К работам при низких температурах на открытом воздухе и в не отапливаемых помещениях допускаются лица не моложе 18 лет, прошедшие...
Проблема типологии научных революций: Глобальные научные революции и типы научной рациональности...
Интересное:
Искусственное повышение поверхности территории: Варианты искусственного повышения поверхности территории необходимо выбирать на основе анализа следующих характеристик защищаемой территории...
Влияние предпринимательской среды на эффективное функционирование предприятия: Предпринимательская среда – это совокупность внешних и внутренних факторов, оказывающих влияние на функционирование фирмы...
Берегоукрепление оползневых склонов: На прибрежных склонах основной причиной развития оползневых процессов является подмыв водами рек естественных склонов...
Дисциплины:
2019-10-30 | 204 |
5.00
из
|
Заказать работу |
|
|
Назначение препроцессора
Компиляция исходного текста программы в среде Си проходит в два этапа. Сначала исходный текст просматривается и корректируется специальной процедурой - препроцессором, а уже после этого он обрабатывается собственно компилятором.
Препроцессор обрабатывает в исходном тексте строки специального вида, начинающиеся со знака # – директивы препроцессора. Каждая директива начинается с # и записывается одной строкой. В очень длинных директивах можно вставлять код продолжения строки последовательным нажатием клавиш / и Enter.
В первой лекции одна из директив препроцесора уже рассматривалась – включение файлов в исходный текст программы. Напомним вид таких директив:
# include < stdio. h >
# include “ stdio. h ”
#include “C:\\Work\\include\\stdio.h”
Там же для задания констант использовалась директива макроопределения #define. Объявим, например, строку
# define ABC 2*3.14
Макроопределение указывает, каким текстом необходимо заменить имя ABC. При обработке этой директивы препроцессор просмотрит весь текст программы и везде, где встретит лексему ABC, заменит ABC на 2*3.14 (такая замена называется макроподстановкой). Так, если встретится строка
L = ABC * R;
то компилятор после обработки программы препроцессором получит
L =2*3.14* R;
Замечание. В языке Си не было констант, вместо них использовали строку #define. В С++ константу можно объявить следующим образом:
const float K =2*3.14;.
Оператор
L = K * R; даст тот же результат, что L=ABC*R, но разница есть.
В первом случае компилируется выражение 2*3.14*R, вычислять его будет процессор во время выполнения программы.
Во втором случае значение константы вычислится на этапе компиляции, выражение K*R будет компилироваться как 6.28*R, поэтому во время выполнения программы процессор будет выполнять на одно умножение меньше.
|
1.2.2. Макроопределения с параметрами
Рассмотренная директива #define является частным случаем макроопределения с параметрами. В нем кроме имени макроопределения (макроса) в скобках, аналогично формальным параметрам функций, могут перечисляться имена одного или нескольких параметров. Например
# define KUB (x) x * x * x
Как видим, формальный параметр далее участвует в теле макроопределения. После этого в тексте программы можно вставлять макрокоманды, в которых формальные параметры заменены фактическими. Например,
float y =5;
float К1= KUB (10)+ KUB (y);
В процессе макроподстановки препроцессор заменит в теле каждой макрокоманды формальный параметр его фактическим значением и компилироваться будет следующая строка
float К1= 10*10*10+ y * y * y;
Замечание. Если в тексте программы применить макрокоманду
K 1= KUB (y +1);
то макроподстановка преобразует ее в строку
K 1= y +1* y +1* y +1;
После замены формального параметра x в макроопределении x*x*x фактическим значением у+1 скобки будут отсутствовать и результатом вычисления не будет (y+1)3. Если необходимо было возводит фактический параметр в куб, правильный макрос должен иметь вид:
#define KUB(x) (x)*(x)*(x)
В подобных ситуациях можно дать общую рекомендацию не жалеть скобок в теле макроопределения. Например, для получения минимального из двух чисел в сдедующем макросе скобки можно не писать
#define MIN(x,y) ((x)<(y)?(y):(x)
Но если формальные параметры x и y могут заменяться не только именами переменных, но и выражениями, каждый такой параметр обязательно заключается в скобки.
Обратите внимание, что при внешнем сходстве с вызовом функций, макрокоманды не передают управление написанной отдельно процедуре. Каждая макрокоманда повторно включает в исходный модуль текст макроса. При этом исключаются затраты времени на запись параметров в стек, выполнение команд вызова (call) и возврата из процедуры (ret).
Кроме того, при макроподстановке отсутствует контроль соответствия типов формальных и фактических параметров.
|
Объявим указатели
int * Ptr,* Ptr 1;
При работе с динамической памятью мы обращали внимание, что функция освобождения памяти не заносит значения NULL в указатель.
В сложных разветвленных программах один указатель может много раз использоваться в операциях запроса и освобождения памяти.
Поэтому при запросе памяти мы проверяли, свободен ли указатель следующим образом
If (Ptr)
printf (“Непредвиденная ошибка.\ n Указатель занят”);
else Ptr = malloc (1000);
Поскольку операция освобождения
Free (Ptr);
не заносит в указатель значения NULL, многие программисты применяют для освобождения памяти макрос
#define FREE(x) if(x) free(x); x =NULL
Макрокоманда FREE(Ptr); освободит память и запишет NULL в указатель Ptr.
Что будет, если между x и =NULL не поставить пробела. Ничего особенного, препроцессор все равно найдет в этой строке параметр x, заменит его на Ptr и сформирует оператор Ptr=NULL. А вот если макроопределение записать так
#define FREE(x) if(x) free(x); x1 =NULL,
компилятор в строке FREE(Ptr); сообщит о том, что переменная x1 нему неизвестна (а не о том, что неизвестна Ptr1). То есть, x не заменится на Ptr и не сформируется переменная Ptr1.
При макроподстановке препроцессор сравнит x1 и x, увидит, что они не совпадают, не будет считать это вхождение x в строку формальным параметром и не заменит его фактическим параметром Par. Чтобы можно было найти параметр в теле макроопределения, он должен быть отдельной лексемой. Что это такое – лексема? Это имя, знак операции, круглые скобки или ключевое слово языка. Границы лексем определяются пробелами или знаками операций. Поэтому в строке x=NULL препроцессор считает лексемой x, а в строке x1=NULL – лексема это x1.
Если макроопределение должно использоваться только в некоторой части исходного текста модуля, можно применять директивы #undef прекращения действия макроопределения. Например:
#undef MIN
#undef FREE
1.2.3. Операция образования строки #
Использование операции # перед именем формального параметра приводит к тому, что в процессе макроподстановки фактический параметр заключается в кавычки.
Пусть при выводе значений переменных Вам настолько часто приходится выводить имя вещественной переменной и ее значение, что Вы решили оформить этот вывод краткой макрокомандой.
|
Соответствующее макроопределение имеет вид:
# define Writeln (x) printf (# x “ = % f \ n“, x);
Объявим в программе переменную
float Area=2.5;
Если в текст этой программы вставить макрокоманду
Writeln (Area);
то в результате макроподстановки сформируется оператор
printf (“Area” ” = %f \n“, Area);
При макроподстановке обозначение #x не только превращается в строку “Area”, эта строка объединяется с соседней “ = %f “ в одну строку (для этого между ними надо оставлять не более одного пробела):
printf (“Area = %f \n“, Area);
В результате на экран будет выводиться строка
Area = 2.5
Условная трансляция
Int MMM;
# ifdef MMM
int MM 1=3;
# endif
не приведет к объявлению переменной MM1, так как в программе нет макроса с именем MMM.
В приведенном примере условная трансляция позволяет включить или исключить часть текста из процесса компиляции. Если нужно выбрать один из двух вариантов, можно, аналогично условному оператору использовать ветвь #else. Директивы условной трансляции могут быть вложенными.
В качестве примера применения этих возможностей рассмотрим, как в С++ для MS DOS определена константа NULL.
Указатели в С++, работающем в среде DOS, могут состоять из двух или четырех байтов в зависимости от модели памяти. Поэтому, например, в модели HUGE для совместимости с указателем константа NULL должна быть длинным целым, а в модели SMALL – словом. Для настройки на модель памяти эта константа в среде Borland определена в файле NULL.h следующим образом
#ifndef NULL
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
# define NULL 0 Если модель крохотная, малая или средняя NULL – это слово.
# else
# define NULL 0 L Для других моделей это двойное слово.
# endif Это #endif для вложенной директивы #if defined.
# endif Это #endif для директивы #ifndef NULL.
Итак, если константа NULL уже определена раньше, препроцессор ничего не вставит в текст программы, если нет, он вставит строку
define NULL 0
или строку
define NULL 0L.
В результате такого определения в языке С++ появляется некоторая нелогичность, которую не очень рекламируют: указатели не совместимы с целыми, но число ноль можно записать в указатель, а значение указателя NULL – в переменную целого типа.
Но сделанное для нуля исключение того стоит, в результате все синтаксические конструкции работают с пустым указателем, как с обычным нулем. Вспомним, как мы проверяли, открылся ли файл:
|
if ((f = fopen (“ txt. txt ”,” w ”))! = NULL) …
потому что в скобках должно быть целое число. Теперь ясно, что можно было проверять и значение указателя f:
if (f=fopen (“txt.txt”,”w”))...
При знакомстве со списками мы видели, как это свойство пустого указателя позволяет просматривать списки циклом for.
В среде Windows указатели всегда 32-битные, используется только одна модель памяти, поэтому аналогичное определение в файле Windef.h выглядит значительно проще:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
Директивы условной трансляции позволяют также выбрать для компиляции один из нескольких вариантов текста при помощи следующей конструкции
# if <условие 1>
<Текст, используемый при выполнении условия 1>
# elif < условие 2>
< Текст, используемый при выполнении условия 2>
# elif < условие n>
< Текст, используемый при выполнении условия n >
# else
< Этот текст компилятор использует, если не выполнено ни дно из условий>
# endif
Следует обратить внимание на отличие условной компиляции от условного оператора:
– в условном операторе обе ветви присутствуют в исполняемом файле, но выполняется только одна;
– при условной компиляции в исполнительном модуле только одна выбранная ветвь. Это уменьшает размер файла и увеличивает скорость, так как во время работы программы не выполняется проверка условий – они проверены на этапе компиляции.
Union
{
float m[4][4];
struct A
{
float m00, m01, m02, m03;
float m10, m11, m12, m13;
float m20, m21, m22, m23;
float m30, m31, m32, m33;
}M; При знакомстве со структурами мы видели, что если здесь нет имени переменной, ошибки компиляции не будет, но и такой структуры в объединении не будет.
} D 3 D;
После этого к элементу, расположенному в первой строке и в первой строкематрицы D3D, можно обращаться двумя способами:
D3D.m[0][0]=0;
D 3 D. M. m 00=0;
Следует отметить, что второй вариант не так уж и удобен даже при обращении к конкретному элементу матрицы, потому что кроме имени матрицы D3D и имени поля mm0 приходитcя указывать не несущее никакой смысловой нагрузки имя структуры M. Но при обращении по имени компилятор указывает конкретный адрес элемента, а для определения адреса в массиве выполняются длинные команды умножения.
На практике мы можем встретить структуры данных, элементы которых в разных задачах (или в разных функциях одной задачи) имеют разный физический смысл. Для обеспечения понятности программы, «самодокументируемости» ее исходного текста, одно поле в этих ситуациях должно иметь разные имена. Это достигается включением поля в структуру в виде объединения нескольких однотипных полей с разными именами.
|
1.3.2. Анонимные объединения
Пусть для вывода на экран точек некоторого изображения объявлена структура POINT3D, определяющая цвет точки Color и ее координаты в плоскости экрана
Struct POINT3D
{
int Color;
union _KOORD
{
int Koord[2];
struct _D2
{ int x;
int y;
} Record;
}uniD2;
}Point;
Координаты точки включены в POINT3D как объединение массива Koord и структуры Record. При этом мы получаем два варианта обращения к координатам, например,
Point.uniD2.Record.x=30;
Point.uniD2.Koord[0]=30;
В каждом из этих обращений приходится указывать составное имя uniD2.Record или uniD2.Koord, потому что имена полей Koord, Record видны только внутри объединения uniD2. В C++ введен специальный тип данных, анонимные объединения, позволяющий расширить область видимости полей за пределы объединения и избавиться в обращениях к полям от имени объединения. Анонимное объединение отличается тем, что в нем не указывают ни имени типа объединения ни имени переменной.
Чтобы в данном примере сделать объединение анонимным, удалим его тип _KOORD и имя uniD2:
Struct POINT3D
{
int Color;
union
{
int Koord[2];
struct _D2
{ int x;
int y;
} Record;
};
} Point;
Теперь поля объединения видны во внешнем по отношении к нему блоке и мы можем написать
Point. Record.x=30;
Point. Koord[0]=30;
Анонимные объединения не обязательно объявлять внутри структуры. Если объявить объединение в следующим образом
Void Function (void)
{
union
{ char cM;
long lM;
}
.
.
.
}
то областью видимости полей сM, lM будет вся функция. При объявлении анонимного объединения вне функций, его поля становятся глобальными переменными. В этом случае обязательно использование класса памяти static, чтобы поля не были доступны из других файлов проекта:
Static union
{ char cM;
long lM;
}
void Function (void)
{ cM=’A’;
.
.
.
}
1.3.3. Анонимные структуры в Visual C++
В стандартном С++ предусматриваются анонимные объединения, но нет анонимных структур. Из-за этого в п. 4.3.1. при работе c элементами матрицы D3D вместо естественного обращения
D3D.m00=0;
состоящего из имени матрицы и имени элемента мы вынуждены были указывать имя M структуры, которая объединяет элементы матрицы:
D3D.M.m00=0;
При указании координат точки экрана в 4.3.2
Point. Record.x=30;
Point. Record.y=30;
также указывалось не несущее смысловой нагрузки имя Record.
Чтобы избавиться от этого недостатка фирма Microsoft ввела в свой диалект стандартного C++ анонимные структуры. Учитывая такую возможность, удалим в рассмотренном ранее (п. 4.3.1) объявлении матрицы имя структуры M:
Union
{
float m[4][4];
struct { Нет имени типа.
float m00, m01, m02, m03;
float m10, m11, m12, m13;
float m20, m21, m22, m23;
float m 30, m 31, m 32, m 33;
} Нет имени переменной.
} D 3 D;
Теперь мы можем написать
float Sum = D 3 D. m 00 + D 3 D. m 11 + D 3 D. m 22;
Сделаем анонимной структуру Record в описании точки экрана:
Struct ROOM
{
int Color;
union
{
int Koord [2];
struct //_D3 Если здесь не указать тип, то потом можно не указывать и имени.
{ int x;
int y;
}; //Record– удалили имя структуры.
};
}P3;
В этом случае мы также можем ограничиться указанием имени переменной и названия координаты:
P3.x=22;
P3.y=20;
1.4. Перечисляемый тип (enum)
Перечисляемый тип (enumeration) - это множество поименованных целых констант. Перечисляемый тип определяет все допустимые значения, которые могут иметь переменные этого типа. Основная форма объявления типа следующая:
enum <имя_типа > {<список_имен> }
Список имен может быть пустым. Пример определения перечислимого типа и переменной данного типа:
enum COLOR { black, red, green, blue };
enum COLOR s=red;;
каждое из имен black, red, green, blue представляет собой константу целого типа, значение которой по умолчанию равно позиции имени в списке, т. е. black =0. Поэтому переменные перечисляемого типа и их значения совместимы с целыми типами.
Мы можем сделать объявление
int i=green;
или выполнить оператор
i=s;
Оператор
printf ("%d %d", black, blue);
выдаст на экран числа 0 и 3.
Значения black, red, green, blue – это имена констант, поэтому в одной области видимости нельзя объявить два перечисляемых типа, использующих одинаковые имена элементов (см. текст по Паскалю). В пределах видимости перечисления нельзя также объявлять переменные (структуры, вещественные и пр.) с именами black, red, green, blue.
Во время объявления типа можно одному или нескольким символам присвоить другие значения, например:
enum Number {one=l, two, three, thousand=1000, th1,th2};
Тогда значение следующих элементов перечисления будут нарастать, начиная с этого значения. При печати элементов перечисления
printf ("%d %d %d %d \n", one, two, thousand, th1);
на экране появятся числа 1, 2, 1000, 1001, т. е. каждый следующий символ увеличивается на единицу по сравнению с предыдущим, если не было явного присваивания.
С элементами перечисления можно производить все операции, которые могут производиться с константами целого типа. Основную причину использования перечислимого типа мы уже указывали при изучении Паскаля – это улучшение читаемости программ.
Хотя и здесь приходиться быть осторожнее. Объявим переменную J:
enum COLOR { black, blue, green=2, red=4, White=0xf };
enum COLOR J;
Если аналогичное перечисление объявить Паскале и организовать цикл по элементам перечисляемого типа, мы получим цикл из пяти повторений.
В С++ цикл с заголовком for (J =Black; J< White; J++)... выполнится 16 раз.
Потому я таких циклов в С++ и не вижу – чаще просто именуют варианты при выборе ветвей в операторе switch.
1.5. Организация ввода вывода
1.5.1. Консольные операции в среде MS DOS
Do
{
unsigned char n = getch (); Программа ждет нажатия клавиши
if (n ==0) Получили ноль - значит нажата клавиша с расширенным кодом,
читаем еще раз.
{ n = getch ();
printf (“\ n Код клавиши % x символ % c “, n, n);
}
while (n!=27); Напомним, что 27 – код клавиши Esc.
Рассмотренную ситуацию можно также пояснять и другими словами: считать, что за функциональными клавишами закреплены не восьмиразрядные а шестнадцатиразрядные коды. Просто шестнадцатиразрядный код передается программе побайтно – сначала старший байт, потом младший. Следует отметить, что в настоящее время фирмами IBM, Microsoft, Oracle и другими внедряется новый стандарт кодировки символов Unicode, в котором каждый символ кодируется двумя байтами, что позволяет кодировать 65535 различных символов. Всем нам скоро придется в этой части переучиваться и учитывать, в своих программах, что каждому символу строки выделяется два байта.
Назначение препроцессора
Компиляция исходного текста программы в среде Си проходит в два этапа. Сначала исходный текст просматривается и корректируется специальной процедурой - препроцессором, а уже после этого он обрабатывается собственно компилятором.
Препроцессор обрабатывает в исходном тексте строки специального вида, начинающиеся со знака # – директивы препроцессора. Каждая директива начинается с # и записывается одной строкой. В очень длинных директивах можно вставлять код продолжения строки последовательным нажатием клавиш / и Enter.
В первой лекции одна из директив препроцесора уже рассматривалась – включение файлов в исходный текст программы. Напомним вид таких директив:
# include < stdio. h >
# include “ stdio. h ”
#include “C:\\Work\\include\\stdio.h”
Там же для задания констант использовалась директива макроопределения #define. Объявим, например, строку
# define ABC 2*3.14
Макроопределение указывает, каким текстом необходимо заменить имя ABC. При обработке этой директивы препроцессор просмотрит весь текст программы и везде, где встретит лексему ABC, заменит ABC на 2*3.14 (такая замена называется макроподстановкой). Так, если встретится строка
L = ABC * R;
то компилятор после обработки программы препроцессором получит
L =2*3.14* R;
Замечание. В языке Си не было констант, вместо них использовали строку #define. В С++ константу можно объявить следующим образом:
const float K =2*3.14;.
Оператор
L = K * R; даст тот же результат, что L=ABC*R, но разница есть.
В первом случае компилируется выражение 2*3.14*R, вычислять его будет процессор во время выполнения программы.
Во втором случае значение константы вычислится на этапе компиляции, выражение K*R будет компилироваться как 6.28*R, поэтому во время выполнения программы процессор будет выполнять на одно умножение меньше.
1.2.2. Макроопределения с параметрами
Рассмотренная директива #define является частным случаем макроопределения с параметрами. В нем кроме имени макроопределения (макроса) в скобках, аналогично формальным параметрам функций, могут перечисляться имена одного или нескольких параметров. Например
# define KUB (x) x * x * x
Как видим, формальный параметр далее участвует в теле макроопределения. После этого в тексте программы можно вставлять макрокоманды, в которых формальные параметры заменены фактическими. Например,
float y =5;
float К1= KUB (10)+ KUB (y);
В процессе макроподстановки препроцессор заменит в теле каждой макрокоманды формальный параметр его фактическим значением и компилироваться будет следующая строка
float К1= 10*10*10+ y * y * y;
Замечание. Если в тексте программы применить макрокоманду
K 1= KUB (y +1);
то макроподстановка преобразует ее в строку
K 1= y +1* y +1* y +1;
После замены формального параметра x в макроопределении x*x*x фактическим значением у+1 скобки будут отсутствовать и результатом вычисления не будет (y+1)3. Если необходимо было возводит фактический параметр в куб, правильный макрос должен иметь вид:
#define KUB(x) (x)*(x)*(x)
В подобных ситуациях можно дать общую рекомендацию не жалеть скобок в теле макроопределения. Например, для получения минимального из двух чисел в сдедующем макросе скобки можно не писать
#define MIN(x,y) ((x)<(y)?(y):(x)
Но если формальные параметры x и y могут заменяться не только именами переменных, но и выражениями, каждый такой параметр обязательно заключается в скобки.
Обратите внимание, что при внешнем сходстве с вызовом функций, макрокоманды не передают управление написанной отдельно процедуре. Каждая макрокоманда повторно включает в исходный модуль текст макроса. При этом исключаются затраты времени на запись параметров в стек, выполнение команд вызова (call) и возврата из процедуры (ret).
Кроме того, при макроподстановке отсутствует контроль соответствия типов формальных и фактических параметров.
Объявим указатели
int * Ptr,* Ptr 1;
При работе с динамической памятью мы обращали внимание, что функция освобождения памяти не заносит значения NULL в указатель.
В сложных разветвленных программах один указатель может много раз использоваться в операциях запроса и освобождения памяти.
Поэтому при запросе памяти мы проверяли, свободен ли указатель следующим образом
If (Ptr)
printf (“Непредвиденная ошибка.\ n Указатель занят”);
else Ptr = malloc (1000);
Поскольку операция освобождения
Free (Ptr);
не заносит в указатель значения NULL, многие программисты применяют для освобождения памяти макрос
#define FREE(x) if(x) free(x); x =NULL
Макрокоманда FREE(Ptr); освободит память и запишет NULL в указатель Ptr.
Что будет, если между x и =NULL не поставить пробела. Ничего особенного, препроцессор все равно найдет в этой строке параметр x, заменит его на Ptr и сформирует оператор Ptr=NULL. А вот если макроопределение записать так
#define FREE(x) if(x) free(x); x1 =NULL,
компилятор в строке FREE(Ptr); сообщит о том, что переменная x1 нему неизвестна (а не о том, что неизвестна Ptr1). То есть, x не заменится на Ptr и не сформируется переменная Ptr1.
При макроподстановке препроцессор сравнит x1 и x, увидит, что они не совпадают, не будет считать это вхождение x в строку формальным параметром и не заменит его фактическим параметром Par. Чтобы можно было найти параметр в теле макроопределения, он должен быть отдельной лексемой. Что это такое – лексема? Это имя, знак операции, круглые скобки или ключевое слово языка. Границы лексем определяются пробелами или знаками операций. Поэтому в строке x=NULL препроцессор считает лексемой x, а в строке x1=NULL – лексема это x1.
Если макроопределение должно использоваться только в некоторой части исходного текста модуля, можно применять директивы #undef прекращения действия макроопределения. Например:
#undef MIN
#undef FREE
1.2.3. Операция образования строки #
Использование операции # перед именем формального параметра приводит к тому, что в процессе макроподстановки фактический параметр заключается в кавычки.
Пусть при выводе значений переменных Вам настолько часто приходится выводить имя вещественной переменной и ее значение, что Вы решили оформить этот вывод краткой макрокомандой.
Соответствующее макроопределение имеет вид:
# define Writeln (x) printf (# x “ = % f \ n“, x);
Объявим в программе переменную
float Area=2.5;
Если в текст этой программы вставить макрокоманду
Writeln (Area);
то в результате макроподстановки сформируется оператор
printf (“Area” ” = %f \n“, Area);
При макроподстановке обозначение #x не только превращается в строку “Area”, эта строка объединяется с соседней “ = %f “ в одну строку (для этого между ними надо оставлять не более одного пробела):
printf (“Area = %f \n“, Area);
В результате на экран будет выводиться строка
Area = 2.5
Операция объединения лексем ##
При создании макроопределений с параметрами можно использовать операцию ## объединения лексем. Она позволяет объединить несколько отдельных лексем в единую строку. (А может быть, лучше сказать, что она позволяет в строке текста выделить тот участок, который надо считать отдельной лексемой и использовать в макроподстановке.)
Чтобы показать, как эту операцию используют на практике, мне придется зайти издалека. В заголовочных файлах среды Си, работающей под управлением DOS, есть макрос
#define _Paste2_x(z, y) z##y
который формирует единое имя соединением двух параметров. Если записать операторы
_ Paste2_ x(Par, 1) = 20;
_ Paste2_ x(Par, 2) = 20;
то после обработки препроцессором они будут компилироваться как
Par1 = 20;
Par2 = 20;
Но нельзя сказать, чтобы этот макрос широко использовался.
В С++ для Windows мы постоянно сталкиваемся с тем, как этот прием применяется для конструирования новых типов данных, несовместимых по присваиванию. В этой операционной системе применяются специальные типы данных (дескрипторов), через которые прикладные программы связываются с экземплярами системных объектов (см. раздел программирования на ассемблере для Windows):
для взаимодействия с меню необходим тип HMENU, который можно описать как
typedef struct HMENU__
{
int unused;
}* HMENU;
для рисования линий нужна переменная типа HPEN «перо»:
typedef struct HPEN__
{
int unused;
}* HPEN;
для обмена с окном объявляется переменная типа HWND:
typedef struct HWND__
{
int unused;
}* HWND;
Существует еще множество типов дескрипторов: HMODULE, HDC, HTHREAD и пр.
Отметим, что два знака подчеркивания в имени шаблона структуры – это обычные буквы – они добавлены, чтобы имя шаблона структуры не совпадало с именем указателя на структуру.
Служебное слово typedef применяется здесь для того, чтобы HMENU, HPEN, HWND были именами типов данных, а не именами переменных.
Мы видим, что переменные этих типов – это указатели на разные структуры данных и потому несовместимы по присваиванию. (Все структуры состоят из одного поля с именем unused, но имена шаблонов структур разные.) На самом деле переменные этих типов используются не как указатели (через них не только не обращаются к неиспользуемому полю unused, они никогда не указывают на объявленный базовый тип). Значение этих переменных нужны просто как константы, идентифицирующие системные объекты: окна, перья, меню и пр.
Дескрипторы описаны как указатели, а не как целые числа, потому что это обеспечивает дополнительный синтаксический контроль: все целые совместимы между собой по присваиванию, а указатели на разные базовые типы – нет.
Таким образом, если объявить переменные
HWND h 1;
HMENU h 2;
то при попытке присваивания
h 2= h 1;
компилятор сообщит об ошибке.
Различных типов дескрипторов так много что для их конструирования введен специальный макрос DECLARE_HANDLE. Приведенные выше описания типов на самом деле в явном виде отсутствуют – они формируются препроцессором из макросов:
DECLARE_HANDLE (HMENU);
DECLARE_HANDLE (HPEN);
DECLARE _ HANDLE (HWND).
Сложность написания такого макроса в том, что из одного параметра например, HWND надо сформировать два имени: имя структуры HWND__ и имя указателя HWND.
В файле Winnt.h можно найти определение макрокоманды DECLARE_HANDLE (я записываю его в две строки, но макросы положено писать одной строкой):
#define DECLARE_HANDLE(name)
struct name ##__ {int unused;}; typedef struct name ##__ * name
Как видим параметр name использован при объявлении типа структура и типа указателя на эту структуру.
Если бы в теле макроопределения не использовался знак ## и было написано
#define DECLARE_HANDLE(name) struct name__ {int unused;}; typedef struct name__ *name
то в макрокоманде DECLARE_HANDLE(HWND) препроцессор не определил бы, что первые четыре символа строки name__ являются лексемой, не сравнивал бы ее с формальным параметром name и не заменил бы на фактический параметр HWND.
Если знаки подчеркивания отделить от name пробелом
#define DECLARE_HANDLE(name) struct name __ {int unused;}; typedef struct name __ *name,
препроцессор заменит name на HMENU, но после макроподстановки пробел останется и вместо имени HMENU__ сформируется бессмысленная конструкция HMENU __.
Таким образом, знак ## позволяет выделить часть строки текста name как лексему, которая должна быть заменена фактическим параметром HWND, но результат подстановки оставляет единой строкой HWND__.
Условная трансляция
|
|
Состав сооружений: решетки и песколовки: Решетки – это первое устройство в схеме очистных сооружений. Они представляют...
Двойное оплодотворение у цветковых растений: Оплодотворение - это процесс слияния мужской и женской половых клеток с образованием зиготы...
Семя – орган полового размножения и расселения растений: наружи у семян имеется плотный покров – кожура...
Общие условия выбора системы дренажа: Система дренажа выбирается в зависимости от характера защищаемого...
© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!