Язык программирования C++ от Страуструпа

       

В этой главе объясняется смысл


Язык программирования С++ задумывался как язык, который будет:
-
лучше языка С;
-          поддерживать абстракцию данных;
-          поддерживать объектно-ориентированное программирование.
В этой главе объясняется смысл этих фраз без подробного описания конструкций языка.
$$1.2 содержит неформальное описание различий "процедурного", "модульного" и "объектно-ориентированного" программирования.  Приведены конструкции языка, которые существенны для каждого из перечисленных стилей программирования. Свойственный С стиль программирования обсуждается в разделах "процедурное программирование и "модульное программирование". Язык С++ - "лучший вариант С". Он лучше поддерживает такой стиль программирования, чем сам С, причем это делается без потери какой-либо общности или эффективности по сравнению с С.  В то же время язык C является подмножеством С++. Абстракция данных и объектно-ориентированное программирование рассматриваются как "поддержка абстракции данных" и "поддержка объектно- ориентированного программирования". Первая базируется на возможности определять новые типы и работать с ними, а вторая – на возможности задавать иерархию типов.
$$1.3 содержит описание основных конструкций для процедурного и модульного программирования. В частности, определяются функции, указатели, циклы, ввод-вывод и понятие программы как совокупности раздельно транслируемых модулей. Подробно эти возможности описаны в главах 2, 3 и 4.
$$1.4 содержит описание средств, предназначенных для эффективной реализации абстракции данных. В частности, определяются классы, простейший механизм контроля доступа, конструкторы и деструкторы, перегрузка операций, преобразования пользовательских типов, обработка особых ситуаций и шаблоны типов. Подробно эти возможности описаны в главах 5, 7, 8 и 9.
$$1.5 содержит описание средств поддержки объектно-ориентированного программирования. В частности, определяются производные классы и виртуальные функции, обсуждаются некоторые вопросы реализации.  Все это подробно изложено в главе 6.
$$1.6 содержит описание определенных ограничений на пути совершенствования как языков программирования общего назначения вообще, так и С++ в частности. Эти ограничения связаны с эффективностью, с противоречащими друг другу требованиями разных областей приложения, проблемами обучения и необходимостью трансляции и выполнения программ в старых системах.
Если какой-то раздел окажется для вас непонятным, настоятельно  советуем прочитать соответствующие главы, а затем, ознакомившись с подробным описанием основных конструкций языка, вернуться к этой главе. Она нужна для того, чтобы можно было составить общее представление о языке. В ней недостаточно сведений, чтобы немедленно начать программировать.




Роль файла в языке С++ сводится к тому, что он определяет файловую область видимости ($$R.3.2). Это область видимости глобальных функций (как статических, так и подстановок), а также глобальных переменных (как статических, так и со спецификацией const). Кроме того, файл является традиционной единицей хранения в системе, а также единицей трансляции. Обычно системы хранят, транслируют и представляют пользователю программу на С++ как множество файлов, хотя существуют системы, устроенные иначе. В этой главе будет обсуждаться в основном традиционное использование файлов.
Всю программу поместить в один файл, как правило, невозможно, поскольку программы стандартных функций и программы операционной системы нельзя включить в текстовом виде в программу пользователя. Вообще, помещать всю программу пользователя в один файл обычно неудобно и непрактично. Разбиения программы на файлы может облегчить понимание общей структуры программы и дает транслятору возможность поддерживать эту структуру. Если единицей трансляции является файл, то даже при небольшом изменении в нем следует его перетранслировать. Даже для программ не слишком большого размера время на перетрансляцию можно значительно сократить, если ее разбить на файлы подходящего размера.
Вернемся к примеру с калькулятором. Решение было дано в виде одного файла. Когда вы попытаетесь его транслировать, неизбежно возникнут некоторые проблемы с порядком описаний. По крайней мере одно "ненастоящее" описание придется добавить к тексту, чтобы транслятор мог разобраться в использующих друг друга функциях expr(), term() и prim(). По тексту программы видно, что она состоит из четырех частей: лексический анализатор (сканер), собственно анализатор, таблица имен и драйвер. Однако, этот факт никак не отражен в самой программе. На самом деле калькулятор не был запрограммирован именно так. Так не следует писать программу. Даже если не учитывать все рекомендации по программированию, сопровождению и оптимизации для такой "зряшной" программы, все равно ее следует создавать из нескольких файлов хотя бы для удобства.
Чтобы раздельная трансляция стала возможной, программист должен предусмотреть описания, из которых транслятор получит достаточно сведений о типах для трансляции файла, составляющего только часть программы. Требование непротиворечивости использования всех имен и типов для программы, состоящей из нескольких раздельно транслируемых частей, так же справедливо, как и для программы, состоящей из одного файла. Это возможно только в том случае, когда описания, находящиеся в разных единицах трансляции, будут согласованы. В вашей системе программирования имеются средства, которые способны установить, выполняется ли это. В частности, многие противоречия обнаруживает редактор связей. Редактор связей - это программа, которая связывает по именам раздельно транслируемые части программы. Иногда его по ошибке называют загрузчиком.


Обычно в программах используются объекты, являющиеся конкретным представлением абстрактных понятий. Например, в С++ тип данных int вместе с операциями +, -, *, / и т.д. реализует (хотя и ограниченно) математическое понятие целого. Обычно с понятием связывается набор действий, которые реализуются в языке в виде основных операций над объектами, задаваемых в сжатом, удобном и привычном виде. К сожалению, в языках программирования непосредственно представляется только малое число понятий. Так, понятия комплексных чисел, алгебры матриц, логических сигналов и строк в С++ не имеют непосредственного выражения. Возможность задать представление сложных объектов вместе с набором операций, выполняемых над такими объектами, реализуют в С++ классы. Позволяя программисту определять операции над объектами классов, мы получаем более удобную и традиционную систему обозначений для работы с этими объектами по сравнению с той, в которой все операции задаются как обычные функции. Приведем пример:
class complex {
  double re, im;
  public:
     complex(double r, double i) { re=r; im=i; }
     friend complex operator+(complex, complex);
     friend complex operator*(complex, complex);
};
Здесь приведена простая реализация понятия комплексного числа, когда оно представлено парой чисел с плавающей точкой двойной точности, с которыми можно оперировать только с помощью операций + и *. Интерпретацию этих операций задает программист в определениях функций с именами operator+ и operator*. Так, если b и c имеют тип complex, то b+c означает (по определению) operator+(b,c). Теперь можно приблизиться к привычной записи комплексных выражений:
void f()
{
  complex a = complex(1,3.1);
  complex b = complex(1.2,2);
  complex c = b;
  a = b+c;
  b = b+c*a;
  c = a*b+complex(1,2);
}
Сохраняются обычные приоритеты операций, поэтому второе выражение выполняется как b=b+(c*a), а не как b=(b+c)*a.


Одним из самых полезных видов классов является контейнерный класс, т.е. такой класс, который хранит объекты каких-то других типов. Списки, массивы, ассоциативные массивы и множества - все это контейнерные классы. С помощью описанных в главах 5 и 7 средств можно определить класс, как контейнер объектов единственного, известного типа. Например, в $$5.3.2 определяется множество целых. Но контейнерные классы обладают тем интересным свойством, что тип содержащихся в них объектов не имеет особого значения для создателя контейнера, но для пользователя конкретного контейнера этот тип является существенным. Следовательно, тип содержащихся объектов должен параметром контейнерного класса, и создатель такого класса будет определять его с помощью типа-параметра. Для каждого конкретного контейнера (т.е. объекта контейнерного класса) пользователь будет указывать каким должен быть тип содержащихся в нем объектов. Примером такого контейнерного класса был шаблон типа Vector из $$1.4.3.
В этой главе исследуется простой шаблон типа stack (стек) и в результате вводится понятие шаблонного класса. Затем рассматриваются более полные и правдоподобные примеры нескольких родственных шаблонов типа для списка. Вводятся шаблонные функции и формулируются правила, что может быть параметром таких функций. В конце приводится шаблон типа для ассоциативного массива.


Широко известна трудность задачи проектирования и реализации стандартных средств ввода-вывода для языков программирования. Традиционно средства ввода-вывода были  рассчитаны  исключительно  на небольшое  число встроенных типов данных. Однако, в нетривиальных программах на С++ есть много пользовательских типов данных, поэтому необходимо предоставить возможность ввода-вывода значений таких типов. Очевидно, что средства ввода-вывода должны быть простыми, удобными, надежными в использовании и, что важнее всего, адекватными. Пока никто не нашел решения, которое удовлетворило бы всех; поэтому необходимо дать возможность пользователю создавать иные средства ввода-вывода, а также расширять стандартные средства ввода-вывода в расчете на определенное применение.

Цель создания С++ была в том, чтобы пользователь мог определить новые типы данных, работа с которыми была бы столь же удобна и эффективна как и со встроенными типами. Таким образом, кажется разумным потребовать, чтобы средства ввода-вывода для С++ программировались с использованием возможностей С++, доступных каждому. Представленные здесь потоковые средства ввода-вывода появились в результате попытки удовлетворить этим требованиям.

Основная задача потоковых средств ввода-вывода - это процесс преобразования объектов определенного типа в последовательность символов и наоборот. Существуют и другие схемы ввода-вывода, но указанная является основной, и если считать символ просто набором битов, игнорируя его естественную связь с алфавитом, то многие схемы двоичного ввода-вывода можно свести к ней. Поэтому программистская суть задачи сводится к описанию связи между объектом определенного типа и бестиповой (что существенно) строкой.

Последующие разделы описывают основные части потоковой библиотеки С++:

10.2     Вывод: То, что для прикладной программы представляется выводом, на самом деле является преобразованием таких  объектов  как  int, char *, complex или Employee_record в последовательность символов. Описываются средства для записи объектов встроенных и пользовательских типов данных.




Создание любой нетривиальной программной системы - сложная и часто выматывающая задача. Даже для отдельного программиста собственно запись операторов программы есть только часть всей работы. Обычно анализ всей задачи, проектирование программы в целом, документация, тестирование, сопровождение и управление всем этим затмевает задачу написания и отладки отдельных частей программы. Конечно, можно все эти виды деятельности обозначить  как  "программирование" и затем вполне обоснованно утверждать: "Я не проектирую, я только программирую". Но как бы не назывались отдельные виды деятельности, бывает иногда важно сосредоточиться на них по отдельности, так же как иногда бывает важно рассмотреть весь процесс в целом. Стремясь поскорее довести систему до поставки, нельзя упускать из вида ни детали, ни картину в целом, хотя довольно часто происходит именно это. Эта глава сосредоточена на тех частях процесса развития программы, которые не связаны с написанием и отладкой отдельных программных фрагментов. Обсуждение здесь менее точное и детальное, чем во всех остальных частях книги, где рассматриваются конкретные черты языка или определенные приемы программирования. Это неизбежно, поскольку нет готовых рецептов создания хороших программ. Детальные рецепты "как" могут существовать только для определенных, хорошо разработанных областей применения, но не для достаточно широких областей приложения. В программировании не существует заменителей разума, опыта и вкуса. Следовательно, в этой главе вы найдете только общие рекомендации, альтернативные подходы и осторожные выводы.

Сложность данной тематики связана с абстрактной природой программ и тем фактом, что приемы, применимые для небольших проектов (скажем, программа в 10000 строк, созданная одним или двумя людьми), не распространяются на средние или большие проекты. По этой причине иногда мы приводим  примеры из менее абстрактных инженерных дисциплин, а не только из программирования. Не преминем напомнить, что "доказательство по аналогии" является мошенничеством, и аналогии служат здесь только в качестве примера. Понятия проектирования, формулируемые с помощью определенных конструкций С++, и поясняемые примерами, мы будем обсуждать в главах 12 и 13. Предложенные в этой главе рекомендации, отражаются как в самом языке С++, так и в решении конкретных программных задач по всей книге.




Разработка библиотеки общего назначения - это гораздо более трудная задача, чем создание обычной программы. Программа - это решение конкретной задачи для конкретной области приложения, тогда как библиотека должна предоставлять возможность решение для множества задач, связанных с многими областями приложения. В обычной программе позволительны сильные допущения об ее окружении, тогда как хорошую библиотеку можно успешно использовать в разнообразных окружениях, создаваемых множеством различных программ. Чем более общей и полезной окажется библиотека, тем в большем числе окружений она будет проверяться, и тем жестче будут требования к ее корректности, гибкости, эффективности, расширяемости, переносимости, непротиворечивости, простоте, полноте, легкости использования и т.д. Все же библиотека не может дать вам все, поэтому нужен определенный компромисс. Библиотеку можно рассматривать как специальный, интересный вариант того, что в предыдущей главе мы называли компонентом. Каждый совет по проектированию и сопровождению компонентов становится предельно важным для библиотек, и, наоборот, многие методы построения библиотек находят применение при проектировании различных компонентов.

Было бы слишком самонадеянно указывать как следует конструировать библиотеки. В прошлом оказались успешными несколько различных методов, а сам предмет остается полем активных дискуссий и экспериментов. Здесь только обсуждаются некоторые важные аспекты этой задачи и предлагаются некоторые приемы, оказавшиеся полезными при создании библиотек. Не следует забывать, что библиотеки  предназначены для совершенно разных областей программирования, поэтому не приходится рассчитывать, что какой-то один метод окажется наиболее приемлемым для всех библиотек. Действительно, нет никаких причин полагать, что методы, оказавшиеся полезными при реализации средств параллельного программирования для ядра многопроцессорной операционной системы,  кажутся наиболее приемлемыми при создании библиотеки, предназначенной для решения научных задач, или библиотеки, представляющей графический интерфейс.



Содержание раздела