Найти в Дзене

Объектно-ориентированное программирование(ООП) в C++

В этой статье будет рассказано: Объектно-ориентированное программирование (ООП) — это особый
концептуальный подход к проектированию программ, и C++ расширяет язык С средствами, облегчающими применение такого подхода. Ниже перечислены наиболее важные характеристики ООП: • абстракция;
• инкапсуляция и сокрытие данных;
• полиморфизм;
• наследование;
• повторное использование кода. Класс — это единственное наиболее важное расширение C++, предназначенное для реализации этих средств и связывающее их между собой. Настоящая статья начинается с объяснения концепции классов. Здесь рассмотрены абстракция, инкапсуляция и сокрытие данных, а также показано, как эти средства реализуются в классах. В этой части статьи рассказывается об определении класса, о предоставлении класса с открытым и закрытым разделами, а также о создании функций-членов, которые работают с данными класса. Кроме того, вы ознакомитесь с конструкторами и деструкторами, которые представляют собой специальные функции-член
Оглавление

В этой статье будет рассказано:

  • ООП
  • Процедурное и объектно-ориентированное программирование
  • Абстракции и классы
  • Что такое тип
  • классы в С++
  • Что такое интерфейс
  • Управление доступом
  • Объектно-ориентированное программирование и С++
  • Управление доступом к членам: puЫic или private
  • Классы и структуры

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

• абстракция;
• инкапсуляция и сокрытие данных;
• полиморфизм;
• наследование;
• повторное использование кода.

Класс — это единственное наиболее важное расширение C++, предназначенное для реализации этих средств и связывающее их между собой. Настоящая статья начинается с объяснения концепции классов. Здесь рассмотрены абстракция, инкапсуляция и сокрытие данных, а также показано, как эти средства реализуются в классах. В этой части статьи рассказывается об определении класса, о предоставлении класса с открытым и закрытым разделами, а также о создании функций-членов, которые работают с данными класса. Кроме того, вы ознакомитесь с конструкторами и деструкторами, которые представляют собой специальные функции-члены, предназначенные для создания и уничтожения объектов, относящихся к классу. И, наконец, вы узнаете, что такое указатель this — важный компонент для программирования некоторых классов. В последующих статьях обсуждение будет продолжено описанием перегрузки операций (другая разновидность полиморфизма) и наследования — фундамента для повторного использования кода.

Процедурное и объектно-ориентированное
программирование

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

Предположим, что вас, как нового члена софтбольной команды "Гиганты Жанра", попросили вести статистику игр команды. Естественно, вы используете для этого компьютер. Если вы — сторонник процедурного программирования, то, скорее всего, будете думать примерно так, как описано ниже.

Итак, посмотрим... Я хочу вводить имя, количество подач, количество
попаданий, средний уровень успешных подач (для тех, что не следит за бейсболом или софтболом: средний уровень успешных подач — это количество попаданий, деленное на официальное количество подач, выполненных игроком; подачи прекращаются, когда игрок достигает базы либо выбивает мяч за пределы поля, но некоторые события, такие как получение обводки, не засчитываются в официальное количество подач), а также другую существенную статистику по каждому игроку. Минутку, но ведь предполагается, что компьютер должен мне облегчать жизнь, поэтому я хочу, чтобы он вычислял автоматически кое-что из этого, например, средний уровень успешных подач. Кроме того, я хочу, чтобы программа выдавала отчет по результатам. Как это организовать? Думаю, это следует делать с помощью функций. Да, я сделаю, чтобы main () вызывала функцию для получения ввода, затем другую функцию — для вычислений, а потом третью — для вывода результатов. Гм, а что случится, когда я получу данные о другой игре? Я не хочу начинать все сначала! Ладно, я могу добавить функцию для обновления статистики. Ну и ну, возможно, мне понадобится меню в main (), чтобы выбирать между вводом, вычислением, обновлением и выводом данных. Гм, а как представить данные? Можно использовать массив строк для хранения имен игроков, другой массив — для хранения числа подач каждого игрока и еще один массив — для хранения числа попаданий и т.д. Нет, это глупость! Я могу спроектировать структуру, чтобы хранить всю информацию о каждом игроке и затем использовать массив таких структур для представления всей команды.

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

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

Итак, посмотрим, что я должен отслеживать? Игроков, конечно. Поэтому мне нужен объект, который представляет игрока в целом, а не только его уровень успешных подач или их общее количество. Да, это будет основная единица данных — объект, представляющий имя и статистику игрока. Мне понадобятся некоторые методы для работы с этим объектом. Гм, думаю, понадобится метод для ввода базовой информации в объект. Некоторые данные должен вычислять компьютер, например, средний уровень успешных подач. Я могу добавить метод для реализации вычислений. И программа должна выполнять вычисления автоматически, чтобы пользователю не нужно было помнить о том, что он должен
просить ее об этом. Кроме того, понадобятся методы для обновления и
отображения информации. То есть у пользователя должно быть три способа взаимодействия с данными: инициализация, обновление и вывод отчетов. Это и будет пользовательский интерфейс.

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

Абстракции и классы

Жизнь полна сложностей, и единственный способ справится со сложностью — это ограничиться упрощенными абстракциями. Человек состоит из более 1048 атомов. Некоторые утверждают, что сознание представляет собой коллекцию множества полуавтономных агентов. Но гораздо проще думать о себе, как о едином целом. В компьютерных вычислениях абстракция — это ключевой шаг в представлении информации в терминах ее интерфейса с пользователем. То есть вы абстрагируете основные операционные характеристики проблемы и выражаете решение в этих терминах. В примере с софтбольной статистикой интерфейс описывает, как пользователь инициирует, обновляет и отображает данные. От абстракций легко перейти к определяемым пользователем типам, которые в C++ представлены классами, реализующими абстрактный интерфейс.

Что такое тип?

Давайте подумаем немного о том, что собой представляет тип. Например, кто такой зануда? Если следовать популярному стереотипу, его можно представить в визуальных образах: толстый, в запотевших очках, карман, полный ручек, и т.п. После некоторых размышлений вы можете прийти к заключению, что зануду лучше описать с помощью присущего ему поведения — например, его реакцией в неловких ситуациях.
Вы находитесь в похожем положении, если вы не имеете в виду искусственные аналогии, с процедурным языком, таким как С. Прежде всего, вы думаете о типе данных в терминах того, как он выглядит — каким образом хранится в памяти. Например, тип char занимает 1 байт памяти, a double — обычно 8 байт. Но если немного подумать, то вы придете к заключению, что тип данных также определен в терминах операций,
которые допустимо выполнять с ним. Например, к типу int можно применять все арифметические операции. Целые числа можно складывать, вычитать, умножать, делить. Можно также применять операцию взятия модуля (%).

С другой стороны, рассмотрим указатели. Указатель может требовать для своего хранения столько же памяти, сколько и тип int. Он даже может иметь внутреннее представление в виде целого числа. Но указатель не позволяет выполнять над собой те же операции, что и целое. Например, перемножить два указателя не удастся. Эта концепция не имеет смысла, поэтому в C++ она не реализована. Таким образом, когда вы объявляете переменную как int или указатель на float, то не просто выделяете память для нее, но также устанавливаете, какие операции допустимы с этой переменной. Короче говоря, спецификация базового типа выполняет три вещи.

• Определяет, сколько памяти нужно объекту.
• Определяет, как интерпретируются биты памяти. (Типы long и float могут
занимать одинаковое количество бит памяти, но транслируются в числовые
значения по-разному.)
• Определяет, какие операции, или методы, могут быть применены с
использованием этого объекта данных.

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

Классы в C++

Класс — это двигатель C++, предназначенный для трансляции абстракци^ в пользовательские типы. Он комбинирует представление данных и методов для манипулирования этими данными в пределах одного аккуратного пакета. Давайте взглянем на класс, представляющий акционерный капитал.

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

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

• приобретение пакета акций компании;
• приобретение дополнительных акций в имеющийся пакет;
• продажа пакета;
• обновление объема доли в пакете акций;
• отображения информации о пакетах, находящихся во владении.

Этот список можно использовать для определения открытого интерфейса класса (и при необходимости позже добавлять дополнительные средства). Для поддержки этого интерфейса необходимо сохранять некоторую информацию. И снова можно воспользоваться упрощенным подходом. Например, не нужно беспокоиться о принятой в США практике оперировать пакетами акций в объемах, кратных 8 долларам.
(Видимо, на Нью-Йоркской фондовой бирже заметили это упрощение в предыдущем издании книги, потому что решили перейти к системе, которая описана здесь.) Ниже приведен список сведений для сохранения:

• название компании;
• количество акций, находящихся во владении;
• объем каждой доли;
• общий объем всех долей.

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

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

Грубо говоря, объявление класса предоставляет общий обзор класса, в то время как
определения методов снабжают необходимыми деталями.

Что такое интерфейс?
Интерфейс — это совместно используемая часть, предназначенная для взаимодействия двух систем, например, между компьютером и принтером или между пользователем и компьютерной программой. Например, пользователем можете быть вы, а программой — текстовый процессор. Когда вы работаете с текстовым процессором, то не переносите слова
напрямую из своего сознания в память компьютера. Вместо этого вы взаимодействуете с интерфейсом, предложенным программой. Вы нажимаете клавишу, и компьютер отображает символ на экране. Вы перемещаете мышь, и компьютер перемещает курсор на экране. Вы случайно щелкаете кнопкой мышки, и абзац, в котором вы печатаете, искажается. Интерфейс программы управляет преобразованием ваших намерений в специфическую информацию, сохраняемую в компьютере. В отношении классов мы говорим об открытом интерфейсе. В этом случае потребителем его является программа, использующая класс, система взаимодействия состоит из объектов класса, а интерфейс состоит из методов, предоставленных тем, кто написал этот класс. Интерфейс позволяет вам, как программисту, написать код, взаимодействующий с объектами класса, и таким образом, дает программе возможность взаимодействовать с объектами класса.
Например, чтобы определить количество символов в объекте string, вам не нужно открывать этот объект и смотреть что у него внутри. Вы просто используете метод size () класса, предоставленный его разработчиком. Таким образом, метод size () является частью открытого интерфейса между пользователем и объектом класса string. Аналогичным образом метод getline () является частью открытого интерфейса класса istream. Программа, использующая сіn, не обращается напрямую к внутренностям объекта сіn для чтения строки ввода; вместо этого всю работу выполняет getline ().
Если вам нужны более персонализированные отношения, то вместо того, чтобы думать о
В программе, использующей класс, как о внешнем пользователе, вы можете думать об авторе программы, использующей этот класс, как о внешнем пользователе. Но в любом случае для работы с классом необходимо знать его открытый интерфейс, и для написания класса потребуется создать такой интерфейс.

Разработка классов и программ, которые их используют, требует выполнения определенных шагов. Вместо того чтобы взять на вооружение их целиком, давайте разделим процесс разработки на небольшие стадии. Обычно программисты на C++ помещают интерфейс, имеющий форму определения класса, в заголовочный файл, а реализацию в форме кода для методов класса — в файл исходного кода. Так что давайте не отступать от обычной практики. В программе ниже представлена первая стадия,
пробное объявление класса под именем Stock. В этом файле применяется *знак решетки*ifndef и т.п. для защиты против многократного включения файла.

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

*знак решетки*ifndef STOCK00_H_
*знак решетки*define STOCK00_H_
*знак решетки*include <string>
class Stock // объявление класса
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot() { total_val = shares * share_val; }
public:
void acquire (const std:: string & со, long n, double pr) ;
void buy (long num, double price);
void sell(long num, double price);
void update(double price);
void show () ; ,
}; // Обратите внимание на точку с запятой в конце.
*знак решетки*endif

На детали реализации класса мы взглянем позднее, а сейчас проанализируем наиболее общие средства. Первым делом, ключевое слово class в C++ идентифицирует код в программе выше как определение класса. (В таком контексте ключевые слова class и typename не являются синонимами, как это было в параметрах шаблона; typename здесь использовать нельзя.) Данный синтаксис идентифицирует Stock в качестве имени типа для нового класса. Это позволяет объявлять переменные, которые называются объектами или экземплярами типа Stock. Каждый индивидуальный объект этого типа представляет отдельный пакет акций, находящийся во владении. Например, следующее объявление создает два объекта с именами sally и sollу:

Stock sally;
Stock solly;

Объект sally, например, мог бы представлять пакет акций определенной
компании, принадлежащий Салли.

Далее обратите внимание, что информация, которую вы решили сохранять, появляется в форме данных-членов класса, таких как company и shares. Член company объекта sally, например, хранит название компании, член share содержит количество долей общего пакета акций компании, которыми владеет Салли, член shareval соответствует объему каждой доли, а член totalval — общему объему всех долей. Аналогично необходимые операции представлены в виде функций-членов (или методов), таких как sell () и update (). Функция-член может быть определена на месте, как, например, settot (), либо подобно остальным функции-члены в этом классе представлена с помощью прототипа. Полные определения остальных функций-членов появятся позже, в файле реализации, но прототипов уже достаточно для описания их интерфейса. Связывание данных и методов в единое целое — наиболее замечательное свойство класса. При таком проектном решении создание объекта типа Stock автоматически устанавливает правила, регулирующие его использованием.

Вы уже видели, что классы istream и ostream имеют функции-члены вроде get () и getline (). Прототипы функций в определении класса Stock демонстрируют установку функций-членов. Заголовочный файл iostream, например, содержит прототип getline () в объявлении класса iostream.

Управление доступом

Новыми также являются ключевые слова private и public. Эти метки
позволяют управлять доступом к членам класса. Любая программа, которая использует объект определенного класса, может иметь непосредственный доступ к членам из раздела public. Доступ к членам объекта из раздела private программа может получить только через открытые функции-члены из раздела public (или же через дружественные функции). Например, единственный способ изменить переменную shares класса Stock — это воспользоваться одной из функций- членов класса Stock. Таким образом, открытые функции-члены действуют в качестве
посредников между программой и закрытыми членами объекта; они предоставляют интерфейс между объектом и программой. Эта изоляция данных от прямого доступа со стороны программы называется сокрытием данных. (В C++ имеется третье ключевое слово для управления доступом — protected, которое объясняется при обсуждении наследования) Все сказанное иллюстрируется на рисунке ниже. В то время как сокрытие данных может быть недобросовестным действием, когда мы говорим в
общепринятом контексте о доступе к информации инвестиционных фондов, это является хорошей практикой в компьютерных вычислениях, поскольку предохраняет целостность данных.

-2

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

Объектно-ориентированное программирование и C++

Объектно-ориентированное программирование (ООП) — это стиль программирования, который в той или иной степени можно применять в любом языке. Безусловно, вы можете реализовать многие идеи ООП в обычной программе на языке С. Функция main () просто определяет переменные этого типа и использует ассоциированные функции для управления этими переменными; main () не имеет непосредственного
доступа к членам структур. По сути, в этом примере объявляется абстрактный тип, который помещает формат хранения и прототипы функций в заголовочный файл, скрывая реальное
представление данных от main ().
Язык C++ включает средства, специально предназначенные для реализации подхода ООП, что позволяет продвинуться в этом направлении на несколько шагов дальше, чем в языке С. Во-первых, размещение прототипов функций в едином объявлении класса вместо того, чтобы держать их раздельно, унифицирует описание за счет размещения его в одном месте. Во-вторых, объявление данных с закрытым доступом разрешает доступ к ним только для
авторизованных функций. Если в примере на С функция main () имеет непосредственный доступ к членам структуры, то это противоречит духу ООП, но не нарушает никаких правил языка С. Однако попытка прямого доступа, скажем, к члену shares объекта stock приводит
к нарушению правила языка C++, и компилятор перехватит это.

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

Управление доступом к членам: public или private?

Объявлять члены класса — будь они элементами данных или функциями-членами — можно как в открытом (public), так и в закрытом (private) разделе класса. Но поскольку одним из главных принципов ООП является сокрытие данных, то единицы данных обычно размещаются в разделе private. Функции-члены, которые образуют интерфейс класса, размещаются в разделе public; в противном случае вызвать эти функции из программы не удастся. Как показывает объявление класса Stock, вы все же можете поместить функции-члены в раздел private. Вызвать такие функции из программы непосредственно не получится, но их могут использовать открытые методы. Как правило, закрытые функции-члены применяются для управления деталями
реализации, которые не формируют часть открытого интерфейса.

Использовать ключевое слово private в объявлении класса не обязательно,
поскольку это спецификатор доступа к объектам класса по умолчанию:

class World
{
float mass; // по умолчанию private
char name [20]; // по умолчанию private
public:
void tellall(void);
. . .
}

Однако в этой книге метка private будет указываться явно, чтобы подчеркнуть концепцию сокрытия данных.

Классы и структуры

Описания классов выглядят очень похожими на объявления структур с дополнениями в виде функций-членов и меток видимости private и public. Фактически C++ расширяет на структуры те же самые свойства, которые есть у классов. Единственная разница состоит в том, что типом доступа по умолчанию у структур является public, в то время как у клаcсов — private. Программисты на C++ обычно используют классы для реализации описаний классов, тогда как ограниченные структуры применяются для чистых объектов данных (которые часто называются простыми старыми структурами данных (plain-old data — POD)).

Конец

Спасибо, если вы прочитали эту статью. Надеюсь вы что-то новое узнали для себя, и конечно же поняли. Подпишитесь, поставьте лайк, напишите комментарий, поддержите меня. Хотелось бы увидеть как улучшать статьи и чего не хватает. Буду анализировать и улучшать контент. Еще раз спасибо, до свидания! :)

#ооп #c++ #языкипрограммирования #классы #структура #компилятор #разработка #программирование #программа #интерфейс