Как обратиться к полю класса
Перейти к содержимому

Как обратиться к полю класса

  • автор:

Объектно-ориентированное программирование

Kotlin поддерживает объектно-ориентированную парадигму программирования, а это значит, что программу на данном языке можно представить в виде взаимодействующих между собой объектов.

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

Для определения класса применяется ключевое слово class , после которого идет имя класса. А после имени класса в фигурных скобках определяется тело класса. Если класс не имеет тела, то фигурные скобки можно опустить. Например, определим класс, который представляет человека:

class Person // либо можно так class Person

Класс фактически представляет новый тип данных, поэтому мы можем определять переменные этого типа:

fun main() < val tom: Person val bob: Person val alice: Person >class Person

В функции main определены три переменных типа Person. Стоит также отметить, что в отличие от других объектно-ориентированных языков (как C# или Java), функция main в Kotlin не помещается в отдельный класс, а всегда определяется вне какого-либо класса.

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

val tom: Person = Person()

Часть кода после знака равно Person() как раз и представляет вызов конструктора, который создает объект класса Person. До вызова конструктора переменная класса не указывает ни на какой объект.

Например, создадим три объекта класса Person:

fun main() < val tom: Person = Person() val bob: Person = Person() val alice: Person = Person() >class Person

Свойства

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

Свойство должно быть инициализировано, то есть обязательно должно иметь начальное значение. Например, определим пару свойств:

class Person

В данном случае в классе Person, который представляет человека, определены свойства name (имя человека) и age (возраст человека). И эти свойства инициализированы начальными значениями.

Поскольку эти свойства определены с var , то мы можем изменить их начальные значения:

fun main() < val bob: Person = Person() // создаем объект println(bob.name) // Undefined println(bob.age) // 18 bob.name = "Bob" bob.age = 25 println(bob.name) // Bob println(bob.age) // 25 >class Person

Для обращения к свойствам используется имя переменной, которая предствляет объект, и после точки указывается имя свойства. Например, получение значения свойства:

val personName : String = bob.name

Установка значения свойства:

bob.name = "Bob"

Функции класса

Класс также может содержать функции. Функции определяют поведение объектов данного класса. Такие функции еще называют member functions или функции-члены класса. Например, определим класс с функциями:

class Person < var name: String = "Undefined" var age: Int = 18 fun sayHello()< println("Hello, my name is $name") >fun go(location: String) < println("$name goes to $location") >fun personToString() : String < return "Name: $name Age: $age" >>

Функции класса определяется также как и обычные функции. В частности, здесь в классе Person определена функция sayHello() , которая выводит на консоль строку «Hello» и эмулирует приветствие объекта Person. Вторая функция — go() эмулирует движение объекта Person к определенному местоположению. Местоположение передается через параметр location . И третья функция personToString() возвращает информацию о текущем объекте в виде строки.

В функциях, которые определены внутри класса, доступны свойства этого класса. Так, в данном случае в функциях можно обратиться к свойствам name и age, которые определены в классе Person.

Для обращения к функциям класса необходимо использовать имя объекта, после которого идет название функции и в скобках значения для параметров этой функции:

fun main() < val tom = Person() tom.name = "Tom" tom.age = 37 tom.sayHello() tom.go("the shop") println(tom.personToString()) >class Person < var name: String = "Undefined" var age: Int = 18 fun sayHello()< println("Hello, my name is $name") >fun go(location: String) < println("$name goes to $location") >fun personToString() : String < return "Name: $name Age: $age" >>

Консольный вывод программы:

Hello, my name is Tom Tom goes to the shop Name: Tom Age: 37

Как обратиться к полю класса

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

public static void main(String[] args)

Для объявления статических переменных, констант, методов и инициализаторов перед их объявлением указывается ключевое слово static .

Статические поля

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

Например, создадим статическую переменную:

public class Program < public static void main(String[] args) < Person tom = new Person(); Person bob = new Person(); tom.displayId(); // 3 // изменяем Person.counter Person.counter = 8; Person sam = new Person(); sam.displayId(); // Person< private int id; static int counter=1; Person()< id = counter++; >public void displayId() < System.out.printf("Id: %d \n", id); >>

Класс Person содержит статическую переменную counter, которая увеличивается в конструкторе и ее значение присваивается переменной id. То есть при создании каждого нового объекта Person эта переменная будет увеличиваться, поэтому у каждого нового объекта Person значение поля id будет на 1 больше чем у предыдущего.

Так как переменная counter статическая, то мы можем обратиться к ней в программе по имени класса:

System.out.println(Person.counter); // получаем значение Person.counter = 8; // изменяем значение

Консольный вывод программы:

Id = 1 Id = 2 3 Id = 8

Статические константы

Также статическими бывают константы, которые являются общими для всего класса.

public class Program < public static void main(String[] args) < double radius = 60; System.out.printf("Radisu: %f \n", radius); // 60 System.out.printf("Area: %f \n", Math.PI * radius); // 188,4 >> class Math

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

System.out.println("hello");

out как раз представляет статическую константу класса System. Поэтому обращение к ней идет без создания объекта класса System.

Статические инициализаторы

Статические инициализаторы предназначены для инициализации статических переменных, либо для выполнения таких действий, которые выполняются при создании самого первого объекта. Например, определим статический инициализатор:

public class Program < public static void main(String[] args) < Person tom = new Person(); Person bob = new Person(); tom.displayId(); // Person< private int id; static int counter; static< counter = 105; System.out.println("Static initializer"); >Person() < id=counter++; System.out.println("Constructor"); >public void displayId() < System.out.printf("Id: %d \n", id); >>

Статический инициализатор определяется как обычный, только перед ним ставится ключевое слово static . В данном случае в статическом инициализаторе мы устанавливаем начальное значение статического поля counter и выводим на консоль сообщение.

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

Static initializer Constructor Constructor Id: 105 Id: 106

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

Статические методы

Статические методы также относятся ко всему классу в целом. Например, в примере выше статическая переменная counter была доступна извне, и мы могли изменить ее значение вне класса Person. Сделаем ее недоступной для изменения извне, но доступной для чтения. Для этого используем статический метод:

public class Program < public static void main(String[] args) < Person.displayCounter(); // Counter: 1 Person tom = new Person(); Person bob = new Person(); Person.displayCounter(); // Counter: 3 >> class Person < private int id; private static int counter = 1; Person()< id = counter++; >// статический метод public static void displayCounter() < System.out.printf("Counter: %d \n", counter); >public void displayId() < System.out.printf("Id: %d \n", id); >>

Теперь статическая переменная недоступна извне, она приватная. А ее значение выводится с помощью статического метода displayCounter. Для обращения к статическому методу используется имя класса: Person.displayCounter() .

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

Вообще методы определяются как статические, когда методы не затрагиют состояние объекта, то есть его нестатические поля и константы, и для вызова метода нет смысла создавать экземпляр класса. Например:

public class Program < public static void main(String[] args) < System.out.println(Operation.sum(45, 23)); // 68 System.out.println(Operation.subtract(45, 23)); // 22 System.out.println(Operation.multiply(4, 23)); // 92 >> class Operation < static int sum(int x, int y)< return x + y; >static int subtract(int x, int y) < return x - y; >static int multiply(int x, int y) < return x * y; >>

В данном случае для методов sum, subtract, multiply не имеет значения, какой именно экземпляр класса Operation используется. Эти методы работают только с параметрами, не затрагивая состояние класса. Поэтому их можно определить как статические.

Поля (Руководство по программированию в C#)

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

Класс или структура может иметь поля экземпляра и (или) статические поля. Поля экземпляра относятся только к экземпляру типа. Если у вас есть класс T с полем F экземпляра, можно создать два объекта типа T и изменить значение каждого объекта, не влияя на значение F в другом объекте. Напротив, статическое поле принадлежит самому типу и является общим для всех экземпляров этого типа. Доступ к статическому полю можно получить только с помощью имени типа. Если вы обращаетесь к статическому полю по имени экземпляра, вы получите ошибки времени компиляции CS0176.

Как правило, следует объявить private или protected специальные возможности для полей. Данные, которые тип представляет клиентскому коду, должны предоставляться через методы, свойства и индексаторы. Используя эти конструкции для косвенного доступа к внутренним полям, можно предотвратить использование недопустимых входных значений. Закрытое поле, которое хранит данные, представленные открытым свойством, называется резервным хранилищем или резервным полем. Вы можете объявить public поля, но затем нельзя запретить коду, использующего тип, задать это поле в недопустимое значение или изменить данные объекта.

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

Поля объявляются в блоке класса или структуры, указав уровень доступа, за которым следует тип, а затем имя поля. Например:

public class CalendarEntry < // private field (Located near wrapping "Date" property). private DateTime _date; // Public property exposes _date field safely. public DateTime Date < get < return _date; >set < // Set some reasonable boundaries for likely birth dates. if (value.Year >1900 && value.Year else < throw new ArgumentOutOfRangeException("Date"); >> > // public field (Generally not recommended). public string? Day; // Public method also exposes _date field safely. // Example call: birthday.SetDate("1975, 6, 30"); public void SetDate(string dateString) < DateTime dt = Convert.ToDateTime(dateString); // Set some reasonable boundaries for likely birth dates. if (dt.Year >1900 && dt.Year else < throw new ArgumentOutOfRangeException("dateString"); >> public TimeSpan GetTimeSpan(string dateString) < DateTime dt = Convert.ToDateTime(dateString); if (dt.Ticks < _date.Ticks) < return _date - dt; >else < throw new ArgumentOutOfRangeException("dateString"); >> > 

Для доступа к полю в экземпляре добавьте точку после имени экземпляра, за которой следует имя поля, как в instancename._fieldName . Например:

CalendarEntry birthday = new CalendarEntry(); birthday.Day = "Saturday"; 

Полю можно присвоить начальное значение с помощью оператора присваивания при объявлении поля. Чтобы автоматически назначить поле Day , например полю «Monday» , нужно объявить Day , как в следующем примере:

public class CalendarDateWithInitialization < public string Day = "Monday"; //. >

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

Инициализатор поля не может ссылаться на другие поля экземпляров.

Поля можно пометить как public , private , , protected , protected internal internal или private protected . Эти модификаторы доступа определяют, каким образом пользователи типа смогут получать доступ к полю. Дополнительные сведения см. в статье Модификаторы доступа.

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

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

Поле можно объявить required . Обязательное поле должно быть инициализировано конструктором или инициализаторами объектов при создании объекта. Атрибут добавляется в System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute любое объявление конструктора, которое инициализирует все необходимые элементы.

required Модификатор не может сочетаться с модификатором readonly в том же поле. Однако свойство может быть required и init только.

Начиная с C# 12, параметры первичного конструктора являются альтернативой объявлению полей. Если тип имеет зависимости, которые должны быть предоставлены при инициализации, можно создать первичный конструктор, предоставляющий эти зависимости. Эти параметры могут быть записаны и использованы вместо объявленных полей в типах. В случае типов параметры первичного конструктора record отображаются как общедоступные свойства.

Спецификация языка C#

Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.

См. также

  • Система типов C#
  • Использование конструкторов
  • Наследование
  • Модификаторы доступа
  • Абстрактные и запечатанные классы и члены классов

Совместная работа с нами на GitHub

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

Доступ к приватным полям класса [дубликат]

Каким образом можно получить доступ к приватным полям класса? При этом не используются аксессоры и классы-друзья.

Отслеживать
задан 28 дек 2011 в 15:02
Стас Литвиненко Стас Литвиненко
253 3 3 серебряных знака 13 13 бронзовых знаков

Глубоко-глубоко было запрятано объявление друга, еще и использование множественного наследования. В общем, вопрос можно закрывать. Но так писать нельзя.

30 дек 2011 в 6:30

4 ответа 4

Сортировка: Сброс на вариант по умолчанию

Известно 3 способа сделать то, о чем вы просите.

  • Задефайнить #define private public и подключить соответствующий заголовочный файл (естественно, бессмысленно в случае PIMPL -подобных схем).
  • Напрямую обращаться по предполагаемому адресу переменной (этот способ вам уже рассказали ). Плох тем, что в зависимости от присутствия в классе vfptr и vcbl , относительное смещение может меняться, т.е способ непортабельный.
  • Воспользоваться трюком с template . Самое красивое и даже в некотором плане элегантное решение.

Отслеживать
ответ дан 30 дек 2011 в 7:20
M. Williams M. Williams
23.6k 1 1 золотой знак 41 41 серебряный знак 58 58 бронзовых знаков
За template спасибо! Надо будет ознакомиться на досуге.
30 дек 2011 в 9:03

Только через адреса этих полей.

#include class A < private: int a; virtual int b () < return a; >>; class B < public: int a; virtual int b (); >; int main() < A obj; B* ptr = static_cast(static_cast((&obj))); ptr->a = 42; std::coutb()

Отслеживать
ответ дан 28 дек 2011 в 15:06
3,255 17 17 серебряных знаков 32 32 бронзовых знака

Нет, обращение идет по имени поля. Как-то это реализовано через указатель и метод с модификатором static. Плюс наследование.

28 дек 2011 в 15:15
@Стас Литвиненко отредактировал ответ
28 дек 2011 в 15:19

Такой метод уже находил. Но немного не похож на данный случай. Тут у меня есть static метод, который возвращает указатель на класс. Вызывая этот метод, я получаю доступ к приватным полям. Явное нарушение принципа инкапсуляции и стандарта плюсов или я чего-то недопонимаю.

28 дек 2011 в 15:29

class A < public: A(int _a) : a(_a) <>private: int a; >; class B < public: B(int _b) : b(_b) <>public: int b; >; int main(int argc, char *argv[]) < A a(0); ((B*)(&a))->b = 1; // A.a = 1 return 0; > 

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

Отслеживать
ответ дан 28 дек 2011 в 15:20
464 2 2 серебряных знака 12 12 бронзовых знаков
Это опять сводится к обращению по адресу. См. выше.
28 дек 2011 в 15:30

Ну ведь приватные поля и предназначены для того, чтобы к ним не было доступа помимо аксессоров / методов класса. То, о чём Вы спрашиваете — само по себе явное нарушение стандартов плюсов (насколько я понимаю :).

28 дек 2011 в 17:15

Проблема в том, что это каким-то образом реализовано в библиотеке на работе. Переписывать этот кусок под стандарт не дадут. Вот и пытаюсь понять, как реализовано. Человек, который это писал, уже не работает в том месте.

28 дек 2011 в 21:53

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

29 дек 2011 в 16:12
В данном случае, если в структуре один член, можно написать так: ((int)&a)=5;
30 дек 2011 в 7:16

Недавно у самого была похожая проблема. Изобрёл новый способ. Не знаю подойдёт он тебе или нет. Если у этого класса есть хотя бы один друг(неважно какой, доступный тебе в коде или нет) то ты можешь объявить свой класс с именем, что и имя друга. Пример:

В файле Graphics.h:

class Material; class Graphics < public: Material* CreateMaterial(Color color) < Material* material=new Material; material->color=color; return material; > >; 

В файле Material.h:

class Material < friend class Graphics; private: Color color; >; 

Допустим, у тебя был такой код и ты его не трогаешь. И тут вдруг мы в файле main.cpp объявляем самозванца Graphics:

#include "Material.h" class Graphics < static void ChangeMaterialColor(Material* material, Color newcolor) < material->color=newcolor; > >; 

Теперь мы можем менять цвет материала при помощи такого самозванца-друга. Единственный момент: два определения Graphics не должны быть объявленыподключены в один cpp-файл, иначе они будут конфликтовать. В моём примере это соблюдается: Material не подключает Graphics, а довольствуется его объявлением.

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

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