Введение в C++ (по материалам Надежды Поликарповой)
Общепринято характеризовать ОО-языки как "чистые" (полностью и исключительно реализующие ОО-концепции) либо как "гибридные" (представляющие смесь объектных и необъектных свойств). Наиболее известным представителем гибридных языков является язык С++, который создавался с целью предоставления С-программистам некоторых ОО-идей и механизмов. Язык С++ по духу отличается от нотации Eiffel, принятой в остальной части книги, и более сложен.
Можно найти много книг и вводных статей о С++, как начинающихся с чистого листа, так и предполагающих знание языка С. Роль этого приложения другая: она ориентирована именно на вас, внимательных читателей этой книги, кто прочел уже сотни страниц и овладел ОО-программированием в его "чистой" форме. Цель в том, чтобы в случае необходимости программирования на С++ применять его конструкции в духе Eiffel. По этой причине многие конструкции C++ будут объясняться - как это сделано для Java и C# - в стиле: "Вот как это можно сделать на Eiffel и как получить такой же эффект на С++".
Язык С, на котором основан С++, сам является важным языком, кратко рассматриваемым в следующем приложении.
Основы языка и стиль
Сегодня идея использования ОО-языка едва ли может удивить кого-либо, но в конце восьмидесятых годов она воспринималась как насмешка, - многим программистам и менеджерам в индустрии и академических кругах ОО-концепции казались привлекательными, но возникали большие сомнения в их применимости. В 1979 году Бьёрн Страструп из Bell
Laboratories спроектировал и реализовал язык, изначально названный "С с классами", расширяющий язык С концепциями, которые были заимствованы у языка Simula 67, - первого ОО-языка. Код транслировался в чистый С препроцессором. Язык вскоре стал хитом и, как было обещано, облегчил переходный период для С-программистов.
В следующие два десятилетия язык интенсивно развивался за счет введения таких конструкций, как шаблоны (форма универсальности) и множественное наследование.
Общая организация программ
Для ОО-подхода программа представляет множество классов. В С++, сохраняющем гибридный дух, не настаивают на соблюдении этого правила. Программа может включать классы, но и другие элементы, не входящие в классы: функции (соответствующие методам, реализуемым либо в виде настоящих функций, либо в виде процедур), переменные, константы и типы, не заданные классами.
Примером независимой функции, появляющейся в каждой исполняемой программе, является функция, называемая main, которая определяет точку входа в программу. В Eiffel эту роль играет корневая процедура создания.
Программная система на С++ представляет скорее не множество классов, а множество единиц трансляции (юнитов или модулей), каждая из которых содержится в отдельном файле, который независимо может быть обработан С++ компилятором. Каждая единица трансляции может содержать объявления классов и других типов, функций, переменных и констант.
Объявление такого элемента может быть его определением, означающим его полное описание, либо может объявлением, не задающим определение, таким как:
class Person; enum Week_day;
При этом понимается, что полное определение этого элемента (здесь класс и тип) появится либо в другом модуле, либо в той же единице, но позже. Это дает возможность использовать элемент без знания его детальных свойств. Для переменной или константы неопределяющее объявление использует ключевое слово extern, указывающее, что определение еще появится:
extern bool has_error; extern const double pi;
Определение функции включает имя, сигнатуру и реализацию:
int factorial (int n) { if (n > 1) { return n _ factorial (n - 1); } else { return 1; } }
Определение класса содержит список членов класса (компонентов):
class Person { // Объекты, представляющие персон string name; // Имя персоны Date birth_date; // Дата рождения void set_name (string s) {name = s;} void set_birth_date (Date d) {birth_date = d;} … // Другие члены класса };
В определении переменной задается ее тип, предшествующий переменной в отличие от Eiffel, где действует соглашение variable_name: TYPE.
int n; bool has_error = false;
Второе из этих объявлений содержит инициализацию переменной, устанавливающую ее начальное значение. Определения не содержат extern: это означает, что переменные не являются внешними, а значит, могут использоваться только в данном модуле трансляции.
Определение переменной означает, что ей будет отведена память, как для развернутых типов Eiffel.
Определение константы всегда включает ее инициализацию:
const double Pi = 3.14159265358
В отличие от Eiffel порядок объявлений существенен: имена, объявленные в модуле трансляции, можно использовать только после точки объявления. Для применения элемента до его определения - такая необходимость возникает, например, в случае множества взаимно рекурсивных определений - предварительно следует задать неопределяющее объявление.
Во избежание трансляции слишком большого модуля можно разделить его на несколько файлов, используя затем директиву включения #include, как в примере:
#include "filename"
Как результат, все определения, содержащиеся в исходном файле filename, будут доступны текущему модулю трансляции. Чаще всего это свойство предполагает использование заголовочных файлов (по соглашению имеющих имена в форме name.h), которые содержат объявления элементов, используемых многими модулями, и включают соответствующую директиву #include.
Директива #include, подобно любым другим предложениям, начинающимся с символа решетки #, адресована препроцессору С++ - инструментарию, обрабатывающему файлы программы до начала компиляции. Другое использование препроцессора связано с условной компиляцией, позволяющей определять препроцессорные переменные и включать в выполнение некоторый код только при условии задания соответствующих переменных. Вот типичный пример:
#ifdef LINUX … Linux-specific code … #endif
Код для платформы Linux будет подключен в зависимости от LINUX-переменной. Такие переменные, не связанные с переменными программы, рассматриваются как опции, действующие на этапе работы препроцессора и компилятора.