Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп
Шрифт:
Интервал:
Закладка:
{ return a<b?b:a; }
Сообщения об ошибке, выдаваемые компилятором, интересны, но не слишком информативны. В случае опасности можете отменить определение макроса.
#undef max
К счастью, этот макрос не привел к большим неприятностям. Тем не менее в широко используемых заголовочных файлах существуют десятки тысяч макросов; вы не можете отменить их все, не вызвав хаоса.
Не все параметры макросов используются как выражения. Рассмотрим следующий пример:
#define ALLOC(T,n) ((T*)malloc(sizeof(T)*n))
Это реальный пример, который может оказаться очень полезным для предотвращения ошибок, возникающих из-за согласованности между желательным типом выделяемой памяти и использованием оператора sizeof.
double* p = malloc(sizeof(int)*10); /* похоже на ошибку */
К сожалению, написать макрос, который позволял бы выявить исчерпание памяти, — нетривиальная задача. Это можно было бы сделать, если бы мы в каком-то месте программы соответствующим образом определили переменную error_var и функцию error().
#define ALLOC(T,n) (error_var = (T*)malloc(sizeof(T)*n),
(error_var==0)
?(error("Отказ выделения памяти"),0)
:error_var)
Строки, завершающиеся символом , не содержат опечаток; это просто способ разбить определение макроса на несколько строк. Когда мы пишем программы на языке C++, то предпочитаем использовать оператор new.
27.8.2. Синтаксис макросов
Можно определить макрос, который приводит текст исходного кода в приятный для вас вид. Рассмотрим пример.
#define forever for(;;)
#define CASE break; case
#define begin {
#define end }
Мы резко протестуем против этого. Многие люди пытались делать такие вещи. Они (и люди, которым пришлось поддерживать такие программы) пришли к следующим выводам.
• Многие люди не разделяют ваших взглядов на то, что считать лучшим синтаксисом.
• Улучшенный синтаксис является нестандартным и неожиданным; остальные люди будут сбиты с толку.
• Использование улучшенного синтаксиса может вызвать непонятные ошибки компиляции.
• Текст программы, который вы видите перед собой, не совпадает с текстом, который видит компилятор, и компилятор сообщает об ошибках, используя свой словарный запас, а не ваш.
Не пишите синтаксические макросы, для того чтобы улучшить внешний вид вашего кода. Вы и ваши лучшие друзья могут считать его превосходным, но опыт показывает, что вы окажетесь в крошечном меньшинстве среди более крупного сообщества программистов, поэтому кому-то придется переписать ваш код (если он сможет просуществовать до этого момента).
27.8.3. Условная компиляция
Представьте себе, что у вас есть два варианта заголовочного файла, например, один — для операционной системы Linux, а другой — для операционной системы Windows. Как выбрать правильный вариант в вашей программе? Вот как выглядит общепринятое решение этой задачи:
#ifdef WINDOWS
#include "my_windows_header.h"
#else
#include "my_linux_header.h"
#endif
Теперь, если кто-нибудь уже определил WINDOWS до того, как компилятор увидел этот код, произойдет следующее:
#include "my_windows_header.h"
В противном случае будет включен другой заголовочный файл.
#include "my_linux_header.h"
Директива #ifdef WINDOWS не интересуется, что собой представляет макрос WINDOWS; она просто проверяет, был ли он определен раньше.
В большинстве крупных систем (включая все версии операционных систем) существуют макросы, поэтому вы можете их проверить. Например, можете проверить, как компилируется ваша программа: как программа на языке C++ или программа на языке C.
#ifdef __cplusplus
// в языке C++
#else
/* в языке C */
#endif
Аналогичная конструкция, которую часто называют стражем включения (include guard), обычно используется для предотвращения повторного включения заголовочного файла.
/* my_windows_header.h: */
#ifndef MY_WINDOWS_HEADER
#define MY_WINDOWS_HEADER
/* информация о заголовочном файле */
#endif
Директива #ifndef проверяет, не было ли нечто определено раньше; например, #ifndef противоположна директиве #ifdef. С логической точки зрения эти макросы, использующиеся для контроля исходного файла, сильно отличаются от макросов, использованных для модификации исходного кода. Просто они используют одинаковый базовый механизм для выполнения своих функций.
27.9. Пример: интрузивные контейнеры
Контейнеры из стандартной библиотеки языка С++, такие как vector и map, являются неинтрузивными; иначе говоря, они не требуют информации о типах данных, использованных как их элементы. Это позволяет обобщить их для практически всех типов (как встроенных, так и пользовательских), поскольку эти типы допускают операцию копирования. Существует и другая разновидность контейнеров — интрузивные контейнеры (intrusive container), популярные в языках C и C++. Для того чтобы проиллюстрировать использование структур, указателей и свободной памяти, будем использовать неинтрузивный список.
Определим двухсвязный список с девятью операциями.
void init(struct List* lst); /* инициализирует lst пустым */
struct List* create(); /* создает новый пустой список
в свободной памяти */
void clear(struct List* lst); /* удаляет все элементы списка lst */
void destroy(struct List* lst); /* удаляет все элементы списка lst,
а затем удаляет сам lst */
void push_back(struct List* lst, struct Link* p); /* добавляет
элемент p в конец списка lst */
void push_front(struct List*, struct Link* p); /* добавляет элемент p
в начало списка lst */
/* вставляет элемент q перед элементом p in lst: */
void insert(struct List* lst, struct Link* p, struct Link* q);
struct Link* erase(struct List* lst, struct Link* p); /* удаляет
элемент p из списка lst */
/* возвращает элемент, находящийся за n до или через n узлов
после узла p:*/
struct Link* advance(struct Link* p, int n);
Мы хотим определить эти операции так, чтобы их пользователям было достаточно использовать только указатели List* и Link*. Это значит, что реализации этих функций можно кардинально изменять, не влияя на работу их пользователей. Очевидно, что выбор имен был сделан под влиянием библиотеки STL. Структуры List и Link можно определить очевидным и тривиальным образом.
struct List {
struct Link* first;
struct Link* last;
};
struct Link { /* узел двухсвязного списка */
struct Link* pre;
struct Link* suc;
};
Приведем графическое представление контейнера List:
В наши намерения на входит демонстрация