Бьерн Страуструп. Язык программирования С++



бет36/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   32   33   34   35   36   37   38   39   ...   124

4.7 Макросредства


Макросредства языка определяются в $$R.16. В С++ они играют гораздо

меньшую роль, чем в С. Можно даже дать такой совет: используйте

макроопределения только тогда, когда не можете без них обойтись.

Вообще говоря, считается, что практически каждое появление

макроимени является свидетельством некоторых недостатков

языка, программы или программиста. Макросредства создают определенные

трудности для работы служебных системных программ, поскольку

они перерабатывают программный текст еще до трансляции. Поэтому, если

ваша программа использует макросредства,

то сервис, предоставляемый такими программами, как отладчик,

профилировщик, программа перекрестных ссылок, будет для нее

неполным. Если все-таки вы решите использовать

макрокоманды, то вначале тщательно изучите описание препроцессора

С++ в вашем справочном руководстве и не старайтесь быть слишком умным.

Простое макроопределение имеет вид:


#define имя остаток-строки
В тексте программы лексема имя заменяется на остаток-строки. Например,

объект = имя

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

объект = остаток-строки

Макроопределение может иметь параметры. Например:
#define mac(a,b) argument1: a argument2: b
В макровызове mac должны быть заданы две строки, представляющие

параметры. При подстановке они заменят a и b в макроопределении

mac(). Поэтому строка
expanded = mac(foo bar, yuk yuk)
при подстановке преобразуется в
expanded = argument1: foo bar argument2: yuk yuk
Макроимена нельзя перегружать. Рекурсивные макровызовы ставят

перед препроцессором слишком сложную задачу:


// ошибка:

#define print(a,b) cout<<(a)<<(b)

#define print(a,b,c) cout<<(a)<<(b)<<(c)
// слишком сложно:

#define fac(n) (n>1) ?n*fac(n-1) :1


Препроцессор работает со строками и практически ничего не знает о

синтаксисе C++, типах языка и областях видимости. Транслятор

имеет дело только с уже раскрытым макроопределением, поэтому

ошибка в нем может диагностироваться уже после подстановки, а не при

определении макроимени. В результате появляются довольно путанные

сообщения об ошибках.

Допустимы такие макроопределения:
#define Case break;case

#define forever for(;;)


А вот совершенно излишние макроопределения:
#define PI 3.141593

#define BEGIN {

#define END }
Следующие макроопределения могут привести к ошибкам:
#define SQUARE(a) a*a

#define INCR_xx (xx)++

#define DISP = 4
Чтобы убедиться в этом, достаточно попробовать сделать подстановку

в таком примере:


int xx = 0; // глобальный счетчик
void f() {

int xx = 0; // локальная переменная

xx = SQUARE(xx+2); // xx = xx +2*xx+2;

INCR_xx; // увеличивается локальная переменная xx

if (a-DISP==b) { // a-=4==b

// ...


}

}
При ссылке на глобальные имена в макроопределении используйте операцию

разрешения области видимости ($$2.1.1), и всюду, где это возможно,

заключайте имя параметра макроопределения в скобки. Например:


#define MIN(a,b) (((a)<(b))?(a):(b))
Если макроопределение достаточно сложное, и требуется комментарий

к нему, то разумнее написать комментарий вида /* */, поскольку

в реализации С++ может использоваться препроцессор С, который не

распознает комментарии вида //. Например:


#define m2(a) something(a) /* глубокомысленный комментарий */
С помощью макросредств можно создать свой собственный язык,

правда, скорее всего, он будет непонятен другим. Кроме того, препроцессор

С предоставляет довольно слабые макросредства. Если ваша задача

нетривиальна, вы, скорее всего, обнаружите, что решить ее с помощью этих

средств либо невозможно, либо чрезвычайно трудно. В качестве

альтернативы традиционному использованию макросредств в язык введены

конструкции const, inline и шаблоны типов. Например:
const int answer = 42;

template

inline T min(T a, T b) { return (a

4.8 Упражнения


1. (*1) Составьте следующие описания: функция с параметрами типа

указатель на символ и ссылка на целое, невозвращающая значения;

указатель на такую функцию; функция с параметром, имеющим тип

такого указателя; функция, возвращающая такой указатель. Напишите

определение функции, у которой параметр и возвращаемое значение

имеют тип такого указателя. Подсказка: используйте typedef.

2. (*1) Как понимать следующее описание? Где оно может пригодиться?

typedef int (rifii&) (int, int);

3. (*1.5) Напишите программу, подобную той, что выдает "Hello, world".

Она получает имя (name) как параметр командной строки и выдает

"Hello, name". Измените программу так, чтобы она получала

произвольное число имен и всем им выдавала свое приветствие:

"Hello, ...".

4. (1.5) Напишите программу, которая, беря из командной строки

произвольное число имен файлов, все эти файлы переписывает

один за другим в cout. Поскольку в программе происходит

конкатенация файлов, вы можете назвать ее cat от слова

concatenation - конкатенация).

5. (*2) Переведите небольшую программу с языка С на С++. Измените

заголовочные файлы так, чтобы они содержали описание всех

вызываемых функций и описание типов всех параметров. По возможности

все команды #define замените конструкциями enum, const или

inline. Удалите из файлов .c все описания внешних, а определения

функций приведите к виду, соответствующему С++. Вызовы malloc() и

free() замените операциями new и delete. Удалите ненужные операции

приведения.

6. (*2) Напишите функцию sort() ($$4.6.9), использующую более

эффективный алгоритм сортировки.

7. (*2) Посмотрите на определение структуры tnode в $$R.9.3. Напишите

функцию, заносящую новые слова в дерево узлов tnode. Напишите

функцию для вывода узлов дерева tnode. Напишите функцию,

которая производит такой вывод в алфавитном порядке.

Измените структуру tnode так, чтобы в ней содержался

только указатель на слово произвольной длины, которое размещается

с помощью new в свободной памяти. Измените функцию так, чтобы

она работала с новой структурой tnode.

8. (*1) Напишите функцию itoa(), которая использовалась в примере

из $$4.6.8.

9. (*2) Узнайте, какие стандартные заголовочные файлы есть в вашей

системе. Поройтесь в каталогах /usr/include или /usr/include/CC

(или в тех каталогах, где хранятся стандартные заголовочные

файлы вашей системы). Прочитайте любой показавшийся интересным

файл.


10. (*2) Напишите функцию, которая будет переворачивать двумерный

массив. (Первый элемент массива станет последним).

11. (*2) Напишите шифрующую программу, которая читает символы из

cin и пишет их в cout в зашифрованном виде. Можно использовать

следующий простой метод шифрации: для символа s зашифрованное

представление получается в результате операции s^key[i], где

key - массив символов, передаваемый в командной строке. Символы

из массива key используются в циклическом порядке, пока не будет

прочитан весь входной поток. Первоначальный текст получается

повторным применением той же операции с теми же элементами key.

Если массив key не задан (или задана пустая строка), шифрация не

происходит.

12. (*3) Напишите программу, которая помогает дешифрировать текст,

зашифрованный описанным выше способом, когда ключ (т.е. массив

key) неизвестен. Подсказка: см. D Kahn "The Codebreakers",

Macmillan, 1967, New York, стр. 207-213.

13. (*3) Напишите функцию обработки ошибок, первый параметр который

подобен форматирующей строке-параметру printf() и содержит форматы

%s, %c и %d. За ним может следовать произвольное количество

числовых параметров. Функцию printf() не используйте. Если смысл

формата %s и других форматов вам неизвестен, обратитесь к $$10.6.

Используйте .

14. (*1) Какое имя вы выбрали бы для типов указателей на функции,

которые определяются с помощью typedef?

15. (*2) Исследуйте разные программы, чтобы получить представление

о разных используемых на практике стилях именования. Как

используются заглавные буквы? Как используется подчерк? В каких

случаях используются такие имена, как i или x?

16. (*1) Какие ошибки содержатся в следующих макроопределениях?

#define PI = 3.141593;

#define MAX(a,b) a>b?a:b

#define fac(a) (a)*fac((a)-1)

17. (*3) Напишите макропроцессор с простыми возможностями, как у

препроцессора С. Текст читайте из cin, а результат записывайте

в cout. Вначале реализуйте макроопределения без параметров.

Подсказка: в программе калькулятора есть таблица имен и

синтаксический анализатор, которыми можно воспользоваться.

18. (*2) Напишите программу, извлекающую квадратный корень из двух (2)

с помощью стандартной функции sqrt(), но не включайте в программу

. Сделайте это упражнение с помощью функции sqrt()

на Фортране.

19. (*2) Реализуйте функцию print() из $$4.6.7.



Достарыңызбен бөлісу:
1   ...   32   33   34   35   36   37   38   39   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет