
- •1.1 Введение
- •1.2 Парадигмы программирования
- •1.2.1 Процедурное программирование
- •Void f ()
- •1.2.5 Объектно-ориентированное программирование
- •1.5.1 Механизм вызова
- •Virtual void rotate ( int );
- •1.5.3 Множественное наследование
- •1.6 Пределы совершенства
- •2.2 Имена
- •2.3.2 Неявное преобразование типа
- •2.4 Литералы
- •2.4.4 Строки
- •2.6. Экономия памяти
- •2.6.1 Поля
- •3.1.1 Анализатор
- •3.1.2 Функция ввода
- •Int main(int argc, char* argv[])
- •3.2 Сводка операций
- •3.2.3 Инкремент и декремент
- •3.2.5 Преобразование типа
- •3.2.6 Свободная память
- •Void generate(enode* n)
- •3.3.2 Оператор goto
- •4.1 Введение
- •4.3.1 Единственный заголовочный файл
- •Inline name* insert(const char* s) { return look(s,1); }
- •Int main(int argc, char* argv[]) { /* ... */ }
- •4.3.2 Множественные заголовочные файлы
- •Int main(int argc, char* argv[]) { /* ... */ }
- •4.4 Связывание с программами на других языках
- •4.6.3 Передача параметров
- •Inline. Удалите из файлов .C все описания внешних, а определения
- •5.1 Введение и краткий обзор
- •Int month, day, year;
- •Int month, day, year;
- •5.3.1 Альтернативные реализации
- •5.3.2 Законченный пример класса
- •Void task::shedule(int p) { /* ... */ }
- •5.5.3 Свободная память
- •Int no_of_members;
- •Int no_of_members;
- •5.5.5 Массивы объектов класса
- •6.1 Введение и краткий обзор
- •Void f()
- •Void g()
- •6.2.3 Иерархия классов
- •6.2.4 Поля типа
- •6.4.1 Монитор экрана
- •6.5 Множественное наследование
- •7.1 Введение
- •7.3 Пользовательские операции преобразования типа
- •7.3.2 Операции преобразования
- •7.3.3 Неоднозначности
- •7.5 Большие объекты
- •Inv() обращает саму матрицу m, а не возвращает новую, обратную m,
- •7.13 Предостережения
- •8.1 Введение
- •8.4.5 Введение операций с помощью параметров шаблонного класса
- •8.7.1 Задание реализации с помощью параметров шаблона
- •Void some_function()
- •V value;
- •9.1 Обработка ошибок
- •9.1.2 Другие точки зрения на особые ситуации
- •9.3.2 Производные особые ситуации
- •X(const char* X, const char* y)
- •9.4.2 Предостережения
- •9.4.3 Исчерпание ресурса
- •10.1 Введение
- •10.2 Вывод: То, что для прикладной программы представляется выводом,
- •10.2 Вывод
- •10.2.1 Вывод встроенных типов
- •10.4.1.2 Поля вывода
- •10.4.1.4 Вывод целых
- •10.5.1 Закрытие потоков
- •10.5.2 Строковые потоки
- •Int printf(const char* format, ...)
- •X Целый параметр выдается в шестнадцатеричной записи;
- •11.1 Введение
- •11.2 Цели и средства
- •11.3 Процесс развития
- •11.3.1 Цикл развития
- •11.3.2 Цели проектирования
- •11.3.3 Шаги проектирования
- •11.3.3.1 Шаг 1: определение классов
- •11.3.3.2 Шаг 2: определение набора операций
- •11.3.3.3 Шаг 3: указание зависимостей
- •11.3.3.4 Шаг 4: определение интерфейсов
- •11.3.3.5 Перестройка иерархии классов
- •11.3.3.6 Использование моделей
- •11.3.4 Эксперимент и анализ
- •11.3.5 Тестирование
- •11.3.6 Сопровождение
- •11.3.7 Эффективность
- •11.4 Управление проектом
- •11.4.1 Повторное использование
- •11.4.2 Размер
- •11.4.3 Человеческий фактор
- •11.5 Свод правил
- •11.6 Список литературы с комментариями
- •12.1 Проектирование и язык программирования.
- •12.1.1 Игнорирование классов
- •12.1.2 Игнорирование наследования
- •12.1.3 Игнорирование статического контроля типов
- •12.1.4 Гибридный проект
- •12.2.1 Что представляют классы?
- •12.2.2 Иерархии классов
- •Void f(Vehicle* p)
- •12.2.3 Зависимости в рамках иерархии классов.
- •Void f()
- •Void q(cfield* p)
- •12.2.6 Отношения использования
- •12.2.7 Отношения внутри класса
- •Void f(Rational r, Big_int I)
- •12.3 Компоненты
- •12.4 Интерфейсы и реализации
- •12.5 Свод правил
- •13.1 Введение
- •13.2 Конкретные типы
- •13.5.1 Информация о типе
- •13.6 Обширный интерфейс
- •Void put(const t*);
- •13.7 Каркас области приложения
- •13.8 Интерфейсные классы
- •13.10 Управление памятью
3.3.2 Оператор goto
Презираемый оператор goto все-таки есть в С++:
goto идентификатор;
идентификатор: оператор
Вообще говоря, он мало используется в языках высокого уровня, но
может быть очень полезен, если текст на С++ создается не человеком,
а автоматически, т.е. с помощью программы. Например,
операторы goto используются при создании анализатора по заданной
грамматике языка с помощью программных средств.
Кроме того, операторы goto могут пригодиться в тех случаях,
когда на первый план выходит скорость работы программы. Один из
них - когда в реальном времени происходят какие-то вычисления во
внутреннем цикле программы.
Есть немногие ситуации и в обычных программах, когда применение
goto оправдано. Одна из них - выход из вложенного цикла или
переключателя. Дело в том, что оператор break во вложенных циклах
или переключателях позволяет перейти только на один уровень выше.
Приведем пример:
void f()
{
int i;
int j;
for ( i = 0; i < n; i++)
for (j = 0; j<m; j++)
if (nm[i][j] == a) goto found;
// здесь a не найдено
// ...
found:
// nm[i][j] == a
}
Есть еще оператор continue, который позволяет перейти на конец
цикла. Что это значит, объяснено в $$3.1.5.
3.4 Комментарии и расположение текста
Программу гораздо легче читать, и она становится намного понятнее, если
разумно использовать комментарии и систематически выделять текст
программы пробелами. Есть несколько способов расположения текста
программы, но нет причин считать, что один из них - наилучший. Хотя
у каждого свой вкус. То же можно сказать и о комментариях.
Однако можно заполнить программу такими комментариями, что читать
и понимать ее будет только труднее. Транслятор не в силах понять
комментарий, поэтому он не может убедиться в том, что комментарий:
[1] осмысленный,
[2] действительно описывает программу,
[3] не устарел.
Во многих программах попадаются непостижимые, двусмысленные и просто
неверные комментарии. Лучше вообще обходиться без них, чем давать
такие комментарии.
Если некий факт можно прямо выразить в языке, то так и следует
делать, и не надо считать, что достаточно упомянуть его в комментарии.
Последнее замечание относится к комментариям, подобным приведенным
ниже:
// переменную "v" необходимо инициализировать.
// переменная "v" может использоваться только в функции "f()".
// до вызова любой функции из этого файла
// необходимо вызвать функцию "init()".
// в конце своей программы вызовите функцию "cleanup()".
// не используйте функцию "weird()".
// функция "f()" имеет два параметра.
При правильном программировании на С++ такие комментарии обычно
оказываются излишними. Чтобы именно эти комментарии стали ненужными,
можно воспользоваться правилами связывания ($$4.2) и областей
видимости, а также правилами инициализации и уничтожения объектов
класса ($$5.5).
Если некоторое утверждение выражается самой программой, не нужно
повторять его в комментарии. Например:
a = b + c; // a принимает значение b+c
count++; // увеличим счетчик count
Такие комментарии хуже, чем избыточные. Они раздувают объем текста,
затуманивают программу и могут быть даже ложными. В то же время
комментарии именно такого рода используют для примеров в учебниках
по языкам программирования, подобных этой книге. Это одна из
многих причин, по которой учебная программа отличается от настоящей.
Можно рекомендовать такой стиль введения комментариев в
программу:
[1] начинать с комментария каждый файл программы: указать в
общих чертах, что в ней определяется, дать ссылки на
справочные руководства, общие идеи по сопровождению
программы и т.д.;
[2] снабжать комментарием каждое определение класса или шаблона
типа;
[3] комментировать каждую нетривиальную функцию, указав: ее
назначение, используемый алгоритм (если только он неочевиден)
и, возможно, предположения об окружении, в котором работает
функция;
[4] комментировать определение каждой глобальной переменной;
[5] давать некоторое число комментариев в тех местах, где
алгоритм неочевиден или непереносим;
[6] больше практически ничего.
Приведем пример:
// tbl.c: Реализация таблицы имен.
/*
Использован метод Гаусса
см. Ральстон "Начальный курс по ..." стр. 411.
*/
// в swap() предполагается, что стек AT&T начинается с 3B20.
/************************************
Авторские права (c) 1991 AT&T, Inc
Все права сохранены
**************************************/
Правильно подобранные и хорошо составленные комментарии играют в
программе важную роль. Написать хорошие комментарии не менее
трудно, чем саму программу, и это - искусство, в котором стоит
совершенствоваться.
Заметим, что если в функции используются только комментарии
вида //, то любую ее часть можно сделать комментарием с помощью
/* */, и наоборот.
3.5 Упражнения
1. (*1) Следующий цикл for перепишите с помощью оператора while:
for (i=0; i<max_length; i++)
if (input_line[i] == '?') quest_count++;
Запишите цикл, используя в качестве его управляющей переменной
указатель так, чтобы условие имело вид *p=='?'.
2. (*1) Укажите порядок вычисления следующих выражений, задав полную
скобочную структуру:
a = b + c * d << 2 & 8
a & 077 != 3
a == b || a == c && c < 5
c = x != 0
0 <= i < 7
f(1,2) + 3
a = - 1 + + b -- - 5
a = b == c ++
a = b = c = 0
a[4][2] *= * b ? c : * d * 2
a-b, c=d
3. (*2) Укажите 5 различных конструкций на С++, значение которых
неопределено.
4. (*2) Приведите 10 разных примеров непереносимых конструкций
на С++.
5. (*1) Что произойдет при делении на нуль в вашей программе на С++?
Что будет в случае переполнения или потери значимости?
6. (*1) Укажите порядок вычисления следующих выражений, задав их
полную скобочную структуру:
*p++
*--p
++a--
(int*)p->m
*p.m
*a[i]
7. (*2) Напишите такие функции: strlen() - подсчет длины строки,
strcpy() - копирование строк и strcmp() - сравнение строк. Какими
должны быть типы параметров и результатов функций? Сравните их
со стандартными версиями, имеющимися в <string.h> и в вашем
руководстве.
8. (*1) Выясните, как ваш транслятор отреагирует на такие ошибки:
void f(int a, int b)
{
if (a = 3) // ...
if (a&077 == 0) // ...
a := b+1;
}
Посмотрите, какова будет реакция на более простые ошибки.
9. (*2) Напишите функцию cat(), которая получает два параметра-строки
и возвращает строку, являющуюся их конкатенацией. Для
результирующей строки используйте память, отведенную с помощью
new. Напишите функцию rev() для перевертывания строки, переданной
ей в качестве параметра. Это означает, что после вызова rev(p)
последний символ p станет первым и т.д. 10. (*2) Что делает следующая функция?
void send(register* to, register* from, register count)
// Псевдоустройство. Все комментарии сознательно удалены
{
register n=(count+7)/8;
switch (count%8) {
case 0: do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while (--n>0);
}
}
Каков может быть смысл этой функции?
11. (*2) Напишите функцию atoi(), которая имеет параметр - строку цифр
и возвращает соответствующее ей целое. Например, atoi("123")
равно 123. Измените функцию atoi() так, чтобы она могла
переводить в число последовательность цифр не только в десятичной,
но и в восьмеричной и шестнадцатеричной записи, принятой в С++.
Добавьте возможность перевода символьных констант С++. Напишите
функцию itoa() для перевода целого значения в строковое
представление.
12. (*2) Перепишите функцию get_token() ($$3.12) так, чтобы она читала
целую строку в буфер, а затем выдавала лексемы, читая по
символу из буфера.
13. (*2) Введите в программу калькулятора из $$3.1 такие функции, как
sqrt(), log() и sin(). Подсказка: задайте предопределенные
имена и вызывайте функции с помощью массива указателей на них.
Не забывайте проверять параметры, передаваемые этим
функциям.
14. (*3) Введите в калькулятор возможность определять пользовательские
функции. Подсказка: определите функцию как последовательность
операторов, будто бы заданную самим пользователем. Эту
последовательность можно хранить или как строку символов, или
как список лексем. Когда вызывается функция, надо выбирать и
выполнять операции. Если пользовательские функции могут
иметь параметры, то придется придумать форму записи и для них.
15. (*1.5) Переделайте программу калькулятора, используя структуру
symbol вместо статических переменных name_string и number_value:
struct symbol {
token_value tok;
union {
double number_value;
char* name_string;
};
};
16.(*2.5) Напишите программу, которая удаляет все комментарии из
программы на С++. Это значит, надо читать символы из cin и
удалять комментарии двух видов: // и /* */. Получившийся текст
запишите в cout. Не заботьтесь о красивом виде получившегося
текста (это уже другая, более сложная задача). Корректность
программ неважна. Нужно учитывать возможность появления символов
//, /* и */ в комментариях, строках и символьных константах.
17. (*2) Исследуйте различные программы и выясните, какие способы
выделения текста пробелами и какие комментарии используются.
* ГЛАВА 4
Итерация присуща человеку,
а рекурсия - богу.
- Л. Дойч
Все нетривиальные программы состоят из нескольких раздельно транслируемых единиц, по традиции называемых файлами. В этой главе описано, как раздельно транслируемые функции могут вызывать друг друга, каким образом они могут иметь общие данные, и как добиться непротиворечивости типов, используемых в разных файлах программы. Подробно обсуждаются функции, в том числе: передача параметров, перегрузка имени функции, стандартные значения параметров, указатели на функции и, естественно, описания и определения функций. В конце главы обсуждаются макровозможности языка.