Б. Страуструп - Язык программирования С++ (1119446), страница 36
Текст из файла (страница 36)
Макросредства создаютопределенные трудности для работы служебных системных программ, поскольку они перерабатываютпрограммный текст еще до трансляции. Поэтому, если ваша программа использует макросредства, тосервис, предоставляемый такими программами, как отладчик, профилировщик, программаперекрестных ссылок, будет для нее неполным. Если все-таки вы решите использовать макрокоманды,то вначале тщательно изучите описание препроцессора С++ в вашем справочном руководстве и нестарайтесь быть слишком умным.Простое макроопределение имеет вид:#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Макроимена нельзя перегружать. Рекурсивные макровызовы ставят перед препроцессором слишком118Бьерн Страуструп.Язык программирования С++сложную задачу:// ошибка:#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);INCR_xx;if (a-DISP==b) {// ...}}////////локальная переменнаяxx = xx +2*xx+2;увеличивается локальная переменная xxa-=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<class T>inline T min(T a, T b) { return (a<b)?a:b; }119Бьерн Страуструп.Язык программирования С++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. Используйте <stdarg.h>.14. (*1) Какое имя вы выбрали бы для типов указателей на функции, которые определяются с помощьюtypedef?120Бьерн Страуструп.Язык программирования С++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(), но не включайте в программу <math.h>. Сделайте это упражнение с помощьюфункции sqrt() на Фортране.19.
(*2) Реализуйте функцию print() из $$4.6.7.121Бьерн Страуструп.Язык программирования С++5. КЛАССЫ"Эти типы не абстрактные, они столь же реальны, как int и float"- Даг МакилройВ этой главе описываются возможности определения новых типов, для которых доступ к даннымограничен заданным множеством функций, осуществляющих его. Объясняется, как можно использоватьчлены структуры данных, как ее защищать, инициализировать и, наконец, уничтожать.
В примерахприведены простые классы для управления таблицей имен, работы со стеком, множеством и реализациидискриминирующего (т.е. надежного) объединения. Следующие три главы завершают описаниевозможностей С++ для построения новых типов, и в них содержится больше интересных примеров.5.1 Введение и краткий обзорПонятие класса, которому посвящена эта и три следующих главы, служит в С++ для того, чтобы датьпрограммисту инструмент построения новых типов.