Поле класса
![]()
Свойство — способ доступа к внутреннему состоянию объекта, имитирующий переменную некоторого типа. Обращение к свойству объекта выглядит так же, как и обращение к структурному полю (в структурном программировании), но, в действительности, реализовано через вызов функции. При попытке задать значение данного свойства вызывается один метод, а при попытке получить значение данного свойства — другой.
Ме́тод в объектно-ориентированном программировании — это функция или процедура, принадлежащая какому-то классу или объекту.
Конте́йнер в программировании — тип, позволяющий инкапсулировать в себе объекты других типов. Контейнеры, в отличие от коллекций, реализуют конкретную структуру данных.
Конста́нта в программировании — способ адресации данных, изменение которых рассматриваемой программой не предполагается или запрещается.
Примитивный (встроенный, базовый) тип — тип данных, предоставляемый языком программирования как базовая встроенная единица языка.
Каламбур типизации является прямым нарушением типобезопасности. Традиционно возможность построить каламбур типизации связывается со слабой типизацией, но и некоторые сильно типизированные языки или их реализации предоставляют такие возможности (как правило, используя в связанных с ними идентификаторах слова unsafe или unchecked). Сторонники типобезопасности утверждают, что «необходимость» каламбуров типизации является мифом.
Блок (также говорят блок кода, блок команд, блок инструкций) в программировании — это логически сгруппированный набор идущих подряд инструкций в исходном коде программы, является основой парадигмы структурного программирования.
В языках программирования объявле́ние (англ. declaration) включает в себя указание идентификатора, типа, а также других аспектов элементов языка, например, переменных и функций. Объявление используется, чтобы уведомить компилятор о существовании элемента; это весьма важно для многих языков (например, таких как Си), требующих объявления переменных перед их использованием.
Анонимная функция в программировании — особый вид функций, которые объявляются в месте использования и не получают уникального идентификатора для доступа к ним. Поддерживаются во многих языках программирования.
Абстракция данных — популярная и в общем неверно определяемая техника программирования. Фундаментальная идея состоит в разделении несущественных деталей реализации подпрограммы и характеристик, существенных для корректного её использования. Такое разделение может быть выражено через специальный «интерфейс», сосредотачивающий описание всех возможных применений программы.
Объектами первого класса (англ. first-class object, first-class entity, first-class citizen) в контексте конкретного языка программирования называются элементы, которые могут быть переданы как параметр, возвращены из функции, присвоены переменной.
Коллекция в программировании — программный объект, содержащий в себе, тем или иным образом, набор значений одного или различных типов, и позволяющий обращаться к этим значениям.
Пара́метр в программировании — принятый функцией аргумент. Термин «аргумент» подразумевает, что конкретно и какой конкретной функции было передано, а параметр — в каком качестве функция применила это принятое. То есть вызывающий код передает аргумент в параметр, который определен в члене спецификации функции.
При́месь (англ. mix in) — элемент языка программирования (обычно класс или модуль), реализующий какое-либо чётко выделенное поведение. Используется для уточнения поведения других классов, не предназначен для порождения самостоятельно используемых объектов.
Абстрактный класс в объектно-ориентированном программировании — базовый класс, который не предполагает создания экземпляров. Абстрактные классы реализуют на практике один из принципов ООП — полиморфизм. Абстрактный класс может содержать (и не содержать) абстрактные методы и свойства. Абстрактный метод не реализуется для класса, в котором описан, однако должен быть реализован для его неабстрактных потомков. Абстрактные классы представляют собой наиболее общие абстракции, то есть имеющие наибольший.
Запись — агрегатный тип данных, инкапсулирующий без сокрытия набор значений различных типов.
Инкапсуляция (англ. encapsulation, от лат. in capsula) — в информатике упаковка данных и функций в единый компонент.
Дестру́ктор — специальный метод класса, служащий для деинициализации объекта (например освобождения памяти).
Замыкание (англ. closure) в программировании — функция первого класса, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами. Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своей области видимости.
Мона́да — это абстракция линейной цепочки связанных вычислений. Монады позволяют организовывать последовательные вычисления.
В информатике и теории автоматов состояние цифровой логической схемы или компьютерной программы является техническим термином для всей хранимой информации, к которой схема или программа в данный момент времени имеет доступ. Вывод данных цифровой схемы или компьютерной программы в любой момент времени полностью определяется его текущими входными данными и его состоянием.
Область видимости (англ. scope) в программировании — часть программы, в пределах которой идентификатор, объявленный как имя некоторой программной сущности (обычно — переменной, типа данных или функции), остаётся связанным с этой сущностью, то есть позволяет посредством себя обратиться к ней. Говорят, что идентификатор объекта «виден» в определённом месте программы, если в данном месте по нему можно обратиться к данному объекту. За пределами области видимости тот же самый идентификатор может быть.
Идиома программирования — устойчивый способ выражения некоторой составной конструкции в одном или нескольких языках программирования. Идиома является шаблоном решения задачи, записи алгоритма или структуры данных путём комбинирования встроенных элементов языка.
Пространство имён — некоторое множество каким-либо образом взаимосвязанных имён или терминов.
Динамическая идентификация типа данных (англ. run-time type information, run-time type identification, RTTI) — механизм в некоторых языках программирования, который позволяет определить тип данных переменной или объекта во время выполнения программы.
Из-за путаницы с терминологией словом «оператор» в программировании нередко обозначают операцию (англ. operator), см. Операция (программирование).Инстру́кция или опера́тор (англ. statement) — наименьшая автономная часть языка программирования; команда или набор команд. Программа обычно представляет собой последовательность инструкций.
Мно́жественное насле́дование — свойство, поддерживаемое частью объектно-ориентированных языков программирования, когда класс может иметь более одного суперкласса (непосредственного класса-родителя), интерфейсы поддерживают множественное наследование во многих языках программирования. Эта концепция является расширением «простого (или одиночного) наследования» (англ. single inheritance), при котором класс может наследоваться только от одного суперкласса.
Объе́кт в программировании — некоторая сущность в цифровом пространстве, обладающая определённым состоянием и поведением, имеющая определенные свойства (атрибуты) и операции над ними (методы). Как правило, при рассмотрении объектов выделяется то, что объекты принадлежат одному или нескольким классам, которые определяют поведение (являются моделью) объекта. Термины «экземпляр класса» и «объект» взаимозаменяемы.
Таблица виртуальных методов (англ. virtual method table, VMT) — координирующая таблица или vtable — механизм, используемый в языках программирования для поддержки динамического соответствия (или метода позднего связывания).
Абстра́ктный тип да́нных (АТД) — это математическая модель для типов данных, где тип данных определяется поведением (семантикой) с точки зрения пользователя данных, а именно в терминах возможных значений, возможных операций над данными этого типа и поведения этих операций.
Интроспекция (англ. type introspection) в программировании — возможность запросить тип и структуру объекта во время выполнения программы. Особое значение имеет в языке Objective C, однако имеется почти во всех языках, позволяющих манипулировать типами объектов как объектами первого класса; среди языков, поддерживающих интроспекцию — C++ (с RTTI), Go, Java, JavaScript, Perl, Ruby, Smalltalk; в PHP и Python интроспекция интегрирована в сам язык. Интроспекция может использоваться для реализации ad-hoc-полиморфизма.
Ссылка в программировании — это объект, указывающий на определенные данные, но не хранящий их. Получение объекта по ссылке называется разыменованием.
Объе́ктный мо́дуль (также — объектный файл, англ. object file) — файл с промежуточным представлением отдельного модуля программы, полученный в результате обработки исходного кода компилятором. Объектный файл содержит в себе особым образом подготовленный код (часто называемый двоичным или бинарным), который может быть объединён с другими объектными файлами при помощи редактора связей (компоновщика) для получения готового исполнимого модуля либо библиотеки.
Зарезерви́рованное сло́во (или ключево́е сло́во) — в языках программирования слово, имеющее специальное значение. Идентификаторы с такими именами запрещены.
Интерфейс (англ. interface) — основной шаблон проектирования, являющийся общим методом для структурирования компьютерных программ для того, чтобы их было проще понять. В общем, интерфейс — это класс, который обеспечивает программисту простой или более программно-специфический способ доступа к другим классам.
Наследование (англ. inheritance) — концепция объектно-ориентированного программирования, согласно которой абстрактный тип данных может наследовать данные и функциональность некоторого существующего типа, способствуя повторному использованию компонентов программного обеспечения.
По одной из классификаций, языки программирования неформально делятся на сильно и слабо типизированные (англ. strongly and weakly typed), то есть обладающие сильной или слабой системой типов. Эти термины не являются однозначно трактуемыми, и чаще всего используются для указания на достоинства и недостатки конкретного языка. Существуют более конкретные понятия, которые и приводят к называнию тех или иных систем типов «сильными» или «слабыми».
В программировании термин «директива» (указание) по использованию похож на термин «команда», так как также используется для описания некоторых конструкций языка программирования (то есть указаний компилятору или ассемблеру особенностей обработки при компиляции).
Побо́чные эффе́кты (англ. side effects) — любые действия работающей программы, изменяющие среду выполнения (англ. execution environment). Например, к побочным эффектам относятся.
В объектно-ориентированном программировании под агрегированием (или как его еще называют — делегированием) подразумевают методику создания нового класса из уже существующих классов путём их включения. Об агрегировании также часто говорят как об «отношении принадлежности» по принципу «у машины есть корпус, колёса и двигатель».
Фу́нкция вы́сшего поря́дка — в программировании функция, принимающая в качестве аргументов другие функции или возвращающая другую функцию в качестве результата. Основная идея состоит в том, что функции имеют тот же статус, что и другие объекты данных. Использование функций высшего порядка приводит к абстрактным и компактным программам, принимая во внимание сложность производимых ими вычислений.
Функции первого класса являются неотъемлемой частью функционального программирования, в котором использование функций высшего порядка является стандартной практикой. Простым примером функции высшего порядка будет функция Map, которая принимает в качестве своих аргументов функцию и список и возвращается список, после применения функции к каждому элементу списка. Чтобы язык программирования поддерживал Map, он должен поддерживать передачу функций как аргумента.
Код операции, операционный код, опкод — часть машинного языка, называемая инструкцией и определяющая операцию, которая должна быть выполнена.
Множество — тип и структура данных в информатике, которая является реализацией математического объекта множество.
Сериализация (в программировании) — процесс перевода какой-либо структуры данных в последовательность битов. Обратной к операции сериализации является операция десериализации (структуризации) — восстановление начального состояния структуры данных из битовой последовательности.
Сравне́ние в программировании — общее название ряда операций над па́рами значений одного типа, реализующих математические отношения равенства и порядка. В языках высокого уровня такие операции, чаще всего, возвращают булево значение («истина» или «ложь»).
Ленивые вычисления (англ. lazy evaluation, также отложенные вычисления) — применяемая в некоторых языках программирования стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат. Ленивые вычисления относятся к нестрогим вычислениям. Усовершенствованная модель ленивых вычислений — оптимистичные вычисления — переходит в разряд недетерминированных стратегий вычисления.
Библиотека стандартных шаблонов (STL) (англ. Standard Template Library) — набор согласованных обобщённых алгоритмов, контейнеров, средств доступа к их содержимому и различных вспомогательных функций в C++.
Кома́нда — это указание компьютерной программе действовать как некий интерпретатор для решения задачи. В более общем случае, команда — это указание некоему интерфейсу командной строки, такому как shell.
Литерал (англ. literal ) — запись в исходном коде компьютерной программы, представляющая собой фиксированное значение. Литералами также называют представление значения некоторого типа данных.
Публичные поля классов
Примечание: Эта страница описывает экспериментальные возможности.
Публичные и приватные поля — это экспериментальная функция (stage 3), предложенная комитетом по стандарту JavaScript TC39.
Поддержка этой возможности в браузерах ограничена, но ее можно использовать посредством транспилирования с такими системами как Babel. Смотрите информацию о совместимости ниже.
И статические, и публичные поля являются изменяемыми, перечисляемыми, настраиваемыми свойствами. Таким образом, в отличие от приватных полей, они участвуют в прототипном наследовании.
Синтаксис
class ClassWithInstanceField instanceField = "instance field"; > class ClassWithStaticField static staticField = "static field"; > class ClassWithPublicInstanceMethod publicMethod() return "hello world"; > >
Примеры
Публичные статические поля
Публичные статические поля полезны тогда, когда необходимо существование одного единственного поля для всего класса, а не для каждого созданного экземпляра по отдельности. Это полезно для кеша, конфигураций или любых прочих данных, которые одинаковы для всех экземпляров.
Публичные статические поля объявляются при помощи ключевого слова static . Они добавляются в конструктор класса во время его создания с помощью Object.defineProperty() . Доступ также осуществляется через конструктор класса.
class ClassWithStaticField static staticField = "static field"; > console.log(ClassWithStaticField.staticField); // Ожидаемый вывод: "static field"
Поля без инициализации имеют значение («javascript.classes») .
class ClassWithStaticField static staticField; > console.assert(ClassWithStaticField.hasOwnProperty("staticField")); console.log(ClassWithStaticField.staticField); // Ожидаемый вывод: "undefined"
Публичные статические поля не переопределяются в наследниках класса, а могут быть доступны через иерархию прототипов.
class ClassWithStaticField static baseStaticField = "base field"; > class SubClassWithStaticField extends ClassWithStaticField static subStaticField = "sub class field"; > console.log(SubClassWithStaticField.subStaticField); // Ожидаемый вывод: "sub class field" console.log(SubClassWithStaticField.baseStaticField); // Ожидаемый вывод: "base field"
При определении полей this ссылается на конструктор класса. Также можно обратиться к нему по имени и использовать super для получения конструктора базового класса, если он существует.
class ClassWithStaticField static baseStaticField = "base static field"; static anotherBaseStaticField = this.baseStaticField; static baseStaticMethod() return "base static method output"; > > class SubClassWithStaticField extends ClassWithStaticField static subStaticField = super.baseStaticMethod(); > console.log(ClassWithStaticField.anotherBaseStaticField); // Ожидаемый вывод: "base static field" console.log(SubClassWithStaticField.subStaticField); // Ожидаемый вывод: "base static method output"
Публичные поля экземпляра
Такие публичные поля имеются у каждого экземпляра данного класса. Объявляя публичные поля, мы можем гарантировать, что поле всегда присутствует, а объявление класса является более самодокументированным.
Публичные поля экземпляра добавляются через Object.defineProperty() либо перед тем, как будет исполнено тело конструктора в базовом классе, либо после того, как завершится super() в классе наследнике.
class ClassWithInstanceField instanceField = "instance field"; > const instance = new ClassWithInstanceField(); console.log(instance.instanceField); // Ожидаемый вывод: "instance field"
Поля без инициализации имеют значение undefined .
class ClassWithInstanceField instanceField; > const instance = new ClassWithInstanceField(); console.assert(instance.hasOwnProperty("instanceField")); console.log(instance.instanceField); // Ожидаемый вывод: "undefined"
Как и свойства, названия полей могут вычисляться.
const PREFIX = "prefix"; class ClassWithComputedFieldName [`$PREFIX>Field`] = "prefixed field"; > const instance = new ClassWithComputedFieldName(); console.log(instance.prefixField); // Ожидаемый вывод: "prefixed field"
При определении полей this ссылается на создающийся экземпляр класса. Как и в публичных методах экземпляра, получить доступ к прототипу базового класса можно с помощью super .
class ClassWithInstanceField baseInstanceField = "base field"; anotherBaseInstanceField = this.baseInstanceField; baseInstanceMethod() return "base method output"; > > class SubClassWithInstanceField extends ClassWithInstanceField subInstanceField = super.baseInstanceMethod(); > const base = new ClassWithInstanceField(); const sub = new SubClassWithInstanceField(); console.log(base.anotherBaseInstanceField); // Ожидаемый вывод: "base field" console.log(sub.subInstanceField); // Ожидаемый вывод: "base method output"
Публичные методы
Публичные статические методы
Ключевое слово static объявляет статический метод класса. Статические методы не вызываются из экземпляра, вместо этого они вызывается из самого класса. Чаще всего это какие-либо служебные функции, такие как функции создания или копирования объектов.
class ClassWithStaticMethod static staticMethod() return "static method has been called."; > > console.log(ClassWithStaticMethod.staticMethod()); // expected output: "static method has been called."
Статические методы добавляются в конструктор класса с помощью Object.defineProperty() во время его создания. Эти методы — изменяемые, неперечисляемые и настраиваемые свойства объекта.
Публичные методы экземпляра
Как и следует из названия, публичные методы экземпляра это методы, доступные для вызова из экземпляров.
class ClassWithPublicInstanceMethod publicMethod() return "hello world"; > > const instance = new ClassWithPublicInstanceMethod(); console.log(instance.publicMethod()); // Ожидаемый вывод: "hello world"
Публичные методы добавляются в прототип класса во время его создания с помощью Object.defineProperty() . Они изменяемы, неперечисляемы и настраиваемы.
Вы можете использовать генераторы, асинхронные функции и асинхронные генераторы.
class ClassWithFancyMethods *generatorMethod() > async asyncMethod() > async *asyncGeneratorMethod() > >
Внутри методов экземпляра, this ссылается на сам экземпляр. В классах наследниках, super даёт доступ к прототипу базового класса, позволяя вызывать его методы.
class BaseClass msg = "hello world"; basePublicMethod() return this.msg; > > class SubClass extends BaseClass subPublicMethod() return super.basePublicMethod(); > > const instance = new SubClass(); console.log(instance.subPublicMethod()); // Ожидаемый вывод: "hello world"
Геттеры и сеттеры это специальные методы, которые привязаны к свойствам класса и которые вызываются, когда к свойству обращаются или записывают. Используйте get и set для объявления публичных геттеров и сеттеров экземпляра.
class ClassWithGetSet #msg = "hello world"; get msg() return this.#msg; > set msg(x) this.#msg = `hello $x>`; > > const instance = new ClassWithGetSet(); console.log(instance.msg); // Ожидаемый вывод: "hello world" instance.msg = "cake"; console.log(instance.msg); // Ожидаемый вывод: "hello cake"
Спецификации
| Specification |
|---|
| ECMAScript Language Specification # sec-class-definitions |
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
- Публичные и приватные поля классов статья на сайте v8.dev.
- Объявление полей класса в JavaScript, от авторов Публичных и приватных полей экземпляра
- Семантика всех элементов JS класса
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 6 янв. 2024 г. by MDN contributors.
Your blueprint for a better internet.
Язык C++
В ходе развития языков программирования и подходов к созданию программ были сформулированы различные парадигмы программирования. Парадигма — это совокупность идей и понятий, определяющих стиль написания программ. Язык C++ позволяет использовать различные парадигмы, самые распространенные из которых — структурное, объектно-ориентированное и обобщенное программирование. Чаще всего используется комбинация идей из разных парадигм, так, лямбда-выражения, которые мы уже освоили, пришли из функционального программирования.
Язык C++ создавался как язык с полной поддержкой объектно-ориентированного программирования (ООП). Всестороннее обсуждение концепций ООП не является нашей целью, однако некоторые базовые принципы необходимо рассмотреть.
ООП рассматривает программу как множество взаимодействующих объектов. Объект обладает некоторым состоянием и предоставляет определённый интерфейс, с помощью которого с ним можно взаимодействовать. Каждый объект является экземпляром какого-то класса.
Мы знакомы уже со многими классами, хотя и не называли их этим термином. Все типы стандартной библиотеки, кроме базовых, являются классами. Например, класс string . Переменная типа string является экземпляром класса. Набор символов строки определяет состояние экземпляра string , а взаимодействие с набором символов возможно только посредством методов, определённых в классе string .
Идея связанности данных и способов работы с ними называется инкапсуляцией и является фундаментом ООП. Инкапсуляция позволяет уменьшить зависимость различных частей программы друг от друга. Представим себе, что разработчик решил изменить структуры данных, либо алгоритмы, которые используются в классе. Если он внесёт изменения, сохранив публичный интерфейс класса, то другие части программы не потребуется изменять. В больших проектах слабая зависимость разных частей кода является абсолютно необходимой.
Теперь мы готовы перейти к обсуждению средств языка C++, реализующих принцип инкапсуляции.
Классы C++: начало
Минимальный класс в C++ выглядит так:
class LorentzVector <>; LorentzVector lv;
Судя по названию, мы решили реализовать класс четырех-векторов. Написанный код уже позволяет создавать объекты класса, однако в остальном наш класс весьма бесполезен.
Чтобы наполнить класс содержанием, в нем определяют поля и методы. Поля класса — это объекты любого типа; набор полей и их значения определяют состояние объекта. Методы — это функции, связанные с классом. Рассмотрим на примере:
class LorentzVector // по умолчанию мы находимся области private double t_; // поле: временная компонента double x_; // поле: пространственная компонента public: // константный публичный метод double t() const return t_; > // константный публичный метод double x() const return x_; > // публичный метод double& t() return t_; > // публичный метод double& x() return x_; > >;
Мы добавили в класс поля t_ и x_ и методы для доступа к ним. Поля и методы класса могут быть публичными или приватными. К публичным методам и полям можно обращаться при работе с объектом класса. Приватные поля и методы доступны только внутри методов самого класса. Ключевые слова private и public выполняют переключение на приватную и публичную части класса. В соответствии с принципом инкапсуляции мы поместили поля в приватную часть класса. Это позволит нам в дальнейшем изменить способ хранения данных, например, вместо двух переменных типа double использовать объект std::array , не меняя при этом публичные методы.
Следующая деталь — поля и методы могут быть константными. Делая метод константным, мы обещаем, что при его вызове видимое состояние объекта не изменяется. Методы чтения полей — хороший пример константных методов. Указывать константность метода не обязательно, но хороший стиль программирования подразумевает указание константности везде, где это уместно. Это, во-первых, повышает выразительность кода, и во-вторых, определяет набор методов, которые доступны константному объекту. Например:
LorentzVector lv1; lv1.t_ = 1.; // ошибка: поле t_ - приватное lv1.t() = 1.; lv1.x() = 0.5; cout "(" lv.t() ", " lv.x() ")\n"; const LorentzVector lv2; lv2.t() == 1.; // ошибка: вызов неконстантного метода // у константного объекта cout "(" lv2.t() ", " // Здесь все хорошо, поскольку методы lv2.x() ")\n"; // чтения определены, как константные
Обратите внимание, что мы определили методы с одинаковыми названиями, но различными сигнатурами. Сигнатура метода определяется:
- набором и типом аргументов
- свойством константности
Компилятор сам выбирает нужный метод в зависимости от контекста. Определение методов с одинаковыми названиями, но различными сигнатурами называется перегрузкой методов.
Конструктор
При создании объекта вызывается специальный метод — конструктор, который выполняет инициализацию полей объекта. В нашем классе конструктор не был определён, поэтому компилятор сгенерировал его автоматически. Поля t_ и x_ инициализировались значениями по умолчанию типа double . Определим пару конструкторов для нашего класса:
class LorentzVector // . public: // конструктор по умолчанию LorentzVector() t_ = 0; x_ = 0; > // ещё один конструктор LorentzVector(double ti, double xi) t_ = ti; x_ = xi; > >
Конструктор — это метод, имя которого совпадает с названием класса, для него не указывается тип возвращаемого значения. Конструкторов может быть несколько, каждый из них должен иметь уникальную сигнатуру. Создавать объекты нашего класса теперь удобнее:
LorentzVector lv(1, 0.5);
В теле конструктора можно выполнять различные действия, например, вызывать другие методы класса. Простую инициализацию полей можно выполнять элегантнее:
class LorentzVector // . public: LorentzVector() = default; // конструктор по умолчанию // ещё один конструктор LorentzVector(double ti, double xi) : t_(ti), x_(xi) <> >
Ключевое слово default , которое мы использовали с конструктором по умолчанию, говорит компилятору самостоятельно создать этот конструктор. В нашем случае это хорошее решение. Если бы мы определили только конструктор с параметрами, то конструктор по умолчанию не был бы создан.
В конструкторе с параметрами мы использовали специальный способ инициализации полей после двоеточия. Это является рекомендуемым способом инициализации полей. Помимо краткости, эта конструкция лучше с точки зрения производительности. Дело в том, что поля объекта в любом случае инициализируются до начала выполнения инструкций в теле конструктора. Первая версия конструктора, таким образом, сначала выполняла инициализацию по умолчанию, а затем — присваивание необходимых значений. Новая версия конструктора избегает лишних манипуляций. Кроме того, это был бы единственный вариант инициализации для константных полей объекта.
Добавим к классу LorentzVector метод boosted , возвращающий вектор с компонентами в другой системе отсчета:
class LorentzVector // . public: // Преобразование Лоренца с фактором beta LorentzVector boosted(double beta) double gamma = std::sqrt(1/(1-beta*beta)); return gamma * (t_ - beta * x_), gamma * (x_ - beta * t_) >; > // . >;
Мы вернули пару чисел в фигурных скобках. Если переданные значения соответствуют аргументам конструктора возвращаемого значения, то компилятор корректно создаст нужный объект.
Перегрузка операторов
C++ позволяет в значительной степени интегрировать пользовательские типы данных в язык и сделать работу с ними столь же удобной, как работу со встроенными типами. Во многом это достигается благодаря перегрузке операторов. Классу LorentzVector явно не хватает арифметических операций. Формально корректным решением было бы создание методов, подобных этому:
class LorentzVector // . public: LorentzVector Add(const LorentzVector& rhs) const return t_ + rhs.t_, x_ + rhs.x_>; > // . >;
Гораздо удобнее, однако, для сложения векторов использовать оператор + . Давайте выполним перегрузку оператора + для работы с типом LorentzVector :
class LorentzVector // . public: LorentzVector operator+(const LorentzVector& rhs) return t_ + rhs.t_, x_ + rhs.x_>; > // . >;
Аналогично можно перегрузить операторы — , += , -= , оператор * для умножения вектора на скаляр. Полный список доступных для перегрузки операторов можно найти в документации.
Если оператору необходим доступ к приватным частям класса, то его можно сделать членом класса. Альтернативным решением является явное разрешение использовать приватные части класса с помощью ключевого слова friend. Оператор при этом не является частью класса:
LorentzVector operator+(const LorentzVector& lhs, const LorentzVector& rhs) return lhs.t() + rhs.t(), lhs.x() + rhs.x()>; >
Выбор из разных способов перегрузки операторов является во многом делом вкуса, но при принятии решений можно иметь в виду следующий аргумент: чем меньше объектов имеют доступ к приватным частям класса, тем проще будет вносить изменения в логику его работы в дальнейшем.
Перегрузка операторов > позволяет использовать класс при работе с потоками:
#include ostream& operator(ostream& os, const LorentzVector& lv) os "(" lv.t() ", " lv.x() ")"; return os; >
#include istream& operator(istream& is, LorentzVector& lv) is >> lv.t() >> lv.x(); return is; >
Нашим классом уже становится приятно пользоваться:
LorentzVector lv1(1, 0.5); LorentzVector lv2(0.2, -0.1); LorentzVector lv3 = lv1 + lv2; cout lv1 " + " lv2 " = " lv3 endl;
Статические поля и методы
Еще одна возможность классов C++ — создание полей и методов, связанных с самим классом, а не с объектами класса. Такие поля и методы называют статическими, они определяются с помощью ключевого слова static . Давайте добавим к нашему классу счётчик всех созданных объектов. Для этого нам понадобится статическое поле и новая логика в конструкторе:
// файл lvec.h class LorentzVector // приватное статическое поле static size_t counter; public: LorentzVector(double ti, double xi) : t_(ti), x_(xi) ++counter; // увеличиваем счётчик объектов > // публичный статический метод static size_t objects_created() return counter; > // . >; // файл lvec.cpp, инициализируем статическое поле size_t LorentzVector::counter = 0;
Обычные методы класса имеют доступ к статическим полям и методам. Таким образом, с помощью изменения статических полей можно изменять поведение всех объектов класса.
Статические поля необходимо инициализировать за пределами определения класса. Более того, чтобы иметь возможность подключать наш класс в другие файлы с помощью директивы #include , инициализация статического поля должна находиться в отдельном файле. Подробнее мы об этом поговорим в конце этого раздела.
Специальные методы класса
Мы уже знаем, что если в классе не определён ни один конструктор, то компилятор попробует сгенерировать конструктор по умолчанию самостоятельно. Это не единственный специальный метод, который компилятор может создавать автоматически. Полная сигнатура нашего «пустого» класса выглядит так:
class LorentzVector public: // конструктор по умолчанию LorentzVector(); // копирующий конструктор LorentzVector(const LorentzVector&); // перемещающий конструктор LorentzVector(LorentzVector&&); // копирующий оператор присваивания LorentzVector& operator=(const LorentzVector&); // перемещающий оператор присваивания LorentzVector& operator=(LorentzVector&&); // деструктор ~LorentzVector(); >;
Кроме конструктора по умолчанию мы видим копирующий и перемещающий конструкторы, которые используются следующим образом:
LorentzVector lv1; // конструктор по умолчанию LorentzVector lv2; // конструктор по умолчанию LorentzVector lv3(lv1); // копирующий конструктор LorentzVector lv4(std::move(lv2)); // перемещающий конструктор
Копирующий конструктор выполняет инициализацию нового объекта, копируя во все его поля значения полей переданного объекта. Перемещающий конструктор выполняет перемещение переданного объекта. В этом случае память, которую использовал переданный объект, передаётся во владение новому объекту. При этом не происходит трата ресурсов на копирование. Переданный объект ( lv2 ) остаётся в корректном состоянии, хотя его состояние становится неопределённым. Это означает, что если мы зададим новые значения для компонент вектора lv2 , мы сможем продолжать его использовать.
Сигнатура LorentzVector&& обозначает rvalue ссылку. Она обозначает временный объект, который может находиться в правой части оператора присваивания, но не может находиться в левой. Функция std::move превращает lvalue -объект в rvalue -объект. Подробнее понятия lvalue и rvalue объектов мы обсудим в другой части.
Копирующий и перемещающий операторы присваивания обеспечивают работу следующих выражений:
LorentzVector lv1; // конструктор по умолчанию LorentzVector lv2; // конструктор по умолчанию lv2 = lv1; // оператор копирующего присваивания lv1 = std::move(lv2); // оператор перемещающего присваивания
Наконец, при выходе объекта из области видимости вызывается еще один специальный метод — деструктор. Деструктор может быть только один, его имя отличается от имени конструктора символом тильда ~ в начале. Деструктор не может иметь параметров. Основное назначение деструктора в освобождении ресурсов, которыми владел объект.
Правило пяти
В классе LorentzVector мы не заботились об определении специальных методов, кроме конструктора по умолчанию. Это проявление общей закономерности: классы можно разделить на две большие группы. Классы первой группы используют пять специальных методов с поведением по умолчанию (речь обо всех специальных методах, кроме конструктора по умолчанию). Классам второй группы поведение по умолчанию не подходит.
Содержание «правила пяти» состоит в том, что если есть необходимость переопределения поведения одного из пяти специальных методов, то скорее всего следует переопределить поведение всех пяти методов. Более того, если определить лишь часть специальных методов, то остальные методы могут не сгенерироваться, поскольку компилятор сделает вывод о том, что поведение по умолчанию не является подходящим. Таким образом, в подавляющем большинстве случаев мы либо не переопределяем ни один из пяти специальных методов, либо переопределяем их все.
Второй случай реализуется, если внутри класса происходит нетривиальная работа с ресурсами: динамической памятью, файловыми дескрипторами, сетевыми подключениями и т.д.
Организация файлов
В заключение обсудим разумную организацию кода из этого раздела в файлах. Классы создаются для их многократного использования. Это значит, что помещать определение класса в файл с функцией main является плохой идеей. Вместо этого разделим наш код на три файла:
- lvec.h — содержит объявление класса LorentzVector
- lvec.cpp — содержит определение класса LorentzVector
- main.cpp — использует файл lvec.h и выполняет логику работы программы
Вот так могут выглядеть файлы lvec.h и lvec.cpp :
// lvec.h # pragma once #include class LorentzVector double t_; double x_; static size_t counter; public: // объявление конструктора LorentzVector(double ti, double xi); // объявления методов double t() const; double x() const; double& t(); double& x(); LorentzVector boosted(double beta); static size_t objects_created(); // . >; // объявление операторов void operator+=(LorentzVector& lhs, const LorentzVector& rhs); // . std::ostream& operator<(std::ostream& os, const LorentzVector& lv); std::istream& operator<(std::istream& is, LorentzVector& lv);
// lvec.cpp #include "lvec.h" #include // std::sqrt size_t LorentzVector::counter = 0; LorentzVector::LorentzVector(double ti, double xi) : t_(ti), x_(xi) <> LorentzVector LorentzVector::boosted(double beta) double gamma = std::sqrt(1/(1-beta*beta)); return gamma * (t_ - beta * x_), gamma * (x_ - beta * t_) >; > size_t LorentzVector::objects_created() return counter; > void operator+=(LorentzVector& lhs, const LorentzVector& rhs) lhs.t() += rhs.t(); lhs.x() += rhs.x(); > // .
// main.cpp #include "lvec.h" #include using namespace std; int main() LorentzVector lv1(1.0, 0.2); LorentzVector lv2 = lv1.boosted(0.2); cout <lv1 <" boost 0.2 -> " <lv2 <endl; return 0; >
Мы полностью разделили объявление и определение класса. Приведем несколько аргументов в пользу такого подхода:
- Определение класса стало более кратким, его проще читать
- Детали реализации — это внутреннее дело нашего класса. Другие части кода подключают заголовочный файл lvec.h . Если изменится заголовочный файл, то изменятся и все файлы, в которые он включен. Выделение определения в отдельный файл позволяет выполнять большинство изменений, касающихся логики работы класса, только в файле lvec.cpp . Это еще одно проявление принципа инкапсуляции: публичную информацию помещаем в lvec.h , а остальное — в lvec.cpp .
- Последний аргумент касается требований языка C++. Согласно стандарту, переменные и функции должны быть определены ( defined ) только в одном месте программы. Объявлений же ( declaration ) может быть несколько. Если бы мы поместили определение статической переменной counter или какого-либо оператора в файл lvec.h , то получли бы ошибку компиляции при попытке скомпилировать совместно файлы lvec.h , lvec.cpp и main.cpp .
Резюме
В этой части мы обсудили идею инкапсуляции — одну из ключевых концепций объектно-ориентированного программирования, и основные инструменты создания классов в C++:
- поля и методы
- приватная и публичная части определения классов
- перегрузка операторов
- статические поля и методы
- методы, которые генерируются компилятором
- организация файлов при создании классов
Эти знания уже позволяют вам реализовывать сложную логику в рамках объектно-ориентированного подхода. Далее мы продолжим изучение концепций ООП и их реализацию в языке C++.
Источники
- en.cppreference.com/w/cpp/language/operators
- isocpp.org/wiki/faq/operator-overloading
- en.cppreference.com/w/cpp/language/friend
- en.cppreference.com/w/cpp/language/rule_of_three
- en.cppreference.com/w/cpp/language/move_constructor
- https://en.cppreference.com/w/cpp/utility/move
- Понимание lvalue и rvalue в C и С++ (Хабр)
- Введение
- Настройка рабочей среды
- Язык C++
- Работа с потоками ввода-вывода
- Строки
- Контейнеры стандартной библиотеки C++
- Эффективная передача параметров в функцию
- Алгоритмы стандартной библиотеки C++
- Итераторы
- Библиотеки numeric и random
- Классы
- Наследование
- Динамическое выделение памяти
- Обобщенное программирование
Поле класса
По́ле кла́сса или атрибу́т (переменная-член, data member, class field, instance variable) в объектно-ориентированном программировании — переменная, связанная с классом или объектом. Все данные объекта хранятся в его полях. Доступ к полям осуществляется по их имени. Обычно тип данных каждого поля задаётся в описании класса, членом которого является поле.
Поля структур
Структурные типы, поддерживаемые большинством языков программирования (называемые структурами (structure) в Си, записями (record) в Паскале и т. д.), являются частным случаем классов — а именно, классами из одних только полей. Вся информация, относящаяся к полям классов, в равной степени относится и к структурным типам.
Статические поля
Обычно, каждому объекту соответствуют собственные значения всех его полей. Также к полям класса относят статические поля (static data members, static class fields, class variables) — поля, общие для всех объектов класса.
Статические поля семантически не отличаются от обычных глобальных переменных, но они доступны только по квалифицированному имени (то есть с указанием имени класса), и поэтому, в отличие от глобальных переменных, не загромождают пространство глобальных имён.
В некоторых объектно-ориентированных языках программирования, таких как Java, не существует глобальных переменных, и поэтому статические поля классов — единственный способ хранения глобальных данных в программах на этих языках.
Битовые поля
Некоторые языки, такие как C++, позволяют определять битовые поля. Эти поля занимают менее одной единицы памяти (байт, слово); компилятор сам упаковывает несколько битовых полей в одну единицу памяти, позволяя при этом обращаться к битовым полям как к отдельным полям класса.
Понятие битовых полей не ограничивается полями классов: по аналогии оно было перенесено и на другие случаи упаковки в одну единицу памяти нескольких значений, например в сетевых протоколах и в форматах файлов.
Советы по использованию
Обычно, область доступа полей класса делают закрытой (private), то есть доступ к ним разрешается только методам того же класса. Чтобы предоставить пользователям класса значения его полей, используются свойства: они позволяют классу контролировать изменение его полей, например проверять принадлежность заданного значения диапазону допустимых значений.
Когда доступ к полю инкапсулируется процедурами свойства, возможно обновлять код, обрабатывающий изменения значения этого свойства, не нарушая совместимость с существующими пользователями класса. Поэтому, принято создавать свойства для доступа к полям класса, даже тогда, когда на начальном этапе разработки не требуется никакой дополнительной обработки присваиваемых полю значений.
Примеры использования
C++
class foo private: int x; // Это поле — закрытое. Доступ к нему возможен только // из методов класса ''foo''. public: int y; //Это поле — открытое. Доступ к нему возможен из //любого места программы. Рекомендуется так не делать. // Эти два метода ([[свойство (программирование)#Процедуры свойств|акцессор]] // и [[w:en:Mutator method|мутатор]]) обеспечивают доступ к // закрытому полю x. Рекомендуется делать именно так. int get_x() return x;>; void set_x(int x) this->x = x; >; static int z; // Это статическое поле. Оно принадлежит классу // foo целиком, а не его объектам. char a:3; // Это битовые поля. В сумме они занимают char b:3; // 8 бит, т.е. они будут размещены в одном char c:1; // байте памяти. Таким образом возможно char d:1; // обращаться к отдельным битам памяти. >;
Delphi
type foo = class private x: Integer; // закрытое поле public y: Integer; // открытое поле // для доступа к закрытому полю в Delphi можно использовать свойства function get_x: Integer; // акцессор procedure set_x (x: Integer); // мутатор property _x: Integer read get_x write set_x; // поддержка статических полей для .NET/Win32 введена в Delphi 8/2007 соответственно class var z: integer; // Это статическое поле. Оно принадлежит классу // foo целиком, а не его объектам. // в Delphi не поддерживаются битовые поля end;
Python
В Питоне поля и методы обычно называются общим термином атрибуты. Статические атрибуты могут быть перегружены в экземпляре. Атрибуты экземпляра определяются не в структуре класса, а в методах (например, конструкторе). Атрибуты, доступ к которым управляется отдельными методами, называются свойствами (синтаксически доступ к атрибутом и свойствам идентичен):
class Foo(object): z = 0 # атрибут класса (статический атрибут) def __init__(self, x, y): # конструктор self._x = x # закрытый атрибут (на уровне соглашения между программистами) self.y = y # открытый атрибут def getx(self): return self.__x # методы для чтения, def setx(self, value): self.__x = value # записи def delx(self): del self.__x # удаления свойства x = property(getx, setx, delx, "Свойство 'x'.") # определение свойства
foo = Foo(2, 4) # создание экземпляра foo.y = 10 # связывание значения с атрибутом foo.x = 30 # то же, но эквивалентно foo.setx(30) print foo.x, foo.y, foo.z # печать атрибутов del foo.x # удаление свойства print foo._x # не рекомендуется print foo.__x # не сработает
PHP
class HelloWorld private $message; // Приватное поле класса HelloWorld public $global_message; // Публичное поле // Доступ к приватному поле public function GetMessage() return $this->message; > > ?>См. также
- Свойство (программирование)
- Класс (программирование)
- Метод (программирование)
- Объектно-ориентированное программирование
- Концепции языков программирования
Wikimedia Foundation . 2010 .