Что такое объект класса
Перейти к содержимому

Что такое объект класса

  • автор:

ООП с примерами (часть 1)

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

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

Первая часть, представленная ниже, посвящена классам, объектам и интерфейсам.
Вторая часть иллюстрирует инкапсуляцию, полиморфизм и наследование

Основные понятия ООП
Класс

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

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

Класс – это способ описания сущности, определяющий состояние и поведение, зависящее от этого состояния, а также правила для взаимодействия с данной сущностью (контракт).

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

С точки зрения структуры программы, класс является сложным типом данных.

В нашем случае, класс будет отображать сущность – автомобиль. Атрибутами класса будут являться двигатель, подвеска, кузов, четыре колеса и т.д. Методами класса будет «открыть дверь», «нажать на педаль газа», а также «закачать порцию бензина из бензобака в двигатель». Первые два метода доступны для выполнения другим классам (в частности, классу «Водитель»). Последний описывает взаимодействия внутри класса и не доступен пользователю.

В дальнейшем, несмотря на то, что слово «пользователь» ассоциируется с пасьянсом «Косынка» и «Microsoft Word», мы будем называть пользователями тех программистов, которые используют ваш класс, включая вас самих. Человека, который является автором класса, мы будем называть разработчиком.

Объект

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

Объект (экземпляр) – это отдельный представитель класса, имеющий конкретное состояние и поведение, полностью определяемое классом.

Говоря простым языком, объект имеет конкретные значения атрибутов и методы, работающие с этими значениями на основе правил, заданных в классе. В данном примере, если класс – это некоторый абстрактный автомобиль из «мира идей», то объект – это конкретный автомобиль, стоящий у вас под окнами.

Интерфейс

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

Интерфейс – это набор методов класса, доступных для использования другими классами.

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

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

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

Что такое объект класса

Классы и объекты в ООП — различные понятия. Понятие класса в ООП – это тип данных (такой же как, например, Real или String), а объект – конкретный экземпляр класса (его копия), хранящийся в памяти компьютера как переменная соответствующего типа.

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

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

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

Основные понятия

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

У каждого студента есть имя — name и фамилия last_name . Также, у него есть промежуточные оценки за весь семестр. Эти оценки мы будем записывать в целочисленный массив из пяти элементов. После того, как все пять оценок будут проставлены, определим средний балл успеваемости студента за весь семестр — свойство average_ball .

Методы — это функции, которые могут выполнять какие-либо действия над данными (свойствами) класса. Добавим в наш класс функцию calculate_average_ball() , которая будет определять средний балл успеваемости ученика.

  • Методы класса — это его функции.
  • Свойства класса — его переменные.

Функция calculate_average_ball() просто делит сумму всех промежуточных оценок на их количество.

Модификаторы доступа public и private

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

Закрытые данные класса размещаются после модификатора доступа private . Если отсутствует модификатор public , то все функции и переменные, по умолчанию являются закрытыми (как в первом примере).

Обычно, приватными делают все свойства класса, а публичными — его методы. Все действия с закрытыми свойствами класса реализуются через его методы. Рассмотрим следующий код.

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

Функция set_average_ball() принимает средний балл в качестве параметра и присваивает его значение закрытой переменной average_ball . Функция get_average_ball() просто возвращает значение этой переменной.

Программа учета успеваемости студентов

Создадим программу, которая будет заниматься учетом успеваемости студентов в группе. Создайте заголовочный файл students.h, в котором будет находиться класс Students .

Мы добавили в наш класс новые методы, а также сделали приватными все его свойства. Функция set_name() сохраняет имя студента в переменной name , а get_name() возвращает значение этой переменной. Принцип работы функций set_last_name() и get_last_name() аналогичен.

Функция set_scores() принимает массив с промежуточными оценками и сохраняет их в приватную переменную int scores[5] .

Теперь создайте файл main.cpp со следующим содержимым.

В самом начале программы создается объект класса Students . Дело в том, что сам класс является только описанием его объекта. Класс Students является описанием любого из студентов, у которого есть имя, фамилия и возможность получения оценок.

Объект класса Students характеризует конкретного студента. Если мы захотим выставить оценки всем ученикам в группе, то будем создавать новый объект для каждого из них. Использование классов очень хорошо подходит для описания объектов реального мира.

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

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

Name already in use

OAP / articles / t6_oop_habr.md

  • Go to file T
  • Go to line L
  • Copy path
  • Copy permalink

0 contributors

Users who have contributed to this file

  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Базовые понятия: объект, его свойства и методы, класс, интерфейс.

В силу своей природы, объектно-ориентированное программирование лучше всего объяснять на примерах. Нашими пациентами будут трансформеры.

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

Таким образом, класс — это описание того, какими свойствами и поведением будет обладать объект. А объект — это экземпляр с собственным состоянием этих свойств.

Мы говорим «свойства и поведение», но звучит это как-то абстрактно и непонятно. Привычнее для программиста будет звучать так: «переменные и функции». На самом деле «свойства» — это такие же обычные переменные, просто они являются атрибутами какого-то объекта (их называют полями объекта). Аналогично «поведение» — это функции объекта (их называют методами), которые тоже являются атрибутами объекта. Разница между методом объекта и обычной функцией лишь в том, что метод имеет доступ к собственному состоянию через поля.

Итого, имеем методы и свойства, которые являются атрибутами. Как работать с атрибутами? В большинстве ЯП оператор обращения к атрибуту — это точка. Выглядит это примерно вот так:

Что мы видим из кода?

Объект может обращаться из своих методов к собственным атрибутам (у нас атрибут «x«). Обращаю внимание, что только к собственным, то бишь, когда трансформер вызывает свой метод, либо меняет собственное состояние. Если снаружи обращение будет выглядеть так: optimus.x, то изнутри, если Оптимус захочет сам обратиться к своему полю «x», в его методе обращение будет звучать так: «x» (или this.x), то есть «я (Оптимус) обращаюсь к своему атрибуту x». В большинстве языков для обращения к аттрибутам класса используются ключевые слова this или self.

Конструктор — это специальный метод, который автоматически вызывается при создании объекта. Конструктор может принимать любые аргументы, как и любой другой метод. В каждом языке конструктор обозначается своим именем. Где-то это специально зарезервированные имена типа __construct или __init__ , а где-то имя конструктора должно совпадать с именем класса (как раз в C# так и сделано). Назначение конструкторов — произвести первоначальную инициализацию объекта, заполнить нужные поля. Про конструкторы мы подробнее поговорим ниже.

В C# для создания экземпляра класса нужно вызвать конструктор класса (как функцию) с ключевым словом new. В этот момент создается объект и вызывается конструктор. В нашем примере, конструктору передается «0» в качестве стартовой позиции трансформера (это и есть вышеупомянутая инициализация).

Методы Transformer (конструктор) и run работают с внутренним состоянием, а во всем остальном не отличаются от обычных функций. Даже синтаксис объявления совпадает.

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

Интерфейс – это набор методов класса, доступных для использования другими классами.

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

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

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

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

Основные принципы ООП.

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

Разберем их по порядку.

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

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

Оптимус Прайм и Мегатрон — оба трансформеры, но один является автоботом, а второй десептиконом. Допустим, что различия между автоботами и десептиконами будут заключаться только в том, что автоботы трансформируются в автомобили, а десептиконы — в авиацию. Все остальные свойства и поведение не будут иметь никакой разницы. В таком случае можно спроектировать систему наследования так: общие черты (бег, стрельба) будут описаны в базовом классе «Трансформер», а различия (трансформация) в двух дочерних классах «Автобот» и «Десептикон».

Этот пример наглядно иллюстрирует, как наследование становится одним из способов избежать дублирования кода с помощью родительского класса, и одновременно предоставляет возможности для мутации в классах-потомках.

Если же в классе-потомке переопределить уже существующий в классе-родителе метод, то сработает перегрузка. Это позволяет не дополнять поведение родительского класса, а модифицировать. В момент вызова метода или обращения к полю объекта, поиск атрибута происходит от потомка к самому корню — родителю. То есть, если у автобота вызвать метод fire(), сначала поиск метода производится в классе-потомке — Autobot, а поскольку его там нет, поиск поднимается на ступень выше — в класс Transformer, где и будет обнаружен и вызван.

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

Положим, у нас есть три трансформера: Оптимус, Мегатрон и Олег. Трансформеры боевые, стало быть обладают методом attack(). Игрок, нажимая у себя на джойстике кнопку «воевать», сообщает игре, чтобы та вызвала метод attack() у трансформера, за которого играет игрок. Но поскольку трансформеры разные, а игра интересная, каждый из них будет атаковать каким-то своим способом. Скажем, Оптимус — объект класса Автобот, а Автоботы снабжаются пушками с плутониевыми боеголовками. Мегатрон — Десептикон, и стреляет из плазменной пушки. Олег — басист, и он обзывается. А в чем польза?

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

Инкапсуляция — это контроль доступа к полям и методам объекта. Под контролем доступа подразумевается не только можно/неможно, но и различные валидации, подгрузки, вычисления и прочее динамическое поведение.

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

  • publiс — к атрибуту может получить доступ любой желающий
  • private — к атрибуту могут обращаться только методы данного класса
  • protected — то же, что и private, только доступ получают и наследники класса в том числе

Как правильно выбрать модификатор доступа? В простейшем случае так: если метод должен быть доступен внешнему коду, выбираем public. В противном случае — private. Если есть наследование, то может потребоваться protected в случае, когда метод не должен вызываться снаружи, но должен вызываться потомками.

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

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

Задача интерфейса — снизить уровень зависимости сущностей друг от друга, добавив больше абстракции.

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

Интерфейсы в C#, однако, могут содержать как абстрактные методы, тик и методы с реализацией (начиная с версии 8.0).

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

Классы с интерфейсами состоят в отношении «многие ко многим»: один класс может имплементировать множество интерфейсов, и каждый интерфейс, в свою очередь, может имплементироваться многими классами.

У интерфейса двустороннее применение:

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

Например, если какой-то объект помимо основного поведения, может быть сериализован, то пускай он имплементирует интерфейс «Сериализуемый». А если объект можно склонировать, то пусть он имплементирует еще один интерфейс — «Клонируемый». И если у нас есть какой-то транспортный модуль, который передает объекты по сети, он будет принимать любые объекты, имплементирующие интерфейс «Сериализуемый».

Представим, что каркас трансформера оборудован тремя слотами: слот для оружия, для генератора энергии и для какого-нибудь сканера. Эти слоты обладают определенными интерфейсами: в каждый слот можно установить только подходящее оборудование. В слот для оружия можно установить ракетную установку или лазерную пушку, в слот для генератора энергии — ядерный реактор или РИТЭГ (радиоизотопный термоэлектрический генератор), а в слот для сканера — радар или лидар. Суть в том, что каждый слот имеет универсальный интерфейс подключения, а уже конкретные устройства должны соответствовать этому интерфейсу. К примеру, на материнских платах используется несколько типов слотов: слот для процессора позволяет подключать различные процессоры, подходящие под данный сокет, а слот SATA — любой SSD или HDD накопитель или даже CD/DVD.

Для определения интерфейса используется ключевое слово interface. Как правило, названия интерфейсов в C# начинаются с заглавной буквы «I», например, IComparable, IEnumerable (так называемая венгерская нотация), однако это не обязательное требование, а больше стиль программирования.

Cлой абстракции в виде интерфейсов между слоем реализации (класс) и слоем-потребителем дает возможность абстрагировать одних от других. Вы можете это наблюдать, посмотрев на каждый слой в отдельности: в слое реализации (слева) нет ни слова про класс Transformer, а в слое-потребителе (справа) нет ни слова про конкретные реализации (там нет слов Radar, RocketLauncher, NuclearReactor и т. д.)

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

№1 Объекты и классы

Объект — это абстрактная сущность, наделенная характеристиками объектов реального мира.

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

Объекты в C++ — это программные конструкции, формируемые так называемыми классам. Определение пере­менной класса также называется созданием экземпляра класса (class instantiation). За создание своих классов полную ответственность несет сам программист. Но он может получить доступ и к классам, разработанным другими про­граммистами. Например, к классам, которые находятся в библиотеке контейнеров или библиотеке потоков компи­лятора Borland C++.

Главное отличие С от С++ — это классы. Уже само по себе существование классов в C++ является заме­чательной особенностью, делающей его объектно-ориентированным языком. Классы — это структуры, которые содержат не только объявления данных, но и функций. Эти функции называются функциями-членами (member functions) и определяют, что может делать класс.

Для того чтобы использовать класс, его нужно вначале объявить точно так же, как это делается со структура­ми. И так же как для структур, полное объявление класса может появиться в программе только один раз. Рассмот­рим пример объявления простого класса:

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

Для идентификаторов класса применимы те же правила, что и для остальных типов или имен переменных. В C++ для идентификаторов предельная длина не определена, но в Borland C++ максимальная длина равна 32 сим­волам. Количество значащих символов можно указать в диалоговом окне, которое появляется при выполнении по­следовательности команд Options|Project|+Conipiler|Source. По умолчанию все 32 символа являются значащими. Написание букв (строчная или прописная) весьма важно. В Borland C++ принято, писать идентификаторы классов и глобальных структур с прописной буквы. Класс class counter <…>; определяет еще один класс, идентичный (за исключением имени) классу Counter. Объявление двух классов с оди­наковыми именами недопустимо, независимо от того, идентичны объявления или нет.

Переменная count определена внутри класса. Таким образом, count — это переменная-чпен (member variable) класса. Любая переменная, определенная в классе, имеет область видимости класса (class scope). Область видимос­ти переменной-члена (не применима в ANSI С) простирается от точки объявления переменной до конца объявления класса.

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

Класс Counter содержит объявление функций SetValue (long) и GetValue (), которые называются функ­циями-членами (member function) класса. Эти функции пока не определены, они только объявлены. Реальное их определение для класса Counter было опущено и приводится только сейчас:

void Counter::Setvalue(long value)

При определении функции-члена после типа возвращаемого значения нужно всегда указывать, членом какого класса является функция. Для этого нужно написать имя класса и поставить за ним два двоеточия. Как и другие функции в C++, функции-члены должны быть объявлены до использования. Объявление должно быть полным, включая тип возвращаемого значения и типы аргументов. В C++ типы аргументов функции должны быть объяв­лены одновременно с функцией-членом с помощью следующей записи:int foo (int parm1, long parm2) ;

Для того чтобы использовать класс, нужно определить объект этого класса. Объек­ты класса определяются точно так же, как структурные или скалярные переменные. Чтобы определить переменную people типа Counter, используйте следующую запись:Counter people;

Объекты классов, определяемые внутри функции, используют ту же запись, что и другие типизированные пере­менные. Область видимости этих объектов начинается в точке объявления и заканчивается в конце блока. Объект people имеет класс памяти auto и хранится в кадре стека (stack frame).

Для вызова функции-члена объекта класса используется та же запись, что и для обращения к элементу структу­ры: за точкой следует имя элемента. В остальном использование функций-членов ничем не отличается от использо­вания традиционных функций С. В функциях-членах используются скобки, в переменных-членах — нет.

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

Класс включает как данные, так и код. Доступ к элементам класса управляем. Это управление ка­сается не только данных, но и кода. Объявление класса должно предшествовать использованию класса. Пользователю предоставляется описание класса, но не обязательно его внутренняя реализация. Насколько это касается пользователя, внутренние детали класса ему не важны и знать их вовсе не обязательно. Более того, чем меньше пользователю нужно знать о «внутренностях» класса, тем лучше. На такую концепцию часто ссылаются как на со­крытие информации (information hiding).

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

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

• сам класс • обычные пользователи • производные классы

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

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

int value_l; // приватный по умолчанию

void f_l(long); // приватный по умолчанию

int value 2; // по-прежнему приватный

int f_2(char*); // приватный

char* value_3; // общедоступный

long f_3 (); // общедоступный

int value 4; // защищенный void

f_4 (long); // защищенный >;

Любое объявление, появляющееся до ключевого слова управления доступом, считается приватным по умолчанию. В приведенном примере переменная value_l и функция f_l (long) являются приватными. В нем есть и явное объявление private. Этот пример иллюстрирует интересную особенность: разделы с разными привилегиями доступа появляются в любом порядке и в любом количестве. Класс AccessControlExample можно объявить следующим образом:

private: int value_l; // приватный

private: void f_l(long); // приватный

private: int value_2; // приватный

private: int f_2(char*); // приватный

public: char* value_3; // общедоступный

public: long f_3; // общедоступный

protected: int value_4; // защищенный

protected: void f_4(long); // защищенный

Последний пример вполне приемлем для C++, но столь многословен, что почти никогда не используется.

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

Например, преобразовав указатель на объект класса в указатель на unsigned char, можно получить доступ ко всему содержимому объекта, включая и приватные, и общедоступные части. Подобное возможно только в экстраординарных случаях, например при реализации отладчика, но не при обычном кодировании.

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

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

3 Область видимости класса

C++ вводит новую область видимости для идентификаторов, объявленных внутри класса. Такие идентифика­торы видимы только в сочетании с объектами класса. При неоднозначном толковании должен использоваться опе­ратор разрешения области видимости .

Пустые классы

Несмотря на то, что назначение класса — инкапсуляция кода и данных, он может быть пустым: class Empty <>;

Сделать много с таким классом вам не удастся, но можно создать объект класса Empty:

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

Вложенные классы

Класс, объявленный внутри объявления другого класса, называется вложенным и может рассматри­ваться как класс-член (member class). Рассмотрим пример вложенного класса:

Класс Inner называется вложенным по отношению к Outer. Объявление такого класса не выделяет памяти для объекта этого класса. Вложенное объявление влияет только на область видимости имени вложенного класса, а память выделяется только при создании экземпляра класса.

Правила доступа для вложенных классов

В AT&T версии 3.0 и в Borland C++ 4.5 вложенный класс никакими особыми привилегиями доступа по отноше­нию к членам включающего класса не обладает. И точно так же включающий класс не обладает особыми привиле­гиями доступа по отношению к членам вложенного класса. Вложение распространяется только на область види­мости имени класса.

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

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

Вложенный класс имеет доступ ко всем обычным нестатическим членам включающего класса только через ука­затели или ссылки.

Создание экземпляра класса

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

Неполные объявления классов

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *