Класс – это разновидность абстрактного типа данных в ООП.
Объект – это некоторая сущность в виртуальном пространстве, обладающая определенным состоянием и поведением, имеющая заданные значения свойств (атрибутов) и операций над ними (методов). Объекты принадлежат одному или нескольким классам, которые определяют поведение (являются моделью) объекта. Термины “экземпляр класса” и “объект” взаимозаменяемы. Объекты обладают свойствами наследования, инкапсуляции и полиморфизма.
Событие – это то, что может случиться с объектом при определенных условиях (например, с кнопкой при клике на нее мышкой), событие может вызывать один или несколько методов.
Весь реальный мир состоит из объектов. Практически любой материальный предмет можно представить в виде совокупности объектов, из которых он состоит.
Так же и программы часто имеют дело с совокупностями данных:
- имя,
- фамилия,
- отчество,
- профессия,
- должность,
- серия и номер паспорта и т.п.
Каждая отдельная составляющая не описывает человека, смысл имеет только вся вместе взятая информация. Такая простая структура, как массив, хорошо подходит для хранения отдельных значений, однако совершенно непригодна для хранения целокупности данных разных типов. Поэтому была создана такая структура, как класс, умеющая объединять несколько разнотипных переменных в одном объекте.
Пример:
Класс, описывающий объект, который содержит имя, фамилию человека и номер его кредитки, может быть создан следующим образом:
class PersonalData
{
public:
char firstname [128];
char lastname [128];
int card;
};
// pd - экземпляр класса PersonalData:
PersonalData pd;
Объявление класса начинается с ключевого слова class которого идет имя класса и фигурных скобок, открывающих и закрывающих тело класса.
После открывающей скобки идет модификатор доступа public – все функции и переменные, которые находятся после него, становятся доступными из всех частей программы.
После ключевого слова public идет описание полей класса. Класс PersonalData содержит поля имени, фамилии и номера кредитки.
Затем объявлена переменная pd, которая имеет тип PersonalData. Таким образом, pd представляет собой запись, описывающую отдельного человека. pd – это экземпляр класса PersonalData. Мы создали этот объект, реализовав классPersonalData.
Поля firstname, lastname и card – это члены класса или свойства класса.
Обратиться к членам созданного класса можно так:
PersonalData pd;
pd.card = 123;
cin >>
pd.firstname;
cin >>
pd.lastname;
Здесь pd – экземпляр класса PersonalData (т.е. отдельный объект типа PersonalData), а целочисленная переменная pd.card – свойство объекта pd.
В этом фрагменте программы происходит объявление объекта pd, который затем может быть использован для описания покупателя. Программа присваивает номер кредитки, а затем считывает имя и фамилию из стандартного ввода. Для хранения имени используется массив символов вместо типа string.
Теперь программа может работать с объектом типа pd как с единым целым, не обращаясь к его отдельным частям, пока в этом не возникнет необходимость:
Итак, классы используются для объединения взаимосвязанных данных в один объект. Приведенный ниже класс Savings (англ. – сбережения) объединяет в себе баланс и уникальный номер счета:
class Savings
{
public:
unsigned accountNumber; float balance;
};
Каждый экземпляр класса Savings содержит одинаковые элементы:
int main ()
{
Savings a; Savings b;
a.accountNumber = 1; // это один счет
b.accountNumber = 2; // а это другой счет
}
Переменная a.accountNumber отличается от переменной b.accountNumber так же, как баланс банковского счета Иванова отличается от баланса банковского счета Петрова, хотя они оба называются балансами.
Чтобы обеспечить ввод/вывод данных для нескольких объектов и избежать при этом дублирования кода, лучше создать отдельные функции для ввода и вывода данных.
1. Описать класс FamilyMember. Свойства: имя, роль, возраст. Создать 3 объекта и 1 внешнюю функцию, которая принимает данные по возрасту и считает среднее арифметическое. В функции main() вывести на экран средний возраст.
Пример результата:
2. Описать класс со свойствами для заданной предметной области (по вариантам).
Метод в ООП – это функция или процедура, принадлежащая какому-то классу или объекту. Как и процедура в процедурном программировании, метод состоит из некоторого количества операторов для выполнения какого-либо действия и имеет набор входных аргументов. Методы предоставляют интерфейс, при помощи которого осуществляется доступ к данным объекта некоторого класса, тем самым обеспечивая инкапсуляцию данных.
Поскольку классы используются для моделирования реально существующих объектов, то чем ближе объекты C++ к реальному миру, тем проще с ними работать в программах. Реальные объекты имеют свойства-данные, например номера счетов и балансы. Но кроме этого, реальные объекты могут выполнять действия, например, сберегательный счет может начислять проценты, микроволновая печь может готовить, карандаш может писать.
Функционально ориентированные программы выполняют все необходимые действия с помощью функций. Программа на C++ может вызвать функцию strcmp() для сравнения двух строк или функцию getline() для ввода строк. Даже операторы работы с потоками ввода-вывода cout<< и cin>> являются особым видом вызова функции.
Пример:
Для выполнения действий классу Saving необходимы собственные активные свойства.
В этом примере помимо номера и баланса в класс Saving добавлен метод Vklad(). Теперь класс Saving может самостоятельно управлять своим состоянием.
Инкапсуляция метода
Пример:
Пользователь получает денежные средства со своего банковского счета: вводит сумму, затем получает купюры или сообщение от банкомата об отсутствии указанной суммы в самом банкомате (не на счете). При этом у пользователя не должно быть доступа к информации о том, сколько денег в данный момент лежит в банкомате.
Результат:
Описание программы.
В функции main():
создан объект cash класса Cash, и через этот объект производится вызов метода getCash(5000);
В описании класса:
В интерфейсе класса (т.е. в части public) метод getCash() принимает значение 5000 и записывает его в свой параметр – summa. Затем в условной конструкции этот метод обращается к инкапсулированной (защищенной) части класса – методу isThereMoney(int sum) – и передает ему значение 5000 в параметр sum.
В этой же инкапсулированной области есть свойство класса cashInBank, которое по чистой случайности инициализировано тоже значением 5000. В методе isThereMoney() проверяется, меньше ли полученное от пользователя значение sum, чем то, которое хранит в себе переменная cashInBank. Если да, то метод isThereMoney() вернет true, иначе вернет false.
true или false возвращается в условие и срабатывает либо первая ветка ветвления, либо вторая. Далее управление возвращается в main().
Разрешение области видимости
Символ :: между именем класса и именем его члена называется оператором разрешения области видимости, поскольку он указывает, какой области видимости принадлежит член класса. Имя класса перед двоеточиями похоже на фамилию, тогда как название функции после двоеточия схоже с именем.
С помощью оператора :: можно также описать функцию–не член, использовав для этого пустое имя класса. В этом случае функция addCourse() должна быть описана как ::addCourse(int,float)
Определение методов вне класса
Для больших функций встраивание тела функции непосредственно в определение класса может привести к созданию очень больших и нечитабельных определений классов. Чтобы избежать этого, C++ предоставляет возможность определять тела методов вне класса. В этом случае в заголовочном файле имеется только объявление, но не определение функции.
Определение класса содержит только прототип метода vklad(), а его тело определено в другом месте: в main() или, чаще всего, в отдельном файле.
При определении метода vklad() требуется указывать полное имя метода: void Savings::vklad (double summa):
Результат работы программы не изменится.
Передача объекта в метод
Задача с решением.
Робинзон Крузо вёз в Европу семена бамбука, но его лодка потерпела крушение у берегов необитаемого острова. Уже наученный горьким опытом Робинзон решил сразу посадить все семена, чтобы уплыть на плоту, построенном из бамбука. Всем известно, что бамбук растет 2 см/час, а для плота необходимы прутья длиной не менее 3 метров. Необходимо написать программу, которая поможет рассчитать, сколько же радостных дней проведет Робинзон на этом острове.
Затея с бамбуковым плотом может оказаться утопической. Поэтому нужно предусмотреть возможность расширения функционала программы и сразу описывать решение с использованием классов.
Описание класса:
Указатели на объекты работают так же, как и указатели на простые типы.
Этот попытка разыменования работать не будет, потому что оператор “.” будет выполнен раньше оператора “*”. Для изменения очередности можно использовать ():
Теперь *pBamboo вычисляет объект, на который указывает pBamboo, а следовательно, .nLength обращается к свойству этого объекта.
Для доступа к свойствам и методам объекта C++ может предоставить более удобный оператор ->, позволяющий избежать неуклюжей конструкции со скобками и оператором *;
таким образом, pBamboo -> nLength эквивалентно (*pBamboo). nLength:
При вызове метода можно передать параметр так же через разыменование указателя или обычным способом через объект.
Результат:
При этом параметры передаются по значению, т.е. передаются копии значений, и если изменить значение параметров внутри метода, то всё равно в функции main() они сохранят свои прежние значения.
1. Изменим в методе значения свойств:
2. В main() выведем значения свойств после обращения к методу:
3. На выводе увидим, что cmLength и cmHourGrow не получили новых значений из метода, несмотря на то, что cmLength передавался особым образом:
Задача 1
Затея с бамбуковым плотом всё-таки оказалась утопической. Поэтому Робинзон решил остаться жить на острове и построить домик на бамбуке. Но для этого надо точно рассчитать максимальную массу дома. Всем известно, что каждый бамбук может выдержать 3 кг груза, а Робинзон вырастил 250 крепких бамбуков.
Затея с домом на дереве может оказаться провальной. Поэтому нужно предусмотреть возможность расширения функционала программы и сразу описывать решение с использованием классов.
Также необходимо вывести для Робинзона информацию о том, на какой высоте он окажется через год, если высота бамбука на момент установки дома уже была 10 метров, а скорость роста бамбука увеличилась вдвое.
Таким образом, необходимо описать 1 класс, 2 метода, 6 свойств, 1 объект. Использовать указатели на свойства и методы.
Результат работы программы должен быть следующим:
Передача объектов в функции
Объекты могут быть переданы в функции тем же способом, что и обычные переменные.
Вызов метода (функции) с передачей объекта по значению
Передача объекта по значению означает, что создается копия объекта, которая и передается функции. Однако тот факт, что создается копия, означает по существу, что создается другой объект.
В следующей программе функция main() создает объект vklad, а затем передает его в метод display(). Осуществляется передача по значению не самого объекта, а его копии. Объект copyVklad начинает свое существование внутри метода display() и является точной копией объекта vklad из main().
Попытка изменить значение объекта в методе (а также во внешней функции) оказывается неуспешной при передаче объекта по значению:
Вызов метода (функции) с передачей указателя
Вместо того, чтобы передавать объект по значению, можно передавать в функцию указатель на объект.
В следующем примере аргумент, передаваемый в Display(), имеет тип указателя на объект myVklad, что записывается как Vklad* (это отражает способ вызова программной функции Display()). Теперь вместо значения объекта myVklad в функцию Display() передается указатель на объект myVklad. При этом соответственно изменяется и способ обращения к аргументам функции внутри ее тела: теперь для разыменования указателя pMyVklad используются операторы-стрелки.
Успешное изменение значения свойства объекта в методе (а так же во внешней функции) при передаче указателя на объект:
Передача объекта по ссылке
Передача объекта по ссылке – всего лишь другой способ передачи в функцию адреса объекта. C++ самостоятельно отслеживает адрес ссылки, в то время как при передаче указателя программист делает это сам. Результат работы программы будет таким же.
Успешное изменение значения свойства объекта в функции при передаче с использованием ссылки:
Задача 2
Робинзон прожил 5 счастливых лет в домике на растущем бамбуке, питаясь только бамбуковыми листьями. У него было достаточно времени, чтобы смастерить смартфон и закрепить над крышей парашют. Однажды утром он проснулся от оглушительного хруста ломающихся стеблей бамбука. Робинзон решил снять улетное видео.
Отважному Робинзону срочно нужно узнать, сколько комментариев к видео он получит за полгода, если каждый день он собирается получать по 100 комментариев, и первый комментарий он планирует оставить сам.
Вся эта затея может ограничиться только комментариями к коду, поэтому, чтобы не опечалить Робинзона, нужно предусмотреть возможность расширения функционала программы и сразу описывать решение с использованием классов.
Результат работы:
1. Написать программу учета успеваемости студентов:
- Описать класс Student:
- public: массивы – фамилия, оценки(3),
- private: средний балл.
- Добавить в класс метод, который будет определять средний балл.
- Создать массив объектов (4 объекта).
- Получить данные по 4 студентам – getData().
- Вывести на экран введенные данные – displayData(), в том числе средний балл каждого студента.
Результат работы программы должен быть следующим:
2. Написать программу с использованием заголовочного файла для расчета времени в пути от Челябинска до Москвы и от Челябинска до Санкт-Петербурга. Известна средняя скорость автомобиля: 70.0 км/ч и расстояния:
- от Челябинска до Москвы 2100.0 км
- от Челябинска до Санкт-Петербурга 3000.0 км
Результат работы программы может быть следующим:
Статический массив объектов
Пример:
В функции main() создается массив из 25 объектов класса Dannye, после чего программа приглашает пользователя ввести информацию. Затем в теле цикла while происходит вызов функции getData(), которая ожидает ввода с клавиатуры содержимого элементов массива. Цикл прерывается, если getData() возвращает false или если количество заполненных объектов достигло максимального значения 25. После этого созданные объекты передаются функции displayData(), которая выводит их на экран.
Функция getData() принимает аргумент типа Dannye, которому внутри функции присваивается имя d. (Символ & здесь – это указатель на объект).
Внутри функции getData() происходит считывание строки из устройства стандартного ввода с последующей его записью в свойство firstname. Если _stricmp() находит, что введенная строка – “exit”, функция getData() возвращает false функции main(), сигнализируя, что пора выходить из цикла ввода информации. Функция _stricmp() сравнивает строки, не обращая внимания на регистр: строки “exit”, “EXIT”, “Exit” и т.д. считаются идентичными. Если введена строка, отличная от “exit”, функция считывает из стандартного ввода фамилию и номер кредитки и записывает их в объект d.
Результат работы программы:
Динамический массив объектов
Пример:
Результат работы программы:
Классы лучше описывать в отдельном заголовочном файле.
Создать проект, сохранить.
Затем Проект ->добавить класс:
Изменить Имя класса:
Затем подключить заголовочный файл.
Пример:
Написать программу с описанием класса Planet:
- В заголовочном файле описать класс Planet: название, диаметр, биологический вид.
- Создать массив из 8-9 объектов.
- С помощью 2 внешних функций получить и вывести данные.
- Предусмотреть возможность ввода/вывода данных для меньшего числа объектов (_stricmp).
- displayData () выводит данные.
- Определить и вывести максимальный диаметр.
Результат работы программы должен быть следующим:
К целям ООП относятся такие, как:
- Защита внутренних элементов класса от внешних функций (например, при производстве стержневых ручек стержни наделяют простейшим “интерфейсом” с внешним миром, пряча содержимое в корпус. Защита членов класса выполняет роль подобного корпуса.);
- Создание класса, способного полноценно управлять своими внутренними членами. Было бы непоследовательно требовать от класса полноценной работы и ответственности за ее результаты и одновременно позволять внешним функциям манипулировать его внутренними членами (как если требовать от изготовителей ручек нести ответственность за манипуляции с элементами ее внутреннего устройства);
- Абстракция – сокращение до минимума внешнего интерфейса класса. Гораздо проще изучать и использовать класс, который имеет ограниченный интерфейс (интерфейс класса – это его открытые члены). Защищенные члены класса скрыты, и их не нужно помнить (иначе интерфейсом становится весь класс);
- Уменьшение уровня взаимосвязи между классом и внешней программой. Ограничив взаимосвязь класса с внешним кодом, при необходимости гораздо проще заменить класс каким-либо другим.
Т.о., возможность сделать член класса защищенным разработана для того, чтобы защитить программиста от обращения к члену класса по невнимательности.
В зависимости от того, какой уровень доступа предоставляет тот или иной метод, выделяют:
- public – открытый интерфейс – общий интерфейс для всех пользователей данного класса;
- protected – защищенный интерфейс – внутренний интерфейс для всех наследников данного класса;
- private – закрытый интерфейс – интерфейс, доступный только изнутри данного класса.
Такое разделение интерфейсов позволяет сохранять неизменным открытый интерфейс, но изменять внутреннюю реализацию.
Ключевое слово protected делает все последующие члены класса защищенными, т.е. недоступными для функций, которые не являются членами класса. Например, есть класс Student и необходимые возможности, которые нужны классу, описывающему студента:
- addCourse (int hours, double grade) – добавить пройденный курс;
- grade() – вернуть текущую среднюю оценку;
- hours() – вернуть количество прослушанных часов.
Оставшиеся члены класса Student можно объявить как защищенные, чтобы другие функции не могли вмешиваться во внутренние дела класса Student:
Теперь свойства semesterHours и gpa доступны только из других членов класса Student, и приведенный ниже пример работать не будет:
Обычно класс начинают описывать с открытых членов, формируя интерфейс класса. Описание защищенных членов класса выполняется позже.
Ключевое слово protected позволяет исключить возможность установки gpa равным недопустимому для этой величины значению (например, меньше 2 или больше 5). Внешнее приложение сможет добавить курс, но не сможет изменить значение среднего балла напрямую. Если же есть необходимость непосредственного изменения gpa, класс может предоставить открытую функцию, предназначенную для этой цели:
Здесь невозможно изменить avg напрямую, но можно изменить avg через метод.
При использовании private вместо protected результат будет таким же.
Ключевое слово friend
Иногда внешним функциям требуется прямой доступ к защищенным данным-членам. Без механизма “дружественности” программист был бы вынужден объявлять такие члены открытыми для всех, а значит обращаться к этим членам могла бы любая внешняя функция (это похоже на ту ситуацию, когда можно оставить ключ от своего дома другу, чтобы он кормил собаку, вместо того, чтобы оставлять дом открытым для всех).
Для доступа к защищенным членам класса можно воспользоваться ключевым словом friend (англ. – друг).
Объявление друзей должно находиться в классе, который содержит защищенные члены. Подобное объявление выполняется почти так же, как и объявление обычных прототипов, и должно содержать расширенное имя друга, включающее типы аргументов и возвращаемого значения.
Дружественная функция
Пример:
Функция initialize() получает доступ ко всем членам класса Student для того, чтобы инициализировать свойства объекта. При использовании private получится то же самое.
Дружественная функция может быть объявлена в блоке с любым модификатором доступа: или в private, или в public, потому что модификатор на нее не влияет.
Функция initialize() при этом не становится членом класса. Если внутри самого класса Student можно использовать this->avg для получения значения avg для текущего объекта:
то изнутри дружественной функции initialize() такой возможности нет:
Одна и та же функция может одновременно быть объявлена другом нескольких классов.
Например, для связи двух классов:
Дружественный метод
Метод одного класса может быть объявлен как друг другого класса. Значит изнутри этого дружественного метода можно будет получить доступ к защищенным членам другого класса.
При этом возможна следующая ошибка: несмотря на то, что классы были объявлены и метод choiceSport() "подружился" с классом Sport, особенность компилятора C++ такова, что он все равно не может найти свойство typeSport и метод Goals():
Решение этой проблемы: вынести метод choiceSport() за пределы класса, а в классе оставить только сигнатуру метода.
Реализацию метода нужно сделать после main(). Это обязательно, иначе компилятор не увидит тела метода.
Итак, теперь есть доступ к закрытым членам класса Sport через дружественный метод из класса Pupil. Если в классе Pupil создать еще какой-то метод (недружественный), то у него доступа к Sport не будет.
Дружественный класс
Класс может быть объявлен как друг другого класса целиком. Это значит, что все методы одного класса становятся друзьями другого класса. Т.е. эти методы одного класса получат доступ ко всем закрытым членам другого класса. Методы тоже следует прописывать после main().
Пример:
Оператор this
Объект, для которого вызывается метод, называется “текущим”, и все имена членов, записанные в сокращенном виде внутри метода, считаются членами текущего объекта. Т.е., сокращенное обращение к членам класса интерпретируется как обращение к членам текущего объекта.
Как метод определяет, какой объект является текущим: адрес этого объекта всегда передается методу как скрытый первый аргумент. При вызове метода происходит преобразование следующего вида:
s.Vklad (100.5) равносильно Saving::Vklad(&s, 100.50) – так компилятор “видит” выражение в своем “внутреннем представлении”.
Каждый раз, когда метод обращается к другому члену класса, не называя имени его объекта явно, компилятор считает, что данный член является членом этого (this) объекта. Можно явно обращаться к членам этого объекта, используя ключевое слово this. Функцию Saving::Vklad() можно переписать следующим образом (результат будет тот же):
this – это указатель, который хранит адрес текущего объекта:
В методе обе части выражения sum=sum; состоят из полученного параметра sum:
В таком случае можно использовать ключевое слово this, чтобы указать, что для текущего объекта его свойству sum присваивается переданное значение:
Геттеры и сеттеры
Метод чтения, геттер (англ. getter — получатель) — специальный метод класса, позволяющий получить данные, доступ к которым напрямую ограничен. Это один из методов ООП, который помогает реализовать гибкий механизм инкапсуляции.
Устанавливающий метод, модифицирующий метод, мутатор, сеттер (англ. setter – "устанавливатель") – метод класса, используемый в ООП для того, чтобы присвоить какое-либо значение инкапсулированному полю, например, обработав при этом недопустимые присваивания.
Предоставление прямого доступа к свойствам класса (т.е. установка модификатора доступа public к свойствам класса) в большинстве случаев является нарушением принципа инкапсуляции.
Когда описывается какой-либо класс, то необходимо скрыть его внутреннее устройство от, например, другого программиста и предоставить ему какой-нибудь интерфейс для работы с этим классом.
Пример:
Если установлен модификатор public для свойств класса Car, то программист сможет изменить значение любого свойства (через объект) несмотря на то что изначально могла подразумеваться другая работа с этим классом:
Геттеры и сеттеры – это обычные методы класса. Геттеры и сеттеры позволяют обеспечить работу с закрытыми членами класса. Геттеры и сеттеры должны быть public-методами, т.к. через геттеры и сеттеры должна быть возможность взаимодействовать с закрытыми свойствами класса извне этого класса. Для геттера и сеттера, как для любого метода, необходимо указывать тип возвращаемого значения. Имя метода геттера и сеттера принято именовать начиная с Get и Set. Далее в имени нужно указать свойство, с которым работает метод.
Пример:
Класс Cat, в котором описано свойство string name. Сеттер и геттер для класса может быть описан следующим образом:
В геттеры и сеттеры при необходимости можно добавлять нужные для решения задачи действия.
Сеттеры и геттеры не обязательно должны присутствовать одновременно. Если определен только блок get, то свойство доступно только для чтения – можно получить его значение, но не установить. И, наоборот, если для свойства определен только блок set, тогда это свойство доступно только для записи – можно только установить значение, но нельзя получить.
Например, требуется установить проверку с учетом веса кошки:
1. Выполнить транспонирование неквадратной матрицы: преобразовать строки двумерного массива в столбцы следующим образом:
Было: | |
---|---|
11 | 22 |
33 | 44 |
55 | 66 |
77 | 88 |
Стало: | |||
---|---|---|---|
11 | 33 | 55 | 77 |
22 | 44 | 66 | 88 |
Класс Matrix должен содержать два метода: один заполнит массив значениями, второй произведет замену значений строк на значения столбцов.
2. Описать два класса: Teacher и Student. Предоставить возможность дружественному классу Teacher изменять значение свойства mark у класса Student.
Результат работы программы может быть следующим:
3. Описать класс домашняя библиотека. Предусмотреть возможность работы с произвольным числом книг, поиска книги по какому-либо признаку (например, по автору или по году издания), добавления книг в библиотеку, удаления книг из нее, сортировки книг по разным полям.
4. Описать класс покупатель: Фамилия, Имя, Отчество, Адрес, Номер кредитной карточки, Номер банковского счета; Методы: установка значений атрибутов, получение значений атрибутов, вывод информации. Создать массив объектов данного класса. Вывести список покупателей в алфавитном порядке и список покупателей, у которых номер кредитной карточки находится в заданном диапазоне.
5. Описать класс абонент: Идентификационный номер, Фамилия, Имя, Отчество, Адрес, Номер кредитной карточки, Дебет, Кредит, Время междугородных и городских переговоров; Методы: установка значений атрибутов, получение значений атрибутов, вывод информации. Создать массив объектов данного класса. Вывести сведения относительно абонентов, у которых время городских переговоров превышает заданное. Сведения относительно абонентов, которые пользовались междугородной связью.