классами, то есть определить точную взаимосвязь между различными
понятиями, часто труднее, чем сначала спланировать отдельные
классы. Лучше, чтобы не получилось неразберихи, когда каждый класс
(понятие) зависит от всех остальных. Рассмотрим два класса, A и B.
Взаимосвязи вроде "A вызывает функции из B", "A создает объекты B"
и "A имеет члены B" редко вызывают большие сложности, а взаимосвязь
вроде "A использует данные из B" обычно можно исключить (просто не
используйте открытые данные-члены). Неприятными, как правило,
являются взаимосвязи, которые по своей природе имеют вид "A есть B
и ...".
Одним из наиболее мощных интеллектуальных средств, позволяющих
справляться со сложностью, является иерархическое упорядочение, то
есть организация связанных между собой понятий в древовидную
структуру с самым общим понятием в корне. В C++ такие структуры
представляются производными классами. Часто можно организавать
программу как множество деревьев (лес?). То есть, программист
задает набор базовых классов, каждый из которых имеет свое
собственное множество производных классов. Для определения набора
действий для самой общей интерпретации понятия (базового класса)
часто можно использовать виртуальные функции (#7.2.8).
Интерпретацию этих действий можно, в случае необходимости,
- стр 17 -
усовершенствовать для отдельных специальных классов (производных
классов).
Естественно, такая организация имеет свои ограничения. В
частности, множество понятий иногда лучше организуется в виде
ациклического графа, в котором понятие может непосредственно
зависеть от более чем одного другого понятия; например, "A есть B и
C и ...". В C++ нет непосредственной поддержки этого, но подобные
связи можно представить, немного потеряв в элегантности и сделав
малость дополнительной работы (#7.2.5).
Иногда для организации понятий некоторой программы оказывается
непригоден даже ациклический граф; некоторые понятия оказываются
взаимозависимыми по своей природе. Если множество взаимозависимых
классов настолько мало, что его легко себе представить, то
циклические зависимости не должны вызвать сложностей. Для
представления множеств взаимозависимых классов с C++ можно
использовать идею friend классов (#5.4.1).
Если вы можете организовать понятия программы только в виде обще-
го графа (не дерева или ациклического направленного графа), и если
вы не можете локализовать взаимные зависимости, то вы, по всей ви-
димости, попали в затруднительное положение, из которого вас не вы-
ручит ни один язык программирования. Если вы не можете представить
какой-либо просто формулируемой зависимости между основными поняти-
ями, то скорее всего справиться с программой не удастся.
Напомню, что большую часть программирования можно легко и
очевидно выполнять, используя только простые типы, структуры
данных, обычные функции и небольшое число классов из стандартной
библиотеки. Весь аппарат, входящий в определение новых типов, не
следует использовать за исключением тех случаев, когда он
действительно нужен.
Вопрос "Как пишут хорошие программы на C++" очень похож на вопрос
"Как пишут хорошую английскую прозу?" Есть два вида ответов:
"Знайте, что вы хотите сказать" и "Практикуйтесь. Подражайте
хорошему языку." Оба совета оказываются подходящими к C++ в той же
мере, сколь и для английского - и им столь же трудно следовать.
Правила Правой Руки (*)
Здесь приводится набор правил, которых вам хорошо бы
придерживаться изучая C++. Когда вы станете более опытны, вы можете
превратить их в то, что будет подходить для вашего рода
деятельности и вашего стиля программирования. Они умышленно сделаны
очень простыми, поэтому подробности в них опущены. Не воспринимайте
их чересчур буквально. Написание хороших программ требует ума,
вкуса и терпения. Вы не собираетесь как следует понять это с самого
начала; поэкспериментируйте!
[1] Когда вы программируете, вы создаете конкретное
представление идей вашего решения некоторой задачи. Пусть
структура отражает эти идеи настолько явно, насколько это
возможно:
[a] Если вы считате "это" отдельным понятием, сделайте его
классом.
____________________
(*) Некоторые легко запоминаемые эмпирические правила, "Правила-
помошники." (прим. перев.)
- стр 18 -
[b] Если вы считате "это" отдельным объектом, сделайте его
объектом некоторого класса.
[c] Если два класса имеют общим нечто существенное, сделайте
его базовым классом. Почти все классы в вашей программе
будут иметь нечто общее; заведите (почти) универсальный
базовый класс, и разработайте его наиболее тщательно.
[2] Когда вы определяете класс, который не реализует
некоторый математический объект, вроде матрицы или
комплексного числа, или тип низкого уровня, вроде связанного
списка, то:
[a] Не используйте глобальные данные.
[b] Не используйте глобальные функции (не члены).
[c] Не используйте открытые данные-члены.
[d] Не используйте друзей, кроме как чтобы избежать [a], [b]
или [c].
[e] Не обращайтесь к данным-членам или другим объектам
непосредственно.
[f] Не помещайте в класс "поле типа"; используйте виртуальные
функции.
[g] Не используйте inline-функции, кроме как средство
существенной оптимизации.
Замечания для программистов на C
Чем лучше кто-нибудь знает C, тем труднее окажется избежать
писания на C++ в стиле C, теряя, тем самым, некоторые возможные
выгоды C++. Поэтому проглядите, пожалуйста, раздел "Отличия от C" в
справочном руководстве (#с.15). Там указывается на области, в
которых C++ позволяет делать что-то лучше, чем C. Макросы (#define)
в C++ почти никогда не бывают необходимы; чтобы определять
провозглашаемые константы, используйте const (#2.4.6) или enum
(#2.4.7), и inline (#1.12) - чтобы избежать лишних расходов на
вызов функции. Старайтесь описывать все функции и типы всех
параметров - есть очень мало веских причин этого не делать.
Аналогично, практически нет причин описывать локальную переменную
не инициализируя ее, поскольку описание может появляться везде, где
может стоять оператор, - не описывайте переменную, пока она вам не
нужна. Не используйте malloc() - операция new (#3.2.6) делает ту же
работу лучше. Многие объединения не нуждаются в имени - используйте
безымянные объединения (#2.5.2).
Глава 1
Турне по C++
Единственный способ изучать новый язык
программирования - писать на нем программы.
- Брайэн Керниган
Эта глава представляет собой краткий обзор основных черт языка
программирования C++. Сначала приводится программа на C++, затем
показано, как ее откомпилировать и запустить, и как такая программа
может выводить выходные данные и считывать входные. В первой трети
этой главы после введения описаны наиболее обычные черты C++:
основные типы, описания, выражения, операторы, функции и структура
программы. Оставшаяся часть главы посвящена возможностям C++ по
определению новых типов, скрытию данных, операциям, определяемым
пользователем, и иерархии определяемых пользователем типов.
1.1 Введение
Это турне проведет вас через ряд программ и частей программ на
C++. К концу у вас должно сложиться общее представление об основных
особенностях C++, и будет достаточно информации, чтобы писать
простые программы. Для точного и полного объяснения понятий,
затронутых даже в самом маленьком законченном примере,
потребовалось бы несколько страниц определений. Чтобы не превращать
эту главу в описание или в обсуждение общих понятий, примеры
снабжены только самыми короткими определениями используемых
терминов. Термины рассматриваются позже, когда будет больше
примеров, способствующих обсуждению.
1.1.1 Вывод
Прежде всего, давайте напишем программу, выводящую строку
выдачи:
#include
main()
{
cout << "Hello, world\n";
}
Строка #include сообщает компилятору, чтобы он включил
стандартные возможности потока ввода и вывода, находящиеся в файле
stream.h. Без этих описаний выражеине cout << "Hello, world\n" не
имело бы смысла. Операция << ("поместить в"*) пишет свой первый
аргумент во второй (в данном случае, строку "Hello, world\n" в
____________________
* Программирующим на C << известно как операция сдвига влево для
целых. Такое использование << не утеряно; просто в дальнейшем <<
было определено для случая, когда его левый операнд является
потоком вывода. Как это делается, описано в #1.8. (прим. автора)
- стр 20 -
стандартный поток вывода cout). Строка - это последовательность
символов, заключенная в двойные кавычки. В строке символ обратной
косой \, за которым следует другой символ, обозначает один
специальный символ; в данном случае, \n является символом новой
строки. Таким образом выводимые символы состоят из Hello, world и
перевода строки.
Остальная часть программы
main() { ... }
определяет функцию, названную main. Каждая программа должна
содержать функцию с именем main, и работа программы начинается с
выполнения этой функции.
1.1.2 Компиляция
Откуда появились выходной поток cout и код, реализующий операцию
вывода < Для получения выполняемого кода написанная на C++
программа должна быть скомпилирована; по своей сути процесс
компляции такой же, как и для С, и в нем участвует большая часть
входящих в последний программ. Производится чтение и анализ текста
программы, и если не обнаружены ошибки, то генерируется код. Затем
программа проверяется на наличие имен и операций, которые
использовались, но не были определены (в нашем случае это cout и
<<). Если это возможно, то программа делается полной посредством
дополнения недостающих определений из библиотеки (есть стандартные
библиотеки, и пользователи могут создавать свои собственные). В
нашем случае cout и << были описаны в stream.h, то есть, были
указаны их типы, но не было дано никаких подробностей относительно
их реализации. В стандартной библиотеке содержится спецификация
пространства и инициализирующий код для cout и <<. На самом деле, в
этой библиотеке содержится и много других вещей, часть из которых
описана в stream.h, однако к скомпилированной версии добавляется
только подмножество библиотеки, необходимое для того, чтобы сделать
нашу программу полной.
Команда компиляции в C++ обычно называется CC. Она используется
так же, как команда cc для программ на C; подробности вы можете
найти в вашем руководстве. Предположим, что программа с "Hello,
world" хранится в файле с именем
- стр 21 -
hello.c, тогда вы можете ее скомпилировать и запустить примерно так
($ - системное приглашение):
$ CC hello.c
$ a.out
Hello,world
$
a.out - это принимаемое по умолчанию имя исполняемого результата
компиляции. Если вы хотите назвать свою программу, вы можете
сделать это с помощью опции -o:
$ CC hello.c -o hello
$ hello
Hello,world
$
1.1.3 Ввод
Следующая (довольно многословная) программа предлагает вам ввести
число дюймов. После того, как вы это сделаете, она напечатает