Классы или структуры, в чем отличия

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

Структуры синтаксически очень похожи на классы, но существует принципиальное отличие, которое заключается в том, что класс – является ссылочным типом (reference type), а структуры – значимым типом (value type) (см. статью «Типы данных«). Следовательно, классы всегда создаются в так называемой “куче” (heap), а структуры создаются в стеке (stack).

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

Чем больше вы будете использовать структуры вместо маленьких классов, тем менее затратным по ресурсам будет использование памяти.


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

Мы выяснили, что все встроенные типы значений задаются структурами, например, числовые типы int, long, float определены структурами System.Int32, System.Int64 и System.Single соответственно. Эти структуры имеют поля и методы. Мы уже вызывали методы у переменных этих типов. Например, каждая из перечисленных структур имеет метод ToString( ). Также у перечисленных структур есть статичные поля, например, Int32.MaxValue или Int32.MinValue. Получается, что мы уже многократно использовали структуры.

В отличие от классов, использование публичных полей в структурах в большинстве случаев не рекомендуется, потому что не существует способа контролирования значений в них. Например, кто-либо может установить значение минут или секунд более 60. Более правильный вариант в данном случае использовать свойства, а в конструкторе осуществить проверку:

using System;
namespace ConsoleApplication1
{
   struct Time
   {
      private int hours, minutes, seconds;
      public Time(int hh, int mm, int ss)
      {
         hours = hh % 24;
         minutes = mm % 60;
         seconds = ss % 60;
      }
      public int Hours()
      {
         return hours;
      }
   }
   class Program
   {
      static void Main()
      {
         Time t = new Time(30,69,59);
         Console.WriteLine(t.Hours());
         Console.ReadKey();
      }
   }
}

В результате будет напечатано число часов:  6 (остаток от деления 30 на 24).
Заменим конструктор Time(…) конструктором без параметров:

public Time()
{
   hours = 7;
   minutes = 4;
   seconds = 0;
}

Получим сообщение об ошибке:
«Структуры не могут содержать явных конструкторов без параметров»

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

Что же касается класса, то компилятор создает конструктор по умолчанию только в том случае, если Вы его не создали. При объявлении класса нет проблем создать собственный конструктор без параметров (замените в программе ключевое слово struct на class, вы получите результат — 7).

Еще один эксперимент. Замените снова class на struct и удалите полностью конструктор Time(). Запустите программу, она будет выполнена, результат – 0. Вы получите три предупреждения типа:
«Полю «ConsoleApplication1.Time.hours» нигде не присваивается значение, поэтому оно всегда будет иметь значение по умолчанию 0».

Это означает, что был сгенерирован конструктор (без параметров) для структуры, который всегда устанавливает поля в 0, false или null (для объектов) – как и для классов. Поэтому Вы можете быть уверенными в том, что созданная структура всегда будет вести себя адекватно в соответствии со значениями по умолчанию в используемых типах.

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

Однако если в этом конструкторе не будет инициализировано какое-нибудь значение, компилятор не будет его инициализировать и покажет ошибку.

Два правила структур

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

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

 Сравнение классов и структур в сжатом виде:

Вопрос Структура Класс
Какого же типа экземпляр объекта? Значимый (value) тип Ссылочный (reference) тип
Где “живут” экземпляры этих объектов? Экземпляры структуры называют значениями и “живут” они в стеке (stack). Экземпляры классов называют объектами и “живут” они в куче (heap).
Можно ли создать конструктор по умолчанию? Нет Да
Если создается свой конструктор, будет ли компилятор генерировать конструктор по умолчанию? Да Нет
Если в своём конструкторе не будут инициализированы некоторые поля, будут ли они автоматически инициализированы компилятором? Нет Да
Разрешается ли инициализировать переменные там, где их объявляют? Нет ДА

Поля структуры могут быть инициализированы при использовании конструктора (если объект объявляется с помощью оператора new), причем не важно, какого «собственного» или «по умолчанию».

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

using System;
namespace ConsoleApplication1
{
   struct Time
   {
      public int hours, minutes, seconds;
   }
   class Program
   {
      static void Main()
      {
         Time t;
         t.hours=7;
         Console.WriteLine(t.hours.ToString());
         Console.ReadKey();
      }
   }
}

В таком случае, переменная t создается, но поля не будут инициализированы конструктором (нет оператора Time t = new Time();). Правда, теперь поля структуры придется объявлять только с модификатором public.

Замечания, над которыми стоит подумать (проверить практикой):

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

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

3. Главное отличие структур и классов: структуры передаются по значению (то есть копируются), объекты классов — по ссылке. Именно это является существенным различием в их поведении, а не то, где они хранятся.

4. Структуру тоже можно передать по ссылке, используя модификаторы out и ref.

Примечание. Как получить размер объекта в .Net.
В .Net оператор sizeof(тип) умеет возвращать размеры только простых значимых типов. При попытке направить его на любой управляемый в куче объект вы получите ошибку. Единственный более-менее достоверный способ узнать объем памяти, занимаемый объектом, это сравнение размеров используемой памяти до инициализации объекта-массива и после, например:

long mem = GC.GetTotalMemory(true);
 int[] intArray = new int[100000];
 mem = GC.GetTotalMemory(true) - mem;
 // В mem - размер объекта в байтах: 4 х 100000 + 16 (?).

Ответ: 400016 байт. Конец примечания.

 

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

Оставьте комментарий

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

Пролистать наверх