Классы и объекты c: C# и .NET | Классы и объекты
Создание классов и объектов. Урок 2 курса «Объектно-ориентированное программирование на Python»
В языке программирования Python классы создаются с помощью инструкции class, за которой следует произвольное имя класса, после которого ставится двоеточие, далее с новой строки и с отступом реализуется тело класса:
class ИмяКласса: код_тела_класса
Если класс является дочерним, то родительские классы перечисляются в круглых скобках после имени класса.
Объект создается путем вызова класса по его имени. При этом после имени класса обязательно ставятся скобки:
То есть класс вызывается подобно функции. Однако в случае вызова класса происходит не выполнение его тела, как это происходило бы при вызове функции, а создается объект. Поскольку в программном коде важно не потерять ссылку на только что созданный объект, то обычно его связывают с переменной. Поэтому создание объекта чаще всего выглядит так:
имя_переменной = ИмяКласса()
В последствии к объекту обращаются через связанную с ним переменную.
Пример «пустого» класса и двух созданных на его основе объектов:
>>> class A: ... pass ... >>> a = A() >>> b = A()
Класс как модуль
В языке программирования Python класс можно представить подобным модулю. Также как в модуле в нем могут быть свои переменные со значениями и функции. Также как в модуле у класса есть собственное пространство имен, доступ к которым возможен через имя класса:
>>> class B: ... n = 5 ... def adder(v): ... return v + B.n ... >>> B.n 5 >>> B.adder(4) 9
Однако в случае классов используется немного иная терминология. Пусть имена, определенные в классе, называются атрибутами этого класса. В примере имена n
и adder
– это атрибуты класса B
. Атрибуты-переменные часто называют полями или свойствами. Свойством является n
. Атрибуты-функции называются методами. Методом в классе B
является adder
. Количество свойств и методов в классе может быть любым.
Класс как создатель объектов
Приведенный выше класс позволяет создавать объекты, но мы не можем применить к объекту метод adder():
>>> l = B() >>> l.n 5 >>> l.adder(100) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: adder() takes 1 positional argument but 2 were given
В сообщении об ошибке говорится, что adder() принимает только один аргумент, а было передано два. Откуда мог взяться второй аргумент, если в скобках было указано только одно число 100?
На самом деле классы – это не модули. Они обладают своими особенностями. Класс создает объекты, которые являются его наследниками. Это значит, что если у объекта нет собственного поля n
, то интерпретатор ищет его уровнем выше, то есть в классе. Таким образом, если мы добавляем объекту поле с таким же именем как в классе, то оно перекрывает, то есть переопределяет, поле класса:
>>> l. n = 10 >>> l.n 10 >>> B.n 5
Здесь l.n
и B.n
– это разные переменные. Первая находится в пространстве имен объекта l
. Вторая – в пространстве класса B
. Если бы мы не добавили поле n
к объекту l
, то интерпретатор бы поднялся выше по дереву наследования и пришел бы в класс, где бы и нашел это поле.
Что касается методов, то они также наследуются объектами класса. В данном случае у объекта l
нет своего собственного метода adder
, значит, он ищется в классе B
.
Однако в случае с методами не так все просто, как с полями. В Python по умолчанию один и тот же метод, вызванный от имени класса (например, B.meth()
) и от экземпляра этого класса (например, l.meth()
), вызывается по-разному. В последнем случае l.meth()
невидимо для нас преобразуется в B.meth(l)
.
Но если метод meth()
определен как непринимающий никаких аргументов, а они в него передаются, это приводит к ошибке:
>>> class A: . .. def meth(): ... print(1) ... >>> a = A() >>> a.meth() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: meth() takes 0 positional arguments but 1 was given >>> A.meth() 1
Зачем в методы передают экземпляры? Потому что методы обычно для этого и предназначены – для изменения свойств конкретных объектов. А все экземпляры даже одного класса – это разные объекты, со своими свойствами, и метод должен иметь ссылку на конкретный экземпляр, с которым ему придется работать.
Понятно, что передаваемый экземпляр, это объект, к которому применяется метод. Выражение l.adder(100) выполняется интерпретатором следующим образом:
Ищу атрибут adder() у объекта
l
. Не нахожу.Тогда иду искать в класс
B
, так как он создал объектl
.Здесь нахожу искомый метод. Передаю ему объект, к которому этот метод надо применить, и аргумент, указанный в скобках.
Другими словами, выражение l.adder(100)
преобразуется в выражение B.adder(l, 100)
.
Таким образом, интерпретатор попытался передать в метод adder() класса B
два параметра – объект l
и число 100. Но мы запрограммировали метод adder() так, что он принимает только один параметр. В Python определения методов не предполагают принятие объекта как само собой подразумеваемое. Принимаемый объект надо указывать явно.
По соглашению в Python для ссылки на объект используется имя self. Вот так должен выглядеть метод adder(), если мы планируем вызывать его через объекты:
>>> class B: ... n = 5 ... def adder(self, v): ... return v + self.n ...
Переменная self связывается с объектом, к которому был применен данный метод, и через эту переменную мы получаем доступ к атрибутам объекта. Когда этот же метод применяется к другому объекту, то self свяжется уже с этим другим объектом, и через эту переменную будут извлекаться только его свойства.
Протестируем обновленный метод:
>>> l = B() >>> m = B() >>> l.n = 10 >>> l.adder(3) 13 >>> m.adder(4) 9
Здесь от класса B
создаются два объекта – l
и m
. Для объекта l
заводится собственное поле n
. Объект m
, за неимением собственного, наследует n
от класса B
. Можно в этом убедиться, проверив соответствие:
>>> m.n is B.n True >>> l.n is B.n False
В методе adder() выражение self.n
– это обращение к свойству n
, переданного объекта, и не важно, на каком уровне наследования оно будет найдено.
Если метод не принимает объект, к которому применяется, в качестве первого параметра, то такие методы в других языках программирования называются статическими. Они имеют особый модификатор и могут вызываться как через класс, так и через объект этого класса. В Python для имитации статических методов используется специальный декоратор, после чего метод можно вызывать не только через класс, но и через объект, не передавая сам объект.
Изменение полей объекта
В Python объекту можно не только переопределять поля и методы, унаследованные от класса, также можно добавить новые, которых нет в классе:
>>> l.test = "hi" >>> B.test Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: type object 'B' has no attribute 'test' >>> l.test 'hi'
Однако в программировании так делать не принято, потому что тогда объекты одного класса будут отличаться между собой по набору атрибутов. Это затруднит автоматизацию их обработки, внесет в программу хаос.
Поэтому принято присваивать полям, а также получать их значения, путем вызова методов:
>>> class User: ... def setName(self, n): ... self.name = n ... def getName(self): . .. try: ... return self.name ... except: ... print("No name") ... >>> first = User() >>> second = User() >>> first.setName("Bob") >>> first.getName() 'Bob' >>> second.getName() No name
Подобные методы называют сеттерами (set – установить) и геттерами (get – получить).
Атрибут __dict__
В Python у объектов есть встроенные специальные атрибуты. Мы их не определяем, но они есть. Одним из таких атрибутов объекта является свойство __dict__. Его значением является словарь, в котором ключи – это имена свойств экземпляра, а значения – текущие значения свойств.
>>> class B: ... n = 5 ... def adder(self, v): ... return v + self.n ... >>> w = B() >>> w.__dict__ {} >>> w.n = 8 >>> w.__dict__ {'n': 8}
В примере у экземпляра класса B сначала нет собственных атрибутов. Свойство n
и метод adder
– это атрибуты объекта-класса, а не объекта-экземпляра, созданного от этого класса. Лишь когда мы выполняем присваивание новому полю n
экземпляра, у него появляется собственное свойство, что мы наблюдаем через словарь __dict__.
В следующем уроке мы увидим, что свойства экземпляра обычно не назначаются за пределами класса. Это происходит в методах классах путем присваивание через self. Например, self.n = 10
.
Атрибут __dict__ используется для просмотра всех текущих свойств объекта. С его помощью можно удалять, добавлять свойства, а также изменять их значения.
>>> w.__dict__['m'] = 100 >>> w.__dict__ {'n': 8, 'm': 100} >>> w.m 100
Практическая работа
Напишите программу по следующему описанию. Есть класс «Воин». От него создаются два экземпляра-юнита. Каждому устанавливается здоровье в 100 очков. В случайном порядке они бьют друг друга. Тот, кто бьет, здоровья не теряет. У того, кого бьют, оно уменьшается на 20 очков от одного удара. После каждого удара надо выводить сообщение, какой юнит атаковал, и сколько у противника осталось здоровья. Как только у кого-то заканчивается ресурс здоровья, программа завершается сообщением о том, кто одержал победу.
Курс с примерами решений практических работ и всеми уроками:
android-приложение, pdf-версия
Объектно-ориентированное программирование. Классы и объекты
Сегодня мы поговорим об объектно-ориентированном программировании и о его применении в python.
Объектно-ориентированное программирование (ООП) — парадигма программирования, в которой основными концепциями являются понятия объектов и классов.
Класс — тип, описывающий устройство объектов. Объект — это экземпляр класса. Класс можно сравнить с чертежом, по которому создаются объекты.
Python соответствует принципам объектно-ориентированного программирования. В python всё является объектами — и строки, и списки, и словари, и всё остальное.
Но возможности ООП в python этим не ограничены. Программист может написать свой тип данных (класс), определить в нём свои методы.
Это не является обязательным — мы можем пользоваться только встроенными объектами. Однако ООП полезно при долгосрочной разработке программы несколькими людьми, так как упрощает понимание кода.
Приступим теперь собственно к написанию своих классов на python. Попробуем определить собственный класс:
>>> # Пример простейшего класса, который ничего не делает ... class A: ... pass
Теперь мы можем создать несколько экземпляров этого класса:
>>> a = A() >>> b = A() >>> a.arg = 1 # у экземпляра a появился атрибут arg, равный 1 >>> b.arg = 2 # а у экземпляра b - атрибут arg, равный 2 >>> print(a.arg) 1 >>> print(b.arg) 2 >>> c = A() >>> print(c.arg) # а у этого экземпляра нет arg Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'A' object has no attribute 'arg'
Классу возможно задать собственные методы:
>>> class A: . .. def g(self): # self - обязательный аргумент, содержащий в себе экземпляр ... # класса, передающийся при вызове метода, ... # поэтому этот аргумент должен присутствовать ... # во всех методах класса. ... return 'hello world' ... >>> a = A() >>> a.g() 'hello world'
И напоследок еще один пример:
>>> class B: ... arg = 'Python' # Все экземпляры этого класса будут иметь атрибут arg, ... # равный "Python" ... # Но впоследствии мы его можем изменить ... def g(self): ... return self.arg ... >>> b = B() >>> b.g() 'Python' >>> B.g(b) 'Python' >>> b.arg = 'spam' >>> b.g() 'spam'
Классы и объекты в Ruby— Ruby Rush
Сегодня мы научимся писать данные в файлы, узнаем как работать со временем в Ruby, напишем программу-дневник.
План урока
- Как работать со временем в Ruby
- Запись в файлы, пишем дневник
youtube.com/embed/_RvhNoqZI2c» frameborder=»0″ allow=»accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture» allowfullscreen=»»/>
Классы! Великие и могучие!
В программах мы постоянно оперируем объектами, мы уже говорили об этом в 4-м уроке: строки, числа, массивы.
Наши объекты хранятся с помощью переменных: неких ярлыков, которые позволяют обращаться к объектам по имени.
Вы уже знаете, что в Ruby есть много разных видов объектов: строки (String), целые числа (Fixnum), массивы (Array). Пришло время осознать, что этих типов гораздо больше: есть ещё файлы (File), ассоциативные массивы (Hash), метки (Symbol) даже моменты времени (Time) и даты (Date), а также много-много всего другого.
С чем-то мы познакомимся в этом блоке нашего курса, что-то ждёт нас в дальнейшем, но сейчас важно одно, что у каждого из этих объектов есть свой тип.
Типы объектов в программировании называются классами. Ruby не исключение.
Ruby вообще очень высокоразвитый язык, там любая закорючка — это объект какого-то класса. Но это так, лирическое отступление.
Напомню, что посмотреть класс любого объекта можно вызвав у этого объекта по цепочке методы .class
и .name
.
Что такое методы объектов вам станет понятно к концу этого урока, а пока просто напомним, что
puts "Я строка".class.name
выведет на экран String
, а
puts ["А","я","массив","строк"].class.name
выведет на экран Array
.
Для чего создаются классы?
Класс — это некое описание типа объектов, которые можно создавать. Прежде чем человек создал первый паровоз, он как-то описал (на бумаге, в своем воображении, в чертежах) новое для того времени понятие «паровоз». Он наверняка придумал какими свойствами должен обладать паровоз, как он должен функционировать и так далее.
Другими словами он придумал новое понятие, новый тип объектов «паровоз». Программисты бы сказали — создал класс Паровоз
.
И уже потом человечество начало производить различные конкретные паровозы, создавать объекты этого класса.
То есть — прежде чем создавать какие-то объекты в вашей программе, Ruby должен знать их класс. А для этого нужно сперва объявить класс. Объявить класс это значит описать в программе, как должен класс называться и главное — какими свойствами и поведением он должен обладать.
До сих пор мы использовали встроенные в Ruby классы (строки, числа, массивы) – мы создавали объекты этих классов и с ними игрались. Нам не нужно было описывать эти классы, ведь они уже описаны в самом языке Ruby.
Что делать, если вам понадобился в программе новый тип объектов, которых нету в языке? А бывает так очень часто. Тогда вам нужно написать свой класс.
Давайте определимся с философским вопросом: «Когда нужно создавать новые классы». Сразу скажем, что понимание это приходит с опытом, поэтому не бойтесь экспериментировать и делать классы когда считаете это нужным.
Ругать вас за это никто не будет. Дадим лишь несколько советов, как понять, что ситуация требует именно нового класса, а не просто метода.
1. Если вы понимаете, что некую часть вашей программы можно выделить в независимый объект. Объект со своим собственным поведением и свойствами.
Главное, чтобы вы чётко понимали, как вы объясните другому человеку, что это за класс. Если вы можете сформулировать на русском языке название класса в виде простого слова — это хороший признак, что он заслуживает существования.
Например, для нашей виселицы из предыдущего урока можно было бы создать класс: «Печатальщик результата», который бы занимался всем, что связано с выводом информации для пользователя в консоль.
2. Если в языке программирования не предусмотрен какой-то уже имеющийся класс для вашей цели.
Чаще всего просто погуглив, вы либо найдёте нужный класс в Ruby, либо поймёте, как в этой ситуации поступают другие люди. Если же информацию быстро найти не удалось, смело делайте свой класс.
3. Если в вашей программе есть абстрактная модель чего-то.
Например, если вы пишете программу для управления библиотекой, то понятно, что вам нужен класс книги или даже класс стеллажа, может быть, понадобится класс автора и класс жанра.
Это всё определяется в момент проектирования программы, подробнее об этом процессе мы говорили в 10-м уроке.
Как создать класс в Ruby?
Во-первых, классы солидны. То есть, класс представляет собой такой конкретный объёмный кусок программы. Часто случается так, что программисты в своей работе используют один и тот же класс в нескольких проектах.
Иногда классы даже включаются в структуру языка, как это стало с классами строки String
и момента времени Time
, настолько они удобные.
Классы принято описывать в отдельных файлах. Каждому классу — свой файл. Это существенно упрощает понимание программы.
Во-вторых, классы важны. Мы просто-таки запрещаем дорогим слушателям нашего курса создавать классы, называя их абы как. Придумайте своему классу осмысленное название, чтобы вы могли посреди ночи проснуться и по названию класса сказать, что он делает, хотя бы приблизительно.
Для примера создадим класс Мост
(Bridge
), который мы опишем в файле bridge.rb
(как обычно, положив его в новую папку c:\rubytut\lesson11
):
class Bridge
# Описание класса
end
Чтобы Ruby понял, что это не просто код, а описание класса, мы обернули его в конструкцию class-end
. Обратите внимание, что все имена классов в Ruby (в других языках тоже) обычно начинаются с большой буквы.
Если бы класс состоял из двух слов, то второе слово тоже было бы с большой буквы
class RoadBridge
...
end
Внутри конструкции class-end
мы пишем методы нашего класса. Как мы уже знаем, методы описываются с помощью конструкции def-end
.
Особое внимание следует обратить на метод initialize
— это так называемый конструктор класса, но об этом чуть позже.
Создание экземпляра класса
Пока мы просто описали класс, ничего интересного не произойдёт. Нужно создать хотя бы один объект этого класса. Для этого нам в нашей основной программе doroga.rb
необходимо подключить файл bridge.rb
с описанием класса Bridge
.
Мы умеем делать это с помощью команды require
:
require "bridge.rb"
После этого можно создать новый объект нашего нового класса Bridge
. Для этого мы пишем
bridge = Bridge.new
Это очень важный момент! Давайте разберёмся, что значит каждое слово в этой записи.
Во-первых, что такое bridge
, оно написано маленькими буквами, значит это не класс, а объект, вернее переменная.
Во-вторых знак равно (=
), он означает, что мы в переменную bridge
хотим что-то записать, хотим, чтобы переменная bridge
указывал на то, что будет справа от знака равно.
В-третьих, мы видим название нашего нового класса Bridge
. Мы только что описали этот класс в отдельном файле bridge.rb
и подключили его (файл) с помощью команды require
.
Наконец, .new
— мы вызвали у нашего класса специальный метод, который как бы говорит классу: «О великий и могучий! Создай для нас свое земное воплощение в виде конкретного объекта!»
А класс отвечает: «Так и быть, я сжалюсь над тобой, смертный и дам тебе свой экземпляр, но при одном условии — я сразу же вызову у этой копии метод initialize
».
Поэтому метод .new
возвращает новый объект данного класса.
Причем при создании объекта у него вызывается специальный метод с именем initialize
. Такой метод в программировании называется конструктор.
class Bridge
def initialize
puts "Мост создан"
end
end
Вы можете объявить в классе такой метод и написать в нем какой-то функционал – тогда этот функционал будет выполнен один раз при создании каждого объекта этого класса. Но можно и не писать, тогда конструктор будет пустой, объект создастся без каких-то дополнительных действий.
Конкретный объект какого-то класса в программировании называется экземпляр класса. По-английски instance
. Запомните эти слова, вот увидите, они несут свет озарения в чистом виде! 😉
Конечно, всю эту драму придумали разработчики, чтобы было удобнее создавать новые классы. В методе initialize
, который вызывается каждый раз, когда создаётся новый объект указанного класса, описывается, что должно произойти с экземпляром класса перед тем, как он будет создан. Если это класс книги, например, то нужно заполнить её название и год издания. Может быть ещё имя и фамилию автора и жанр. Всё на усмотрение разработчика класса.
Ещё раз, объект (экземпляр класса) и класс — это разные вещи, как есть вот мы, «Миша» и «Вадим» — объекты, а есть «Человек» — класс, некий собирательный образ, абстракция для всех людей на Земле (и на её орбите, а возможно и в других галактиках).
Итак, мы создали новый экземпляр класса Bridge
и сделали так, что переменная bridge
указывает на этот объект.
Если мы напишем
puts bridge.class.name
То увидим название нашего класса Bridge
.
А теперь смертельный номер. Просьба всех слабонервных удалиться. Если всё в Ruby это экземпляр какого-то класса, то что же тогда такое этот наш Bridge
? Какого будет вам узнать, что это тоже объект! «Какой же у него класс?» — спросите вы. Посмотрите сами, вы уже не маленькие.
Использование методов класса
def open
puts "Мост открыт, можно ехать"
end
Внутри нашего класса Bridge
мы написали метод open
. Этот метод на самом деле есть не у самого класса, а именно у его экземпляра.
Для того, чтобы «открыть» мост (объект класса Bridge
), на который указывает переменная bridge
, нам необходимо вызвать у этого объекта метод open
. Это делается очень просто и изящно:
bridge.open
и мы увидим в консоли наш текст открытия моста:
Мост открыт, можно ехать!
Именно вызов метода экземпляра класса мы делали, когда вызывали у массива, например, метод to_s
:
array = [1,2,3]
puts array.to_s
выводит в консоль "[1, 2, 3]"
— мы вызываем у объекта array
(экземпляра класса Array
) метод to_s
, который возвращает этот массив но уже как строку (экземпляр класса String
).
Поля класса
В методы класса, как и в обычные методы можно передавать параметры, как и обычные методы, они возвращают (или нет) какие-то значения.
Единственное отличие этих методов, в том, что они привязаны к экземпляру класса и в этих методах в связи с этим доступны «поля класса» или «переменные экземпляра класса» или «переменные объекта». Такие переменные используются для хранения состояния экземпляра класса, его свойств.
Например, наш мост bridge
(экземпляр класса Bridge
) может быть каменным или деревянным, длинным или коротким, узким или широким, пешеходным или автомобильным (или даже железнодорожным) и так далее.
Давайте сделаем наш мост открывающимся и для этого создадим поле класса opened
(открыт). В руби поля класса начинаются с символа «собаки» — @
(чтобы не путались с методами), поэтому в конструкторе мы опишем поведение моста по умолчанию в таком виде:
def initialize
puts "Мост создан"
@opened = false
end
а в метод open
добавим изменение этого внутреннего поля на true
def open
@opened = true
puts "Мост открыт, можно ехать"
end
Все важные поля вашего объекта должны быть объявлены в конструкторе! Вам нужно сообщить Ruby заранее какими свойствами будут обладать объекты вашего класса.
Текущее значение всех полей какого-то объекта определяют так называемое состояние объекта. Фактически один объект отличается от другого объекта того же класса своим состоянием (один мост открыт, другой закрыт, например).
Мы также напишем новый метод ‘is_opened?’, который будет возвращать true
, если мост открыт и false
, если закрыт:
def is_opened?
return @opened
end
Программисты Ruby договорились между собой, что все методы, которые возвращают true
или false
, будут заканчиваться знаком вопроса. В других языках как правило знак вопроса не используют в названиях методов.
Обратите внимание, что мы никак не можем достучаться до поля класса из нашей программы, именно поэтому для каждого обращения к ней нам нужен отдельный метод (если это действительно необходимо делать из нашей программы).
В самой программе doroga.rb
мы теперь перепишем открытие моста только для случая, когда мост закрыт:
if !bridge.is_open?
bridge.open
end
После этого наш мост откроется и напишет Мост открыт, можно ехать!
.
Ещё раз обратим ваше внимание, что если мы создадим новый мост
another_bridge = Bridge.new
то этот новый мост будет закрыт. another_bridge.is_open?
вернёт false
.
Надо просто немного привыкнуть к этой концепции класс-объект.
После небольшой практики вы будете в этом как рыба в воде.
Кстати, рыба и селедка — селедка это объект (если конкретная селедка, вот эта).
А просто «рыба» это уже класс 😉
В этом уроке мы познакомились с очень важным понятием классов. Узнали, что такое класс и чем он отличается от своего экземпляра. Поняли как, а главное зачем их создавать, как наполнять их методами, что такое методы класса, как их писать и использовать. Узнали также о полях класса и как в них хранятся свойства объекта, его состояние.
А на следующем уроке мы будем использовать классы в реальных задачах и перепишем с их помощью программу Виселица — сделаем ее более наглядной и привлекательной.
C ++ — Классы и объекты
Основной целью программирования на C ++ является добавление ориентации объекта на язык программирования C, а классы — центральная функция C ++, которая поддерживает объектно-ориентированное программирование и часто называется пользовательскими типами.
Класс используется для указания формы объекта и объединяет представление данных и методы для управления этими данными в один аккуратный пакет. Данные и функции внутри класса называются членами класса.
Определения классов C ++
Когда вы определяете класс, вы определяете схему для типа данных. Это фактически не определяет какие-либо данные, но определяет определение имени класса, то есть то, что будет состоять из класса класса и какие операции могут выполняться на таком объекте.
Определение класса начинается с класса ключевого слова, за которым следует имя класса; и тело класса, заключенное в пару фигурных скобок. Определение класса должно выполняться либо точкой с запятой, либо списком деклараций. Например, мы определили тип данных Box с использованием класса ключевого слова следующим образом:
class Box {
public:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
Ключевое слово public определяет атрибуты доступа для членов класса, которые следуют за ним. Доступ к публичному члену можно получить извне класса в пределах области объекта класса. Вы также можете указать членов класса как частные или защищенные, которые мы обсудим в подразделе.
Определение объектов C ++
Класс предоставляет чертежи для объектов, поэтому в основном объект создается из класса. Мы объявляем объекты класса с точно таким же объявлением, что мы объявляем переменные основных типов. Следующие утверждения объявляют два объекта класса Box —
Box Box1; // Declare Box1 of type Box
Box Box2; // Declare Box2 of type Box
Оба объекта Box1 и Box2 будут иметь собственную копию данных.
Доступ к членам данных
Доступ к публичным элементам данных объекта класса осуществляется с помощью оператора прямого доступа (.). Давайте попробуем следующий пример, чтобы все было ясно —
#include <iostream>
using namespace std;
class Box {
public:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main() {
Box Box1; // Declare Box1 of type Box
Box Box2; // Declare Box2 of type Box
double volume = 0.0; // Store the volume of a box here
// box 1 specification
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box 2 specification
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// volume of box 1
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Volume of Box1 : " << volume <<endl;
// volume of box 2
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Volume of Box2 : " << volume <<endl;
return 0;
}
Когда приведенный выше код компилируется и выполняется, он производит следующий результат:
Volume of Box1 : 210
Volume of Box2 : 1560
Важно отметить, что доступ к закрытым и защищенным элементам невозможен напрямую, используя оператор прямого доступа к члену (.). Мы узнаем, как можно получить доступ к закрытым и защищенным членам.
Классы и объекты в деталях
До сих пор у вас есть очень общее представление о C ++ классах и объектах. Существуют и другие интересные концепции, связанные с классами и объектами C ++, которые мы обсудим в следующих подразделах, перечисленных ниже:
Функции членов класса
Функция-член класса — это функция, которая имеет определение или ее прототип в определении класса, как и любая другая переменная.
Модификаторы доступа к классу
Член класса может быть определен как открытый, закрытый или защищенный. По умолчанию члены будут считаться закрытыми.
Конструктор и деструктор
Конструктор класса является специальной функцией в классе, который вызывается при создании нового объекта класса. Деструктор также является специальной функцией, которая вызывается при удалении созданного объекта.
Копировать конструктор
Конструктор копирования — это конструктор, который создает объект, инициализируя его объектом того же класса, который был создан ранее.
Функции друга
Функция друга разрешает полный доступ к закрытым и защищенным членам класса.
Встроенные функции
С помощью встроенной функции компилятор пытается развернуть код в теле функции вместо вызова функции.
this указатель
Каждый объект имеет специальный указатель , этот , который указывает на сам объект.
Указатель на классы C ++
Указатель на класс выполняется точно так же, как указатель на структуру. На самом деле класс — это просто структура с функциями в нем.
Статические члены класса
Оба элемента данных и члены функции класса могут быть объявлены как статические.
Подобный вопрос может служить проверкой Ваших знаний терминологии объектно-ориентированного программирования (object oriented programming, сокращенно OOP). Такой же вопрос можно было бы задать в контексте интервью программиста C++, или любой позиции вакансии, которая требует понимания концепции объектов в программировании.
Термины class и object определенно связаны друг с другом, но каждый из них имеет свой отдельный смысл. Начнем с объяснения смысла термина «class» в контексте OOP. Определение class относится к реально написанной части кода, которая используется для определения поведения любого такого класса. Итак, class это статическая часть кода, состоящая из атрибутов, которые не меняются во время выполнения программы — наподобие определений методов класса.
Объект это экземпляр класса. Однако термин object относится к реально существующему экземпляру класса. Каждый объект должен принадлежать классу. Объекты создаются и уничтожаются в программе по мере необходимости, поэтому их время жизни ограничено. Пока объекты «живые», их свойства могут значительно меняться.
Конкретный пример поможет разобраться в том, о чем тут идет речь. Предположим, что у нас есть класс Animal (животное). Все животные имеют тела и мозги — и поэтому они могли бы быть атрибутами нашего вымышленного класса Animal. Также мы можем добавить к классу некоторые методы, которые общие у всех животных — такие как movement (перемещение), потому что все животные могут двигаться. Идея, которую Вы хотите осуществить в своем представлении, состоит в том, что этот очень общий шаблон Animal не изменяется, он просто дает несколько строк кода, которые определяют класс Animal.
Экземпляр класса Animal был бы определенным животным — львом, кошкой или зеброй. Эти экземпляры класса называются объектами. Принимая во внимание, что класс Animal — общее понятие, экземпляры этого класса — львы, коты и т. д. — берут за основу общее понятие класса и создают его реальные экземпляры.
Именно по этой причине программисты определяют конструкторы в своих классах — так происходит всегда, когда они хотят создавать объект с определенными свойствами. Например, какого вида животное должно быть, как его зовут, сколько оно весит и т. д. Так что Вы можете думать о конструкторе объекта как о чем-то таком, которое вводит класс в жизнь программы — отсюда и пошло название constructor, потому что функция конструктора создает определенный экземпляр класса.
У объектов есть понятие «время жизни», а у класса нет. И как ясно показывает наш пример класса Animal, каждый объект в программе имеет связанный с ним интервал времени жизни — кот или зебра не могут жить вечно. И свойства этих объектов могут меняться в течении их «жизни»; если мы определим переменную size в классе Animal, то она может обозначать размер животного (например, размер кота как объекта со временем может расти).
Общее отличие объекта от класса. Можно сказать, что в то время как класс является общей концепцией предметного понятия (наподобие Animal), объект это очень определенное воплощение этого класса, причем с ограниченным временем жизни в программе. Другой способ думать об отличии между классом и объектом — класс предоставляет шаблон для чего-то более специального, которое должен определить программист при создании объекта этого класса.
[Ссылки]
1. In Java, what’s the difference between an object and a class? site:programmerinterview.com. |
Основы работы с классами в Pyton 3. Класс и объект в Pyton
Объектно-ориентированное программирование – это способ введения кодовой информации для создания новой программы. В случае с ООП – это распределение на отдельные объекты и классы. Что представляют собой классы, из чего состоят и как с ними работать – разберем в этой статье.
Что такое объектно-ориентированное программирование в Python
Тот, кто проходил обучение программированию, знает, как писать код в процедурном стиле. Ведь именно с него начинается весь процесс обучения. Что же это такое? По сути, процедурное заполнение представляет собой последовательно заполненный код, состоящий из подпрограмм или функций, растянутый на множество строк.
В то время, когда ООП еще не было изобретено, процедурный код использовался повсеместно. Он уместен для создания программ небольшого размера, и по сей день с успехом используется в некоторых ситуациях. Но в том случае, когда требуется создать более совершенные программы, содержащие коды на 10000 строк и больше, процедурный код доставит массу неудобств. В первую очередь это его создание.
При необходимости внесения корректировок внутри программы, придется проанализировать всю приведенную информацию, на что может уйти не один час, после чего приступить к изменениям.
С появлением ООП решить эту задачу стало намного проще. В основе объектно-ориентированного программирования лежит идея, что все состоит из отдельных объектов взаимодействующих друг с другом посредством программных связей. Каждый объект отвечает за выполнение той или иной задачи, ведя себя обособлено, но являясь частичкой одного большого кода.
Как создавать класс в Python
Класс представляет собой проект объекта. Для примера можно взять небольшой код:
Здесь, в последней строке, можно увидеть переменную «х», которая может выглядеть, как большой объем информации, но в ней имеется множество методов. Для их обнаружения, необходимо вписать ключевое слово dir. Из этого следует, что строка основана на классе, а переменная х является экземпляром класса.
Для создания класса, первоначально потребуется ввести команду class, после чего поставить двоеточие. Далее прописывается описание класса, но этот момент не является обязательным и может быть пропущен по желанию пользователя. Чтобы открыть доступ к строке, введите команду Class._doc_ Для полного ознакомления с классом, рассмотрим из чего он состоит.
Атрибут
Эта часть относится к переменной в классе. Другими словами, все его действия происходят внутри классового кода. Атрибут Class способен создать только одну копию внутри класса. Она же, впоследствии, работает со всеми функциями и объектами этого класса. Если это утверждение разобрать на примере, то оно будет выглядеть следующим образом:
class class_attribute: val = 1 def product(self): class_attribute.val *= 10 print(class_attribute.val) obj1 = class_attribute() obj1.product() obj2 = class_attribute() obj2.product()
Для создания переменной класса используем слово val и придадим ему значение 1. Чтобы получить доступ к переменной val, прописываем ниже функцию product(). Создаем манипуляцию значения, умножив его на 10.
Ниже можно увидеть, что после создания двух объектов копия переменной будет использоваться в них обоих. Так как начальное значение атрибута равно 1, а после оно умножается на 10, то в obj1 мы получим итоговое значение val=10, а вот при вызове obj2, val умножается на 10 и получает значение 100. Как итог на экране высветиться следующее:
Метод
Это функция, которая является элементом класса, и отвечает за выполнение какого-либо действия. В качестве первого параметра метод принимает экземпляр класса. Чтобы начать им пользоваться, нужно вызвать функцию через объект. Всего различается три вида методов:
- Методы экземпляра класса. Наиболее часто встречающийся метод. В качестве первого аргумента self, экземпляр класса принимает объект класса, указывающий на сам экземпляр. Данный вид метода имеет неограниченное количество параметров. Использование аргумента self поможет поменять состояние объекта и обратиться к другим методам и параметрам, привязанным к нему. К примеру, при использовании атрибута self._class_, возможно получить доступ к атрибутам класса и изменить его состояние. Из этого следует, что экземпляры класса предоставляют возможность изменения состояния отдельного объекта или класса в целом.
- Методы класса. Сущность метода класса в том, что он в качестве параметра cls принимает класс. Декларируя методы, необходимо использовать декоратор classmethod. В отличие от предыдущего метода, метод класса привязан только к классу, но не имеет привязанности к его экземпляру. Метод класса работает над изменением состояния класса, что отражается на всех объектах, содержащихся внутри класса, но он не способен изменить состояние конкретно выбранного объекта.
- Статические методы. Декларация статического метода производится использованием декоратора staticmethod. Характерная особенность этого вида в том, что они не способны изменять состояний класса или их экземпляров. Они работают в статическом состоянии и потому не нуждаются в предварительном записывании первого аргумента.
Конструктор
Объектно-ориентированное программирование характеризует конструктор как метод, проявляемый автоматически при создании объектов. Другими словами, он участвует в построении объектов класса. В каждом языке программирования конструктору дается свое имя, так в Python это метод _init_().
Наличествующие знаки подчеркивания перед и после имени метода, указывают на его принадлежность к группе методов перезагрузки операторов. Присутствие таких методов говорит о возможности участия объектов в операциях сложения, вычитания, вызове как функции и других.
- Отображается символическим значением _init_.
- Его необходимость заключена в создании объекта.
- Через свойства объекта конструктор передает значения аргументов.
- Независимо, какого размера класс, он вмещает только один конструктор.
- В случае невозможности обнаружения конструктором класса, программа переключается на родительский класс.
# Прямоугольник. class Rectangle : 'Это класс Rectangle' # Способ создания объекта (конструктор) def __init__(self, width, height): self.width= width self.height = height def getWidth(self): return self.width def getHeight(self): return self.height # Метод расчета площади. def getArea(self): return self.width * self.height
Создаем объект используя класс Rectangle:
# Создаем 2 объекта: r1 & r2 r1 = Rectangle(10,5) r2 = Rectangle(20,11) print("r1.width = ", r1.width) print("r1.height = ", r1.height) print("r1.getWidth() = ", r1.getWidth()) print("r1.getArea() = ", r1.getArea()) print("-----------------") print("r2.width = ", r2.width) print("r2.height = ", r2.height) print("r2.getWidth() = ", r2.getWidth()) print("r2.getArea() = ", r2.getArea())
Что происходит при создании объекта с помощью класса
Работая над конструированием объекта, программа автоматически запускает конструктор. При этом атрибуты получают значения аргументов.
Конструктор с аргументами по умолчанию
Рассматривая иные языки, можно заметить, что они содержат по несколько конструкторов. Питон допускает использование одного. Но преимущество в том, что возможна установка значений по умолчанию.
Примечание от эксперта! Аргументы, участвующие в построении класса, указываются до аргументов со значениями по умолчанию.
class Person: # Параметры возраста и пола имеют значение по умолчанию. def __init__(self, name, age=1, gender="Male"): self.name = name self.age = age self.gender= gender def showInfo(self): print("Name: ", self.name) print("Age: ", self.age) print("Gender: ", self.gender)
Где gender =”Male” относится к значению по умолчанию. Дальше задаем другие значения:
from person import Person # Создать объект Person. aimee = Person("Aimee", 21, "Female") aimee.showInfo() print(" --------------- ") # возраст по умолчанию, пол. alice = Person( "Alice" ) alice.showInfo() print(" --------------- ") # Пол по умолчанию. tran = Person("Tran", 37) tran.showInfo()
В итоге получаем результат с итоговыми значениями.
Сравниваем объекты
Объекты в Python, создаваемые конструктором, имеют координаты расположения. Что указывает на наличие адреса. Допустим, существует объект АА, ссылающийся на ВВ. В таком случае объекту АА выделяется отдельная ячейка, ведь основным объектом в данном случае считается ВВ.
Оператор ==, прописанный в коде, необходим для проведения анализа: на одну или разные ячейки ссылаются выбранные объекты. Если ссылка ведет к одному месту, то оператор == возвратит True. Для понимания ссылаются или нет выбранные объекты на места с разными адресатами, прописывается другой оператор !=
from rectangle import Rectangle r1 = Rectangle(20, 10) r2 = Rectangle(20 , 10) r3 = r1 # Сравните r1 и r2 test1 = r1 == r2 # --> False # Сравните r1 и r3 test2 = r1 == r3 # --> True print ("r1 == r2 ? ", test1) print ("r1 == r3 ? ", test2) print (" -------------- ") print ("r1 != r2 ? ", r1 != r2) print ("r1 != r3 ? ", r1 != r3)
Где конечным результатом будет:
Что такое атрибуты
В Питоне также существует второе понятие Атрибуты, которое имеет отличие от первого, изученного нами. Если первое относится к «Переменным класса», то второе к «Атрибутам». Разберем на примере:
class Player: # Переменная класса minAge = 18 maxAge = 50 def __init__(self, name, age): self.name = name self.age = age
Атрибут
Для создания объектов из одного класса используются разные ячейки памяти. Следовательно, их атрибуты с одинаковым именем будут ссылаться на различную адресацию. К примеру:
from player import Player player1 = Player("Tom", 20) player2 = Player("Jerry", 20) print("player1.name = ", player1.name) print("player1.age = ", player1.age) print("player2.name = ", player2.name) print("player2.age = ", player2.age) print(" ------------ ") print("Assign new value to player1.age = 21 ") # Присвойте новое значение атрибуту возраста player1. player1.age = 21 print("player1.name = ", player1.name) print("player1.age = ", player1.age) print("player2.name = ", player2.name) print("player2.age = ", player2.age)
Итоговый результат:
В Питоне присутствует возможность создания новых атрибутов для созданных ранее объектов. На примере ниже, можно разглядеть, как для объекта player1 создается атрибут address.
from player import Player player1 = Player("Tom", 20) player2 = Player("Jerry", 20) # Создайте новый атрибут с именем «address» для player1. player1.address = "USA" print("player1.name = ", player1.name) print("player1.age = ", player1.age) print("player1.address = ", player1.address) print(" ------------------- ") print("player2.name = ", player2.name) print("player2.age = ", player2.age) # player2 е имеет атрибута 'address' (Error!!) print("player2.address = ", player2.address)
В результате получаем вывод:
player1.name = Tom player1.age = 20 player1.address = USA ------------------- player2.name = Jerry player2.age = 20 Traceback (most recent call last): File "C:/Users/gvido/class.py", line 27, in <module> print("player2.address = ", player2.address) AttributeError: 'Player' object has no attribute 'address'
Атрибуты функции
Для получения доступа к атрибутам объекта, используется оператор «точка» (к примеру, такие значения: player.age). Для облегчения задачи используют различные функции.
getattr (obj, name[,default]) | Работает над возвратом значений атрибута или проводит установку значений по умолчанию, если они не были обнаружены. |
hasattr (obj, name) | Работает над проверкой атрибута объекта: совершалась ли передача аргументом name. |
setattr (obj, name, value) | Работает над приданием значений атрибуту. При его отсутствии создается новый. |
delattr (obj, name) | Работает над удалением атрибута. |
Разновидности встроенных атрибутов класса
Благодаря тому, что объекты являются дочерними элементами атрибутов встроенного языка Питон, они могут заимствовать некоторые атрибуты от него:
Атрибут | Описание |
_dict_ | Предоставление самых необходимых данных о классе. |
_doc_ | Возвращение строки с описанием класса. При невозможности определения значения, возвращается None. |
_class_ | Возвращение объекта, содержащего информацию о классе. |
_module_ | Возвращение имя модуля класса. При определении класса в выполняемом модуле возвращается _main_. |
Виды переменных класса
Если приходилось сталкиваться с другими языками программирования, такими как Java или C#, то наверняка знаете, что такое Field. Переменные класса в Питоне имеют примерно то же значение.
Совет от эксперта! Для получения доступа к переменной, воспользуйтесь именем, а не его объектом класса. В таком случае не произойдет путаницы между переменной и атрибутами.
Для переменной выделяется отдельная ячейка, и она абсолютна доступна для всех объектов класса.
Варианты составляющих класса или объекта
Чтобы найти все атрибуты, методы, объекты и переменные класса, можно воспользоваться вводом функции dir.
from player import Player # Вывести список атрибутов, методов и переменных объекта 'Player' print(dir(Player)) print("\n\n") player1 = Player("Tom", 20) player1.address ="USA" # Вывести список атрибутов, методов и переменных объекта 'player1' print(dir(player1))
Как результат на экран выведутся следующие значения:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'maxAge', 'minAge'] ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'address', 'age', 'maxAge', 'minAge', 'name']
Заключение
Объектно-ориентированное программирование предусматривает работу с классами, которые были разобраны в данной статье. Что они включают в свой состав. И какие функции, методы и атрибуты используют для выполнения различных действий.
Оцените качество статьи. Нам важно ваше мнение:
Радченко Глеб Игоревич
Научные интересы
- Грид-вычисления.
- Облачные вычисления.
- Распределенные вычислительные системы.
Публикации
Проекты
- Проект Erasmus+ PWs@PhD. Основная цель проекта PWs@PhD – поддержка развития, модернизации, интернационализации высшего образования, а именно исследовательской составляющей европейского образования уровня PhD, содействие созданию новых PhD-программ в странах-партнерах в области программной инженерии.
- Сервисно-ориентированный подход к использованию проблемно-ориентированных пакетов в распределенных и грид-средах (проект DiVTB).
- Параллельная реализация нейросетевого алгоритма распознавания раздельной речи (Часть 1, Часть 2, Часть 3).
Новости
- [2013-12-25] Обновления страниц курсов:
- [2013-12-17] Обновления страниц курсов:
- [2013-11-28] Обновления страниц курсов:
- [2013-11-07] Размещены слайды презентаций:
- [2013-10-26] Размещены слайды презентаций:
- [2013-06-03] Размещены слайды презентаций:
[Архив новостей]
Ссылки
- Mendeley — система для каталогизации и управления библиографией. Встраивается в Microsoft Word, позволяя автоматизировать процесс управления списками литературы при подготовке статей. Поддерживает множество форматов оформления библиографических ссылок, включая ГОСТ-7.0.5-2008.
- Memsource — операционная среда для выполнения письменных переводов, включающая базы памяти переводов, встроенный машинный перевод, модуль управления терминологией, а также текстовый редактор MemSource Editor. Может импортировать и экспортировать документы всех стандартных форматов, включая Word и PowerPoint.
Мой профиль
Классы и объекты C ++
Классы / объекты C ++
C ++ — объектно-ориентированный язык программирования.
Все в C ++ связано с классами и объектами вместе с их атрибутами и
методы. Например: в реальной жизни автомобиль — это объект . Автомобиль имеет атрибутов , таких как вес и цвет, и .
методы , такие как привод и тормоз.
Атрибуты и методы — это в основном переменных и
функции , принадлежащие классу.Их часто называют
«ученики класса».
Класс — это определяемый пользователем тип данных, который мы можем использовать в нашей программе, и он
работает как конструктор объектов или «план» для создания объектов.
Создать класс
Чтобы создать класс, используйте ключевое слово class
:
Пример
Создайте класс под названием « MyClass
»:
class MyClass {
// Класс
public:
// Спецификатор доступа
int myNum; //
Атрибут (переменная типа int)
string myString; //
Атрибут (строковая переменная)
};
Объяснение примера
- Ключевое слово
class
используется для создания класса с именемMyClass
. - Ключевое слово
public
— это спецификатор доступа , который указывает, что члены (атрибуты и методы) класса доступны извне. Вы узнаете больше о спецификаторах доступа позже. - Внутри класса есть целочисленная переменная
и строковая переменная
myNummyString
. Когда объявлены переменные
внутри класса они называются атрибутами . - Наконец, завершите определение класса точкой с запятой
;
.
Создать объект
В C ++ объект создается из класса. Мы уже создали класс с именем MyClass
,
так что теперь мы можем использовать это для создания объектов.
Чтобы создать объект MyClass
, укажите
имя класса, за которым следует имя объекта.
Для доступа к атрибутам класса ( myNum
и myString
) используйте точечный синтаксис (.
)
на объекте:
Пример
Создайте объект с именем « myObj
» и откройте
атрибуты:
class MyClass {// Класс
общедоступный:
// Спецификатор доступа
int myNum; //
Атрибут (переменная типа int)
string myString; //
Атрибут (строковая переменная)
};
int main () {
MyClass myObj ;
// Создание объекта MyClass
// Доступ к атрибутам и установка значений
myObj.myNum
= 15;
myObj.myString = «Немного текста»;
// Распечатать значения атрибутов
cout << myObj.myNum << "\ n";
cout << myObj.myString;
возврат 0;
}
Попробуй сам »
Несколько объектов
Можно создать несколько объектов одного класса:
Пример
// Создаем класс Car с некоторыми атрибутами.
class Car {
public:
string brand; Модель струны
;
инт
год;
};
int main () {
// Создаем объект Car
Car carObj1;
carObj1.марка = «BMW»;
carObj1.model = «X5»;
carObj1.year = 1999;
// Создаем еще один объект Car
Car
carObj2;
carObj2.brand = «Форд»;
carObj2.model =
«Мустанг»;
carObj2.year = 1969;
// Печать
значения атрибутов
cout << carObj1.brand
<< "" << carObj1.model << "" << carObj1.year << "\ n";
cout <<
carObj2.brand << "" << carObj2.модель << "" << carObj2.year << "\ n";
возврат 0;
}
Попробуй сам »
class — Почему классы не являются объектами в C ++?
Я всегда думал, что классы являются синонимами объектов
Язык в литературе ООП иногда не конкретен. Не помогает и то, что языки программирования имеют несколько иное представление о том, что такое объект.
Класс — это шаблон или определение, из которого создаются объекты (экземпляры этого класса).То есть класс предоставляет структуру, сигнатуры типов и поведение объектов этого класса (или типа … подробнее об этом позже).
Объект — это просто место в памяти экземпляра этого класса.
Википедия предоставляет хорошую документацию по этому поводу. Предлагаю вам прочитать:
http://en.wikipedia.org/wiki/Class_(computer_programming)
http://en.wikipedia.org/wiki/Object_(object-oriated_programming)
Также есть концепт типа .Тип (или интерфейс, как его иногда называют в литературе или языках программирования) обычно представляет собой набор сигнатур типа / метода (и, возможно, поведения). Такие вещи, как интерфейсы Java и чистые виртуальные классы C ++, как правило, представляют типов (но не совсем то же самое).
Тогда класс, который соответствует этому типу (будь то интерфейс или чистый виртуальный класс), является реализацией этого типа .
Этот класс, реализация типа — это всего лишь рецепт того, как создавать объекты этого класса / типа в памяти.
Когда вы создаете экземпляр класса / типа, вы реифицируете, создаете экземпляр (объект) этого класса в памяти.
В C ++ класс не является объектом, поскольку сам класс не создается. Класс C ++ не является экземпляром какого-либо другого класса (см. Определения, которые я дал выше).
OTH, в таких языках, как Java, сам класс представлен экземплярами первичного класса (java.lang.Class). Итак, у класса X есть связанный с ним объект в памяти (экземпляр java.lang.Class).И с его помощью, с этим объектом «class» , вы можете (теоретически) создать или создать другой экземпляр (или объект) класса / типа X.
Это может сбить с толку. Я настоятельно рекомендую вам поискать и прочитать литературу по классам, типам, прототипам и объектам / экземплярам.
, и я не понимаю, что означает это утверждение. Как классы не
объекты,
Как объяснено выше. Класс — это не объект. Объект — это экземпляр, часть памяти, созданная и инициализированная по «рецепту» класса или типа.
и почему это важно, если язык статичен?
Эта часть книги немного вводит в заблуждение, потому что Java, например, имеет статическую типизацию, и тем не менее классы сами могут быть объектами. Возможно, текст относится к динамически типизированным языкам (например, JavaScript), где классы также могут быть объектами или экземплярами.
Я предлагаю никогда не использовать слово «объект», а просто ограничить словарь «классами» и «экземплярами». Но это мое личное пристрастие.Другие могут не согласиться, и пусть будет так.
классов и объектов C ++ | Studytonight
Классы — самая важная особенность C ++, ведущая к объектно-ориентированному программированию. Класс — это определяемый пользователем тип данных, который содержит свои собственные элементы данных и функции-члены, к которым можно получить доступ и использовать, создав экземпляр этого класса.
Переменные внутри определения класса вызываются как члены данных, а функции называются функциями-членами.
Например: Класс птиц, все птицы умеют летать, и у всех есть крылья и клювы.Итак, здесь полет — это поведение, а крылья и клюв — часть их характеристик. В этом классе много разных птиц с разными именами, но все они обладают таким поведением и характеристиками.
Точно так же класс — это просто предварительный план, который объявляет и определяет характеристики и поведение, а именно элементы данных и функции-члены соответственно. И все объекты этого класса будут разделять эти характеристики и поведение.
Подробнее о классах
- Имя класса должно начинаться с заглавной буквы (хотя это не обязательно).Если имя класса состоит из более чем одного слова, то первая буква каждого слова должна быть в верхнем регистре. Пример ,
класс Study, класс StudyTonight и т. Д.
- Классы содержат элементы данных и функции-члены, и доступ этих элементов данных и переменной зависит от спецификаторов доступа (обсуждаемых в следующем разделе).
- могут быть определены внутри определения класса или вне определения класса.
- в C ++ аналогичен структурам в C, с той лишь разницей, что по умолчанию для класса используется частный контроль доступа, а в качестве структуры по умолчанию используется общедоступная структура.
- Все возможности OOPS вращаются вокруг классов в C ++. Наследование, инкапсуляция, абстракция и т. Д.
- Объекты класса содержат отдельные копии членов данных. Мы можем создать столько объектов класса, сколько нам нужно.
- Классы обладают большим количеством характеристик, например, мы можем создавать абстрактные классы, неизменяемые классы, все это мы изучим позже.
Функции-члены класса
Класс
Объекты классов
Class — это просто план или шаблон. Когда мы определяем класс, хранилище не назначается.Объекты — это экземпляры класса, который содержит переменные данных, объявленные в классе, и функции-члены работают с этими объектами класса.
Каждый объект имеет разные переменные данных. Объекты инициализируются с помощью специальных функций класса, называемых Конструкторы . О конструкторах мы поговорим позже.
И всякий раз, когда объект выходит за пределы своей области видимости, вызывается другая специальная функция-член класса, называемая Деструктор , чтобы освободить память, зарезервированную объектом.В C ++ нет автоматического сборщика мусора, как в JAVA, в C ++ эту задачу выполняет деструктор.
класс Abc
{
int x;
пустой дисплей ()
{
// какое-то заявление
}
};
int main ()
{
Abc obj; // Создан объект класса Abc
}
Классы и объекты | C ++
Одна из лучших особенностей C ++ — это способность работать с большим количеством данных и отслеживать их. Фактически данные являются движущей силой большинства программ, которые вы напишете.Программисты постоянно разбирают строки, складывают и вычитают числа, считают вещи и передают различные фрагменты данных.
Во всех основных приложениях чрезвычайно важна возможность организованной и структурированной работы с данными. Большие приложения часто сталкиваются с отображением, хранением и управлением сложной информацией о самых разных вещах.
Одним из ограничений наших способностей к программированию до этого момента курса было то, что мы научились работать только с очень ограниченными типами данных.На самом деле, если задуматься, мы способны представлять и работать только с текстом, числами и логическими значениями.
Эти ограниченные типы данных будут представлять проблему для нас, если мы хотим писать все более и более сложные программы в будущем. Проблема в том, что помимо актуальных чисел, текста и истинной / ложной информации, большинство вещей , с которыми мы взаимодействуем и используем в реальном мире, не могут быть легко сохранены в строке, числе или логическом значении.
Создание книги
Представьте, например, что мы пишем программу для библиотеки.В этой программе мы хотели отслеживать и работать с кучей книг. Возникает вопрос, а как мы храним эти книги?
Мы не можем хранить их в логических значениях, мы могли бы сохранить количество страниц и даты их публикации в виде чисел, и, возможно, мы могли бы сохранить имя и заголовок автора в строках. Но, надеюсь, вам ясно, что нет простого решения для представления книги с ограниченными типами данных, которые мы даем. По крайней мере, в книге есть несколько переменных.
Объектно-ориентированное программирование (ООП)
На заре компьютерного программирования, разработчики просто немного потрудились и сделали все возможное, чтобы писать программы так, как я только что описал. Но в конце концов было разработано лучшее решение — объектно-ориентированное программирование.
Объектно-ориентированное программирование позволяет разработчикам создавать свои собственные типы данных. Таким образом, в дополнение к строкам, логическим значениям и числам разработчик может создать собственный тип данных Book
. Этот тип данных книги затем может быть сохранен в переменных, передан в функции, распечатан и во всем остальном, что могут делать другие базовые типы данных.
Создавая собственные типы данных в своих программах, вы теоретически можете смоделировать любой предмет или сущность реального мира и использовать их так же, как строку или число.
Большинство реальных приложений, которые вы видите, будь то facebook, google, gruhhub или небольшие программы, которые мы напишем в дальнейшем в курсе, будут использовать эти настраиваемые типы данных для лучшей организации сложных объектов, таких как книги.
Классы
Так как же нам создать эти настраиваемые типы данных? На самом деле это довольно просто, и мы можем сделать это, создав класс.Класс — это спецификация типа данных в нашей программе. Поскольку мы создаем настраиваемые типы данных, мы сначала должны описать, как они выглядят и как ведут себя, чтобы C ++ знал, что с ними делать.
Классы можно рассматривать как чертежи. Они описывают, как должен выглядеть тип данных, и описывают все атрибуты и функции, которые будут отличаться от типа данных.
C ++ уже предоставляет нам три основных типа данных: логические, строки (текст) и числа.Когда мы создаем наши настраиваемые типы данных, они будут состоять из этих базовых типов данных. Поэтому при создании класса мы можем указать атрибуты, которые класс будет содержать в терминах этих базовых типов данных.
Давайте посмотрим, как будет выглядеть класс для нашей Книги:
Копия классная книга {
общественность:
строковое название;
автор строки;
int numPages;
};
Помните, что класс — это спецификация, план, он описывает структуру и состав этого нового типа данных книги.
В приведенном выше коде мы указали, что книга состоит из названия, автора и количества страниц. Обратите внимание на то, что этот тип данных книги состоит из базовых типов данных, которые нам предоставил язык. Со временем мы также сможем добавить функциональность и в этот книжный класс, но давайте сохраним это для будущего урока.
Объектов
Итак, теперь, когда мы создали наш класс book, C ++ знает, как выглядит этот новый тип данных, и мы можем начать работать с книгами в нашей программе.
Поскольку класс book — это просто спецификация, мы не можем использовать его в нашей программе, вместо этого мы должны создавать так называемые объекты. Объект — это экземпляр класса. В этом случае объектом будет настоящая книга с фактическим названием, автором и количеством страниц.
Это важный терминологический момент. Класс — это план типа данных, объект — это экземпляр этого плана.
Создадим объект книги на C ++:
Копия классная книга {
общественность:
строковое название;
автор строки;
int numPages;
};
int main () {
Книга book1;
книга1.title = "Гарри Поттер";
book1.author = "Дж. К. Роулинг";
book1.numPages = 500;
cout << book1.title << endl;
Книжная книга2;
book2.title = "Властелин колец";
book2.author = "Дж. Р. Р. Толкин";
book2.numPages = 300;
cout << book2.title << endl;
возврат 0;
}
Итак, мы создали объект. Это книга с названием, автором и количеством страниц! Что касается C ++, это в основном то же самое, что и любые другие типы данных, с которыми мы работали на протяжении этого курса.
В случае объекта книги мы назначили ему ряд атрибутов, к которым мы можем получить доступ и изменить, как обычно.
Название и автор - это просто строки, а количество страниц - это число. Для доступа к этим атрибутам мы используем .
. за которым следует имя атрибута. Этот процесс называется отменой ссылки на объект книги.
Завершение
Вот и все! Классы и объекты печально известны тем, что сбивают с толку новых программистов, но пока вы научитесь видеть в них просто более сложные пользовательские типы данных, большая часть тайны исчезнет.
Мы создали класс книги и объект, но помните, что вы можете создавать классы для моделирования чего угодно. Попробуйте поиграть с некоторыми из ваших собственных классов или добавьте дополнительные атрибуты в нашу книгу! Также не забудьте посмотреть видео выше, где я проведу вас по написанию этого кода построчно!
11.2 - Классы и члены классов
Хотя C ++ предоставляет ряд фундаментальных типов данных (например, char, int, long, float, double и т. Д.), Которых часто достаточно для решения относительно простых задач, решение сложных проблем может быть затруднено. проблемы с использованием только этих типов.Одна из наиболее полезных функций C ++ - это возможность определять собственные типы данных, которые лучше соответствуют решаемой задаче. Вы уже видели, как перечисляемые типы и структуры могут использоваться для создания ваших собственных типов данных.
Вот пример структуры, используемой для хранения даты:
struct DateStruct { int year {}; int месяц {}; int день {}; }; |
Перечислимые типы и структуры только для данных (структуры, содержащие только переменные) представляют собой традиционный мир не объектно-ориентированного программирования, поскольку они могут содержать только данные.В C ++ 11 мы можем создать и инициализировать эту структуру следующим образом:
DateStruct сегодня {2020, 10, 14}; // использовать унифицированную инициализацию |
Теперь, если мы хотим вывести дату на экран (что мы, вероятно, очень хотим сделать), имеет смысл написать функцию для этого. Вот полная программа:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 14 18 19 20 21 22 23 | #include struct DateStruct { int year {}; int месяц {}; int день {}; }; void print (const DateStruct & date) { std :: cout << date.год << '/' << date.month << '/' << date.day; } int main () { DateStruct сегодня {2020, 10, 14}; // использовать единую инициализацию today.day = 16; // используйте оператор выбора члена, чтобы выбрать член структуры print (today); возврат 0; } |
Эта программа напечатает:
16.10.2020
Классы
В мире объектно-ориентированного программирования мы часто хотим, чтобы наши типы не только хранили данные, но и предоставляли функции, которые также работают с данными.В C ++ это обычно делается с помощью ключевого слова class . Ключевое слово class определяет новый определяемый пользователем тип, называемый классом.
В C ++ классы и структуры по сути одинаковы. Фактически, следующая структура и класс практически идентичны:
struct DateStruct { int year {}; int месяц {}; int день {}; }; class DateClass { public: int m_year {}; int m_month {}; int m_day {}; }; |
Обратите внимание, что единственное существенное отличие - это ключевое слово public: в классе.Мы обсудим функцию этого ключевого слова в следующем уроке.
Как и объявление структуры, объявление класса не выделяет памяти. Он только определяет, как выглядит класс.
Как и в случае со структурами, одна из самых простых ошибок C ++ - это забыть о точке с запятой в конце объявления класса. Это вызовет ошибку компилятора в следующей строке кода . Современные компиляторы, такие как Visual Studio 2010, укажут вам, что вы, возможно, забыли точку с запятой, но более старые или менее сложные компиляторы могут этого не делать, что может затруднить обнаружение фактической ошибки.
Определения классов (и структур) похожи на план - они описывают, как будет выглядеть результирующий объект, но на самом деле они не создают объект. Чтобы фактически создать объект класса, необходимо определить переменную этого типа класса:
DateClass сегодня {2020, 10, 14}; // объявляем переменную класса DateClass |
Функции-члены
Помимо хранения данных, классы (и структуры) также могут содержать функции! Функции, определенные внутри класса, называются функциями-членами (или иногда методами ).Функции-члены могут быть определены внутри или вне определения класса. Мы определим их сейчас внутри класса (для простоты), а позже покажем, как определять их вне класса.
Вот наш класс Date с функцией-членом для печати даты:
class DateClass { public: int m_year {}; int m_month {}; int m_day {}; void print () // определяет функцию-член с именем print () { std :: cout << m_year << '/' << m_month << '/' << m_day; } }; |
Как и члены структуры, доступ к членам (переменным и функциям) класса осуществляется с помощью оператора выбора члена (.):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 14 18 19 20 21 22 23 24 | #include class DateClass { public: int m_year {}; int m_month {}; int m_day {}; void print () { std :: cout << m_year << '/' << m_month << '/' << m_day; } }; int main () { DateClass сегодня {2020, 10, 14}; сегодня.m_day = 16; // используйте оператор выбора члена, чтобы выбрать переменную члена класса today.print (); // использовать оператор выбора члена для вызова функции-члена класса return 0; } |
Это отпечатки:
16.10.2020
Обратите внимание, насколько эта программа похожа на версию структуры, которую мы написали выше.
Однако есть несколько отличий. В версии print () DateStruct из приведенного выше примера нам нужно было передать саму структуру функции print () в качестве первого параметра.В противном случае print () не узнает, какой DateStruct мы хотим использовать. Затем нам пришлось явно указать этот параметр внутри функции.
Функции-члены работают несколько иначе: все вызовы функций-членов должны быть связаны с объектом класса. Когда мы вызываем today.print (), мы говорим компилятору вызвать функцию-член print (), связанную с объектом today.
Теперь давайте снова посмотрим на определение функции-члена print:
void print () // определяет функцию-член с именем print () { std :: cout << m_year << '/' << m_month << '/' << m_day; } |
Что на самом деле означает m_year, m_month и m_day? Они относятся к связанному объекту (как определено вызывающим).
Итак, когда мы вызываем «today.print ()», компилятор интерпретирует m_day
как today.m_day
, m_month
как today.m_month
и m_year
как today.m_year
. Если бы мы вызвали «завтра.print ()», m_day
вместо этого будет ссылаться на завтра.m_day
.
Таким образом, связанный объект по существу неявно передается функции-члену. По этой причине его часто называют неявным объектом .
Мы подробнее поговорим о том, как работает неявная передача объектов, в следующем уроке этой главы.
Ключевым моментом является то, что с функциями, не являющимися членами, мы должны передавать данные функции для работы. С функциями-членами мы можем предположить, что у нас всегда есть неявный объект класса, с которым можно работать!
Использование префикса «m_» для переменных-членов помогает отличать переменные-члены от параметров функции или локальных переменных внутри функций-членов. Это полезно по нескольким причинам.Во-первых, когда мы видим присвоение переменной с префиксом «m_», мы знаем, что меняем состояние экземпляра класса. Во-вторых, в отличие от параметров функции или локальных переменных, которые объявляются внутри функции, переменные-члены объявляются в определении класса. Следовательно, если мы хотим знать, как объявляется переменная с префиксом «m_», мы знаем, что должны искать в определении класса, а не внутри функции.
По соглашению имена классов должны начинаться с заглавной буквы.
Назовите классы, начиная с заглавной буквы.
Вот еще один пример класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 14 18 19 20 21 22 23 24 25 26 27 28 29 31 9000 | #include #include class Employee { public: std :: string m_name {}; int m_id {}; двойная м_заработка {}; // Выводит информацию о сотрудниках на экран void print () { std :: cout << "Имя:" << m_name << "Id:" << m_id << "Заработная плата: $" << m_wage << '\ n'; } }; int main () { // Объявить двух сотрудников Сотрудник alex {"Alex", 1, 25.00}; Сотрудник Джо {"Джо", 2, 22.25}; // Распечатать информацию о сотруднике alex.print (); joe.print (); возврат 0; } |
Это дает результат:
Имя: Алекс Id: 1 Заработная плата: 25 долларов Имя: Джо Идентификатор: 2 Заработная плата: 22,25 доллара
С обычными функциями, не являющимися членами, функция не может вызывать функцию, которая определена «ниже» (без предварительного объявления):
void x () { // Вы не можете вызвать y () отсюда, если компилятор уже не увидел форвардное объявление для y () } void y () { } |
Для функций-членов это ограничение не действует:
class foo { public: void x () {y (); } // здесь можно вызвать y (), даже если y () не определена позже в этом классе void y () {}; }; |
Типы элементов
Помимо переменных-членов и функций-членов, класс
es может иметь типы-члены или вложенные типы (включая псевдонимы типов).В следующем примере мы создаем калькулятор, в котором мы можем быстро изменить тип используемого числа, если нам когда-нибудь понадобится.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 14 18 19 20 21 22 23 24 25 26 27 28 29 30 34 | #include #include class Calculator { public: using number_t = int; // это псевдоним вложенного типа std :: vector number_t add (number_t a, number_t b) { автоматический результат {a + b}; m_resultИстория.push_back (результат); вернуть результат; } }; int main () { Калькулятор-калькулятор {}; std :: cout << Calculator.add (3, 4) << '\ n'; // 7 std :: cout << Calculator.add (99, 24) << '\ n'; // 123 для (Calculator :: number_t result: calculator.m_resultHistory) { std :: cout << result << '\ n'; } возврат 0; } |
Выход
7 123 7 123
В таком контексте имя класса эффективно действует как пространство имен для вложенного типа.Изнутри класса нам нужна только ссылка number_t
. Извне класса мы можем получить доступ к типу через Calculator :: number_t
.
Когда мы решаем, что int
больше не удовлетворяет нашим требованиям, и мы хотим использовать double
, нам нужно только обновить псевдоним типа, вместо того, чтобы заменять каждое вхождение int
на double
.
Члены псевдонима типа упрощают сопровождение кода и сокращают набор текста.Классы шаблонов, о которых мы поговорим позже, часто используют элементы псевдонима типа. Вы уже видели это как std :: vector :: size_type
, где size_type
- это псевдоним для целого числа без знака.
Вложенные типы не могут быть объявлены вперед. Как правило, вложенные типы следует использовать только тогда, когда вложенный тип используется исключительно внутри этого класса. Обратите внимание: поскольку классы являются типами, можно вкладывать классы внутри других классов - это необычно и обычно выполняется только опытными программистами.
Замечание о структурах в C ++
В C структуры могут содержать только данные и не имеют связанных функций-членов. В C ++ после разработки классов (с использованием ключевого слова class) Бьярн Страуструп потратил некоторое время на размышления о том, следует ли предоставлять структурам (которые были унаследованы от C) возможность иметь функции-члены. Поразмыслив, он решил, что они должны частично иметь единый набор правил для обоих. Поэтому, хотя мы написали вышеуказанные программы с использованием ключевого слова class, мы могли бы вместо этого использовать ключевое слово struct.
Многие разработчики (включая меня) считают, что это неправильное решение, поскольку оно может привести к опасным предположениям. Например, справедливо предположить, что класс очистится после себя (например, класс, который выделяет память, освободит ее перед уничтожением), но небезопасно предполагать, что структура будет. Следовательно, мы рекомендуем использовать ключевое слово struct для структур, содержащих только данные, и ключевое слово class для определения объектов, которые требуют объединения как данных, так и функций.
Используйте ключевое слово struct для структур только данных. Используйте ключевое слово class для объектов, которые имеют как данные, так и функции.
Вы уже использовали классы, не зная об этом
Оказывается, стандартная библиотека C ++ полна классов, созданных для вашего удобства. std :: string, std :: vector и std :: array - это все типы классов! Итак, когда вы создаете объект любого из этих типов, вы создаете экземпляр объекта класса. И когда вы вызываете функцию с использованием этих объектов, вы вызываете функцию-член.
#include #include #include #include int main () { std :: string s {"Здравствуйте, Мир!" }; // создание экземпляра объекта строкового класса std :: array std :: vector std :: cout << "length:" << s.length () << '\ n'; // вызов функции-члена return 0; } |
Заключение
Ключевое слово class позволяет нам создать пользовательский тип в C ++, который может содержать как переменные-члены, так и функции-члены. Классы составляют основу объектно-ориентированного программирования, и мы проведем оставшуюся часть этой главы и многие другие главы, исследуя все, что они могут предложить!
Время викторины
а) Создайте класс с именем IntPair, содержащий два целых числа.Этот класс должен иметь две переменные-члены для хранения целых чисел. Вы также должны создать две функции-члены: одну с именем «set», которая позволит вам присваивать значения целым числам, и одну с именем «print», которая будет печатать значения переменных.
Должна выполняться следующая основная функция:
int main () { IntPair p1; п1.набор (1, 1); // устанавливаем значения p1 равными (1, 1) IntPair p2 {2, 2}; // инициализируем значения p2 в (2, 2) p1.Распечатать(); p2.print (); возврат 0; } |
и произведем вывод:
Пара (1, 1) Пара (2, 2)
Показать решение
1 2 3 4 5 6 7 8 9 10 11 12 13 14 14 18 19 20 21 22 23 24 25 26 27 28 29 31 9000 | #include class IntPair { public: int m_first {}; int m_second {}; void set (int first, int second) { m_first = first; м_секунда = секунда; } void print () { std :: cout << "Pair (" << m_first << "," << m_second << ") \ n"; } }; int main () { IntPair p1; п1.набор (1, 1); IntPair p2 {2, 2}; p1.print (); p2.print (); возврат 0; } |
(ч / т читателю Пашка 2107 за идею викторины)
б) Почему мы должны использовать класс для IntPair вместо структуры?
Показать решение
Этот объект содержит как данные-члены, так и функции-члены, поэтому мы должны использовать класс. Мы не должны использовать структуры для объектов, у которых есть функции-члены.
классов (ООП) | Блестящая вики по математике и науке
Создание класса
В Python классы объявляются ключевым словом class
, за которым следует имя класса. Оператор class
определяет новый класс так же, как оператор def
определяет новую функцию.
В следующем примере будет определен простой класс, определяющий пользователей Brilliant.
|
Метод конструктора
После объявления имени класса программист должен определить метод конструктора .В Python это обозначается __init __ ()
. Функция __init__
принимает self
в качестве первого аргумента, а затем любое количество аргументов по желанию программиста. В этом примере, описывающем блестящих пользователей, программист хочет знать имя, возраст и рейтинг каждого пользователя.
Имя __init __ ()
используется для «метода конструктора» класса. Хотя класс является планом для нового типа данных, программисту по-прежнему необходимо создавать значения этого типа данных, чтобы иметь что-то, что можно хранить в переменных или передавать функциям.
При вызове конструктор создает новый объект, запускает код в конструкторе и возвращает новый объект. Это строка user = brilliantUser ('Mursalin', 17, 4). Независимо от имени класса конструктор всегда называется __init__
.
Пока у нас
|
Вышеупомянутое определяет метод для класса brilliantUsers.Методы используются для функций, принадлежащих классу.
Переменные и тело метода __init__
Чтобы получить доступ к аргументам и связать их с конкретным экземпляром класса, в методе __init__
создайте переменные для каждого аргумента следующим образом: self.variableName = variableName
.
Еще одним компонентом, связанным с классами, являются атрибутов . Атрибуты - это характеристики объекта.Метод __init __ ()
используется для инициализации атрибутов объекта. Так же, как методы - это функции, определенные в классе, атрибуты - это переменные, определенные в классе.
Каждый метод в определении класса начинается со ссылки на объект-экземпляр. По соглашению это называется «я».
В Python первым параметром для методов является self
. Параметр self
используется для создания переменных-членов. Внутри класса мы инициализируем любые переменные, которые могут иметь разные значения в зависимости от конкретного экземпляра класса, как self.Имя переменной
. В примере с автомобилем нам может потребоваться доступ к переменной color
для car_1
и переменной color
для car_2
, и для того, чтобы назначить каждой машине свое собственное значение цвета
, нам понадобится self
.
Тело функции-конструктора для примера пользователей Brilliant выглядит следующим образом:
|
Этот код создает переменные-члены для объекта, созданного конструктором.Переменные-члены будут начинаться с self
, чтобы показать, что они являются переменными-членами, принадлежащими объекту, а не просто обычными локальными переменными в методе.
В целом класс для описания блестящих пользователей выглядит так:
|
Создание экземпляра
Экземпляр - это особый объект, созданный из определенного класса.Чтобы создать экземпляры класса, вызовите класс, используя имя класса и передайте любые аргументы, которые принимает его метод __init__
- в этом примере метод __init__
принимает имя
, возраст
и рейтинг
.
|
Здесь мы создаем новый экземпляр класса brilliantUser
. Или, другими словами, мы создаем экземпляр класса brilliantUser
.
13 Классы и объекты
13 Классы и объекты
Эта глава основана на статье [Flatt06].
Выражение класса обозначает первоклассное значение,
точно так же, как лямбда-выражение:
(class superclass-expr decl-or-expr ...)
superclass-expr определяет суперкласс для нового
класс. Каждый decl-or-expr является либо объявлением, относящимся к
методы, поля и аргументы инициализации, или это выражение
который оценивается каждый раз, когда создается экземпляр класса.В другом
слов, вместо конструктора, подобного методу, класс имеет
выражения инициализации, чередующиеся с полем и методом
декларации.
По соглашению имена классов заканчиваются на%. Встроенный корневой класс
объект%. Следующее выражение создает класс с
общедоступные методы get-size, grow и eat:
(объект класса% (размер инициализации); аргумент инициализации (определение текущего размера); поле (супер-новый); инициализация суперкласса (define / public (get-size) current-size) (define public (grow amt)
(set! current-size (+ amt current-size))) (define / public (eat other-fish) (grow (send other-fish get-size))))
Аргумент инициализации размера должен быть передан через именованный
аргумент при создании экземпляра класса через новую форму:
(новый (объект класса% (размер инициализации)....) [размер 10])
Конечно, мы также можем назвать класс и его экземпляр:
В определении fish% текущий размер - это
частное поле, которое начинается со значения размера
аргумент инициализации. Аргументы инициализации, такие как размер
доступны только во время создания экземпляра класса, поэтому они не могут быть
ссылаются непосредственно из метода. Поле текущего размера в
напротив, доступны методы.
Выражение (супер-новое) в fish% вызывает
инициализация суперкласса.В этом случае суперклассом является
объект%, который не принимает аргументов инициализации и выполняет
нет работы; в любом случае необходимо использовать суперновый, потому что класс должен
всегда вызывать инициализацию своего суперкласса.
Аргументы инициализации, объявления полей и выражения, такие как
(суперновые) могут появляться в классе в любом порядке,
и их можно чередовать с объявлениями методов. Относительная
порядок выражений в классе определяет порядок оценки
во время создания экземпляра. Например, если для начального значения поля требуется
вызов метода, который работает только после инициализации суперкласса, затем
объявление поля должно быть помещено после супернового
вызов.Таким образом, упорядочивание полей и деклараций инициализации помогает
избегайте императивного задания. Относительный порядок объявлений методов
не имеет значения для оценки, потому что методы полностью определены
перед созданием экземпляра класса.
13.1 Методы
Каждое из трех определений / публичных объявлений в
fish% представляет новый метод. В декларации используется то же
синтаксис как функция Racket, но метод недоступен как
независимая функция. Вызов метода grow для
Для объекта fish% требуется форма отправки:
> (отправьте charlie grow 6) > (отправьте charlie get-size) 16
Within fish%, самостоятельные методы могут называться как функции,
потому что имена методов входят в область видимости.Например, съесть
метод в fish% напрямую вызывает рост
метод. Внутри класса попытка каким-либо образом использовать имя метода
кроме вызова метода приводит к синтаксической ошибке.
В некоторых случаях класс должен вызывать методы, предоставляемые суперклассом.
но не отменено. В этом случае класс может использовать send
с этим, чтобы получить доступ к методу:
(определить голод-рыба% (класс рыба% (супер-новый) (определить / общедоступный (есть-больше рыбы2 рыба3) ( отправить эту съесть рыбу2) (отправить эту съесть рыбу3))))
В качестве альтернативы, класс может объявить о существовании метода, используя наследование,
который вводит имя метода в область действия для прямого вызова:
(определить голодную рыбу% (класс рыба% (супер-новый) (наследовать есть) (определить / public (есть-больше рыбы2 рыбы3) (есть рыбу2) (есть рыбу3))))
С декларацией о наследовании, если у рыбы% не было декларации о наследовании
при условии, что метод ест, ошибка будет сигнализироваться в
оценка формы класса голодной рыбы%.В
напротив, с (отправить это ....) ошибка не будет
сигнализируется до тех пор, пока не будет вызван метод ешьте больше и
форма отправки оценивается. По этой причине наследование
предпочтительнее.
Еще одним недостатком send является то, что он менее эффективен, чем
наследовать. Вызов метода через send включает
поиск метода в классе целевого объекта во время выполнения, что делает
send можно сравнить с вызовом метода на основе интерфейса в Java. В
напротив, вызовы методов на основе наследования используют смещение
в таблице методов класса, которая вычисляется, когда класс
созданный.
Для достижения производительности, аналогичной вызовам методов на основе наследования, когда
вызывая метод извне класса метода, программист должен использовать
общая форма, которая производит зависящие от класса и метода
универсальный метод, который будет вызываться с помощью send-generic:
(определить размер get-fish (общий размер рыбы%))
> (send-generic charlie get-fish-size) 16
> (send-generic (new hungry-fish% [size 32]) get-fish-size) 32
> (send-generic (новый объект%) get-fish-size) generic: get-size: target не является экземпляром общего
класса
target: (объект)
имя класса: fish%
Грубо говоря, форма переводит класс и внешний
имя метода в место в таблице методов класса.Как показано
в последнем примере отправка через общий метод проверяет, что его
Аргумент является экземпляром универсального класса.
Вызывается ли метод непосредственно в классе,
через общий метод,
или через отправку, переопределение метода работает обычным образом:
> (отправить daisy eat charlie) > (send daisy )
32
Метод выращивания в% разборчивой рыбы заявлен с
определить / переопределить вместо определения / общедоступного, потому что
Grow подразумевается как приоритетное объявление.Если вырастет
был объявлен с помощью define / public, ошибка будет
был сигнализирован при оценке выражения класса, потому что
рыбы% уже запасы растут.
Использование определения / переопределения также позволяет вызывать
переопределенный метод с помощью супервызова. Например,
реализация Grow в% разборчивых применений
super для делегирования реализации суперкласса.
13.2 Аргументы инициализации
Так как picky-fish% не объявляет аргументов инициализации, любые
значения инициализации, предоставленные в (new picky-fish%....) находятся
распространяется на инициализацию суперкласса, то есть на fish%.
Подкласс может предоставить дополнительные аргументы инициализации для своего
суперкласс в супер-новом вызове, и такая инициализация
аргументы имеют приоритет над аргументами, переданными в new. Для
Например, следующий класс size-10-fish% всегда генерирует
рыба размером 10:
В случае размера-10-fish%, предоставление размера
аргумент инициализации с новым приведет к
ошибка инициализации; потому что размер в супер-новом
имеет приоритет, размер, поставляемый в новый, будет иметь
нет объявления цели.
Аргумент инициализации является необязательным, если форма класса
объявляет значение по умолчанию. Например, следующий default-10-fish%
класс принимает аргумент инициализации размера, но его значение по умолчанию равно
10, если при создании экземпляра значение не указано:
(определите default-10-fish% (class fish% (init [size 10]) (super-new [size) размер])))
> (новый default-10-fish%) (object: default-10-fish%...)
> (новый default-10-fish% [size 20]) (object: default-10-fish% ...)
In в этом примере супер-новый вызов распространяет свой собственный
значение размера в качестве аргумента инициализации размера для
суперкласс.
13.3 Внутреннее и внешнее имена
Два использования размера в default-10-fish% раскрывают
двойная жизнь идентификаторов членов класса. Когда размер
первый идентификатор заключенной в квадратные скобки пары в новых или
супер-новый, размер - внешнее имя, которое
символически соответствует аргументу инициализации в классе.Когда
размер отображается как выражение внутри
default-10-fish%, размер - внутреннее имя
это лексическая область видимости. Точно так же вызов унаследованного
Метод eat использует в качестве внутреннего имени eat, тогда как
send of eat использует eat как внешнее имя.
Полный синтаксис формы класса позволяет программисту
указать отдельные внутренние и внешние имена для члена класса. С
внутренние имена являются локальными, их можно переименовать, чтобы избежать затенения или
конфликты. Такое переименование требуется нечасто, но есть обходные пути.
при отсутствии переименования может быть особенно громоздким.
13.4 Интерфейсы
Интерфейсы полезны для проверки того, что объект или класс
реализует набор методов с определенным (подразумеваемым) поведением.
Такое использование интерфейсов полезно даже без системы статических типов.
(что является основной причиной того, что у Java есть интерфейсы).
Интерфейс в Racket создается с помощью интерфейса
форма, которая просто объявляет имена методов, необходимых для реализации
интерфейс. Интерфейс может расширять другие интерфейсы, что означает, что
реализации интерфейса автоматически реализуют расширенный
интерфейсы.
(interface (superinterface-expr ...) id ...)
Чтобы объявить, что класс реализует интерфейс,
Вместо class необходимо использовать форму class *:
(class * superclass-expr (interface-expr ...) decl-or-expr ...)
Например, вместо принуждение всех классов рыб быть производными от
fish%, мы можем определить fish-интерфейс и изменить
fish%, чтобы объявить, что он реализует
fish-interface:
Если определение fish% не включает
методы увеличения, роста и употребления, затем
ошибка сигнализируется при оценке формы class *,
потому что для реализации интерфейса fish-interface требуется
эти методы.
Это а? предикат принимает объект в качестве первого аргумента
и либо класс, либо интерфейс в качестве второго аргумента. Когда дается
класс, это-а? проверяет, является ли объект экземпляром этого
класс или производный класс. Когда предоставляется интерфейс, является ли?
проверяет, реализует ли класс объекта интерфейс. В
кроме того, реализация? предикат проверяет, есть ли
данный класс реализует данный интерфейс.
13.5 Final, Augment и Inner
Как и в Java, метод в форме класса может быть определен как
final, что означает, что подкласс не может переопределить
метод.Последний метод объявляется с помощью public-final или
override-final, в зависимости от того, предназначено ли объявление для
новый метод или преобладающая реализация.
Между крайностями разрешения произвольной отмены и запрещения
полностью переопределяя, система классов также поддерживает бета-стиль
расширяемые методы [Goldberg04]. Метод
объявленный с помощью pubment похож на общедоступный, но метод
нельзя переопределить в подклассах; его можно только увеличить. А
метод публикации должен явно вызывать расширение (если есть)
используя внутренний; подкласс дополняет метод, используя
увеличивать, а не отменять.
Как правило, метод может переключаться между режимами дополнения и отмены в
производное класса. Спецификация метода augride
указывает на дополнение к методу, в котором само увеличение
можно переопределить в подклассах (хотя реализация суперкласса
не может быть отменен). Точно так же переопределение переопределяет метод
и делает основную реализацию расширяемой.
13.6 Управление объемом внешних имен
Модификаторы доступа Java (например, protected)
играют роль, аналогичную define-member-name, но
в отличие от Java, механизм Racket для контроля доступа
основан на лексической области видимости, а не на иерархии наследования.
Как указано во Внутренних и Внешних именах, члены класса имеют оба
внутренние и внешние имена. Определение члена связывает внутреннюю
имя локально, и эту привязку можно переименовать локально. Внешний
имена, напротив, по умолчанию имеют глобальную область видимости, а член
определение не связывает внешнее имя. Вместо этого член
определение относится к существующей привязке для внешнего имени, где
имя члена привязано к ключу члена; класс в конечном итоге
сопоставляет ключи членов с методами, полями и аргументами инициализации.
Вспомните выражение класса «голодная рыба%»:
(определите% голодной рыбы (класс рыбы% .... (наследовать есть) (определить / общедоступно (есть-больше) рыба2 рыба3) (есть рыбу2) (есть рыбу3))))
Во время оценки, голодные рыбы% и рыба%
классы относятся к одной и той же глобальной привязке eat. На бегу
время, призывы поесть в% голодной рыбы совпадают с
метод eat в fish% через общий метод
ключ, который обязательно съест.
Привязка по умолчанию для внешнего имени является глобальной, но
программист может ввести привязку внешнего имени с помощью
форма определения-члена-имени.
В частности, используя (generate-member-key) в качестве
member-key-expr, внешнее имя может быть локализовано для
конкретная область, потому что сгенерированный ключ члена недоступен
вне рамок. Другими словами, определение-член-имя дает
внешнее имя своего рода закрытой области видимости пакета, но обобщенное из
пакеты к произвольным областям привязки в Racket.
Например, следующие классы fish% и pond% взаимодействуют
с помощью метода получения глубины, доступного только для
взаимодействующие классы:
Внешние имена находятся в пространстве имен, которое отделяет их от других Racket
имена. Это отдельное пространство имен неявно используется для имени метода в
send, для имен аргументов инициализации в новом или для
внешнее имя в определении члена. Особая форма
ключ-имя-член обеспечивает доступ к привязке внешнего имени
в произвольной позиции выражения: (идентификатор-имя-члена)
производит привязку ключа члена к id в текущей области.
Значение ключа-члена в основном используется с
форма определения-члена-имени. Обычно тогда
(member-name-key id) фиксирует ключ метода id
так что его можно передать с помощью определения-члена-имени
в другом объеме. Эта возможность оказывается полезной для
обобщающие миксины, как обсуждается далее.
13.7 Mixins
Поскольку класс - это форма выражения, а не верхний уровень
объявление, как в Smalltalk и Java, форма класса может быть
вложен в любую лексическую область видимости, включая лямбда.Результат
представляет собой миксин, то есть расширение класса, которое параметризовано
относительно своего суперкласса.
Например, мы можем параметризовать класс% придирчивой рыбы через
его суперкласс для определения разборчивой смеси:
Множество мелких различий между классами в стиле Smalltalk и Racket
классы способствуют эффективному использованию миксинов. В частности,
использование определения / переопределения явно указывает на то, что
разборчивый-mixin ожидает класс с методом Grow. Если
разборчивый миксин применяется к классу без увеличения
метод, ошибка выдается, как только разборчивый миксин
применяемый.
Аналогичным образом, использование наследования обеспечивает «существование метода».
требование при применении миксина:
Преимущество миксинов в том, что мы можем легко комбинировать их для создания
новые классы, совместное использование реализации которых не вписывается в
иерархия с одинарным наследованием -
множественное наследование. Оснащен разборчивым миксином и
hungry-mixin, создающий класс для голодной, но придирчивой рыбы
прост:
(define-picky-hungry-fish% (hungry-mixin (разборчивый-mixin fish%)))
Использование аргументов инициализации ключевого слова имеет решающее значение для простого
использование миксинов.Например, разборчивый миксин и
hungry-mixin может дополнить любой класс подходящей едой
и методы выращивания, потому что они не указывают инициализацию
аргументов и не добавляйте ничего в их супер-новые выражения:
Наконец, использование внешних имен для членов класса (вместо
идентификаторы с лексической областью видимости) делает использование миксина удобным. Применение
разборчивый миксин с человеком% работает, потому что имена
есть и выращивать матч, без какого-либо априорного заявления
что есть и расти должны быть одним и тем же методом в
рыба% и человек%.Эта функция является потенциальной
недостаток, когда имена участников случайно сталкиваются; некоторые случайные
коллизии можно исправить, ограничив внешние имена области, как
обсуждается в разделе «Управление объемом внешних имен».
13.7.1 Микшины и интерфейсы
При использовании реализации ?, придирчивый миксин может потребовать
что его базовый класс реализует интерфейс садовода, который может
быть реализовано как fish%, так и person%:
Еще одно использование интерфейсов с миксином - тегирование классов, сгенерированных
миксин, чтобы можно было распознать экземпляры миксина.В другом
слова, это-а? не может работать с миксином, представленным как
функция, но он может распознавать интерфейс (что-то вроде
интерфейс специализации), который последовательно реализуется
миксином. Например, классы, созданные разборчивым миксином
может быть помечен разборчивым интерфейсом, что позволяет
придирчив? предикат:
13.7.2 Форма миксина
Для кодификации шаблона лямбда-плюс-класс для
реализация миксинов, включая использование интерфейсов для домена
и диапазон миксина, система классов предоставляет миксин
макрос:
(mixin (interface-expr...) (interface-expr ...) decl-or-expr ...)
Первый набор interface-exprs определяет домен
mixin, а второй набор определяет диапазон. То есть расширение
это функция, которая проверяет, реализует ли данный базовый класс
первая последовательность интерфейсов-выражений и создает класс, который
реализует вторую последовательность интерфейсов-выражений. Другой
требования, такие как наличие унаследованных методов в
суперкласс, затем проверяются на предмет расширения класса
форма миксина.Например:
> (определить разборчивый интерфейс (интерфейс () выбрать?)) > (определить голодный интерфейс (интерфейс () есть))
> (определить разборчивый -eater-mixin (mixin (choosy-interface) (hungry-interface) (наследовать, выбрать?) (super-new) (define / public (eat x) (cond [(выбрать? X) (printf "chomp chomp chomp on ~ a.\ n "x)] [else (printf" Я не без ума от ~ a. \ n "x)])))) > ( определите% селедочника (разборчивый-едок-смешанный любитель сельди%)) > (определите едока (новый человек-селедочник%)) > (отправьте едока есть "бузина") Я не в восторге от бузины
> (отправь поедатель съесть "селедку") пощипать пощипать селедку.
> (отправьте едока съесть "мороженое из сельди") chomp chomp chomp chomp on herring ice cream.
Миксины не только переопределяют методы и вводят общедоступные методы, они
может также расширять методы, вводить методы только для дополнений, добавлять
отменяемое увеличение и добавление расширяемого переопределения -
то, что может делать класс (см. Final, Augment и Inner).
13.7.3 Параметризованные микшеры
Как отмечалось в разделе «Управление областью внешних имен», внешние имена могут быть связаны с
определить-имя-члена.Это средство позволяет миксину быть
обобщены в отношении методов, которые он определяет и использует. Для
Например, мы можем параметризовать голодный миксин относительно
внешний ключ-член для eat:
Чтобы получить конкретный голодный миксин, мы должны применить эту функцию к
ключ-член, который относится к подходящему
eat, который мы можем получить, используя ключ-имя-члена:
Выше мы применяем hungry-mixin к анонимному классу, который предоставляет
есть, но мы также можем объединить его с классом, который предоставляет
chomp вместо:
13.8 Traits
Trait похож на миксин тем, что инкапсулирует набор
методов, которые будут добавлены в класс. Черта отличается от примеси
в том, что его отдельные методы можно манипулировать с операторами признаков
такие как trait-sum (объединить методы двух черт), trait-exclude
(удалить метод из признака) и псевдоним признака (добавить копию
метод с новым именем; не перенаправляйте звонки на старое имя).
Практическое различие между миксинами и трейтами состоит в том, что два трейта
могут быть объединены, даже если они включают общий метод и даже если
ни один метод не может разумно переопределить другой.В этом случае
программист должен явно разрешить конфликт, обычно с помощью псевдонима
методы, исключая методы, и объединяя новую черту, которая использует
псевдонимы.
Предположим, наш программист fish% хочет определить два класса
расширения, пятна и полосы, каждая из которых
включает метод получения цвета. Пятнистый цвет рыбы не должен
изменить цвет полосы и наоборот; вместо этого
пятна + полосы-рыба% должны сочетать два цвета, что
невозможно, если пятна и полосы реализованы как
простые миксины.Если все же пятна и полосы
реализованы как черты, их можно комбинировать. Во-первых, мы псевдоним
получить цвет в каждой характеристике к неконфликтному имени. Второй,
методы get-color удалены из обоих, а черты
только с псевдонимами. Наконец, новая черта используется для создания
класс, который вводит свой собственный метод получения цвета на основе
два псевдонима, производящие желаемые пятна + расширение полос.
13.8.1 Черты как наборы миксинов
Один естественный подход к реализации черт в Racket - это набор
миксинов, с одним миксином на каждый метод признака.Например, мы могли бы
попытайтесь определить признаки пятен и полос следующим образом, используя
списки ассоциаций для представления наборов:
(определение пятна-признака (список (cons 'get-color (лямбда (%) (класс% (супер-новый) ( define / public (get-color) 'black)))))) (define stripes-trait (list (cons' get-color (lambda (%) (class% (super-new) (define / public (get-color) 'red))))))
. сумма и
trait-exclude простыми манипуляциями; к сожалению, это так
не поддерживает оператор trait-alias.Хотя миксин может быть
дублируется в списке ассоциаций, миксин имеет фиксированное имя метода,
например, get-color и миксины не поддерживают переименование метода
операция. Чтобы поддерживать псевдоним признака, мы должны параметризовать
подмешивает имя внешнего метода точно так же, как и eat
был параметризован в параметризованных миксинах.
Для поддержки операции trait-alias, пятна-черта
должен быть представлен как:
Когда методу get-color в пятнах-признаке применяется псевдоним
для получения-цвета-черты, а метод получения-цвета -
удален, результирующий признак будет таким же, как
Чтобы применить признак T к классу C и получить производный
class, мы используем ((trait-> mixin T) C).Черта-> миксин
функция снабжает каждый миксин T ключом для миксина
метод и частичное расширение C:
Таким образом, когда признак выше сочетается с другими признаками, а затем
применительно к классу использование get-color становится ссылкой
к внешнему имени get-trait-color.
13.8.2 Inherit and Super in Traits
Эта первая реализация трейтов поддерживает trait-alias, и это
поддерживает метод черты, который вызывает сам себя, но не поддерживает
методы, которые вызывают друг друга.В частности, предположим, что пятнистая рыба
рыночная стоимость зависит от цвета его пятен:
(определите пятно-признак (список (минусы (имя-члена-ключ получить-цвет) ....) (минусы ( имя-члена-ключ get-price) (лямбда (get-price%) .... (class% .... (define / public (get-price) )
.... (get-color) ....))))))
В этом случае определение пятна-признака не выполняется, потому что
Get-Color не входит в стоимость Get-Price
миксин. Действительно, в зависимости от порядка применения миксина, когда
trait применяется к классу, метод get-color не может быть
доступно, когда к классу применяется миксин get-price.
Поэтому добавление объявления (наследование get-color) к
Примесь get-price не решает проблемы.
Одно из решений - потребовать использования (отправьте этот цвет) в
такие методы, как get-price.Это изменение работает, потому что
send всегда откладывает поиск метода до тех пор, пока вызов метода не будет
оценен. Отложенный поиск дороже прямого вызова,
тем не мение. Хуже того, это также задерживает проверку того, является ли метод get-color
даже существует.
Второе эффективное и действенное решение - изменить кодировку.
черт. В частности, мы представляем каждый метод в виде пары миксинов:
одна представляет метод, а другая - реализует. Когда
применяется к классу, все примеси, вводящие методы,
применяется первым.Тогда миксины, реализующие метод, могут использовать
наследовать для прямого доступа к любому введенному методу.
(определить черту пятна (list (list (local-member-name-key get-color) (lambda (get-color get-price%) .... (class% .... (define / public (get-color) (void))) (lambda (get-color get-price%).... (class% .... (define / override (get-color) 'black)))) (list (local-member-name-key get-price ) (лямбда (get-price get-color%) .... (class% .... (define / public (get-price) (void)))) (лямбда (get-color, get-price%).... (класс% .... (наследование get-color) (определение / переопределение (get-price) .... (get-color) ....))))))
С этой кодировкой признака trait-alias добавляет новый метод с
новое имя, но оно не меняет никаких ссылок на старый метод.
13.8.3 Форма признака
Шаблон универсального признака явно слишком сложен для
программатор для прямого использования, но он легко кодируется в
макрос признака:
(признак признака-предложение...)
Идентификаторы в необязательном предложении наследования доступны для прямого
ссылка в методе exprs, и они должны быть предоставлены
либо другими чертами, либо базовым классом, к которому
эта черта в конечном итоге применяется.
Использование этой формы вместе с операторами признаков, такими как
trait-sum, trait-exclude, trait-alias и
trait-> mixin, мы можем реализовать пятна-черты и
полосы-черта по желанию.
(определить черту пятна (черта (define / public (get-color) 'black) (define / public (get-price)... (get-color) ...))) (define stripes-trait (trait (define / public (get-color) 'red))) (определить пятна + полосы-черта (черта-сумма (черта-исключить (черта-псевдоним пятна-черта получить цвет пятна ) get-color) (trait-exclude (trait-alias stripes-trait get-color get-stripes-color) get-color) trait (наследовать get-spot-color get-stripes-color) (define / public (get-color) .... (get-spot-color) .... (get-stripes-color) ....))))
13.9 Контракты классов
Поскольку классы являются значениями, они могут переходить границы контрактов , и мы
могут пожелать защитить части данного класса контрактами. За это,
используется форма class / c. Форма class / c имеет много
подформы, которые описывают два типа контрактов по полям и методам:
те, которые влияют на использование через созданные объекты, и те, которые влияют
подклассы.
13.9.1 Контракты внешних классов
В своей простейшей форме class / c защищает общедоступные поля и методы
объектов, созданных из свернутого класса.Также есть
форма объекта / c, которую можно использовать для аналогичной защиты общедоступных полей
и методы конкретного объекта. Возьмем следующее определение
animal%, который использует публичное поле в качестве атрибута размера:
Для любого экземпляра животного%, доступ к полю размера
должен вернуть положительное число. Кроме того, если задано поле размера,
ему следует присвоить положительное число. Наконец, метод еды
должен получить аргумент, который является объектом с полем размера
который содержит положительное число.Для обеспечения этих условий определим
класс% animal с соответствующим контрактом:
Здесь мы используем -> m для описания поведения еды, так как мы
не нужно описывать какие-либо требования к этому параметру.
Теперь, когда у нас есть класс с контрактом, мы видим, что контракты
для размера и еды применяются:
> (define bob (new animal%)) > (set-field! size bob 3) > (get-field size bob) 3
> (set-field! Size bob 'large) животных%: нарушение контракта
ожидаемое: положительное / c
'большой
in: поле размера в
(class / c
(есть
(-> m
000 (-> m
02) / c (поле (размер положительный / c)))
недействительно?))
(поле (размер положительный / c)))
контракт от: на животных%)
обвинение: верхний уровень
(при условии, что контракт правильный)
at: eval: 31.0
> (определить ричи (новое животное%)) > (отправить боб есть ричи) > (получить размер боба) 13
> (определить камень (новый объект%))
> (отправить боб есть камень) есть: нарушение контракта;
без размера общедоступного поля
в: 1-й аргумент
метод eat в
(class / c
(-> m
(объект / c (поле (размер положительный / c)))
пусто?)
(размер поля положительный / c)))
контракт от: (определение животного%)
контракт на: животное%
обвинение: высший уровень
договор правильный)
at: eval: 31.0
> (определить гигант (новый (объект класса% (супер-новый) (поле [размер 'большой])))) > (отправить боб есть гигант) есть: нарушение контракта
ожидаемое: положительное / c
заданное: 'большое
в: поле размера в
1-й аргумент
метод eat в
(class / c
(eat
(-> m
(размер объекта / c (размер объекта / c) )))
недействительно?))
(поле (размер положительный / c)))
контракт от: (определение животного%) 900 06
контракт на: животное%
обвинение: верхний уровень
(при условии, что контракт правильный)
при оценке: 31.0
Есть два важных предостережения для контрактов внешнего класса. Первый,
контракты с внешними методами применяются только тогда, когда цель динамического
диспетчеризация - это реализация метода контрактного класса, который
лежит в границах контракта. Отмена этой реализации и
Таким образом, изменение цели динамической отправки будет означать, что контракт
больше не применяется для клиентов, так как доступ к методу больше не
пересекает границу контракта. В отличие от контрактов по внешнему методу, внешние
полевые контракты всегда применяются для клиентов подклассов, поскольку поля
не может быть отменен или затенен.
Во-вторых, эти контракты не ограничивают подклассы животных%
в любом случае. Поля и методы, которые наследуются и используются подклассами
не проверяются этими контрактами, и использование методов суперкласса
via super тоже не отмечены. Следующий пример иллюстрирует
оба предостережения:
> (определить слона (новое крупное животное%)) > (отправить слона поесть (новый объект%) 602 Nom nom nom
> (слон размером с поле) animal%: нарушил собственный контракт
обещал: положительный / c
9000 произведено большой in: поле размера в
(class / c
(есть
(-> m
000 (-> m
000) c (поле (размер положительный / c)))
пусто?))
(поле (размер положительный / c))
9037 7
контракт от: (определение животных%)
обвинение: (определение животных%)
(при условии, что контракт правильный)
at: eval: 31.0
13.9.2 Внутренние контракты классов
Обратите внимание, что получение поля размера из объекта
слон обвиняет животное% в нарушении контракта.
Эта вина правильная, но несправедливая по отношению к классу% животных,
поскольку мы еще не предоставили ему метод защиты от
подклассы. С этой целью мы добавляем внутренние контракты класса, которые
предоставить подклассам директивы о том, как они могут получить доступ и переопределить
особенности суперкласса.Это различие между внешним и внутренним
контракты классов позволяют заключать более слабые контракты в иерархии классов, где
инварианты могут быть нарушены внутри подклассами, но должны выполняться
для внешнего использования через созданные объекты.
В качестве простого примера доступных видов защиты мы приводим
пример, нацеленный на класс животных%, который использует все применимые
формы:
Этот контракт класса не только гарантирует, что объекты класса animal%
защищены, как и раньше, но также гарантируют, что подклассы животных%
хранить только соответствующие значения в поле размера и использовать
выполнение размера от животного% соответственно.Эти формы контрактов влияют только на использование в иерархии классов, и только
для вызовов методов, которые пересекают границу контракта.
Это означает, что наследование будет влиять только на использование метода подклассом.
пока подкласс не переопределит этот метод, и это переопределит только
влияет на вызовы суперкласса в переопределяющую реализацию подкласса
этого метода. Поскольку они влияют только на внутреннее использование, переопределение
форма не включает подклассы автоматически в обязательства, когда объекты
эти классы используются.Кроме того, использование переопределения имеет смысл, и
таким образом, может использоваться только для методов, в которых не использовались улучшения в бета-стиле.
место. В следующем примере показано это различие:
> (определить свинью (новый неаккуратный%)) > (определить slop1 (новое животное%)) > (определить помой2 (новое животное %)) > (определить slop3 (% новых животных)) > (отправить свинью есть помои1) (объект: animal%...)
> (размер get-field slop1) 5
> (send pig gulp (list slop1 slop2 slop3)) есть 9036 нарушение контракта
ожидается: недействительно?
задано: (объект: животное% ...)
в: диапазоне
метод питания в
(
c
(класс / c
)
(переопределить (есть
(-> m
(объект / c
(поле (размер положительный / c))
000
недействительно?))))
контракт от: (определение обжора%)
контракт на: обжора%
обвинение: (определение sloppy-eater%)
(при условии, что контракт правильный)
at: eval: 47.0
Помимо показанных здесь форм контрактов внутреннего класса, есть
аналогичные формы для расширяемых методов в стиле бета.