препроцесорът е програма за подготовка за процеса на компилация; тази възможност е характерна за езиците за системно програмиране; обикновено в средите за програмиране ние извикваме компилатора, а той препроцесора;
препроцесорът заменя всички коментари с интервали; те не влияят на ефективността на програмата, тъй като компилатора ги пропуска;
в C и C++ ние можем да задаваме специални инструкции към препроцесора; това става с помощта на така наречените директиви на препроцесора; общият вид на една директива е следния:
#ключова_дума вид_на_заместването
ключовата дума се записва с малки букви, а вида на заместването определя какви промени ще се извършват; в края на директивата не се поставя ‘;’; директивите се записват самостоятелно на един ред на произволно място в програмата; възможно е директивите да се записват на няколко реда, но в края на всеки ред (с изключение на последния) трябва да поставим символа ‘\’;
например:
#ключова_дума\
вид_на_заместването
директивите не са част от C и C++ - те задават определени действия, които се извършват върху първичния файл в ASCII код;
макроопределения се извършват с помощта на директивата define; синтаксисът е следния:
#define име_на_макрос низ
името е произволен идентификатор; след като срещне директивата define, препроцесорът замества всяко по-нататъчно срещане на името на макроса с указания низ; този процес наричаме разширяване на макроса; прието е имената на макросите да се записват с главни букви за да се отличават от останалите елементи в програмата; примери:
#define PI 3.14159
#define LIMIT 100
int masiv[LIMIT];
for (int i = 0; i < LIMIT; i++) { masiv[i] = …; }
възможно е низът в директивата define сам по себе си да съдържа макрос; например:
#define EXPR (PI*r*r)
това е възможно, тъй като след като извърши разширяването на целия текст, препроцесорът преглежда наново файла дали не са възможни още разширявания;
разширяването на EXPR в програмата ще се извършва на две стъпки: EXPR (PI*r*r) (3.14159*r*r);
ако името на макроса се среща в символна или низова константа, то при изпълнение на define препроцесорът не разширява това срещане;
едно и също име на макрос може да бъде дефинирано многократно в програмата; новото определение отменя действието на последното въведено определение;
действието на define може да бъде отменено с помощта на директивата undef със следния синтаксис:
#undef име_на_макрос
името на макроса определя макроса, който искаме да отменим; след тази директива този макрос престава да се разширява от препроцесора; пример:
#define GLOB 10
…
#define GLOB 500
…
#undef GLOB
…
между двете директиви define GLOB ще се замества с 10, а след втората директива define GLOB ще се замества с 500 до директивата undef, а след нея макросът GLOB вече не е дефиниран;
възможно е да се дефинира макрос без да се задава низ; например:
#define TEST
такива макроси се наричат празни и те се използват при условна компилация; празните макроси не се разширяват, т.е. ако в програмата препроцесорът срещне TEST той няма да го разшири и компилаторът ще даде синтактична грешка;
при разработване на програми много често възниква необходимостта от обединяването на няколко първични файла в един; това се постига с директивата include със следния синтаксис:
#include “име_на_файл”
#include <име_на_файл>
когато препроцесорът срещне директива include, той открива посочения първичен файл (в ASCII код) и след това на мястото на директивата записва текста на файла; самият файл може също да съдържа директиви на препроцесора – те ще бъдат обработени при следващото разширение;
разликата в синтаксиса на include е в начина по който се търси файла; ако файлът е записан с “”, той се търси първо в текущата директория и след това в include-директорията, т.е. директорията със стандартните файлове за включване; ако файлът е записан с <>, той директно се търси в include-директорията;
34. Макроопределения с аргументи.
макроопределения с аргументи се дефинират по следния начин:
#define име_на_макрос(списък_от_формални_аргументи) низ
между името на макроса и отварящата скоба на списъка от аргументи не трябва да има интервали – ако има интервал препроцесорът ще интепретира директивата като макроопределение без аргументи; имената на формалните аргументи са валидни само за дефиницията и те могат да съвпадат с други идентификатори в програмата без това да води до грешка; когато използваме името на макроса в програмния текст, фактическите аргументи могат да бъдат произволна последователност от символи; когато препроцесорът срещне в програмата макрос с аргументи, той го обработва по следния начин:
-
в низа на директивата define формалните аргументи се заместват с фактическите аргументи;
-
името на макроса, следвано от списъка с фактическите аргументи се замества с променения в 1. низ;
примери:
#define PI 3.14159
#define CIRCLE(r) (PI*(r)*(r))
…
double x, y, z;
…
z = CIRCLE (x);
заместването се извършва на две стъпки:
CIRCLE (x) (PI*(x)*(x)) (3.14159*(x)*(x));
скобите, които заграждат формалния аргумент r са необходими;
например, ако запишем CIRCLE (x+1) и те липсваха, препроцесорът би извършил следното заместване:
CIRCLE (x+1) (PI*x+1*x+1) (3.14159*x+1*x+1), което очевидно не е желания резултат;
скобите, които заграждат целия низ за заместване също са необходими; например, ако запишем 10/CIRCLE (x) и те липсваха, препроцесорът би извършил следното заместване:
10/CIRCLE (x) 10/PI*(x)*(x) 10/3.14159*(x)*(x), което очевидно не е желания резултат;
една функция може да се реализира като макрос; например:
#define sqr(x) ((x)*(x))
възможно е да възникнат странични ефекти във връзка с реализирането на функция като макрос;
например, ако запишем sqr (y++) ние очакваме функцията да върне
y*y и след това y да се увеличи с 1; препроцесорът, обаче, извършва следното заместване:
sqr (y++) ((y++)*(y++)) – тук функцията отново връща y*y, но y се увеличава с 2;
макросът с аргументи е алтернатива на използването на функции; когато се прави избор каква реализация да се използва, трябва да се имат предвид следните предимства и недостатъци:
-
макросът се изпълнява по-бързо от функцията, тъй като при него няма предаване на аргументи по време на изпълнение на програмата;
-
всяко обръщение към макроса се замества с програмен текст, докато описанието на функцията като програмен текст е единствено за всички обръщения;
-
макросите могат да породят странични ефекти;
-
при обръщение към макрос липсва какъвто и да е контрол върху съответствието на типовете на формалните и фактическите аргументи; това се прави при функциите с помощта на прототипа;
-
макросът води до разширение в първичния файл преди компилацията, докато функцията е постоянна програмна единица обработвана от компилатора – това води до трудно откриване на грешки при работа с големи макроси;
35. Условна компилация.
препроцесорът на C и C++ има набор от директиви с помощта на които може да се определи алтернативно дали даден фрагмент от програмния текст да се компилира или не; тези директиви се наричат директиви за условна компилация; те реализират механизъм за алтернативен избор от вида if…else…endif, подобен на този при условните оператори;
частта if има вида:
#ifdef име_на_макрос
текст на програма на C или C++
#ifndef име_на_макрос
текст на програма на C или C++
#if константен_израз
текст на програма на C или C++
името на макроса в директивите ifdef, ifndef и константния израз в директивата if са условието, което определя дали ще се компилира текста след if или текста след else;
частта else има вида:
#else
текст на програма на C или C++
частта endif има вида:
#endif
с помощта на частта endif се определя края на конструкцията if…else…;
ако преди частта if в програмата е дефиниран макрос с име, съвпадащо с името в директивата ifdef, условието е изпълнено; ако няма такъв макрос, условието не е изпълнено;
ако преди частта if в програмата е дефиниран макрос с име, съвпадащо с името в директивата ifndef, условието не е изпълнено; ако няма такъв макрос, условието е изпълнено;
тъй като в тези случаи е важен само факта дали макросът е дефиниран или не за целите на условната компилация могат да се използват празни макроси;
за директивата if условието е константен израз, който се пресмята по общоприетите правила; ако стойността му е различна от 0, то условието е изпълнено, в противен случай условието не е изпълнено;
препроцесорът проверява дали условието в директивите ifdef, ifndef или if е изпълнено; ако то е изпълнено, програмният текст в частта if се включва в програмата, а този в частта else се пропуска;
ако условието не е изпълнено, програмният текст в частта if се пропуска, а този в частта else се включва в програмата;
да разгледаме един пример; един от възможните начини за тестване на програмата е да се извеждат междинни резултати за контрол; след като завърши тестването, извеждането на междинни резултати вече не е нужно, така че самото извеждане може да се включи в директиви за условна компилация по следния начин:
#ifdef DEBUG
#define PRINT(x) x
#else
#define PRINT(x)
#endif
на етапа тестване на програмата, в началото се дефинира макросът DEBUG чрез директивата #define DEBUG; това води до включване в програмата на всички междинни резултати, реализирани чрез макроса PRINT;
например: PRINT (cout << x << y << z;)
след като се изчистят грешките в програмата, директивата
#define DEBUG се изтрива или се отменя чрез директивата
#undef DEBUG
при повторно компилиране ще се изпълни директивата #else и всички макроси PRINT ще се заменят с празен низ;
предимството на този подход е, че само с една директива #ifdef се управляват извежданията на всички междинни резултати;
друг начин за тестване чрез използване на условна компилация е следния: при всяко извеждане да използваме ifdef;
#ifdef DEBUG
cout << x << y << z;
#endif
директивите за условна компилация могат да се използват и без else, също така те могат да бъдат вложени;
директивите за условна компилация също могат да се използват, ако в програмата има машинно зависими части; например:
#define PC_16 //шестнадесетбитова реализация
#ifdef PC_16
#define INT 16
#else
#define INT 32
#endif
Край
28.01.2003
Сподели с приятели: |