Основы C#: часть II…

Классы и структуры

У класса две различные роли: модуля и типа данных. Класс — это модуль, архитектурная единица построения программной системы. Модульность построения — основное свойство программных систем. Модуль может не представлять собой содержательную единицу — его размер и содержание определяется архитектурными соображениями, а не семантическими.
Вторая роль класса не менее важна. Класс — это тип данных, задающий реализацию некоторой абстракции данных, характерной для задачи, в интересах которой создается программная система. С этих позиций классы — не просто кирпичики, из которых строится система. Каждый кирпичик теперь имеет важную содержательную начинку.

В теле класса могут быть объявлены:
• константы;
• поля;
• конструкторы и деструкторы;
• методы;
• события;
• делегаты;
• классы (структуры, интерфейсы, перечисления).

C# является языком объектно-ориентированного программирования, поэтому классы играют в нем основополагающую роль. Более того, все типы данных C#, как встроенные, так и определенные пользователем, порождены от базового класса object. Иными словами, в отличие от Java, где примитивные типы данных отделены от объектных типов, все типы данных в C# являются классами и могут быть разделены на две группы:

ссылочные (reference types);
обычные (value types).

Внешне ссылочные и обычные типы очень похожи, так как аналогично Cи++ в них можно объявлять конструкторы, поля, методы, операторы и т.д. Однако, в отличие от Cи++, обычные типы в C# не позволяют определять классы и не поддерживают наследования. Они описываются с помощью ключевого слова struct и в основном используются для создания небольших объектов. Ссылочные же типы описываются с помощью ключевого слова class и являются указателями, а экземпляры таких типов ссылаются на объект, находящийся в куче (heap). Продемонстрируем сказанное на примере:

using System;

class CValue
{

public int val;
public CValue(int x) {val = x;}
}

class Example_1
{
public static void Main()
{
CValue p1 = new CValue(1);
CValue p2 = p1;
Console.WriteLine(”p1 = {0}, p2 = {1}”,
p1.val, p2.val);
p2.val = 2;
Console.WriteLine(”p1 = {0}, p2 = {1}”,
p1.val, p2.val);
}
}

Откомпилировав и выполнив программу, получим следующий результат:
p1 = 1, p2 = 1
p1 = 2, p2 = 2

Как нетрудно видеть, p2 является всего лишь ссылкой на p1. Тем самым становится очевидно, что при изменении поля val экземпляра класса p2 в действительности изменяется значение соответствующего поля p1. Подобный подход не очень удобен при работе с примитивными типами данных, которые должны содержать само значение, а не ссылку на него (Complex, Point, Rect, FileInfo и т.д.). Для описания таких объектов и предназначены типы значений:

using System;
struct SValue
{
public int val;
public SValue(int x) {val = x;}
}
class Example_2
{
public static void Main()
{
SValue p1 = new SValue(1);
SValue p2 = p1;
Console.WriteLine(”p1 = {0}, p2 = {1}”,
p1.val, p2.val);
p2.val = 2;
Console.WriteLine(”p1 = {0}, p2 = {1}”,
p1.val, p2.val);
}
}

Вот что получится после запуска вышеприведенной программы:
p1 = 1, p2 = 1
p1 = 1, p2 = 2

Из этого следует, что экземпляр класса p2 является самостоятельным объектом, который содержит собственное поле val, не связанное с p1. Использование обычных типов позволяет избежать дополнительного расходования памяти, поскольку не создаются дополнительные ссылки, как в случае с экземплярами классов. Конечно, экономия невелика, если у вас имеется всего несколько небольших объектов типа Complex или Point. Зато для массива, содержащего несколько тысяч таких элементов, картина может в корне измениться. В таблице приведены основные отличия типов class и struct:

  Тип class Тип struct
Представление экземпляра типа указатель значение
Местоположение объекта куча стек
Значение по умолчанию null заполняется нулями
Результат операции присваивания для экземпляров типа копируется указатель копируется сам объект
Базовый тип встроенный тип string встроенный тип int

продолжение следует…

Опубликовано в CSharp.

Ответить