Смекни!
smekni.com

Правила правой руки 17 Замечания для программистов на c 17 Глава 1 (стр. 25 из 43)

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

значение кнопок мыши часто меняется в зависимости от ситуации. Эти

изменения осуществляются (частично) посредством смены значений

указателей кнопок. Когда пользователь выбирает пункт меню, например

пункт 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 << "&bsol;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 <<"&bsol;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 Знакомство и краткий обзор

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

последующие главы, состоит в том, чтобы предоставить программисту

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

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

способом использования не должен отличаться от встроенных типов,