задающая текст, который высвечивается. При использовании системы
значение кнопок мыши часто меняется в зависимости от ситуации. Эти
изменения осуществляются (частично) посредством смены значений
указателей кнопок. Когда пользователь выбирает пункт меню, например
пункт 3 для кнопки 2, выполняется связанное с ним действие:
(button2[3])();
Один из способов оценить огромную мощь указателей на функции - это
попробовать написать такую систему не используя их. Меню можно
менять в ходе использования программы, внося новые функции в
таблицу действий. Во время выполнения можно также легко
сконструировать новое меню.
Указатели на функции можно использовать для задания полиморфных
подпрограмм, то есть подпрограмм, которые могут применяться к
объектам многих различных типов:
- стр 134 -
typedef int (*CFT)(char*,char*);
int sort(char* base, unsigned n, int sz, CFT cmp)
/*
Сортирует "n" элементов вектора "base"
в возрастающем порядке
с помощью функции сравнения, указываемой "cmp".
Размер элементов "sz".
Очень неэффективный алгоритм: пузырьковая сортировка
*/
{
for (int i=0; iname, Puser(q)->name);
}
int cmp2(char*p, char* q) // Сравнивает числа dept
{
return Puser(p)->dept-Puser(q)->dept;
}
Эта программа сортирует и печатает:
main ()
{
sort((char*)heads,6,sizeof(user),cmp1);
print_id(heads,6); // в алфавитном порядке
cout << "\n";
sort((char*)heads,6,sizeof(user),cmp2);
print_id(heads,6); // по порядку подразделений
}
Можно взять адрес inline-функции, как, впрочем, и адрес
перегруженной функции(#с.8.9).
- стр 136 -
4.7 Макросы
Макросы* определяются в #с.11. В C они очень важны, но в C++
применяются гораздо меньше. Первое правило относительно них такое:
не используйте их, если вы не обязаны это делать. Как было
замечено, почти каждый макрос проявляет свой изъян или в языке, или
в программе. Если вы хотите использовать макросы, прочитайте,
пожалуйста, вначале очень внимательно руководство по вашей
реализации C препроцессора.
Простой макрос определяется так:
#define name rest of line
Когда name встречается как лексема, оно заменяется на rest of line.
Например:
named = name
после расширения даст:
named = rest of line
Можно также определить макрос с параметрами. Например:
#define mac(a,b) argument1: a argument2: b
При использовании mac должно даваться две строки параметра. После
расширения mac() они заменяют a и b. Например:
expanded = mac(foo bar, yuk yuk)
после расширения даст
expanded = argument1: foo bar argument2: yuk yuk
Макросы обрабатывают строки и о синтаксисе C++ знают очень мало,
а о типах C++ или областях видимости - ничего. Компилятор видит
только расширенную форму макроса, поэтому ошибка в макросе
диагностируется когда макрос расширен, а не когда он определен. В
результате этого возникают непонятные сообщения об ошибках.
Вот такими макросы могут быть вполне:
#define Case break;case
#define nl <<"\n"
#define forever for(;;)
#define MIN(a,b) (((a)<(b))?(a):(b))
Вот совершенно ненужные макросы:
#define PI 3.141593
#define BEGIN {
#define END }
А вот примеры опасных макросов:
____________________
* часто называемые также макроопределениями. (прим. перев.)
- стр 137 -
#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)
и заключайте вхождения имени параметра макроса в скобки везде, где
это возможно (см. MIN выше).
Обратите внимание на различие результатов расширения этих двух
макросов:
#define m1(a) something(a) // глубокомысленный комментарий
#define m2(a) something(a) /* глубокомысленный комментарий */
например,
int a = m1(1)+2;
int b = m2(1)+2;
расширяется в
int a = something(1) // глубокомысленный комментарий+2;
int b = something(1) /* глубокомысленный комментарий */+2;
С помошью макросов вы можете разработать свой собственный язык.
Скорее всего, для всех остальных он будет непостижим. Кроме того, C
препроцессор - очень простой макропроцессор. Когда вы попытаетесь
сделать что-либо нетривиальное, вы, вероятно, обнаружите, что
сделать это либо невозможно, либо чрезвычайно трудно (но см.
#7.3.5).
4.8 Упражнения
1. (*1) Напишите следующие описания: функция, получающая параметр
типа указатель на символ и ссылку на целое и не возвращающая
значения; указатель на такую фунцию; функция, получающая такой
указатель в качестве параметра; и функция, возвращающая такой
указатель. Напишите определение функции, которая получает
такой указатель как параметр и возвращает свой параметр как
возвращаемое значение. Подсказка: используйте typedef.
2. (*1) Что это значит? Для чего это может использоваться?
- стр 138 -
typedef int (rifii&) (int, int);
3. (*1.5) Напишите программу вроде "Hello, world", которая
получает имя как параметр командной строки и печатает "Hello,
имя". Модифицируйте эту программу так, чтобы она получала
получала любое количество имен и говорила hello каждому из
них.
4. (*1.5) Напишите программу, которая читает произвольное число
файлов, имена которых задаются как аргументы командной стоки,
и пишет их один за другим в cout. Поскольку эта программа при
выдаче конкатенирует свои параметры, вы можете назвать ее cat
(кошка).
5. (*2) Преобразуйте небольшую C программу в C++. Измените
заголовочные файлы так, чтобы описывать все вызываемые функции
и описывать тип каждого параметра. Замените, где возможно,
директивы #define на enum и const или inline. Уберите из .c
файлов описания extern и преобразуйте определения функций к
синтаксису C++. Замените вызовы malloc() и free() на new и
delete. Уберите необязательные приведения типа.
6. (*2) Реализуйте sort() (#4.6.7) используя эффективный алгоритм
сортировки.
7. (*2) Посмотрите на определение struct tnode в с.#8.5. Напишите
функцию для введения новых слов в дерево узлов tnode.
Напишите функцию для вывода дерева узлов tnode. Напишите
функцию для вывода дерева узлов tnode со словами в алфавитном
порядке. Модифицируйте tnode так, чтобы в нем хранился
(только) указатель на слово произвольной длины, помещенное с
помощью new в свободную память. Модифицируйте функции для
использования нового определения tnode.
8. (*2) Напишите "модуль", реализующий стек. Файл .h должен
описывать функции push(), pop() и любые другие удобные функции
(только). Файл .c определяет функции и данные, необходимые для
хранения стека.
9. (*2) Узнайте, какие у вас есть стандартные заголовочные
файлы. Составьте список файлов, находящихся в /usr/include и
/usr/include/CC (или там, где хранятся стандртные заголовочные
файлы в вашей системе). Прочитайте все, что покажется
интересным.
10. (*2) Напишите функцию для обращения двумерного мосссива.
11. (*2) Напишите шифрующую программу, которая читает из cin и
пишет в cout закодированные символы. Вы можете воспользоваться
следующей простой схемой шифровки: Зашифрованная форма символа
c - это c^key[i], где key (ключ) - строка, которая передается
как параметр командной строки. Программа использует символы из
key циклически, пока не будет считан весь ввод.
Перекодирование зашифрованного текста с той же строкой key
дает исходный текст. Если не передается никакого ключа (или
передается пустая строка), то никакого кодирования не
делается.
12. (*3) Напишите программу, которая поможет расшифровывать
тексты, зашифрованные описанным выше способом, не зная ключа.
Подсказка: David Kahn: The Code-Breakers, Macmillan, 1967, New
York, pp 207-213.
13. (*3) Напишите функцию error, которая получает форматную
строку в стиле printf, которая содержит директивы %s, %c и %d,
и произвольное количество параметров. Не используйте printf().
- стр 139 -
Если вы не знаете значения %s и т.д., посмотрите #8.2.4.
Используйте .
14. (*1) Как вы будете выбирать имя для указателя на тип функции,
определенный с помощью typedef?
15. (*2) Посмотрите какие-нибудь программы, чтобы создать
представление о разнообразии стилей и имен, использующихся на
практике. Как используются буквы в верхнем регистре? Как
используется подчерк? Где используются короткие имена вроде x
и y?
16. (*1) Что неправильно в следующих макроопределениях?
#define PI = 3.141593
#define MAX(a,b) a>b?a:b
#define fac(a) (a)*fac((a)-1)
17. (*3) Напишите макропроцесор, который определяет и расширяет
простые макросы (как C препроцессор). Читайте из cin и пишите
в cout. Сначала не пытайтесть обрабатывать макросы с
параметрами. Подсказка: В настольном калькуляторе (#3.1) есть
таблица имен и лексический анализатор, которые вы можете
модифицировать.
* Глава 5 *
Классы
Эти типы не "абстрактны",
они столь же реальны, как int и float.
- Дуг МакИлрой
В этой главе описываются возможности определения новых типов в
C++, для которых доступ к данным ограничен заданным множеством
функций доступа. Объясняются способы защиты структуры данных, ее
инициализации, доступа к ней и, наконец, ее уничтожения. Примеры
содержат простые классы для работы с таблицей имен, манипуляции
стеком, работу с множеством и реализацию дискриминирующего (то
есть, "надежного") объединения. Две следующие главы дополнят
описание возможностей определения новых типов в C++ и познакомят
читателя еще с некоторыми интересными примерами.
5.1 Знакомство и краткий обзор
Предназначение понятия класса, которому посвящены эта и две
последующие главы, состоит в том, чтобы предоставить программисту
инструмент для создания новых типов, столь же удобных в обращении
сколь и встроенные типы. В идеале тип, определяемый пользователем,
способом использования не должен отличаться от встроенных типов,