С++ для "чайников" .

Дэвис Стефан Рэнди

Часть 6.  ВЕЛИКОЛЕПНАЯ ДЕСЯТКА...329

 

 

                В этой части...

Ни одна книга ...для "чайников" не может обойтись без "Великолепных десяток". В , описано десять способов, благодаря которым вы сможете оградить свою программу от ошибок. Многое из того, что здесь сказано, подходит и для языка С. В , вы познакомитесь с десятком наиболее важных опций компилятора Dev-C++.

 

Глава 29. ДЕСЯТЬ СПОСОБОВ ИЗБЕЖАТЬ ОШИБОК...331

 

        В этой главе...

►Включение всех предупреждений и сообщений об ошибках  331

►Добейтесь чистой компиляции  332

►Используйте последовательный стиль программирования  332

►Ограничивайте видимость  332

►Комментируйте свою программу  334

►Хотя бы один раз выполните программу пошагово  334

►Избегайте перегрузки операторов 334

►Работа с кучей  334

►Используйте для обработки ошибок исключительные ситуации  355

►Избегайте множественного наследования  355

 

►Включение всех предупреждений и сообщений об ошибках...331

Синтаксис С++ позволяет проверять всё и вся. Когда компилятор встречается с конструкцией, которую он не может понять, у него не остаётся никакого выбора, кроме генерации сообщения об ошибке. И хотя компилятор честно пытается перейти к следующей строке, он даже не сочтёт нужным создать выполняемую программу.

Выключение сообщений об ошибках и предупреждений подобно отключению красных габаритных огней на вашей машине, потому что они вас раздражают. Игнорирование проблемы не заставит её исчезнуть. Если ваш компилятор имеет режим абсолютной проверки кода, включите его. И Visual С++ .NET, и Dev-C++ предоставляют режим "Включить все сообщения" ( Enable All Messages ), который должен постоянно находиться в рабочем состоянии. В конце концов эта многословность сбережёт ваше время.

Рассматривая ваш исходный код, умный компилятор С++ помимо ошибок ищет всякие подозрительные конструкции. Вот пример:

    #include "student.h"

    #include "class.h"

    Student* addNewStudent ( Class class , char *pName ,

                                        SSNumber ss )

    {

        Student pS ;

        if( pName != 0 )

        {

            pS = new Student( pName , ss ) ;

            class.addStudent( pS ) ;

        }

        return pS ;

    } 

Умный компилятор понимает, что если pName равно 0 , то переменная pS никогда не будет проинициализирована; он выводит сообщение об этом и обращает ваше внимание, что было бы неплохо взглянуть на эту проблему ещё раз.

 

►Добейтесь чистой компиляции...332

Не начинайте отладку кода, пока не избавитесь от всех предупреждений ( или хотя бы не поймёте их причину ). Выключение режима вывода всех сообщений об ошибках, чтобы они не надоедали, не приведёт ни к чему хорошему. Если вы не поняли сообщение сразу, посидите и разберитесь с ним. Проблемы появляются именно там, где вы чего-то не понимаете.

 

►Используйте последовательный стиль программирования...332

Программирование с использованием чистого последовательного стиля не только улучшает читаемость программы, но и уменьшает количество ошибок программирования. Помните: чем меньше сил будет уходить на расшифровку кода, тем больше их останется на обдумывание логики создаваемой программы. Хороший стиль программирования позволяет легко справляться с такими задачами:

■■■

■ различать имена классов, имена объектов и имена функций;

■ получать определённую информацию об объекте по его имени;

■ отличать команды препроцессора от команд С++;

■ различать блоки программы С++ по уровню отступов.

■■■

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

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

 

►Ограничивайте видимость...332

Ограничение видимости внутреннего содержимого класса для внешнего мира — один из краеугольных камней объектно-ориентированного программирования. Класс сам отвечает за своё содержимое, а приложение лишь использует класс для решения поставленных задач.

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

Открытые функции-члены должны как можно меньше доверять внешнему коду — любой аргумент, переданный открытой функции-члену, должен рассматриваться как потенциальный источник ошибки, пока не будет достоверно доказано обратное. Функция, приведённая ниже, представляет собой мину, которая только и ждёт своего часа.

_________________

332 стр. . Великолепная десятка

    class Array

    {

    public :

        Array( int s )

        {

            size = 0 ;

            pData = new int[ s ] ;

            if ( pData )

            {

                size = s ;

            }

        }

        ~Array( )

        {

            delete pData ;

            size = 0 ;

            pData = 0 ;

        }

        /* Вернуть или установить данные в массиве */

        int data( int index )

        {

            return pData[ index ] ;

        }

        int data ( int index , int newValue )

        {

            int oldValue = pData[ index ] ;

            pData[ index ] = newValue ;

            return oldValue ;

        }

    protected :

        int size ;

        int *pData ;

    } ;

Функция data( int ) позволяет внешнему приложению читать данные из Array. Эта функция слишком доверчива: она не проверяет, находится ли переменная index в допустимом диапазоне. А что, если она выйдет за предусмотренные границы? Функция data( int , int ) с этой точки зрения ещё хуже, поскольку производит запись в неизвестное место в памяти.

В следующем примере показано, как осуществить такую проверку. Для краткости приведём только пример функции data( int ).

    int data( unsigned int index )

    {

        if ( index >= size )

        {

            throw Exception( "Индекс массива вне "

                            "допустимого диапазона" ) ;

        }

        return pData[ index ] ;

    }

Теперь, если переменная index будет выходить за пределы допустимого диапазона, это будет выявлено на этапе проверки ( беззнаковость переменной index избавляет от необходимости проверки на отрицательность значения индекса ).

_________________

333 стр. . Десять способов избежать ошибок

 

►Комментируйте свою программу...334

Я считаю, что можно избежать излишних ошибок, если комментировать программу в процессе её написания, вместо того чтобы ждать, когда программа заработает, и только потом возвращаться и добавлять комментарии к тексту.

Понятно, что никому не хочется тратить время на написание объёмистых заголовков и пояснений к функциям, но всегда можно найти пару минут, чтобы добавить краткий комментарий к программе. Когда вы вернётесь к коду, написанному недели две назад, короткие и содержательные комментарии значительно облегчат "вживание" в программу.

Кроме того, последовательно используемые отступы и соглашения об именах сделают код гораздо доступнее для чтения и понимания. Конечно, очень приятно, когда код легко читать по завершении написания программы, но просто необходимо, чтобы код хорошо воспринимался и при написании — именно тогда, когда вам действительно нужна помощь.

 

►Хотя бы один раз выполните программу пошагово...334

Существенно облегчит понимание работы программы пошаговое выполнение программы с помощью хорошего отладчика ( обычно для этого вполне подходят отладчики, включенные в среды разработки компиляторов ).

Когда некоторая функция готова и может быть добавлена к программе, её следует тщательно проверить хотя бы один раз, проходя все возможные варианты ветвления потока управления. Гораздо легче отлавливать ошибки в отдельной функции, чем в комплексе с другими функциями программы.

 

►Избегайте перегрузки операторов...334

Настоятельно советую избегать перегрузки операторов, за исключением оператора присвоения operator = ( ), пока вы как следует не освоитесь в С++. Хотя хороший набор перегруженных операторов и может значительно повысить полезность класса и читаемость кода, перегрузка почти никогда не бывает крайне необходимой и может значительно усложнить жизнь начинающего программиста. Тот же эффект вы можете получить при помощи определения и использования соответствующих открытых функций-членов.

После того как вы интенсивно поработаете с языком С++ по меньшей мере несколько месяцев, можете начинать перегружать операторы как вам заблагорассудится.

 

►Работа с кучей...334

Основное правило, касающееся кучи, заключается в том, что выделение и освобождение памяти из кучи должно происходить на одном уровне. Если функция-член MyClass::create( ) выделяет блок памяти и возвращает его вызывавшему коду, то должна существовать и функция MyClass::create( ), которая освобождает блок памяти, возвращая его в кучу. MyClass::create( ) не должна требовать от вызывающей функции самостоятельного освобождения памяти. Это, конечно, не помогает избежать всех проблем ( например, вызывающая функция может просто "забыть" вызвать MyClass::create( ) ), однако всё  же снижает вероятность их возникновения.

_________________

334 стр. . Великолепная десятка

 

►Используйте для обработки ошибок исключительные ситуации...335

Механизм исключений введён в С++ специально для удобства и эффективности обработки ошибок. Вы должны использовать именно его, а не возврат определённых значений в случае ошибки. Такой код легче писать, читать и поддерживать. Конечно, подходить к вопросу надо разумно — так, нет необходимости генерировать исключение в функции, которая возвращает индикатор "не работает" в половине вызовов.

 

►Избегайте множественного наследования...335

Множественное наследование, как и перегрузка операторов, на порядок усложняют программу, что совсем не нужно начинающему программисту. Большинство отношений реального мира могут быть выражены с помощью одиночного наследования ( некоторые утверждают, что множественное наследование вообще не нужно, но я не из таких ).

В любом случае вы можете спокойно использовать классы с множественным наследованием из коммерческих библиотек, например классы Microsoft MFC.

Лишь убедившись в том, что вы твёрдо понимаете концепции С++, можно начинать экспериментировать с множественным наследованием. Вы будете уже достаточно подготовлены к неожиданным ситуациям, которые могут возникнуть при использовании этого механизма.

_________________

335 . Десять способов избежать ошибок

 

  Глава 30.    ДЕСЯТЬ ОСНОВНЫХ ВОЗМОЖНОСТЕЙ DEV-C++...336

 

 

        В этой главе...

►Настройка редактора по вашему вкусу  336

►Подсветка парных скобок  337

►Включение обработки исключений  337

►Включение отладочной информации  338

►Создание файла проекта  338

►Настройка справки  338

►Переустановка точек останова после редактирования файла  339

►Избегайте некорректных имён файлов  339

►Включите заголовочные файлы в ваш проект  339

►Работа с профайлером  339

В этой главе рассматриваются некоторые установки Dev-C++, которые могут облегчить ваш программистский труд. Здесь мы также коснёмся вопроса о профайлере Dev-C++.

 

►Настройка редактора по вашему вкусу...336

Программирование должно доставлять удовольствие. В С++ и так достаточно сложностей, поэтому постарайтесь сделать всё  возможное, чтобы работа с редактором приносила вам как можно больше радости. Для этого в Dev-C++ воспользуйтесь командой меню Сервис => Параметры редактора ( ToolsoEditor => Options ), которая позволяет изменить настройки редактора.

Начнём с наиболее незначительных установок. Например, можно установить табуляцию, которая будет соответствовать четырём пробелам, и ширину строки, равную 60 символам ( эти параметры вы, безусловно, можете изменить на свой вкус ).

Флаг Подсветка синтаксиса ( Syntax Highlighting ) заставляет редактор выделять различным цветом слова в вашей программе — в зависимости от того, что они собой представляют: комментарий, ключевое слово, идентификатор и т.д. Многоцветность текста может сначала казаться раздражающей, но в конечном итоге она оказывается очень полезной. Вы можете поменять используемые цвета по своему усмотрению.

Опция Автоотступ ( Auto Indent ) оградит вас от вбивания необходимого количества пробелов или табуляций в начале строки для выравнивания текста и получения красивого отступа — редактор сделает это за вас, как только вы нажмёте клавишу . К сожалению, редактор не понимает, что при закрывающей скобке, например, надо уменьшить уровень отступа. Однако к вашим услугам есть опция Backspace — обратный отступ ( Backspace Unindents ).

_________________

336 стр. . Великолепная десятка

Обычно я отменяю опцию Использовать табуляцию ( Use Tab Character ), что заставляет редактор для позиционирования курсора использовать пробелы и только пробелы. В основном я делал это потому, что постоянно переносил текст из редактора Dev-C++ в текстовый редактор, в котором готовил данную книгу.

 

►Подсветка парных скобок...337

Опция Подсвечивать парные скобки ( highlight matching braces/parenthesis ) также находится в рассмотренном в предыдущем разделе окне параметров редактора. Если эта опция установлена, редактор Dev-C++ находит при вводе закрывающей скобки соответствующую открывающую. Кроме того, когда выбираете открывающую или закрывающую скобки, Dev-C++ изменяет соответствующую скобку, выделяя её шрифтом.

Эта возможность редактора оградит вас от ошибок при вводе скобок. В процессе ввода программы вы можете забыть о закрывающей скобке, так что возможность во время ввода текста убедиться, что всё  в порядке, оказывается далеко не лишней.

В Dev-C++ 4.9.8.0 имеется, однако, один серьёзный недостаток: вы не можете открыть модуль, в котором количество открывающих скобок превышает количество закрывающих. Похоже, что при открытии .срр-файла редактор сканирует его содержимое и "зависает", будучи не в состоянии найти необходимое количество закрывающих скобок.

Таким образом, если при открытии исходного файла Dev-C++ кажется зависшим, попытайтесь сделать следующее.

24. Завершите работу Dev-C++, для чего нажмите клавиши , выберите в появившемся окне опцию Task Manager, а в его окне — Dev-C++. После этого щёлкните на кнопке End Task.

25. Запустите Dev-C++ без открытия файла.

26. Отмените опцию Подсвечивать парные скобки ( highlight matching braces/parenthesis ).

27. Откройте ваш файл.

Последнюю версию программы вы найдёте на Web-узле  — возможно, в более новой версии эта ошибка исправлена.

 

►Включение обработки исключений...337

Обработка исключений рассматривалась в . Воспользуйтесь командой меню Сервис => Параметры компилятора ( Tools => Compiler Options ) и в открывшемся диалоговом окне выберите вкладку Настройки ( Settings ). В левой части окна выберите Генерация кода ( Code Generation ), и убедитесь, что флаг Включить обработку исключений ( Enable exception handling ) установлен равным Yes ( значение по умолчанию — No ).

При использовании обработки исключений ваша программа немного увеличится в размере и станет немного медленнее работать, это небольшая цена за возможность использовать механизм обработки исключений.

_________________

337 стр. . Десять основных возможностей Dev-C++

 

►Включение отладочной информации...338

Флаг Генерировать отладочную информацию ( Generate debugging information ) можно найти на той же вкладке, выбрав в левой части окна Компоновщик ( Linker ). Этот флаг должен быть установлен равным Yes, пока идёт отладка программы ( отладчик не будет работать, если этот флаг будет иметь значение No ). Кроме того, Dev-C++ выдаст только очень небольшую информацию о происшедшем в случае аварийного завершения программы.

Когда флаг Генерировать отладочную информацию ( Generate debugging information ) равен Yes, Dev-C++ включает в выполнимый файл информацию о местоположении каждой метки и каждой строки кода ( именно поэтому отладчик знает, где следует устанавливать точки останова ). Включается даже информация о строках кода библиотечных программ.

Я скомпилировал одну из моих программ с включенным флагом генерации отладочной информации, а затем — с выключенным. В первом случае у меня получился выполнимый файл размером 1.2 Мбайта, а во втором — 440 Кбайт. Отсюда мораль: этот флаг должен быть включен во время разработки программы и выключен при сборке окончательной версии программы.

 

►Создание файла проекта...338

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

 

►Настройка справки...338

Справка Dev-C++ по умолчанию ограничивается помощью по компилятору и не включает ни справку по С++, ни по его библиотекам. К счастью, Dev-C++ позволяет вам настроить меню Справка ( help ), добавив в него файлы в формате Microsoft Help ( .hlp ) или Compiled HTML ( .chm ). ( Примечание: вы должны сами найти соответствующие файлы, например, в Web. Ни Dev-C++, ни не предоставляют дополнительные справочные файлы. )

В качестве примера я загрузил из Web доступный бесплатно файл справки Win32.hlp, в котором описаны вызовы интерфейса прикладного программирования ( API ) операционной системы Windows. Выберите в меню команду Справка => Настроить меню справки ( help => Customize Help Menu ) для вызова диалогового окна Редактора меню справки ( help Menu Editor ).

Щёлкните на кнопке Добавить ( Add ) в верхней части диалогового окна. Dev-C++ выведет окно открытия файла, в котором вы должны выбрать добавляемый вами файл и щёлкнуть на кнопке Open. После этого в окне редактора вы увидите добавленный вами файл справки, содержимое которого отныне будет доступно для вас через команду меню Справка ( help ).

В меню можно добавить любое количество справочных файлов.

_________________

338 стр. . Великолепная десятка

 

►Переустановка точек останова после редактирования файла...339

Dev-C++ устанавливает точки останова, основываясь на номерах строк исходного текста. К сожалению, он не перемещает точки останова автоматически при вставке или удалении строк в исходном файле. Предположим, например, что я установил точку останова в 10 строке моей программы. Если после этого я добавлю комментарий между 9 и 10 строками, точка останова будет указывать на этот комментарий. Понятно, что комментарий не выполняется, так что данная точка останова теряет смысл.

Не забывайте переустанавливать точки останова после редактирования исходных файлов.

 

►Избегайте некорректных имён файлов...339

В плане использования имён файлов Dev-C++ не совсем корректная программа. В случае неверного имени файла компилятор генерирует сообщения об ошибках, которые способны только ввести в заблуждение. Кроме того, Dev-C++ не в состоянии работать с именами файлов и папок, которые содержат пробелы в имени.

Dev-C++ может работать с файлами в сети, в отличие от консольного окна, так что вы сможете скомпилировать программу \\Randy\MyFolder\MyProgram.срр, но не сможете отладить полученный выполнимый файл.

 

►Включите заголовочные файлы в ваш проект...339

С++ позволяет вам собрать инструкции, расположенные в разных файлах, в одну программу при помощи директивы препроцессора #include. Никаких ограничений на то, что может содержаться в заголовочном файле, нет. Однако следует придерживаться определённых правил и размещать в заголовочных файлах только следующее.

■■■

■ Прототипы функций.

■ Определения классов.

■ Определения шаблонов всех видов.

■ Определения всех глобальных переменных.

■■■

Вы не должны включать в заголовочные файлы выполнимые команды ( за исключением функций, определённых внутри класса ). Не забудьте также добавить включаемый файл в список файлов проекта, даже если он не содержит исходный код. Включение заголовочных файлов в проект обеспечит автоматическое перекомпилирование проекта при внесении изменений в заголовочный файл.

 

►Работа с профайлером...339

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

_________________

339 стр. . Десять основных возможностей Dev-C++

Однако если алгоритмические ресурсы исчерпаны, а программа работает слишком медленно, вам на помощь может прийти профайлер Dev-C++. Это инструмент, который выясняет, сколько времени затрачивает ваша программа на выполнение тех или иных действий, и позволяет вам сосредоточить свои усилия на наиболее "узком" месте программы.

Для включения профайлера воспользуйтесь командой меню Сервис => Параметры компилятора ( Tools => Compiler Options ), затем выберите вкладку Настройки ( Settings ), в левой части окна — Профилирование кода ( Code profiling ), а в правой установите опцию Генерировать профилирующую информацию для анализа ( Generate Profiling Info for Analysis ) равной Yes.

Давайте профилируем немного изменённую программу DeepCopy из .

    //

    /* DeepCopy — программа для демонстрации профилирования */

    //

    #include

    #include

    #include

    #include

    using namespace std ;

    class Person

    {

      public :

        Person( char *pN )

        {

            pName = new char[ strlen( pN ) + 1 ] ;

            if ( pName != 0 )

            {

                strcpy( pName , pN ) ;

            }

        }

        Person( Person& p )

        {

            pName = new char[ strlen( p.pName ) + 1 ] ;

            if ( pName != 0 )

            {

                strcpy( pName , p.pName ) ;

            }

        }

        ~Person( )

        {

            delete pName ;

            pName = 0 ;

        }

      protected :

        char *pName ;

    } ;

_________________

340 стр. . Великолепная десятка

        void fn1( Person &p )

    {

        /* Создаём новый объект */

        Person p1( p ) ;

    }

    void fn2( Person p )

    {

        /* Создаём новый объект */

        Person* p1 = new Person( p ) ;

        delete p1 ;

    }

    int main( int nNumberofArgs , char* pszArgs[ ] )

    {

        Person p( "Very, very long name" ) ;

        for ( int i = 0 ; i < 10000000 ; i++ )

        {

            fn1( p ) ;

            fn2( p ) ;

        }

        return 0 ;

    }

Эта программа вызывает функции fn1( ) и fn2( ) десять миллионов раз — просто нереально получить сколь-нибудь точную картину происходящего, если программа выполняется меньше секунды.

Кроме того, я удалил из программы весь вывод на экран. Если вы вернёте его назад, то сразу убедитесь, что это очень медленный процесс, так что время, затраченное на вывод на экран, не даст возможности получить результаты по другим функциям.

После выполнения программы воспользуйтесь командой меню Выполнить => Анализ профиля ( Execute => Profile Analysis ). Появляющееся при этом окно показано на рис. 30.1.

Рис. 30.1. Анализ профиля программы

_________________

341 стр. . Десять основных возможностей Dev-C++

Интерпретация профиля требует определённой практики. В окне показаны функции, вызываемые в процессе выполнения программы ( в программе могут быть и другие функции, которые никогда не вызываются ). В первом столбце перечислены имена функций, за которыми следуют время их работы в процентах от общего времени работы программы. В нашем случае больше всего времени занимает выполнение конструктора копирования. В столбце Self Sees указано общее время, затраченное на выполнение этой функции.

Означает ли приведённая информация, что конструктор копирования — самая медленная функция программы? Не обязательно. Просто программа вызывает эту функцию чаще других — и в функции fn1( ), и в fn2( ).

Опустившись ниже по списку, мы видим, что fn2( ) работает почти в два раза больше, чем fn1( ). В основном это связано с тем, что функция main( ) передаёт функции fn2( ) объект Person по значению, что приводит к дополнительному вызову конструктора копирования.

_________________

342 стр. . Великолепная десятка

 

Глава 31. ПРОГРАММА BUDGET...343

 

        В этой главе...

►BUDGET1  343

►BUDGET2  348

►BUDGET3  355

►BUDGET4  366

►BUDGET5  377

В этой дополнительной главе описана серия программ для работы с воображаемым банковским счётом. Первая из них, BUDGET1, решает поставленную задачу с использованием простейших ( и подверженных ошибкам ) технологий функционального программирования. В программе BUDGET2 информация о счетах хранится в двух классах, Savings и Checking. Программа BUDGET3 выражает взаимоотношения классов Savings и Checking путём порождения их из общего класса Account. Объекты в этой программе хранятся в связанном списке. Программа BUDGET4 обобщает связанный список, превращая его в шаблон класса LinkedList< class Т >, а в программе BUDGET5 мы перестаём изобретать велосипед и используем один из контейнеров STL. Все исходные тексты программ находятся на прилагаемом компакт-диске.

 

►BUDGET1...343

Главы, составляющие две первые части книги, предоставляют вам достаточно информации для написания собственной нетривиальной программы. Такой программой является рассматриваемая далее программа BUDGET 1.

В этой программе используются концепции С++, рассмотренные в первых двух частях книги. Программа BUDGET моделирует простейший банковский счёт ( в очень упрощённом виде ). Эта программа будет выполнять такие действия:

■■■

■ создавать один или несколько банковских счетов;

■ присваивать уникальный номер каждому счёту;

■ работать со счётом — создание депозита и снятие денег;

■ выводить окончательный баланс всех счетов, после того как пользователь решит выйти из программы.

■■■

Программа имитирует банковские транзакции, подчиняющиеся следующим правилам ( в процессе развития программы к этим правилам будут добавлены и другие ).

■■■

■ Баланс не может быть отрицательным ( каким бы дружественным не был к вам ваш банк, настолько дружественным он быть просто не в состоянии ).

■ Создание депозита не влечёт за собой никаких расходов.

■■■

_________________

343 стр. . Программа BUDGET

Вот как выглядит исходный текст данной программы.

    /* BUDGET1.CPP — "Функциональная" программа бюджета */

    #include

    #include

    #include

    using namespace std ;

    /* Максимальное количество счетов */

    const int maxAccounts = 10 ;

    /* Данные, описывающие счёт */

    unsigned accountNumber[ maxAccounts ] ;

    double balance[ maxAccounts ] ;

    /* Прототипы функций */

    void process(  unsigned& accountNumber ,

                  double& balance ) ;

    void init( unsigned* pAccountNumber ,

               double* pBalance ) ;

    /* main — собирает начальные входные данные и выводит конечные суммы */

    int main( int nNumberofArgs , char* pszArgs[ ] )

    {

        setlocale ( LC_ALL , ".1251" ) ; /* печать кириллицы */

        cout << "Нажмите С для продолжения, X для выхода:\n" ;

        // Цикл ввода

        int noAccounts = 0 ; /* Количество счетов */

        /* Нельзя создать счетов больше, чем выделено места */

        cout << "Программа создаёт банковские счета\n" << endl ;

        while ( noAccounts < maxAccounts )

        {

            char transactionType ;

            cout << "С для создания счёта, X для выхода: " ;

            cin >> transactionType ;

            /* Выход, если пользователь ввёл X */

            if ( transactionType == 'x' ||

                 transactionType == 'X' )

            {

                break ;

            }

            /* Если введено С */

            if ( transactionType == 'c' ||

                 transactionType == 'C' )

            {

                /* Инициализация нового счёта */

                init( &accountNumber[ noAccounts ] ,

&balance[ noAccounts ] ) ;

                /* Ввод информации о транзакции */

                process(  accountNumber[ noAccounts ] ,

                         balance[ noAccounts ] ) ;

                /* Увеличение индекса */

_________________

344 стр. . Великолепная десятка

                noAccounts++ ;

            }

        }

        double total = 0 ;

        cout << "Информация о счёте\n" ;

        for ( int i = 0 ; i < noAccounts ; i++ )

        {

            cout << "Баланс счёта "

                  << accountNumber[ i ]

                  << " = "

                  << balance[ i ]

                  << "\n" ;

            /* Накопление общей информации */

            total += balance[ i ] ;

        }

        // Вывод

        cout << "Баланс по всем счетам = "

             << total

             << "\n" ;

        /* Пауза для того, чтобы посмотреть на результат работы программы */

        system( "PAUSE" ) ;

        return 0 ;

    }

    /* init — Инициализация счёта путём чтения номера и обнуления баланса */

    void init( unsigned* pAccountNumber ,

               double* pBalance )

    {

        cout << "Введите номер счёта:" ;

        cin >> *pAccountNumber ;

        *pBalance = 0.0 ;

    } 

    /* process — Обновление баланса */

    void process(  unsigned& accountNumber ,

                  double& balance )

    {

        cout << "Введите положительную сумму вклада,\n"

             << "отрицательную сумму для снятия со счёта\n"

             << "или нуль для завершения работы\n"

             << endl ;

        double transaction ;

        do

        {

            cout << ":" ;

            cin >> transaction ;

            // Вклад

            if ( transaction > 0 )

_________________

345 стр. . Программа BUDGET

            {

                 /* Добавление на счёт */

                 balance += transaction ;

            }

            /* Снятие со счёта */

            if ( transaction < 0 )

            {

                transaction = -transaction ;

                if ( balance < transaction )

                {

                    cout << "Недостаточно денег: всего"

                         << balance

                         << ", снимаем "

                         << transaction

                         <<" \n" ;

                }

                else

                {

                    balance -= transaction ;

                }

            }

        } while ( transaction != 0 ) ;

    }

Демонстрация работы данной программы:

    Нажмите С для продолжения, X для выхода:

    Программа создаёт банковские счета

    С для создания счёта, X для выхода: С

    Введите номер счёта: 1234

    Введите положительную сумму вклада,

    отрицательную сумму для снятия со счёта

    или нуль для завершения работы

    : 200

    : -100

    : -200

    Недостаточно денег: всего 100 , снимаем 200

    : 0

    С для создания счёта, X для выхода: с

    Введите номер счёта: 2345

    Введите положительную сумму вклада,

    отрицательную сумму для снятия со счёта

    или нуль для завершения работы

    : 200

    : -50

    : -50

    : 0

    С для создания счёта, X для выхода: х

    Информация о счёте

    Баланс счёта 1234 = 100

    Баланс счёта 2345 = 100

    Баланс по всем счетам = 200

    Press any key to continue...

_________________

346 стр. . Великолепная десятка

Разберёмся в том, как работает BUDGET. В этой программе было создано два массива, один из которых содержит номера счетов, а второй — балансы. Эти массивы синхронизированы таким образом, что элемент balance[ n ] содержит баланс счёта с номером из accountNumber[ n ], независимо от значения n. В связи с ограничением длины массива количество счетов, содержащихся в программе, не может превышать MAXACCOUNTS.

Главная программа разделена на две части: первая отвечает за сбор информации ( в ней происходит считывание размеров вкладов, снятие денег и запись результата ), а вторая — за вывод информации. Фрагмент, отвечающий за сбор информации, организован в виде цикла, в котором счета обрабатываются каждый в отдельности. В начале цикла пользователю предлагается ввести С для продолжения работы и X — для завершения. Если был введён символ X, происходит выход из цикла и переход во вторую часть main( ).

Программа выходит из цикла, если количество созданных счетов достигло MAXACCOUNTS, независимо от того, был ли введён X.

Обратите внимание, что происходит проверка введённого символа на равенство как 'X', так и 'х' — ведь в отличие от компьютера человек может не обратить внимания на регистр вводимых символов.

Если пользователь ввёл 'С', то управление передаётся функции init( ), которая создаёт счёт и заполняет его необходимой информацией. После этого функция process(  ) добавляет в счёт информацию о транзакции.

Аргументами функций init( ) и process(  ) являются указатели, так что эти функции могут изменять значения своих аргументов. В противном случае обновленная информация о счёте была бы утрачена по окончании работы функций.

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

Функция init( ) создаёт новый счёт после приглашения ввести его номер и обнуляет создаваемый счёт.

Очень важно не забыть проинициализировать новый элемент. Нулевой баланс счёта лучше непредсказуемого значения ( например, отрицательного ).

Функция process(  ) использует цикл для ввода каждой новой транзакции. Положительные значения считаются вкладом, а отрицательные — снятием со счёта. Для завершения работы со счётом достаточно ввести нуль.

Значение "0" используется программой в качестве флага. Это хотя и довольно распространённый, но не самый хороший метод. Я использовал его в этой программе только потому, что он помогает сэкономить довольно много места.

Существует множество способов улучшить программу BUDGET. Их поиск станет основой для последующего изучения языка С++. В конце книги вы увидите, как эта программа превратится в полнофункциональную объектно-ориентированную программу на С++. 

 

Стиль программирования...347

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

Важно, чтобы вы научились правильно именовать свои переменные, корректно располагать скобки и выполнять многое другое, что составляет стиль программирования. Разработав стиль программирования, придерживайтесь его, и он войдёт в привычку. Однажды вы заметите, что пишете программы быстрее, а их чтение не вызывает затруднений.

_________________

347 стр. . Программа BUDGET

Это особенно важно, когда над проектом работают несколько программистов, поскольку правильный стиль помогает избежать проблем, которые обрушились на строителей Вавилонской башни. Кроме того, я бы настоятельно советовал тщательно разбираться в каждом сообщении об ошибке или предупреждении компилятора. Даже если считать, что предупреждение — это ещё не ошибка, то зачем дожидаться, пока оно превратится в ошибку? Тем более, что, если предупреждение такое простое, каким кажется, разобраться в нём и устранить его не составит труда. В большинстве случаев предупреждения вызываются ошибочным стилем программирования, который лучше исправить. Одни говорят, что недосмотры — это их личное дело, другие же считают, что это лишняя трата времени. Однако в любом случае обидно будет обнаружить ошибку, о которой компилятор предупреждал вас давным-давно.

 

►BUDGET2...348

Программа BUDGET2 представляет собой результат преобразования программы BUDGET1, основанной на использовании функций, в объектно-ориентированную программу с применением классов.

«Для того чтобы разобраться в программе BUDGET2, вы должны быть хорошо знакомы с концепциями, представленными в третьей части книги .»

[]

В программе будет решаться задача по представлению счетов в том виде, какой они имеют в банке. Эта простая программа будет поддерживать возможности вклада ( это хорошо ) и снятия денег со счёта ( это ещё лучше ). ( Первоначальная версия работала только с одним типом банковского счёта. ) Эта версия поддерживает два типа счетов, каждый из которых будет иметь собственные, несколько отличные от другого счёта правила.

Чековый счёт:

■■■

■ удерживать 20 центов за каждый обработанный чек, если баланс падает ниже 500 долларов;

■ не удерживать 20 центов, если баланс больше 500 долларов.

■■■

Сберегательный счёт:

■■■

■ не удерживать денег при первом снятии со счёта за месяц;

■ удерживать 5 долларов за каждое последующее снятие.

■■■

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

Как и любой класс, Checking и Savings нуждаются в конструкторе, чтобы проинициализировать объекты правильными значениями ( как минимум, обнулить баланс ). Кроме того, понадобятся ещё две функции — deposit( ) ( вклад ) и withdrawal( ) ( снятие ).

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

Вот текст этой программы:

    /* BUDGET2.CPP — программа бюджета, основанная на классах */

    #include

    #include

    #include

_________________

348 стр. . Великолепная десятка

    using namespace std ;

    /* Максимальное количество счетов */

    const int maxAccounts = 10 ;

    /* Checking — здесь описан чековый счёт */

    class Checking

    {

    public :

        Checking( int initializeAN = 0 )

             :accountNumber( initializeAN ) , balance( 0.0 ){ }

        /* Функции обращения */

        int accountNo( )

        {

            return accountNumber ;

        }

        double acntBalance( )

        {

            return balance ;

        }

        /* Функции транзакций */

        void deposit( double amount )

        {

            balance += amount ;

        }

        void withdrawal( double amount ) ;

        /* Функция вывода объекта в cout */

        void display( )

        {

            cout << "Счёт " << accountNumber

                 << " = " << balance

                 << "\n" ;

        }

    protected :

        unsigned accountNumber ;

        double balance ;

    } ;

    /* withdrawal — эта функция-член слишком */

    /*             велика для inline-функции */

    void Checking::withdrawal( double amount )

    {

        if ( balance < amount )

        {

            cout << "Недостаточно денег: баланс равен "

                 << balance

                 << ", сумма чека равна " << amount

                 << "\n" ;

        }

        else

        {

            balance -= amount ;

            /* Если баланс падает слишком низко... */

            if ( balance < 500.00 )

            {

_________________

349 стр. . Программа BUDGET

             /* ...удержать деньги за обслуживание */

                 balance -= 0.20 ;

             }

        }

    }

    /* Savings — вы и сами можете написать этот класс */

    class Savings

    {

    public :

        Savings( int initialAN = 0 )

            : accountNumber( initialAN ) ,

              balance( 0.0 ) , noWithdrawals( 0 ) { }

        /* функции обращения */

        int accountNo( )

        {

            return accountNumber ;

        }

        double acntBalance( )

        {

            return balance ;

        }

        /* функции транзакций */

        void deposit( double amount )

        {

            balance += amount ;

        }

        void withdrawal( double amount ) ;

        /* Функция display — отображает объект */

        void display( )

        {

            cout << "Счёт " << accountNumber

                 << " = " << balance

                 << " ( номер снятия = "

                 << noWithdrawals

                 << " )\n" ;

        }

    protected :

        unsigned accountNumber ;

        double balance ;

        int noWithdrawals ;

    } ;

    void Savings::withdrawal( double amount )

    {

        if ( balance < amount )

        {

            cout << "Недостаточно денег на счёте: "

                 << "баланс равен " << balance

                 << ", снимается " << amount

                 << "\n" ;

        }

        else

        {

        /* После первого в месяце снятия денег... */

_________________

350 стр. . Великолепная десятка

            if ( ++noWithdrawals > 1 )

            {

            /* ...удерживать $5 */

            balance -= 5.00 ;

            }

            /* Снять деньги */

            balance -= amount ;

        }

    }

    /* Объявление прототипов */

    void process( Checking* pChecking ) ;

    void process( Savings* pSavings ) ;

    /* Объекты чековых и сберегательных счетов */

    Checking* chkAcnts[ maxAccounts ] ;

    Savings* svgAcnts[ maxAccounts ] ;

    /* main — собирает и выводит данные */

    int main( int argcs , char* pArgs[ ] )

    {

        setlocale ( LC_ALL , ".1251" ) ; /* печать кириллицы */

        /* Повторять цикл до ввода 'X' или 'х' */

        int noChkAccounts = 0 ; /* Содержит количество счетов */

        int noSvgAccounts = 0 ;

        char accountType ; /* Тип счёта — 'S' или 'С' */

        while ( 1 )

        {

            cout << "Введите S для сберегательных счетов, " << "\n"

                 << "С для чековых, "

                 << "X для выхода:" ;

            cin >> accountType ;

        /* Выйти из цикла, если пользователь введёт X */

        if ( accountType == 'x' || accountType == 'X' )

        {

            break ;

        }

        /* В противном случае обрабатывать соответствующий счёт */

        switch ( accountType )

        {

        /* чековые счета */

            case 'c' :

            case 'C' :

            if ( noChkAccounts < maxAccounts )

            {

                int acnt ;

                cout << "Введите номер счёта:" ;

                cin >> acnt ;

                chkAcnts[ noChkAccounts ] = new Checking( acnt ) ;

                process( chkAcnts[ noChkAccounts ] ) ;

                noChkAccounts++ ;

            }

            else

            {

                cout << "Для чековых счетов больше нет места\n" ;

_________________

351 стр. . Программа BUDGET

            }

            break ;

            /* сберегательные счета */

            case 's' :

            case 'S' :

            if ( noSvgAccounts < maxAccounts )

            {

                int acnt ;

                cout << "Введите номер счёта:" ;

                cin >> acnt ;

                svgAcnts[ noSvgAccounts ] = new Savings( acnt ) ;

                process( svgAcnts[ noSvgAccounts ] ) ;

                noSvgAccounts++ ;

            }

            else

            {

                cout << "Для сберегательных счетов "

                     << "больше нет места\n" ;

            }

            break ;

            default :

                cout << "Непонятный символ...\n" ;

            }

        }

        /* А теперь показать общую сумму */

        double chkTotal = 0 ;

        cout << "Чековые счета:\n" ;

        for ( int i = 0 ; i < noChkAccounts ; i++ )

        {

            chkAcnts[ i ]  ->  display( ) ;

            chkTotal += chkAcnts[ i ]  ->  acntBalance( ) ;

        }

        double svgTotal = 0 ;

        cout << "Сберегательные счета:\n" ;

        for ( int j = 0 ; j < noSvgAccounts ; j++ )

        {

            svgAcnts[ j ]  ->  display( ) ;

            svgTotal += svgAcnts[ j ]  ->  acntBalance( ) ;

        }

        double total = chkTotal + svgTotal ;

        cout << "Сумма по чековым счетам = "

             << chkTotal

             << "\n" ;

        cout << "Сумма по сберегательным счетам = "

             << svgTotal

             << "\n" ;

        cout << "Общая сумма    = "

             << total

             << "\n" ;

_________________

352 стр. . Великолепная десятка

        /* Пауза для того, чтобы посмотреть на результат работы программы */

        system( "PAUSE" ) ; return 0 ;

    }

    /* обработка( Checking ) — ввод данных по чековым счетам */

    void process( Checking* pChecking )

    {

        cout << "Введите положительное число для вклада,\n"

             << "отрицательное для снятия, 0 для завершения\n" ;

        double transaction ;

        do

        {

            cout << ":" ;

            cin >> transaction ;

            // Вклад

            if ( transaction > 0 )

            {

                pChecking -> deposit( transaction ) ;

            }

            // Снятие

            if ( transaction < 0 )

            {

                pChecking -> withdrawal( -transaction ) ;

            }

        } while ( transaction != 0 ) ;

    }

    /* обработка( Savings ) — ввод данных для сберегательных счетов */

    void process( Savings* pSavings )

    {

        cout << "Введите положительное число для вклада,\n"

             << "отрицательное для снятия, 0 для завершения\n" ;

        double transaction ;

        do

        {

            cout << ":" ;

            cin >> transaction ;

            // Вклад

            if ( transaction > 0 )

            {

                pSavings -> deposit( transaction ) ;

            }

            // Снятие

            if ( transaction < 0 )

            {

                pSavings -> withdrawal( -transaction ) ;

            }

        } while ( transaction != 0 ) ;

    }

_________________

353 стр. . Программа BUDGET 

Я запустил эту программу с приведёнными ниже данными для того, чтобы продемонстрировать, как она работает. Жирным шрифтом выделен пользовательский ввод, а обычным представлены сообщения программы.

    Введите S для сберегательных счетов,

            С для чековых, X для выхода: S

    Введите номер счёта: 123

    Введите положительное число для вклада,

    отрицательное для снятия, 0 для завершения

    : 200

    : -20

    : 0

    Введите S для сберегательных счетов,

            С для чековых, X для выхода: S

    Введите номер счёта:234

    Введите положительное число для вклада,

    отрицательное для снятия, 0 для завершения

    : 200 

    : -10

    : -10

    : 0

    Введите S для сберегательных счетов,

            С для чековых, X для выхода: С

    Введите номер счёта: 345

    Введите положительное число для вклада,

    отрицательное для снятия, 0 для завершения

    : 200

    : -20

    : 0

    Введите S для сберегательных счетов,

            С для чековых, X для выхода: С

    Введите номер счёта: 456

    Введите положительное число для вклада,

    отрицательное для снятия, 0 для завершения

    : 600

    : -20

    : 0

    Введите S для сберегательных счетов,

            С для чековых, X для выхода: Х

    Чековые счета:

    Счёт 345 = 179.8

    Счёт 456 = 580

    Сберегательные счета:

    Счёт 123 = 180 ( номер снятия = 1 )

    Счёт 234 = 175 ( номер снятия = 2 )

    Сумма по чековым счетам = 759.8

    Сумма по сберегательным счетам = 355

    Общая сумма      = 1114.8

    Press any key to continue...

Рассмотрим каждую из функций-членов, начиная с класса Checking. Конструктор присваивает счёту его номер. Значение по умолчанию "= 0" позволяет программе создавать объект с номером счёта по умолчанию, равным нулю.

    Checking c1 = new Checking( 124 ) ;

    Checking с2 = new Checking( ) ;

_________________

354 стр. . Великолепная десятка

В данном случае объект c1 класса Checking создаётся с номером счёта, равным 123, тогда как объект с2 создаётся с номером счёта по умолчанию, который равен нулю.

Функции accountNo( ) и acntBalance( ) предоставляют внешнему миру доступ к защищённым членам accountNumber и balance. Задачей этих функций является предоставление внешним функциям — не членам значений, изменить которые невозможно. Кроме того, эти функции, обеспечивающие доступ к членам, предохраняют внешние функции от необходимости внесения изменений при переменах в методе хранения номера счёта или баланса.

Функции deposit( ) и withdrawal( ) отвечают за вложение и снятие денег со счёта. Поскольку функция deposit( ) довольно проста, она была определена как inline-функция. Функция withdrawal( ), будучи несколько сложнее, объявлена в классе, но определяется позже.

Функция display( ) выводит важные данные на устройство стандартного вывода.

Класс Savings, в сущности, идентичен классу Checking, за исключением дополнительного члена noWithdrawals, который отслеживает количество проведённых снятий.

Место под объекты сберегательного и чекового счёта выделяется в массивах svgAcnts и chkAcnts соответственно. Максимальное количество счетов определено величиной maxAccounts.

Функция main( ) несколько сложнее своей сестры из программы BUDGET1, поскольку она имеет дело с двумя разными типами счетов. После проверки ввода на равенство "X" функция main( ) использует конструкцию switch, чтобы выбрать тип счёта: С для чекового и S для сберегательного. Конструкция switch использована в этой программе по двум причинам: во-первых, её проще расширить, добавляя к ней дополнительные варианты; во-вторых, она предоставляет вариант default ( вариант по умолчанию ) для обработки неверного ввода.

Как и ранее, вторая часть функции main( ) обеспечивает отображение информации о счёте, собранной в первой части этой функции.

Обратите внимание на то, как содержимое классов Checking и Savings скрыто от main( ). Так, например, main( ) просит объект показать своё содержимое, однако при этом не имеет никакого представления о том, как класс выбирает, что именно и как это показать.

Функция process( ), которая обрабатывает текущие вложения и снятия, полагается на функции-члены deposit( ) и withdrawal( ), которые выполняют за неё всю чёрную работу. Хотя вы и знаете, как именно выполняются эти действия, помните, что process(  ) об этом не имеет никакого понятия. Работа счёта касается только самого класса счёта.

Советую пройти эту программу в пошаговом режиме. Ничто иное не даст более полного представления о программе, чем её рассмотрение в действии.

Хотите — верьте, хотите — нет, но с позиции программирования BUDGET2 разрабатывается легче, чем BUDGET1. Когда я писал класс Savings, я не должен был волноваться о том, как он будет использоваться главной программой ( то же относится и к классу Checking ). Когда же я работал над функцией main( ), то не думал о содержимом класса.

Однако в этой программе есть один небольшой недостаток: классы Savings и Checking имеют очень много общего, и хотелось бы найти возможность уменьшить количество повторений кода. Эта возможность реализована в очередной версии нашей программы — BUDGET3.

 

►BUDGET3...355

 

Здесь продолжается преобразование исключительно функциональной версии программы BUDGET1, которая затем прошла через объектно-основанный этап своей эволюции — BUDGET2, в объектно-ориентированную программу BUDGET3.

_________________

355 стр. . Программа BUDGET  

«В этой программе использованы концепции, представленные в четвёртой части книги.»

[]

Программа осуществляет вложение денег на счёт и снятие со счёта в воображаемом банке. Пользователь поочередно вводит номера банковских счетов и суммы вкладов на этот счёт и снятий с него. После того как пользователь выполнил все транзакции, программа показывает баланс каждого счёта и общий баланс. Обе программы — BUDGET2 и BUDGET3 — эмулируют Checking ( чековый ) и Savings ( сберегательный ) счета. Чековые счета взимают небольшой гонорар за обслуживание при каждом снятии, если баланс упал ниже 500 долларов, тогда как сберегательный счёт взимает большой гонорар за обслуживание при первом снятии, независимо от баланса.

Программа BUDGET2 превосходит BUDGET1 только в одном: она изолирует особенности классов, описывающих счёт, от внешних функций, которые манипулировали счетами. К сожалению, BUDGET2 содержала большое количество дублированного кода в классах Savings и Checking, и именно от него мы и хотим избавиться, используя принципы наследования.

Программа BUDGET3 вносит новые улучшения. С помощью такого "супергероя" объектно-ориентированного программирования, как наследование, и его верного бокового удара — полиморфизма мы смогли оптимизировать два класса счетов, объединив в один класс Account всё  то общее, что присуще этим двум классам, и получив в результате меньшее и более компактное множество классов.

Кроме того, вместо массивов в программе используется связанный список для хранения объектов Account, что снимает ограничение на количество счетов, присущее более ранним версиям программы. Кроме того, код программы разделён на модули с различными пространствами имён .

 

Реализация модуля со связанным списком...356

Основное ограничение, присущее первым двум версиям программы, заключается в том, что они обе используют для хранения счетов массивы фиксированного размера, так что количество счетов, которые эти программы могут обработать, изначально ограничено. Более разумное решение состоит в использовании связанных списков объектов. Проблема лишь в том, что связанные списки ничего не знают о банковских счетах.

Очевидно, что можно разделить концепцию связанных списков на два класса. Этот тип разделения хотя и отличен от рассматриваемого в книге, но не менее важен. Он разносит классы с отношением СОДЕРЖИТ в различные файлы.

#i_351.jpg

«Контейнер ( такой как массив или связанный список ) СОДЕРЖИТ счета.»

[]

Интерфейс соответствующего класса определён в заголовочном файле AccountLinkedList.h

    /* AccountLinkedList — поддерживает связанный */

    /*                     список объектов Account */

    #ifndef _ACCOUNTLINKEDLIST_

    #define _ACCOUNTLINKEDLIST_

    /* Данное предварительное объявление — неприятное следствие того, что Account не является частью пространства имён */

_________________

356 стр. . Великолепная десятка

    /* Lists. Этой неприятности мы сумеем избежать в следующей версии программы */

    class Account ;

    namespace Lists

    {

        /* Предварительное объявление классов */

        class AccountLinkedList ;

        class Node ;

        /* LinkedList — связанный  список объектов Node */

        class AccountLinkedList

        {

          public :

            AccountLinkedList( ) { pHead = 0 ; }

            void addNode( Node* pNode ) ;

            Node* firstNode( ) { return pHead ; }

          protected :

            Node* pHead ;

        } ;

        /* Node — узел в связанном списке, указывающий на объект Account */

        class Node

        {

            friend class AccountLinkedList ;

          public :

            Node( AccountLinkedList* pL , Account* pAcc )

            {

                pList = pL ;

                pNext = 0 ;

                pAccount = pAcc ;

                pL -> addNode( this ) ;

            }

            static Node* firstNode( AccountLinkedList* pList )

            {

                return pList -> firstNode( ) ;

            }

            Node* nextNode( ) { return pNext ; }

            Account* currentAccount( ) { return pAccount ; }

          protected :

            AccountLinkedList* pList ;

            Node* pNext ;

            Account* pAccount ;

        } ;

    }

    #endif

Файл AccountLinkedList.cpp реализует простой связанный список банковских счетов.

_________________

357 стр. . Программа BUDGET 

    /* AccountLinkedList — поддерживает связанный список объектов Account */

    #include "AccountLinkedList.h"

    namespace Lists

    {

        /* addNode — добавляет узел в начало текущего связанного списка */

        void AccountLinkedList::addNode( Node* pNode )

        {

            pNode -> pNext = pHead ;

            pHead = pNode ;

        }

    }

Каждый объект Node связан со своим объектом Account. Указатель Node::pNext указывает на следующий счёт в списке. Объект AccountLinkedList представляет весь связанный список целиком; указатель AccountLinkedList::pHead указывает на первый объект Node в списке. Для простоты функция addNode( ) добавляет объекты Node в начало списка. 

 

Работа со счетами...358

Данная версия программы BUDGET использует связанный список, реализованный в файле AccountLinkedList.срр. Этот класс позволяет программе хранить и работать с количеством счетов, ограниченным только объёмом свободной памяти.

Файл BUDGET3.срр представляет собой главный модуль программы, в котором содержится код приложения.

     //

    /* BUDGET3.СРР — Программа банковского бюджета с наследованием и полиморфизмом. Теперь одна функция может обрабатывать и чековые, и сберегательные счета ( а также любые другие, которые вы можете придумать в будущем ). */

    //

    /* Кроме того, вместо массива, который может иметь только определённую длину, эта версия хранит счета в связанном списке. */

    #include

    #include

    #include

    #include "AccountLinkedList.h"

    using namespace std ;

    using namespace Lists ;

    /* Account — абстрактный класс, включающий общие свойства различных счетов */

    class Account

    {

      public :

        Account::Account( AccountLinkedList* pList , int accNo )

           : node( pList , this )

        {

            /* Инициализация данных-членов */

            accountNumber = accNo ;

            balance = 0 ;

            count++ ;

        }

_________________

358 стр. . Великолепная десятка

        /* Функции доступа */

        int accountNo( ) { return accountNumber ; }

        double acntBalance( ) { return balance ; }

        static int noAccounts( ) { return count ; }

        /* Функции транзакций */

        void deposit( double amount ) { balance += amount ; }

        virtual bool withdrawal( double amount )

        {

            if ( balance < amount )

            {

                cout << "Недостаточно денег: на счету " << balance

                     <<", снимаем "            << amount

                     << endl ;

                return false ;

            }

            balance -= amount ;

            return true ;

        }

        /* Функция вывода на экран */

        void display( )

        {

            cout << type( )

                 << " счёт " << accountNumber

                 << " = "    << balance

                 << endl ;

        }

        virtual char* type( ) = 0 ;

      protected :

        /* Информация о связанном списке */

        Node node ;

        static int count ; /* Количество счетов */

        unsigned accountNumber ;

        double balance ;

    } ;

    /* Переменная для сбора статистики */

    int Account::count = 0 ;

    /* Checking — свойства, уникальные для чекового счёта */

    class Checking : public Account

    {

      public :

        Checking::Checking( AccountLinkedList* pLL ,

                                unsigned accNo ) :

           Account( pLL , accNo )

        { }

        /* Перегрузка чисто виртуальных функций */

        virtual bool withdrawal( double amount ) ;

        virtual char* type( ) { return "Чековый" ; }

    } ;

    /* withdrawal — перегрузка Account::withdrawal( ) */

_________________

359 стр. . Программа BUDGET  

    bool Checking::withdrawal( double amount )

    {

        bool success = Account::withdrawal( amount ) ;

        if ( success && balance < 500.00 )

        {

            balance -= 0.20 ;

        }

        return success ;

    }

    /* Savings — свойства, уникальные для сберегательного счёта */

    class Savings : public Account

    {

      public :

        Savings::Savings( AccountLinkedList* pLL ,

                           unsigned accNo ) :

           Account( pLL , accNo )

        { noWithdrawals = 0 ; }

        /* Функции транзакций */

        virtual bool withdrawal( double amount ) ;

        virtual char* type( ) { return "Сберегательный" ; }

      protected :

        int noWithdrawals ;

    } ;

    /* withdrawal — перегрузка Account::withdrawal( ) */

    bool Savings::withdrawal( double amount )

    {

        if ( ++noWithdrawals > 1 )

        {

            balance -= 5.00 ;

        }

        return Account::withdrawal( amount ) ;

    }

    /* Прототипы функций */

    unsigned getAccntNo( ) ;

    void   process( Account* pAccount ) ;

    void   getAccounts( AccountLinkedList* pLinkedList ) ;

    void   displayResults( AccountLinkedList* pLinkedList ) ;

    /* main — собирает и выводит данные */

    int main( int argcs , char* pArgs[ ] )

    {

        setlocale ( LC_ALL , ".1251" ) ; /* печать кириллицы */

        /* Создание связанного списка */

        AccountLinkedList* pLinkedList = new AccountLinkedList( ) ;

        /* Чтение пользовательского ввода */

        getAccounts( pLinkedList ) ;

        /* Вывод связанного списка */

        displayResults( pLinkedList ) ;

_________________

360 стр. . Великолепная десятка

        /* Пауза для того, чтобы посмотреть на результат работы программы */

        system( "PAUSE" ) ; return 0 ;

    }

    /* getAccounts — загрузка массива счетов */

    void getAccounts( AccountLinkedList* pLinkedList )

    {

        Account* pA ;

        /* Цикл, пока не введено 'X' или 'х' */

        char accountType ;       /* S или С */

        while ( true )

        {

            cout << "Введите S для сберегательного счёта,\n"

                 << "С для чекового, X для выхода: " ;

            cin >> accountType ;

            switch ( accountType )

            {

              case 'c' :

              case 'C' :

                pA = new Checking( pLinkedList , getAccntNo( ) ) ;

                break ;

              case 's' :

              case 'S' :

                pA = new Savings( pLinkedList , getAccntNo( ) ) ;

                break ;

              case 'x' :

              case 'X' :

                return ;

              default :

                cout << "Неверный ввод.\n" ;

            }

            /* Обработка вновь созданного объекта */

            process( pA ) ;

        }

    }

    /* displayResults — вывод информации о счетах в связанном списке */

    void displayResults( AccountLinkedList* pLinkedList )

    {

        double total = 0.0 ;

        cout << "\nИтоговая информация:\n" ;

        for ( Node* pN = Node::firstNode( pLinkedList ) ;

                      pN != 0 ;

                      pN = pN -> nextNode( ) )

        {

            Account* pA = pN -> currentAccount( ) ;

            pA -> display( ) ;

             total += pA -> acntBalance( ) ;

        }

_________________

361 стр. . Программа BUDGET  

        cout << "Итого = " << total << "\n" ;

    }

    /* getAccntNo — номер счёта для его создания */

    unsigned getAccntNo( )

    {

        unsigned accntNo ;

        cout << "Введите номер счёта: " ;

        cin >> accntNo ;

        return accntNo ;

    }

    /* process( Account ) — обработка счёта */

    void process( Account* pAccount )

    {

        cout << "Введите положительное число для вклада,\n"

              << "отрицательное для снятия,"

              << " 0 для завершения работы\n" ;

        double transaction ;

        do

        {

            cout << ":" ;

            cin >> transaction ;

            // Вклад

            if ( transaction > 0 )

            {

                pAccount -> deposit( transaction ) ;

            }

            // Снятие

            if ( transaction < 0 )

            {

                pAccount -> withdrawal( -transaction ) ;

            }

        } while ( transaction != 0 ) ;

    }

Первый класс, содержащийся в BUDGET3, — Account. Этот класс инкапсулирует всё, что мы знаем об обобщённых счетах, а именно:

■■■

■ они распознаются по номерам;

■ каждый счёт имеет баланс;

■ пользователь может вкладывать или снимать деньги со счёта.

■■■

В этом классе сведён воедино код, общий для классов Savings и Checking из программы BUDGET2. Теперь Savings и Checking представляют собой подклассы Account. Я сделал функцию Account::type( ) чисто виртуальной, так что она обязана быть переопределена в каждом из подклассов.

Конструктор Account создаёт уникальную для каждого счёта информацию, записывая номер счёта и начальный баланс ( который приравнивается нулю, если при создании счёта не был задан другой баланс ). Затем увеличивается на единицу значение статического члена count, с помощью которого отслеживается количество существующих в данный момент объектов Account. Конструктор также инициализирует член node, который используется для объединения счетов в единый связанный список.

_________________

362 стр. . Великолепная десятка

«Для одного класса существует только одна копия каждого статического объекта. К ней имеют доступ все объекты класса.»

[]

Функции accountNo( ) и accountBalance( ) служат для того, чтобы предоставлять возможность считывания номера счёта и информации о балансе из внешнего мира, но не допускать непосредственного изменения этих значений.

Функции display( ) и type( ) придают всем счетам одинаковый формат отображения. Виртуальный метод type( ) будет переопределён в классах-наследниках. Так, метод Checking::type( ) вернёт строку "Чековый". Этот распространённый трюк позволяет методам базового класса, таким как Account::display( ), использовать точное описание класса.

Подкласс Checking класса Account достаточно прост. Конструктор класса Checking только передаёт аргументы конструктору класса Account. Единственная настоящая функция-член в этом классе — это withdrawal( ), которая реализует правила работы с чековыми счетами.

Класс Savings идентичен в этом отношении классу Checking: всё, что он делает, — это реализует метод withdrawal( ).

«Любой подкласс класса Account , который не переопределяет функцию type( ) , будет абстрактным, и вы не сможете создать объект этого класса.»

[]

Функции, составляющие главную программу, теперь упрощены до предела. Функция getAccount( ) создаёт счёт класса Checking или Savings ( в зависимости от символа, введённого пользователем ). Это единственное место в программе, где происходит непосредственное обращение к подклассам класса Account.

Функция displayResults( ) проходит по связанному списку, опрашивая каждый объект Account для вывода информации о чековом или сберегательном счёте ( а также о других типах счетов, если таковые встретятся в дальнейшем ). Аналогично функция process(  ) выполняет вклады и снятия со счетов ( объектов Account ). Как именно выполняются эти действия — определяют сами счета.

Метод displayResults( ) модифицирован для работы со связанным списком. В качестве аргумента этой функции передаётся связанный список, из которого функция считывает информацию. Цикл for начинается с первого объекта в списке, который возвращает вызов Node::firstNode( ). Переход к следующему объекту в списке осуществляется при помощи функции nextNode( ). Цикл завершает свою работу, когда вызов nextNode( ) возвращает 0.

Вывод на экран этой версии программы идентичен выводу предыдущей программы BUDGET2 с той разницей, что итоговая информация о счетах выводится в обратном порядке. Это связано с тем, что новые счета добавляются в начало списка. 

 

Классы связанных списков...363

Связанный список создаётся при помощи двух классов — AccountLinkedList и Node, которые определены в заголовочном файле AccountLinkedList.h.

    /* AccountLinkedList — поддерживает связанный */

    /*                    список объектов Account */

    #ifndef _ACCOUNTLINKEDLIST_

    #define _ACCOUNTLINKEDLIST_

_________________

363 стр. . Программа BUDGET

    /* Данное предварительное объявление — неприятное следствие того, что Account не является частью пространства имён Lists. Этой неприятности мы сумеем избежать в следующей версии программы */

    class Account ;

    namespace Lists

    {

        /* Предварительное объявление классов */

        class AccountLinkedList ;

        class Node ;

        /* LinkedList — связанный список объектов Node */

        class AccountLinkedList

        {

          public :

            AccountLinkedList( ) { pHead = 0 ; }

            void addNode( Node*  pNode ) ;

            Node* firstNode( ) { return pHead ; }

          protected :

            Node* pHead ;

        } ;

        /* Node — узел в связанном списке, указывающий на объект Account */

        class Node

        {

            friend class AccountLinkedList ;

          public :

            Node( AccountLinkedList* pL , Account* pAcc )

            {

                pList = pL ;

                pNext = 0 ;

                pAccount = pAcc ;

                pL -> addNode( this ) ;

            }

            static Node* firstNode( AccountLinkedList* pList )

            {

                return pList -> firstNode( ) ;

            }

            Node* nextNode( ) { return pNext ; }

            Account* currentAccount( ) { return pAccount ; }

          protected :

            AccountLinkedList* pList ;

            Node* pNext ;

            Account* pAccount ;

        } ;

    }

    #endif

_________________

364 стр. . Великолепная десятка

Я поместил оба класса — и AccountLinkedList, и Node — в пространство имён Lists для того, чтобы отделить их от класса Account. Класс AccountLinkedList содержит только заголовочный указатель связанного списка объектов Node.

«Заголовочный указатель — это указатель на первый элемент списка.»

[]

Основная работа выполняется в классе Node. Каждый узел Node указывает на следующий в списке объект при помощи члена pNext. Кроме того, узел также указывает на объект Account при помощи указателя pAccount. Указатель pList указывает на связанный список, которому принадлежит данный узел.

Ещё раз взгляните на исходный файл BUDGET3.срр. Функция main( ) определяет объект класса AccountLinkedList — это и есть связанный список. Ссылка на него передаётся конструктору Account. Конструктор Node( ), который вызывается из конструктора Account, создаёт узел, который является членом данного связанного списка и указывает на создаваемый счёт.

Маленький исходный файл AccountLinkedList.срр нужен для того, чтобы позволить классу AccountLinkedList обратиться к члену Node. Дело в том, что класс Node определён в заголовочном файле после класса AccountLinkedList, поэтому обращаться к его членам в определении класса AccountLinkedList нельзя. Изменение порядка объявлений не решает данную проблему, поскольку класс Node в AccountLinkedList.h также содержит ссылки на класс AccountLinkedList.

    /* AccountLinkedList — поддерживает связанный */

    /*                   список объектов Account */

    #include "AccountLinkedList.h"

    namespace Lists

    {

        /* addNode — добавляет узел в начало текущего связанного списка */

        void AccountLinkedList::addNode( Node* pNode )

        {

            pNode -> pNext = pHead ;

            pHead = pNode ;

        }

    }

 

Оценка бюджета...365

Задача, решаемая программой BUDGET, очень проста. Тем не менее сравнение разных версий этой программы даёт вам представление об отличии чисто функционального программирования ( BUDGET1 ) от объектно-основанного ( BUDGET2 ) и объектно-ориентированного программирования ( BUDGET3 ).

Основная проблема программы BUDGET3 в том, что связанный список, который в ней реализован, в состоянии работать только с классом Account и никаким другим. Проделав такую работу по созданию связанного списка, вы, вероятно, захотите использовать его и в других программах — например, для хранения информации о студентах.

Программа BUDGET4 расширяет рамки применения класса LinkedList. Чтобы разобраться в том, как она это делает, вы должны сперва ознакомиться с материалом .

_________________

365 стр. . Программа BUDGET 

 

►BUDGET4...366

 

Программа BUDGET имитирует работу банка, позволяя вкладывать и снимать деньги со счёта. Пользователь вводит последовательность банковских счетов, причём для каждого счёта вводится серия вкладов на счёт и снятия денег со счёта. После того как будут введены все счета и все транзакции, программа выводит состояние всех счетов ( а также общий баланс по всем счетам ). В программах BUDGET2 и BUDGET3 имитируются два вида счетов — чековый и сберегательный. Чековый счёт отличается тем, что если на нём остаётся меньше 500 долларов, то за каждый обработанный чек удерживается 20 центов. Первое снятие со сберегательного счёта выполняется бесплатно, а каждое последующее обходится в 5 долларов, независимо от состояния счёта. Программа BUDGET2 использует для решения поставленной задачи функции, a BUDGET3 — объектно-ориентированные концепции из четвёртой части книги.

Кроме того, программа BUDGET3 использует для хранения счетов связанный список, что позволяет снять ограничение на количество обрабатываемых счетов, налагаемое в первых двух версиях использованием массивов. К сожалению, представленный в программе класс AccountLinkedList недостаточно гибок из-за привязки к классу Account.

Программа BUDGET4 использует шаблоны, которые позволяют убрать ограничения, состоящие в привязке списка к конкретному типу хранимых данных.

«Шаблонам посвящён материал главы 27, "Шаблоны С++" .»

[]

 

Реализация связанного списка в виде шаблона класса...366

Приведённый далее шаблон класса LinkedList выглядит практически идентично классу AccountLinkedList из программы BUDGET3, если заменить класс Node обобщённым классом Т.

    /* LinkedList — связанный список произвольных объектов */

    #ifndef _ACCOUNTLINKEDLIST_

    #define _ACCOUNTLINKEDLIST_

    /* Предварительное объявление класса LinkedList */

    template < class T > class LinkedList;

    /* Node — узел связанного списка; каждый */

    /*          узел указывает на объект Т */

    template < class T > class Node

    {

    public:

        Node(LinkedList< T >* pL, T* pT)

        {

            pList = pL;

            pNext = 0;

            pObject = pT;

        }

        Node< T >* next( ) { return pNext; }

        Node* next(Node< T >* pN) { pNext = pN;

                                        return pNext; }

        T* current( ) { return pObject; }

_________________

366 стр. . Великолепная десятка

    protected:

        LinkedList< T >* pList;

        Node< T >* pNext;

        T* pObject;

    };

    /* LinkedList — связанный список объектов Node */

    template < class T > class LinkedList

    {

      public :

        LinkedList< T >( ) { pFirst = 0 ; }

        Node< T >* firstNode( ) { return pFirst ; }

        Node< T >* lastNode( )

        {

            /* Если список пуст, возвращает 0 */

            if ( pFirst == 0 )

            {

                return 0 ;

            }

            /* В противном случае ищем последний элемент списка */

            Node< T >* pN = pFirst ;

            while ( true )

            {

                Node< T >* pNext = pN -> next( ) ;

                if ( pNext == 0 )

                {

                    break ;

                }

                pN = pNext ;

            }

            return pN ;

        }

        void addNode( Node< T >* pNode )

        {

            Node< T >* pN = lastNode( ) ;

            if ( pN == 0 )

            {

                pFirst = pNode ;

            }

            else

            {

                pN -> next( pNode ) ;

            }

        }

      protected :

        Node< T >* pFirst ;

    } ;

    #endif 

_________________

367 стр. . Программа BUDGET 

«Дальнейшее рассмотрение может оказаться проще, если вы мысленно замените обобщённый класс Т действительным классом Account . При этом вы увидите, насколько программа становится похожей на свою предшественницу — программу BUDGET3 .»

[]

Выражение template < class Т > class LinkedList представляет собой предварительное объявление шаблона, необходимое для класса Node.

«Не забывайте о том, что шаблоны классов LinkedList и Node не являются реальными классами до тех пор, пока параметр Т не будет заменён действительным классом.»  

[]

Шаблон класса Node сконструирован для работы в качестве узла связанного списка. Каждый узел указывает на объект класса Т, который будет определён позже. Конструктор инициализирует члены-указатели: pList указывает на LinkedList, членом которого является данный объект Node, pObject указывает на объект типа Т, a pNext инициализируется значением 0 , указывающим, что пока узел не является членом списка.

"Активный метод" next ( Node< T >* ) добавляет текущий узел в список путём инициализации указателя pNext. "Пассивный метод" next( ) просто возвращает следующий объект Node< T > в списке. Это обычная практика кодирования, когда функция fn( ) возвращает текущее значение объекта, a fn( Т ) устанавливает его значение на основе аргумента.

Эта версия addNode( ) более усовершенствована по сравнению с представленной в программе BUDGET3, поскольку добавляет узел в конец списка. Преимущество такого подхода в том, что объекты считываются из списка в том же порядке, в котором были в него внесены, а недостаток в том, что метод lastNode( ) должен выполнять проход по всему списку всякий раз, когда требуется внести в список новый объект. Это может существенно замедлить работу программы при большом количестве элементов в списке.

 

Исходный код BUDGET4...368

Исходный код программы BUDGET4 практически идентичен коду BUDGET3.

    /* BUDGET4.CPP — в этой версии используется */

    /*                 шаблон класса LinkedList */

    //

    #include

    #include

    #include

    using namespace std ;

    #include "LinkedList.h"

    /* Account — абстрактный класс, включающий */

    /*          общие  свойства различных счетов */

    class Account ;

    template class LinkedList< Account > ;

    template class Node< Account > ;

    class Account

    {

      public :

        Account::Account( LinkedList< Account >* pList ,

                              unsigned accNo )

        {

            /* Инициализация данных-членов */

_________________

368 стр. . Великолепная десятка

            accountNumber = accNo ;

            balance = 0 ;

            /* Внесение в список */

            pNode = new Node< Account >( pList , this ) ;

            pList -> addNode( pNode ) ;

            count++ ;

        }

        /* Функции доступа */

        int accountNo( ) { return accountNumber ; }

        double acntBalance( ) { return balance ; }

        static int noAccounts( ) { return count ; }

        static Account* first( LinkedList< Account >* pLinkedList )

        {

            Node< Account >* pNode = pLinkedList -> firstNode( ) ;

            return pNode -> current( ) ;

        }

        Account* next( )

        {

            Node< Account >* pNextNode = pNode -> next( ) ;

            return pNextNode -> current( ) ;

        }

        /* Функции транзакций */

        void deposit( double amount ) { balance += amount ; }

        virtual bool withdrawal( double amount )

        {

            if ( balance < amount )

            {

                cout << "Недостаточно денег: на счету " << balance

                      << ", снимаем " << amount

                      << endl ;

                return false ;

            }

            balance -= amount ;

            return true ;

        }

        /* Функция вывода на экран */

        void display( )

        {

            cout << type( )

                   << " счёт " << accountNumber

                   << " = " << balance

                   << endl ;

        }

        virtual char* type( ) = 0 ;

      protected :

        Node< Account >* pNode ;

        static int count ; /* Количество счетов */

        unsigned accountNumber ;

        double balance ;

    } ;

_________________

369 стр. . Программа BUDGET 

    /* Переменная для сбора статистики */

    int Account::count = 0 ;

    /* Checking — свойства, уникальные для чекового счёта */

    class Checking : public Account

    {

      public :

        Checking::Checking( LinkedList< Account >* pLL ,

                          unsigned accNo ) :

         Account( pLL , accNo )

        { }

        /* Перегрузка чисто виртуальных функций */

        virtual bool withdrawal( double amount ) ;

        virtual char* type( ) { return "Чековый" ; }

    } ;

    /* withdrawal — перегрузка Account::withdrawal( ) */

    bool Checking::withdrawal( double amount )

    {

        bool success = Account::withdrawal ( amount ) ;

        if ( success && balance < 500.00 )

        {

            balance -= 0.20 ;

        }

        return success ;

    }

    /* Savings — свойства, уникальные для сберегательного счёта */

    class Savings : public Account

    {

      public :

        Savings::Savings( LinkedList< Account >* pLL ,

                            unsigned accNo ) :

            Account( pLL , accNo )

        { noWithdrawals = 0 ; }

        /* Функции транзакций */

        virtual bool withdrawal( double amount ) ;

        virtual char* type( ) { return "Savings" ; }

      protected :

        int noWithdrawals ;

    } ;

    /* withdrawal — перегрузка Account::withdrawal( ) */

    bool Savings::withdrawal( double amount )

    {

        if ( ++noWithdrawals > 1 )

        {

            balance -= 5.00 ;

        }

        return Account::withdrawal( amount ) ;

    }

_________________

370 стр. . Великолепная десятка

    /* Прототипы функций */

    unsigned getAccntNo( ) ;

    void process( Account* pAccount ) ;

    void getAccounts( LinkedList< Account >* pLinkedList ) ;

    void displayResults( LinkedList< Account >* pLinkedList ) ;

    /* main — собирает и выводит данные */

    int main( int argcs , char* pArgs[ ] )

    {

        setlocale ( LC_ALL , ".1251" ) ; /* печать кириллицы */

        /* Создание связанного списка */

        LinkedList< Account > linkedList ;

        /* Чтение пользовательского ввода */

        getAccounts( &linkedList ) ;

        /* Вывод связанного списка */

        displayResults( &linkedList ) ;

        /* Пауза для того, чтобы посмотреть на результат работы программы */

        system( "PAUSE" ) ; return 0 ;

    }

    /* getAccounts — загрузка массива счетов */

    void getAccounts( LinkedList< Account >* pLinkedList )

    {

        Account* pA ;

        /* Цикл, пока не введено 'X' или 'х' */

        char accountType ;         /* S or С */

        while ( true )

        {

            cout << "Введите S для сберегательного счёта,\n"

                   << "С для чекового, X для выхода: " ;

            cin >> accountType ;

            switch ( accountType )

            {

                case 'c' :

                case 'C' :

                  pA = new Checking( pLinkedList , getAccntNo( ) ) ;

                  break ;

                case 's' :

                case 'S' :

                  pA = new Savings( pLinkedList , getAccntNo( ) ) ;

                  break ;

                case 'x' :

                case 'X' :

                  return ;

                default :

                  cout << "Неверный ввод.\n" ;

            }

_________________

371 стр. . Программа BUDGET 

            /* Обработка вновь созданного объекта */

            process( pA ) ;

        }

    }

    /* displayResults — вывод информации о */

    /*               счетах в связанном списке */

    void displayResults( LinkedList< Account >* pLinkedList )

    {

        double total = 0.0 ;

        cout << "\nИтоговая информация: \n" ;

        for ( Node< Account >* pN = pLinkedList -> firstNode( ) ;

                                  pN != 0 ;

                                  pN = pN -> next( ) )

        {

            Account* pA = pN -> current( ) ;

            pA -> display( ) ;

            total += pA -> acntBalance( ) ;

        }

        cout << "Итого = " << total << "\n" ;

    }

    /* getAccntNo — номер счёта для его создания */

    unsigned getAccntNo( )

    {

        unsigned accntNo ;

        cout << "Введите номер счёта: " ;

        cin >> accntNo ;

        return accntNo ;

    }

    /* process( Account ) — обработка счёта */

    void process( Account* pAccount )

    {

        cout << "Введите положительное число для вклада,\n"

                 << "отрицательное для снятия,"

                 << " 0 для завершения работы\n" ;

        double transaction ;

        do

        {

            cout << " : " ;

            cin >> transaction ;

            // Вклад

            if ( transaction > 0 )

            {

                pAccount -> deposit( transaction ) ;

            }

            // Снятие

            if ( transaction < 0 )

            {

                pAccount -> withdrawal( -transaction ) ;

            }

        } while ( transaction != 0 ) ;

    }

_________________

372 стр. . Великолепная десятка

Первые строки перед определением класса Account инстанцируют шаблоны классов LinkedList и Node в классы LinkedList< Node > и Node< Account > соответственно. Эти строки создают классы, необходимые для объединения объектов Account в связанный список.

Другая программа может с тем же успехом создать класс LinkedList< Student >, LinkedList< Name >, или другой придуманный вами класс.

Класс Account работает так же, как и в предыдущей программе BUDGET3.    

 

Подведение итогов...373

Программа BUDGET3 использует объектно-ориентированные технологии для реализации набора классов счетов, а также включает реализацию связанного списка, который позволяет хранить неограниченное количество счетов. Единственная проблема заключается в том, что класс AccountLinkedList оказывается привязан к классу Account.

В программе BUDGET4 определён шаблон класса LinkedList< class Т >, который может работать с объектами любого типа. В этой версии единственной проблемой ( если это можно считать проблемой ) является то, что нам надо самим реализовывать связанный список.

Связанные списки используются уже очень давно, так что мы изобретаем велосипед. И хотя с методической точки зрения это совсем неплохо, в STL имеется соответствующий шаблон класса, который можно использовать в качестве контейнера ( в STL имеется масса различных контейнеров ). В следующей версии программы BUDGET мы используем этот предопределённый шаблон для хранения объектов Account.         

 

►BUDGET5...373

 

Программа BUDGET5 — последняя в длинном ряду программ этой главы, призванных решать одну и ту же простую задачу имитации банковских счетов. В программе BUDGET3 эти счета представлены в виде трёх классов — Account, Savings и Checking, для хранения которых используется связанный список. Из жёстко кодированного списка для хранения объектов типа Account в программе BUDGET4 он становится шаблоном класса LinkedList< class Т >.

Программа BUDGET5 идёт на один шаг дальше и использует шаблон класса из STL.

#i_357.jpg

«Для того чтобы понять эту программу, вы должны ознакомиться с главой 28, "Стандартная библиотека шаблонов" .»

[]

 

Использование шаблона класса из STL...373  

Стандартная библиотека шаблонов определяет множество различных типов контейнеров, которые можно использовать для хранения групп объектов. В следующей версии программы BUDGET используется шаблон класса list.

    /* BUDGET5.CPP — идентична другим программам */

    /*              Budget за исключением использования */

    /*              шаблона класса из STL */

    #include

    #include

    #include

    #include   

_________________

373 стр. . Программа BUDGET 

    using namespace std ;

    /* Account — абстрактный класс, включающий */

    /*            общие свойства различных счетов */

    class Account

    {

      public :

        Account::Account( unsigned accNo )

        {

            /* Инициализация данных-членов */

            accountNumber = accNo ;

            balance = 0 ;

            count++ ;

        }

        /* Функции доступа */

        int accountNo( ) { return accountNumber ; }

        double acntBalance( ) { return balance ; }

        static int noAccounts( ) { return count ; }

        /* Функции транзакций */

        void deposit( double amount ) { balance += amount ; }

        virtual bool withdrawal( double amount )

        {

            if ( balance < amount )

            {

                cout <<"Недостаточно денег: на счету " << balance

                        <<", снимаем "              << amount

                        << endl ;

                return false ;

            }

            balance -= amount ;

            return true ;

        }

        /* Функция вывода на экран */

        void display( )

        {

            cout << type( )

                  << " счёт " << accountNumber

                  << " = " << balance

                  << endl ;

        }

        virtual char* type( ) { return "Account" ; }

      protected :

        static int count ; /* Количество счетов */

        unsigned accountNumber ;

        double balance ;

    } ;

    /* Переменная для сбора статистики */

    int Account::count = 0 ;

    /* Checking — свойства, уникальные для чекового счёта */

    class Checking : public Account

    {

_________________

374 стр. . Великолепная десятка

        public :

        Checking::Checking( unsigned accNo ) :

           Account( accNo )

        { }

        /* Перегрузка чисто виртуальных функций */

        virtual bool withdrawal( double amount ) ;

        virtual char* type( ) { return "Чековый" ; }

    } ;

    /* withdrawal — перегрузка Account::withdrawal( ) */

    bool Checking::withdrawal( double amount )

    {

        bool success = Account::withdrawal( amount ) ;

        if ( success && balance < 500.00 )

        {

            balance -= 0.20 ;

        }

        return success ;

    }

    /* Savings — свойства, уникальные для сберегательного счёта */

    class Savings : public Account

    {

      public :

        Savings::Savings( unsigned accNo ) : Account( accNo )

        { noWithdrawals = 0 ; }

        /* Функции транзакций */

        virtual bool withdrawal( double amount ) ;

        virtual char* type( ) { return "Сберегательный" ; }

      protected :

        int noWithdrawals ;

    } ;

    /* withdrawal — перегрузка Account::withdrawal( ) */

    bool Savings::withdrawal( double amount )

    {

        if ( ++noWithdrawals > 1 )

        {

            balance -= 5.00 ;

        }

        return Account::withdrawal( amount ) ;

    }

    /* AccountPtr — мы храним указатели на объекты */

    /*                      Account, а не сами объекты */

    typedef Account* AccountPtr ;

    /* Прототипы функций */

    unsigned getAccntNo( ) ;

    void   process( AccountPtr pAccount ) ;

    void   getAccounts( list< AccountPtr >& accList ) ;

    void   displayResults( list< AccountPtr >& accList ) ;

_________________

375 стр. . Программа BUDGET 

    /* main — собирает и выводит данные */

    int main( int argcs , char* pArgs[ ] )

    {

        setlocale ( LC_ALL , ".1251" ) ; /* печать кириллицы */

        /* Создание связанного списка */

        list< AccountPtr > listAccounts ;

        /* Чтение пользовательского ввода */

        getAccounts( listAccounts ) ;

        /* Вывод связанного списка */

        displayResults( listAccounts ) ;

        /* Пауза для того, чтобы посмотреть на результат работы программы */

        system( "PAUSE" ) ; return 0 ;

    }

    /* getAccounts — загрузка массива счетов */

    void getAccounts( list< AccountPtr >& accList )

    {

        AccountPtr pA ;

        /* Цикл, пока не введено 'X' или 'х' */

        char accountType ;          /* S or С */

        while ( true )

        {

            cout << "Введите S для сберегательного счёта,\n"

                   << "С для чекового, X для выхода: " ;

            cin >> accountType ;

            switch ( accountType )

            {

                case 'c' :

                case 'C' :

                  pA = new Checking( getAccntNo( ) ) ;

                  break ;

                case 's' :

                case 'S' : 

                  pA = new Savings( getAccntNo( ) ) ;

                  break ;

                case 'x' :

                case 'X' :

                  return ;

                default :

                  cout << "Неверный ввод.\n" ;

            }

            /* Обработка вновь созданного объекта */

            accList.push_back( pA ) ;

            process( pA ) ;

        }

    }

_________________

376 стр. . Великолепная десятка

    /* displayResults — вывод информации о */

    /*                счетах в связанном списке */  

    void displayResults( list< AccountPtr >& accntList )

    {

        double total = 0.0 ;

        cout << "\nИтоговая информация:\n" ;

        /* Создание итератора и проход по списку */

        list< AccountPtr >::iterator iter ;

        iter = accntList.begin( ) ;

        while ( iter != accntList.end( ) )

        {

            AccountPtr pAccount = *iter ;

            iter++ ;

            pAccount -> display( ) ;

            total += pAccount -> acntBalance( ) ;

        }

        cout << "Итого = " << total << endl ;

    }

    /* getAccntNo — номер счёта для его создания */

    unsigned getAccntNo( )

    {

        unsigned accntNo ;

        cout << "Введите номер счёта: " ;

        cin >> accntNo ;

        return accntNo ;

    }

    /* process( Account ) — обработка счёта */

    void process( AccountPtr pAccount )

    {

        cout << "Введите положительное число для вклада,\n"

              << "отрицательное для снятия,"

              << "0 для завершения работы\n" ;

        double transaction ;

        do

        {

            cout << ":" ;

            cin >> transaction ;

            // Вклад

            if ( transaction > 0 )

            {

                pAccount -> deposit( transaction ) ;

            }

            // Снятие

            if ( transaction < 0 )

            {

                pAccount -> withdrawal( -transaction ) ;

            }

        } while ( transaction != 0 ) ;

    }

Заголовочный файл list содержит определение шаблона класса list из STL. Классы Account, Checking и Savings остаются неизменными ( т.е. такими, как в программе BUDGET3 ). Изменения начинаются с определения типа AccountPtr примерно в середине программы. 

_________________

377 стр. . Программа BUDGET 

 

Создание списка счетов...378  

Функция main( ) создаёт список объектов listAccounts, который имеет тип list< AccountPtr >. 

Теоретически я могу реализовать шаблон класса как list< Account* >, но так поступают редко — дабы не портить определения внутри шаблонов классов STL, обычно используют синонимы указателей, полученные при помощи оператора typedef.

#i_358.jpg

«Тип AccountPtr определён с использованием ключевого слова typedef и представляет собой то же, что и Account* . Таким образом, везде, где написано AccountPtr , можно читать "указатель на Account ".»

[]

Функция main( ) передаёт список указателей на объекты Account функциям getAccounts( ) и displayResults( ). Метод getAccounts( ) добавляет объекты Account в конец списка при помощи функции-члена push_back( ).

Функция displayResults( ) может удалять объекты Account из списка при помощи одного из предназначенных для этой цели методов; однако это будет так называемое деструктивное чтение, которое изменяет список ( в нашем случае — удаляя из него объекты ). Поскольку мы хотим иметь возможность работать со списком и после вывода его на экран, нам надо воспользоваться итератором — объектом, который указывает на объекты в списке. Программа в цикле перемещает итератор от одного элемента списка к следующему.

Функция displayResults( ) определяет итератор iter в начале цикла while( ). Присвоение iter = accntList.begin( ) инициализирует объект iter первым элементом списка. Значение accntList.end( ) представляет собой "объект, непосредственно следующий за последним объектом в контейнере". Таким образом, цикл должен полностью обойти весь список к моменту, когда iter становится равным accntList.end( ). Выражение *iter даёт нам то, что можно назвать "текущим объектом", а выражение iter++ перемещает итератор к следующему объекту в списке.

В остальном программа BUDGET5 эквивалентна программам BUDGET4 и BUDGET3.

_________________

378 стр. . Великолепная десятка