Итак, идем дальше…
…теперь мы находимся в разделе Классы и объекты. Они еще не затронули ничего, связанного с классами, но мы их видели. Хотя они говорили об использовании отдельных переменных типа данных, которые могут содержать одиночные значения, классы описываются следующим образом:
Тип данных, определяющий набор переменных и методов для объявленного объекта.
Мне очень нравится, как они объясняют, что такое класс: у вас может быть класс с именем BankAccount, который имеет свойства и методы для управления отдельным банковским счетом (т.е. балансом, депозитом , изъятие и др.). Они также продолжают говорить (и мы видели это раньше):
Класс подобен чертежу. Он определяет данные и поведение типа.
Для начала они заявляют, что определение класса начинается с ключевого слова class, за которым следует имя этого класса, например:
class BankAccount { // variables, methods, etc. }
Последний фрагмент в первом уроке этого раздела также очень важен, поскольку он не просто оставляет слово object без объяснения причин. Хотя сам класс не является объектом, он определяет тип данных для объекта. Объект — это «вещь», основанная на классе. Когда вы создаете объект, его иногда называют экземпляром класса.
Они продолжают говорить об объектах, связывая их с тем, как вы можете «объявлять» переменные, заявляя, что вы можете «объявлять» объекты (или отдельные «экземпляры» класса). Затем они связывают это с тем, чем занимается «архитектор» и как они работают с чертежами. Вероятно, пришло время обновить эту аналогию.
Так или иначе, архитектор готовит проект всех аспектов нового здания, используя чертежи (или, как они это называют, чертежи). Эти чертежи, в свою очередь, можно использовать (теоретически) снова и снова для создания одного и того же здания. Однако мне нравится, как они относятся к программированию:
Мы определяем (или разрабатываем) класс, который является образцом для создания объектов.
Они также относятся к термину тип, который используется для обозначения имени класса, например, мы создаем объект определенного типа. Итак, как только мы написали класс, мы можем создавать объекты на его основе. Создание объекта называется созданием экземпляра. Таким образом, объект называется экземпляром класса.
Идя дальше с объектами, они обсуждают, как объект имеет свои собственные характеристики. Каждый объект имеет свой собственный набор значений (например, у человека есть имя, возраст, пол и т. д.), который отличает их от другого объекта того же типа (т.е. другого человека). Эти характеристики называются свойствами. Эти свойства описывают текущее состояние объекта.
Хранение данных по ссылке или значению
Как было показано ранее, существует два способа хранения данных: по ссылке и по значению. Я неоднократно упоминал типы данных (например, int и double) и то, как они используются для объявления переменных, которые являются типами значений. Значение этих переменных хранится в памяти в месте, которое называется стек. Описание стека в предыдущей статье при попытке понять рекурсию похоже на ту же концепцию. Итак, когда мы видим, например, int x = 10; значение переменной x теперь хранится в стеке.
Типы ссылок используются для хранения объектов (а не значений). Например, мы говорили о создании экземпляра/объекта класса. Этот объект сохраняется как ссылочный тип. В то время как типы значений хранятся в стеке, ссылочные типы хранятся в части памяти, называемой кучей.
Пытаясь описать это дальше, приложение SoloLearn объясняет, что при создании экземпляра объекта данные для этого объекта сохраняются в куче. Его адрес памяти хранится в стеке. Затем он продолжает описывать это следующим образом:
Вот почему он называется ссылочным типом — он содержит ссылку (адрес памяти) на фактический объект в куче. Как видите, объект p1 типа Person в стеке хранит адрес памяти кучи, где хранится фактический объект.
Стек используется для выделения статической памяти, которая включает все ваши типы значений, например x.
Куча используется для динамического выделения памяти, включая настраиваемые объекты, которым может потребоваться дополнительная память во время выполнения вашей программы.
Это кажется важным, и я не совсем уверен, что понимаю. Итак, вернемся к C-Sharp Corner и сообщению под названием C# Heap(ing) vs Stack(ing) in .NET: Part I и далее, действительно понятное объяснение:
Стек более или менее отвечает за отслеживание того, что выполняется в нашем коде (или что «вызывается»). Куча более или менее отвечает за отслеживание наших объектов (наших данных, ну… большей части; мы вернемся к этому позже).
Думайте о стеке как о наборе коробок, поставленных одна на другую. Мы отслеживаем, что происходит в нашем приложении, помещая еще один блок сверху каждый раз, когда мы вызываем метод (называемый фреймом). Мы можем использовать только то, что находится в верхнем поле в стеке. Когда мы закончим с верхним блоком (метод завершится выполнением), мы выбрасываем его и продолжаем использовать материал из предыдущего блока на вершине стека.
Куча аналогична, за исключением того, что ее целью является хранение информации (а не отслеживание выполнения большую часть времени), поэтому все в нашей куче может быть доступно в любое время. При использовании кучи нет ограничений на то, к чему можно получить доступ, как в стеке. Куча похожа на кучу чистого белья на нашей кровати, которую мы еще не успели убрать; мы можем получить то, что нам нужно быстро.
Я думаю, что на данный момент это все, что приложение SoloLearn могло бы описать стек и куча, поэтому я возьму предыдущее определение как «достаточно информации». сейчас".
Создание класса
Следующий процесс, через который мы должны пройти, — это создание реального класса. Как и раньше, я собираюсь сделать это непосредственно в Visual Studio, создав новое консольное приложение с именем ClassExample. Хотя он уже начинается с класса с именем Program, я собираюсь создать еще один класс под ним со следующим кодом:
class Person { int age; string name; public void SayHi() { Console.WriteLine(“Hi.”); } }
Итак, этот класс называется Person, он имеет два поля с именами age и name, а также имеет общедоступный (что означает вне класса) с именем SayHi.
Теперь, когда у нас есть этот класс, мы можем создать экземпляр объекта (или создать экземпляр) этого типа в методе Main. Это делается с помощью оператора new. Итак, чтобы создать его, мы используем следующий код в методе Main:
Person p1 = new Person(); p1.SayHi();
В переводе на английский язык первая строка означает создать новый объект Person с именем p1. Теперь объект с именем p1 обладает всеми функциями класса Person. Таким образом, чтобы вызвать любой открытый член класса (например, метод с именем SayHi), вы вводите имя объекта, за которым следует оператор с точкой (.), а затем имя метода, который вы хотите вызвать (как вы можете видеть во второй строке кода выше. Просто чтобы вы знали, вы можете получить доступ не только к методам внутри класса, но эти элементы по-прежнему должны быть общедоступными, как в следующем примере:
class Dog { public string name; public int age; } static void Main(string[] args) { Dog bob = new Dog(); bob.name = “Bobby”; bob.age = 3; Console.WriteLine(Bob.age); }
Вот что происходит, когда этот код выполняется:
- Во-первых, объявляется новый класс с именем Dog с двумя общедоступными переменными: name (это строка) и age (это целое число).
- Затем в методе Main создается новый экземпляр класса с именем Dog и называется bob.
- Теперь, когда этот новый объект с именем bob существует, он может получить доступ к любому из общедоступных членов класса Dog. Во-первых, он обращается к переменной с именем name и устанавливает для нее значение Bobby. Затем он обращается к переменной с именем age и устанавливает для нее значение 3.
- Наконец, он записывает значение переменной age в объект с именем Bob, который является экземпляром класса с именем Dog, т.е. 3.
Модификаторы инкапсуляции и доступа
Далее мы подходим к другому программному слову: инкапсуляция. Это процесс объединения членов вместе внутри класса, а также потенциального ограничения доступа к этим членам только внутренней работой класса. Это реализовано с помощью модификаторов доступа, как мы видели ранее (например, public).
Модификатор доступа public делает член доступным за пределами класса. И наоборот, модификатор доступа private доступен только внутри класса. Эти два модификатора доступа используются в следующем коде:
class BankAccount { private double balance=0; public void Deposit(double n) { balance += n; } public void Withdaw(double n) { balance -= n; } public double GetBalance() { return balance; } }
В приведенном выше коде элемент balance доступен только внутри класса, поскольку для его модификатора доступа задано значение private. Итак, как видите, три метода, называемые Deposit, Withdraw и GetBalance (все они общедоступны, и поэтому доступны за пределами класса) имеют доступ к элементу balance. К члену balance невозможно получить доступ из-за пределов класса BankAccount. Таким образом, в действительности доступ к элементу баланса известен как ограниченный в том смысле, что его можно прочитать через GetBalance. > и модифицировать с помощью методов Deposit и Withdraw.
Конструкторы
Далее идет концепция, которая будет много обсуждаться в рамках обучения Revit API: класс конструкторы. Это:
Специальный метод-член класса, который выполняется всякий раз, когда создается новый объект этого класса. конструктор имеет то же имя, что и его класс, является общедоступным и не имеет возвращаемого типа.
class Person { private int age; public Person() { Console.WriteLine(“Hi there”); } }
В приведенном выше коде строки, выделенные жирным шрифтом, представляют метод-член конструктора класса с именем Person. После создания экземпляра объекта типа Person автоматически вызывается конструктор, например:
static void Main(string[] args) { Person p = new Person(); }
Сначала я не понимал, почему это было полезно, и последняя часть о том, что он вызывается автоматически, действительно не бросилась мне в глаза. Причина, по которой этот конструктор полезен, заключается в том, что при вызове класса этот специальный метод вызывается автоматически и немедленно, поэтому вам не нужно этого делать. Итак, представьте, что при создании нового банковского счета мы хотели, чтобы владельцу было отправлено уведомление по электронной почте. Хотя это можно сделать с помощью отдельного общедоступного метода, это не произойдет автоматически, как с конструктором.
Далее объясняется еще одно преимущество конструкторов, заключающееся в том, что вы можете использовать их для установки начальных значений для определенных переменных. Мне пришлось прочитать следующий код несколько раз, чтобы точно понять, что он делает, так что продолжайте читать:
class Person { private int age; private string name; public Person(string nm) { name = nm; } public string getName() { return name; } } static void Main(string[] args) { Person p = new Person(“David”); Console.WriteLine(p.getName()); }
Опять же, в приведенном выше коде конструктор выделен жирным шрифтом. Вот что происходит:
- Во-первых, создается класс Person с двумя личными (имеется в виду доступными только напрямую внутри класса) переменными: на основе целого числа с именем age и на основе строки с именем name.
- Он также содержит метод открытого конструктора с именем Person, в котором объявлена основанная на строке переменная с именем nm. Внутри него значение, которое передается этому методу при вызове и которое было присвоено переменной nm, присваивается основанной на строке переменной name. В результате к частной строковой переменной nm теперь можно получить косвенный доступ.
- Наконец, он также содержит метод с именем getName, который возвращает значение основанной на строке переменной name.
- В методе Main создается новый экземпляр класса Person, которому передается значение «David». В результате того, что было описано во втором пункте списка, основанная на строке переменная nm теперь получает и присваивается значение "David". и, когда выполняется метод-конструктор, это значение присваивается строковой переменной name (к которой, опять же, теперь косвенно обращаются ).
- Затем внутри объекта с именем p (который является экземпляром класса с именем Person) вызывается метод getName, который затем возвращает текущее значение переменной name (то есть «David»), которое затем выводится на экран.
Для меня это совершенно логично, и поэтому я довольно хорошо это понимаю. Вспоминая концепцию перегрузки, описанную в Части 5с, как и любой другой метод, это можно применить и к методам-конструкторам. Напоминаем, что перегрузка — это когда существует несколько методов с одинаковыми именами, и в зависимости от типа отправляемой переменной вызывается соответствующая версия метода.
Характеристики
Я мог бы закончить эту статью здесь и поговорить о последнем разделе, Свойства, в следующем, но ядвигаюсь прямо сейчас, так что статья также .
Приложение SoloLearn ссылается на инкапсуляцию отдельных членов класса и возможность предоставления доступа к ним только с помощью общедоступных методов. Однако свойство является членом класса, который имеет более гибкий способ чтения, записи в или используетсядля вычисления значения частного поля. Эти свойства используются так, как если бы они были общедоступными, но они включают специальные методы, называемые средствами доступа.
Я уже смотрю на пример кода со ссылками на get и set, который я видел раньше, но так и не понял. Get и set — это ранее упомянутые «специальные методы доступа».
Это исполняемые операторы, которые помогают в получении (чтении или вычислении) или настройке. (запись) в соответствующее поле.
Стоит отметить, что объявление этих методов доступа может включать get или set или оба, как в следующем коде:
class Person { private string name; this is the field public string Name { this is the property get { return name; } set { name = value; } } }
Как вы можете видеть в приведенном выше коде, объявляется частно доступный член на основе строки с именем name, и это поле. общедоступный элемент на основе строки с именем Имя (обратите внимание на заглавную букву «N») — это свойство, так как оно имеет вышеупомянутые специальные методы доступа, называемые get и set. В этом коде метод get возвращает текущее значение частно доступного члена на основе строки с именем name (обратите внимание на нижний регистр «n»), таким образом получая доступ к нему косвенно. Что касается именования в верхнем и нижнем регистре, обычное соглашение о кодировании состоит в том, чтобы имя свойства совпадало с именем частного поля, но с заглавной буквой.
Внутри свойства, на первый взгляд, я бы сказал, что метод доступа set устанавливает значение члена name в любое текущее значение переменной value равно. Однако приложение описывает ключевое слово value как специальное ключевое слово, которое представляет значение, которое мы присваиваем свойству с помощью метода доступа set. Я могу Не сказать, что я полностью понимаю эту концепцию.
На Dot Net Perls есть пост, в котором также говорится:
С параметрами в свойстве мы можем использовать специальное ключевое слово «значение». Таким образом, нам не нужно указывать тип. Тип определяется окружающим типом свойства.
Другими словами, поскольку типом свойства является string, каким бы ни было значение аргумента, который отправляется члену (называемому Имя), оно должно быть также строкой. Похоже, это еще одна «эффективность». Дело в том, что нам не нужно указывать тип во второй раз.
Меня немного смутил порядок этих двух методов доступа, а также то, что каждый из них написал в фигурных скобках. Кажется, что они должны быть противоположными и иметь другую кодировку, но я думаю интерпретировать это следующим образом:
- Свойство Name сначала должно «получить» свое значение, и оно делает это, возвращаясь к нему, к самому свойству с именем name.
- Затем, поскольку теперь он содержит свойство с именем name, он присваивает ему любое значение на основе string, которое оно получает (предположительно, когда объект типа Person создается экземпляр.
Я не уверен, что это точно, и был бы признателен за некоторые отзывы. Однако из-за этой неопределенности я решил немного погуглить…
На сайте Полный материал по C# есть сообщение под названием C# Get Set Modifier. Они объясняют методы доступа get и set очень просто и понятно:
…метод get используется для извлечения значения из закрытого поля, тогда как метод set используется для сохранения значения в закрытых переменных.
Затем это приводит к следующему коду:
class Access { private string name; public print() { Console.WriteLine(“\nMy name is “ + name); } public string Name { get { return name; } set { name = value; } } } static void Main(string[] args) { Access ac = new Access(); Console.Write(“Enter your name:\t”); ac.Name = Console.ReadLine(); ac.print(); Console.ReadLine(); }
Итак, вот что происходит:
- Объявляется класс с именем Access. Внутри него создается частная переменная на основе строки с именем name.
- Метод объявляется в классе Access с именем print. Вместе с полученным значением переменной с именем name на экран будет выведено «Меня зовут», а затем вставлено это значение.
- Метод-конструктор объявлен в классе Access с именем Name. Он будет автоматически вызываться при создании экземпляра объекта типа Access и сначала получит значение, которое будет использоваться для основанной на строке переменной с именем name. Затем он установит эту переменную name равной полученному значению.
- В методе Main сначала создается экземпляр нового объекта типа Access с именем ac, что дает ему доступ ко всем членам класса Access.
- Затем он выводит на экран «Введите свое имя:».
- После этого пользователь вводит свое имя, и выполняется метод конструктора с именем Name. Он получает отправленное ему значение (т. е. имя пользователя), а затем задает его как значение для переменной name.
- Затем вызывается метод print, который выводит на экран «Меня зовут:», а затем вставляет значение, присвоенное переменной name.
- Console.ReadLine(); просто оставляет окно открытым на экране.
Теперь я довольно хорошо понимаю, как вызывается каждый метод, а также как извлекаются значения, устанавливаются в качестве значений переменных и затем возвращаются.
Как упоминалось ранее, вам не нужно включать обаget и set. Если, например, не указывать set, свойство становится доступным только для чтения, как показано ниже:
class Person { private string name; public string Name { get { return name; } } }
Они также отмечают, что свойство также может быть частным, ограничивая его вызов только внутри класса. Также задается соответствующий вопрос: зачем вообще использовать свойства, а не просто объявить переменную-член как public, а затем получить к ней прямой доступ? Их ответ:
С помощью свойств у вас есть возможность управлять логикой доступа к переменной.
Другими словами, вы можете оценить определенное условие, чтобы решить, следует ли присваивать проверяемое значение самой переменной. Как в следующем:
class Person { private int age; public int Age { get { return age; } set { if (value > 0) age = value; } } }
При выполнении свойство Age будет только присваивать полученное значение переменной age, если оно равно больше 0.
Однако, когда вам не нужна какая-либо пользовательская логика, можно использовать эффективность. Вместо того, чтобы писать следующее:
public string Name { get { return name; } set { name = value; } }
Вы можете просто написать:
public string Name { get; set; }
Это, очевидно, короче и быстрее написать. Еще одна вещь, которую вам не нужно делать, — это объявлять имя частного поля отдельно. Закрытое поле с именем имя создается свойством автоматически. Поэтому свойство на самом деле называется автоматически реализуемым свойством или автоматическим свойством.
На этом мы подошли к концу довольно длинного поста. Однако я рад, что прошел все это одним выстрелом, а не разбил его на 3 или 4 части. Буду очень признателен за любые исправления или разъяснения.
Кроме того, вот ссылка на предыдущую статью Learning to Code — Part 5d: Making a Pyramid.