Способы использования объектов класса

Классы и объекты

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

Мы можем написать одинаковую программу в разных парадигмах. Парадигмы не имеют чёткого определения и часто пересекаются.

Давайте посмотрим на пример. Допустим, в нашей игре есть танки и они могут стрелять, при стрельбе у них уменьшается боезапас. Как мы можем это смоделировать без ООП:

У нас есть структура, которая содержит поле, представляющее количество снарядов, и есть функция атаки, в которую мы передаём танк. Внутри функции мы меняем количество снарядов. Так может выглядеть игра на языке C: структуры отдельно от функций, которые совершают действия со структурными переменными. Данную ситуацию можно смоделировать по-другому с помощью объектно-ориентированного программирования (Object-Oriented Programming, OOP) — ООП.В ООП действия привязываются к объектам.

Определение классов в C++

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

В C++ ключевые слова struct и class очень близки и могут использоваться взаимозаменяемо. У них есть только одно отличие (об этом ниже). Вот как можно определить такой же класс с помощью struct:

Отличие только первом ключевом слове. В одном из прошлых уроков мы уже обсуждали структуры. что мы видим новое? Ключевые слова private и public — это спецификаторы доступа. Также мы видим, что внутри класса мы можем вставлять определения функций.

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

Переменные и методы класса

Класс состоит из членов класса (class members). Члены класса могут быть переменными (data members) или методами (function members или methods). Переменные класса могут иметь любой тип данных (включая другие структуры и классы). Методы — это действия, которые может выполнять класс. По сути, это обычные функции.

Все методы класса имеют доступ к переменным класса. Обратите внимание, как мы обращаемся к ammo в методе Attack.

Создание объектов класса

Теперь у нас есть свой тип данных и мы можем создавать переменные данного типа. Если после определения структур мы могли создавать структурные переменные, то в случае классов, мы создаём объекты классов (или экземпляры). Разница между классами и структурами только в терминах. Для C++ это почти одно и то же.

Вот так мы можем создать объекты класса Tank и вызвать метод Attack:

t1 и t2 — объекты класса Tank. Для C++ объект класса — это всего-лишь переменная. Тип данных этих переменных — Tank. Ещё раз повторю, что классы (и структуры) позволяют создавать пользовательские типы данных.

В англоязычной литературе создание объектов классов также называется созданием экземпляров — instantiating.

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

В нашем примере каждый объект имеет доступ к своей копии ammo. ammo — переменная класса (data member). Attack — метод класса. У каждого объекта своя копия переменных класса, но все объекты одного класса вызывают одни и те же методы.

Размер объекта включает все данные, но не методы

В памяти переменные класса располагаются последовательно. Благодаря этому мы можем создавать массивы объектов и копировать их все вместе (если в классе этих объектов нет динамического выделения памяти). Это будет важно для нас, когда мы начнём работать с графикой в DirectX/OpenGL. Размер объекта класса можно узнать с помощью функции sizeof. При этом в качестве аргумента можно использовать как объект, так и сам класс:

Читайте также:  Семена чиа способ употребления

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

Указатель this

Вот как для компилятора выглядит любой метод:

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

Указатель this нужен, когда методу необходимо вернуть указатель на текущий объект.

Указатели на объекты

При работе с объектам в C++ вам неизбежно придётся работать с указателями (и ссылками). Как мы помним, при передаче в функцию по значению создаётся копия переменной. Если у вас сложный класс, содержащий большой массив или указатели, то копирование такого объекта может потребовать ненужное выделение дополнительной памяти или может быть вообще невозможным, в случае если в классе вы динамически выделяете память. Поэтому очень часто объекты создаются динамически. Для доступа к таким объектам используется оператор непрямого доступа (стрелочка):

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

Чуть ниже мы увидим один случай, когда не обойтись без ссылок.

Конструктор класса (Constructor)

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

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

Перегрузка конструктора класса

Перегрузка (overloading) конструктора позволяет создать несколько конструкторов для одного класса с разными параметрами. Всё то же самое, что и при перегрузке функций:

Начальные значения можно задавать в виде списка инициализации. Выше в конструкторе мы инициализировали переменные внутри тела. Список инициализации идёт перед телом конструктора и выглядит так:

В списке инициализации можно задать значение только части переменных класса.

Копирующий конструктор (Copy Constructor)

Без каких-либо действий с нашей стороны мы можем присваивать объектам другие объекты:

Здесь используется копирующий конструктор. Копирующий конструктор по умолчанию просто копирует все переменные класса в другой объект. Если в классе используется динамическое выделение памяти, то копирующий конструктор по умолчанию не сможет правильно создать новый объект. В таком случае вы можете перегрузить копирующий конструктор:

В копирующем конструкторе всегда используются ссылки. Это обязательно. Параметр point — это объект, стоящий справа от оператора присваивания.

Деструктор класса

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

Допустим в нашем танке есть экипаж, пусть это будет один объект типа Unit. При создании танка мы выделяем память под экипаж. В деструкторе нам нужно будет освободить память:

Имя деструктора совпадает с именем класса и перед ним ставится тильда

. Деструктор может быть только один.

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

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

Инкапсуляция данных — Encapsulation

Что означает слово Encapsulation? Корень — капсула. En — предлог в. Инкапсуляция — это буквально помещение в капсулу. Что помещается в капсулу? Данные и действия над ними: переменные и функции. Инкапсуляция — связывание данных и функций. Давайте ещё раз взглянем на класс Tank:

Собственно, здесь в класс Tank мы поместили переменную ammo и метод Attack. В методе Attack мы изменяем ammo. Это и есть инкапсуляция: члены класса (данные и методы) в одном месте.

В C++ есть ещё одно понятие, которое связано с инкапсуляцией — сокрытие данных. Сокрытие предполагает помещение данных (переменных класса) в область, в которой они не будут видимы в других частях программы. Для сокрытия используются спецификаторы доступа (access specifiers). Ключевые слова public и private и есть спецификаторы доступа. public говорит, что весь следующий блок будет видим за пределами определения класса. private говорит, что только методы класса имеют доступ к данным блока. Пример:

Читайте также:  Удаление простаты лапароскопическим способом

Здесь мы видим, что объект может получить доступ только к членам класса, находящимся в блоке public. При попытке обратиться к членам класса (и переменным, и методам) блока private, компилятор выдаст ошибку. При этом внутри любого метода класса мы можем обращаться к членам блока private. В методе Move мы изменяем скрытые переменные x и y.

Хороший стиль программирования в ООП предполагает сокрытие всех данных. Как тогда задавать значения скрытых данных и получать доступ к ним? Для этого используются методы setters и getters.

Setters and Getters

Setters и Getters сложно красиво перевести на русский. В своих уроках я буду использовать английские обозначения для них. Setter (set — установить) — это метод, который устанавливает значение переменной класса. Getter (get — получить) — метод, который возвращает значение переменной:

Имена не обязательно должны включать Set и Get. Использование setters и getters приводит к увеличению количества кода. Можно ли обойтись без инкапсуляции и объявить все данные в блоке public? Да, можно. Но данная экономия кода имеет свои негативные последствия. Мы будем подробно обсуждать данный вопрос, когда будем говорить об интерфейсах.

Следующая концепция ООП — наследование.

Наследование (Inheritance) в C++

Производный класс не может получить доступ к private членам. Поэтому в классе Unit используется спецификатор protected. Данный спецификатор разрешает доступ к данным внутри класса и внутри дочерних классов, private же разрешает доступ только в методах самого класса.

При наследовании производный класс имеет доступ ко всем членам (public и protected) базового класса. Именно поэтому мы можем вызвать метод Move для объекта типа Archer.

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

Полиморфизм (Polymorphism)

Наследование открывает доступ к полиморфизму. Poly — много, morph — форма. Это очень мощная техника, которую мы будем использовать постоянно.

Полиморфизм позволяет поместить в массив разные типы данных:

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

Заключение

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

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

Единственное отличие между классом и структурой в C++: по умолчанию в структуре используется спецификатор доступа public, а в классе — private. Часто в коде вы будете видеть, что структуры используются без методов, чисто для описания каких-либо сущностей. Но это делать необязательно это всего лишь соглашение.

Источник

Урок №113. Классы, Объекты и Методы

Обновл. 13 Сен 2021 |

Хотя язык C++ предоставляет ряд фундаментальных типов данных (например, char, int, long, float, double и т.д.), которых бывает достаточно для решения относительно простых проблем, для решения сложных проблем функционала этих простых типов может не хватать.

Классы

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

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

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

Результат выполнения программы:

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

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

Единственным существенным отличием здесь является public — ключевое слово в классе (о нем мы поговорим детально на соответствующем уроке).

Так же, как и объявление структуры, объявление класса не приводит к выделению какой-либо памяти. Для использования класса нужно объявить переменную этого типа класса:

Читайте также:  Фланцевый способ соединения трубопроводов

В языке C++ переменная класса называется экземпляром (или «объектом») класса. Точно так же, как определение переменной фундаментального типа данных (например, int x ) приводит к выделению памяти для этой переменной, так же и создание объекта класса (например, DateClass today ) приводит к выделению памяти для этого объекта.

Методы классов

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

Класс Date с методом вывода даты:

Точно так же, как к членам структуры, так и к членам (переменным и функциям) класса доступ осуществляется через оператор выбора членов ( . ):

Результат выполнения программы:

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

Методы класса работают несколько иначе: все вызовы методов должны быть связаны с объектом класса. Когда мы вызываем today.print() , то мы сообщаем компилятору вызвать метод print() объекта today .

Рассмотрим определение метода print() еще раз:

На что фактически ссылаются m_day , m_month и m_year ? Они ссылаются на связанный объект today (который определен caller-ом).

Поэтому, при вызове today.print() , компилятор интерпретирует:

m_day , как today.m_day ;

m_month , как today.m_month ;

m_year , как today.m_year .

Если бы мы вызвали tomorrow.print() , то m_day ссылался бы на tomorrow.m_day .

По сути, связанный объект неявно передается методу. По этой причине его часто называют неявным объектом.

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

Использование префикса m_ (англ. «m» = «members») для переменных-членов помогает различать переменные-члены от параметров функции или локальных переменных внутри методов класса. Это полезно по нескольким причинам:

во-первых, когда мы видим переменную с префиксом m_ , то мы понимаем, что работаем с переменной-членом класса;

во-вторых, в отличие от параметров функции или локальных переменных, объявленных внутри функции, переменные-члены объявляются в определении класса. Следовательно, если мы хотим знать, как объявлена ​​переменная с префиксом m_ , мы понимаем, что искать нужно в определении класса, а не внутри функции.

Обычно программисты пишут имена классов с заглавной буквы.

Правило: Пишите имена классов с заглавной буквы.

Вот еще один пример программы с использованием класса:

Результат выполнения программы:

Name: John
Id: 5
Wage: $30

Name: Max
Id: 6
Wage: $32.75

В отличие от обычных функций, порядок, в котором определены методы класса, не имеет значения!

Примечание о структурах в C++

В языке Cи структуры могут только хранить данные и не могут иметь связанных методов. После проектирования классов (используя ключевое слово class) в языке С++, Бьёрн Страуструп размышлял о том, нужно ли, чтобы структуры (которые были унаследованы из языка Си) имели связанные методы. После некоторых размышлений он решил, что нужно. Поэтому в программах, приведенных выше, мы также можем использовать ключевое слово struct, вместо class, и всё будет работать!

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

Правило: Используйте ключевое слово struct для структур, используемых только для хранения данных. Используйте ключевое слово class для объектов, объединяющих как данные, так и функции.

Заключение

Оказывается, Стандартная библиотека C++ полна классов, созданных для нашего удобства. std::string, std::vector и std::array — это всё типы классов! Поэтому, когда вы создаете объект любого из этих типов, вы создаете объект класса. А когда вы вызываете функцию с использованием этих объектов, вы вызываете метод:

Источник

Оцените статью
Разные способы