Смекни!
smekni.com

Препроцессорные средства в C и С++ (стр. 1 из 7)

3.1 Основные понятия препроцессорной обработки

Препроцессорная обработка (макрообработка) — это преобразование текста путем замены препроцессорных переменных их значениями и выполнения препроцессорных операторов (директив препроцессора).

В общем случае препроцессорные средства включают:

- определение препроцессорных переменных и присвоенных им значений;

- средства управления просмотром преобразуемого текста;

- правила подстановки значений макропеременных.

Определение препроцессорной переменной часто называют макроопределением или макросом, а подстановку ее значения в обрабатываемый текст — макрорасширением.

Макрообработка состоит в последовательном просмотре исходного текста и выделения в нем лексем — сканировании текста. Если выделенная лексема является препроцессорной переменной, она заменяется на свое значение, т.е. строится макрорасширение. Если встречается препроцессорная директива, то она выполняется. Лексемы, не являющиеся препроцессорными переменными или директивами, переносятся в выходной текст без изменения. Результатом такой обработки является текст, не содержащий препроцессорных директив и препроцессорных переменных. Если исходный текст был программой на C или C++, то после макрообработки должен быть получен синтаксически правильный текст на C или C++.

Как правило, строковые литералы (строки в кавычках) рассматриваются препроцессором как отдельные лексемы и переносятся в выходной текст без изменения.

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

# include имя файла

Если включаемый файл находится в одном из оглавлений, указываемых в установках интегрированной среды в пункте options-directory-include, где можно указать несколько путей, разделяя их точкой с запятой, имя файла заключается в уголковые кавычки, например

# include <iostream.h>
в остальных случаях указывается полный путь к включаемому файлу в кавычках:
# include “c:&bsol;myinclud&bsol;includ1.h”

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

Отметим, что директивы препроцессора всегда записывается с новой строки и первым символом директивы должен быть знак #, которому могут предшествовать только пробелы и знаки табуляции. Концом текста директивы служит конец строки. Если директива не помещается в одной строке, в конце строки ставится знак &bsol; и директива продолжается на следующей строке. Количество строк продолжения не ограничивается.

3.2. Препроцессорные переменные

Препроцессорная переменная (макроимя) объявляется директивой

# define идентификатор значение

Например:

# define L_NAME 6# define END_FORMULA ‘;’# define DEBUG

Если объявленное таким способом макроимя встретится в последующем тексте программы, оно будет заменено на соответствующее значение:

char namevar [L_NAME]; // эквивалентно char namevar[6]; if ( c != END_FORMULA ) ...... // if ( c != ‘;’).....

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

Объявленное в define макроимя известно препроцессору от точки его объявления до конца файла или пока не встретится директива

# undef имя

Например, #undef DEBUG

Если в последующем тексте встретится имя DEBUG, оно будет рассматриваться как обычное, а не препроцессорное имя.

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

_ _LINE_ _ - номер строки в исходном файле,_ _FILE_ _ - имя обрабатываемого файла,_ _DATE_ _ - дата начала обработки препроцессором,_ _TIME_ _ - время начала обработки,_ _STDC_ _ - программа должна соответствовать стандарту ANSI._ _cplusplus - компилировать программу в соответствии с синтаксисом Си++,_ _PASCAL_ _ - последующие имена по умолчанию имеют тип “имя языка Pascal”

Предопределенные имена нельзя объявлять в #define или отменять в #undef.

Макросы FILE, DATE и TIME могут использоваться в сообщениях, выдаваемых в начале программы для указания, какая версия программы используется, например,

cout << “&bsol;n Файл “ << __FILE__ << “ От “ << __DATE__ << “&bsol;n”;

Макрос PASCAL применяется при описании функций, предназначенных для использования в программах, написанных на языке Pascal, а также функций, вызываемых операционной системой Windows.

При программировании на Си директивы типа

#define MAX_LEN 80
обычно применяются для задания именованных констант. Введение описателя const в последние версии Си и в Си++ позволяет определять именованные константы так же, как и обычные переменные.

3.3. Макроопределения (макросы)

Рассмотренный выше вариант директивы #define — частный случай. Полный синтаксис этой директивы имеет вид:

# define идентификатор(параметры) список_замены

Параметры задаются их именами, список замены - это текст на C, C++, содержащий имена параметров, например:

# define MAX(a,b) ( (a) > (b) )? (a) : (b)# define PRINT(a) cout<< #a<<“= “<<(a)<<endl;

Если в области действия этих макроопределений встретится текст

x = MAX( y + p, s);

то он будет заменен на

x = ( (y + p) > (s)) ? (y + p) : (s);

оператор PRINT(x) будет заменен на

cout<<“x”<<“= “<<(x)<<endl;

Знак # перед именем параметра означает, что значение аргумента рассматривается как строковый литерал. Если между двумя параметрами в макрорасширении стоят знаки ##, то значения аргументов сцепляются по правилу сцепления строк, например,

# define VAR(a,b) ( a##b )x = d [ VAR(i,j)]; // x = d [ ( ij )];

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

#if defined(__cplusplus)# define _PTRDEF(name) typedef name * P##name;# define _REFDEF(name) typedef name & R##name;# define _STRUCTDEF(name) struct name; &bsol; _PTRDEF(name) &bsol; _REFDEF(name) &bsol;#endif
то мы получим возможность одной строкой программы
_STRUCTDEF(MyStruct)
объявить имя структурного типа MyStruct, указатель на этот тип PMyStruct и тип ссылки на него RMyStruct, т.е. получить в выходном тексте строчки
struct MyStruct;typedef MyStruct *PMyStruct;typedef MyStruct &RMystruct;

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

Директивы препроцессора # if, # else , # endif и # elif позволяют, в зависимости от результатов проверки некоторых условий, включать в программу один из нескольких вариантов текста:

# if препроцессорное_условие

текст 1

# else

текст 2

# endif

дальнейший текст.

Условие — это константное выражение, которое строится из макроимен, констант и знаков операций, включая логические связки && и | | . Допускается также выражение sizeof (имя_типа) и препроцессорная функция defined( макроимя ), возвращающая 1, если это макроимя определено, и 0, если оно не определено. Вместо директивы

# if defined( DEBUG )
можно написать
# ifdef DEBUG
а вместо
# if !defined( DEBUG )
написать
# ifndef DEBUG

Комбинации #if - #else могут быть вложенными, причем последовательность #else - #if заменяется одной директивой #elif с условием:

# if препроцессорное_условие_1

текст 1

# elif препроцессорное_условие_2

текст_2

# else

текст_3

# endif

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

#if !defined(_ _DEFS_H)#define _ _DEFS_H /* Текст объявляемых заголовков */.......................................#endif /* Конец _ _DEFS_H */

4. Объектно-ориентированные средства С++

4.1 Объектные типы данных

Объектные типы данных - это агрегатные типы, полностью определяемые программистом, описание объектного типа должно содержать компоненты-данные, определяющие область возможных значений переменных этого типа, и описание операций, допустимых над переменными этого типа и компонентами-данными, составляющими переменную. Для сохранения совместимости с программами на Си синтаксис описания объектного типа в Си++ выбран подобным описанию структурного типа или типа объединения в Си. В сущности структуры и объединения в Си++ рассматриваются как варианты объектных типов. Имеются три варианта объектных типов: структура (struct), объединение (union) и класс (class), различающиеся возможностями доступа к компонентам типа. В дальнейшем для краткости все варианты объектных типов будем называть классами. Описание объектного типа строится по схеме:

вариант_типа имя_типа : список_базовых_классов { компоненты (члены ) класса }

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

Для каждого компонента класса устанавливается уровень доступа либо явно, указанием уровня доступа одним из ключевых слов public, protected или private с двоеточием, либо неявно, по умолчанию. Указание уровня доступа относится ко всем последующим компонентам класса, пока не встретится указание другого уровня доступа. Уровень доступа public разрешает доступ к компонентам класса из любого места программы, в котором известна переменная этого класса. Уровень доступа private разрешает доступ к компонентам класса только из методов этого класса. Уровень доступа protected имеет смысл только в иерархической системе классов и разрешает доступ к компонентам этого уровня из методов производного класса. По умолчанию для всех компонент класса типа struct принимается уровень доступа public, но можно явно задавать и другие уровни доступа, уровень доступа к компонентам класса типа class по умолчанию private, явно можно определять и другие уровни, для класса типа union уровень доступа public и не может быть изменен.

Например, пусть программист решил в классе TPoint (точка) запретить внешний доступ к координатам точки и разрешить внешний доступ к методам перемещения точки на плоскости. Описание класса TPoint можно построить так:

class TPoint { private: int x,y; public: void movePoint ( int newx, int newy); // вновуюточку void relmove ( int dx, int dy ); // смещениена dx,dy int getx ( void ) ( return x ; }; int gety ( void ) { return y ; };};

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