ТА_Методички / Lec_8
.pdf
ЛЕКЦІЯ № 8
ДИРЕКТИВИ ПРЕПРОЦЕСОРА
Директива #define
Директиви умовної компіляції
Оператори
Коментарі
Директиви препроцесора
У програми мовою C/C++ можна включати різні інструкції, що регламентують роботу компілятора. Ці інструкції називаються директивами npenpоцессора (preprocessor directives). Хоча вони не є частиною мов С або C++, їх застосування дозволяє розширити можливості програм. Крім цих директив в лекції розглядаються коментарі.
Препроцесор
Перш ніж приступити до опису препроцесора, згадаймо його історію. Препроцесор мови C++ успадкований від мови С. Більш того, він практично збігається з препроцесором мови С. Єдина відмінність між ними полягає в ступені їхньої важливості. У мові С кожна директива препроцесора є необхідною. У мові C++ деякі надлишкові директиви компенсуються елементами самої мови.
Фактично однією з довгострокових цілей мови C++ є повне виключення препроцесора. Проте в даний час директиви препроцесора застосовуються дуже часто, і в осяжному майбутньому це положення не зміниться.
2/29
Лекція № 8
Препроцесор містить наступні директиви.
#define |
#elif |
#else |
#endif |
#error |
#if |
#ifdef |
#ifndef |
#include |
#line |
#pragma |
#undef |
Всі директиви препроцесора починаються зі знака #. Крім того, кожна директива повинна розташовуватися в окремому рядку програми. Наприклад, фрагмент програми, наведений нижче, невірний.
#include <stdio.h> |
#include <stdlib.h> |
Директива #define
Директива #define визначає ідентифікатор і послідовність символів; яка замінює його в тексті програми. Цей ідентифікатор називається іменем макросу, а процес заміни - макропідстановки (macro replacement).
Загальний вигляд цієї директиви такий.
#define імя_макросу послідовністъ_символів
3/29
Директиви препроцесора
Зверніть увагу на те, що ця директива не містить крапки з комою. Між ідентифікатором і послідовністю символів може розташовуватися скільки завгодно пробілів, але сама послідовність повинна закінчуватися символом переходу на новий рядок.
Наприклад, якщо ви хочете використовувати замість одиниці слово LEFT, а замість нуля - слово RIGHT, необхідно оголосити дві директиви #define.
#define LEFT 1 #define RIGHT 0
Ці директиви змусять компілятор замінювати слова LEFT і RIGHT у вихідному файлі значеннями 1 і 0 відповідно. Наприклад, наступний оператор виводить на екран числа 0 1 2.
printf("%d %d %d", RIGHT, LEFT, LEFT +1);
Якщо в програмі визначено ім'я макросу, його можна використовувати для визначення іншого макросу. Наприклад, у наведеному нижче фрагменті програми визначаються значення ідентифікаторів ONE, TWO і THREE.
#define ONE 1 #define TWO ONE+ONE
#define THREE ONE+TWO
4/29
Лекція № 8
Макропідстановки означає просту заміну ідентифікатора послідовністю символів, пов'язаної з ним. Отже, при необхідності за допомогою директиви #define можна визначити стандартне повідомлення про помилку.
#define E_MS "стандартна помилка при вводі \ n" / * ... * /
printf(E_MS);
Кожен раз, коли в початковому тексті програми зустрінеться ідентифікатор E_MS, компілятор підставить замість нього рядок "стандартна помилка при вводі \ n". З точки зору компілятора, наведений вище виклик функції printf () виглядає наступним чином.
printf("стандартна помилка при вводі \n");
Якщо ідентифікатор є частиною рядка в подвійних лапках, макропідстановка не проводиться. Наприклад, фрагмент програми
#define XYZ це перевірка printf("XYZ");
виведе на екран не рядок "це перевірка", а символи XYZ.
5/29
Директиви препроцесора
Якщо послідовність символів займає кілька рядків, в кінці кожного з них слід поставити зворотну косу риску.
#define LONG_STRING "у цьому прикладі \
використовується дуже довгий рядок "
Для назви ідентифікаторів програмісти зазвичай використовують великі літери. Це дозволяє легко виявляти макропідстановки в тексті програм. Крім гого, краще поміщати всі директиви #define на самому початку вихідного файлу або в окремому заголовному файлі, а не розкидати їх по всій програмі.
Макроси часто застосовуються для визначення імен констант, що зустрічаються в програмі. Припустимо, у програмі визначено масив, який використовується в декількох модулях. Вкрай небажано "зашивати" його розмір у текст програми. Замість цього слід застосувати макропідстановку, використовуючи директиву #define.
Тоді для того, щоб змінити розмір масиву, достатньо буде модифікувати один рядок програми, яка містить директиву #define, а не вишукувати всі посилання на розмір масиву в тексті. Після цього програму необхідно перекомпілювати. Розглянемо приклад.
6/29
Лекція № 8
#define MAX_SIZE 100 /* */
float balance [MAX_SIZE]; /* */
for(i = 0; i<MAX_SIZE; i++) printf("%f", balance [i]); /* */
for(i = 0; i<MAX_SIZE; i++) x =+balance[i];
Оскільки розмір масиву balance визначається ідентифікатором MAX_SIZE, то для того, щоб визначити новий розмір масиву, досить просто модифікувати визначення макросу. При повторній компіляції програми всі наступні посилання на цей ідентифікатор будуть оновлені автоматично.
У мові C + + є простіший спосіб визначення констант, заснований на застосуванні ключового слова const.
7/29
Директиви препроцесора
Визначення функцій у вигляді макросів
Директива #define володіє ще однією потужною властивістю: макрос може мати формальні аргументи. Кожен раз, коли в тексті програми зустрічається ім'я макросу, його формальні аргументи замінюються фактичними. Цей вид макросу називається функціональним (function-like macro). Розглянемо приклад.
#include <stdio.h>
#define ABS(a) (a) <0? -(a): (a) int main (void)
{
printf("abs of -1 and 1:%d %d", ABS (-l), ABS (l)); return 0; }
При компіляції програми визначення макросу а буде замінено значеннями: -1 і 1. Дужки, в які вміщено макрос а, гарантують правильну підстановку. Подивимося, що станеться, якщо прибрати дужки навколо макросу а. У цьому випадку вираз
ABS (10-20)
після макропідстановки буде перетворено в наступний оператор.
10-20 <0? -10-20: 10-20
Очевидно, що результат цього виразу буде помилковим.
8/29
Лекція № 8
Використання функціональних макросів замість справжніх функцій збільшує швидкість виконання програми, оскільки в ній відсутні виклики функцій. Однак, якщо розмір функціонального макросу досить великий, швидкодія програми досягається на шкоду її розмірам, оскільки багато фрагментів програми просто дублюються.
Незважаючи на те що параметризовані макроси досить корисні, в мові C++ є більш ефективний спосіб створення підставляється коду, заснований на використанні ключового слова inline.
Директива #error
Директива #error змушує компілятор припинити компіляцію. Вона використовується в основному при налагодженні програм. Загальний вигляд директиви #error такий.
#error повідомлення про помилку
Параметр повідомлення про помилку являє собою рядок без лапок. При виконанні директиви #error на екран виводиться повідомлення про помилку, яке може супроводжуватися іншою інформацією, визначеною компілятором.
9/29
Директиви препроцесора
Директива #include
Директива #include змушує компілятор зчитати і підставити у вихідний текст програми файл із заданим ім'ям. Це ім'я поміщається в подвійні лапки або кутові дужки. Наприклад, директиви
#include "stdio.h" #include <stdio.h>
заставляють компілятор зчитати і скомпілювати заголовні файли стандартних бібліотечних функції вводу-виводу.
Файли, що включаються, самі можуть містити директиви #include. Такі директиви називаються вкладеними. Глибина вкладення директив залежить від компілятора. Стандарт мови C допускає вісім рівнів вкладення, а стандарт мови
C++ - до 256.
Лапки і кутові дужки, в яких вказуються імена включення файлів, визначають спосіб їх пошуку на жорсткому диску. Якщо ім'я файлу міститься в кутових дужках, він повинен знаходитися в каталозі, зазначеному компілятором. Зазвичай це каталог INCLUDE, призначений для зберігання всіх заголовних файлів. Якщо ім'я файлу укладено в лапки, як правило, його пошук виконується в робочому каталозі. Якщо файл не знайдено, пошук повторюється так, ніби ім'я файлу містилося в кутових дужках.
10/29
