Общий взгляд
Знакомство с новым языком программирования разумно начинать с изучения системы типов этого языка. Как в нем устроена система типов данных? Какие есть простые типы, как создаются сложные, структурные типы, как определяются собственные типы, динамические типы, как определяются классы?
В первых языках программирования понятие класса отсутствовало - рассматривались только типы данных. При определении типа явно задавалось только множество возможных значений, которые могут принимать переменные этого типа. Например, тип integer задает целые числа в некотором диапазоне. Неявно с типом всегда связывался и набор разрешенных операций. В типизированных языках, к которым относится большинство языков программирования, понятие переменной естественным образом связывалось с типом. Если есть тип Т и переменная x типа Т, то это означало, что переменная может принимать значения из множества, заданного типом, и к ней применимы операции, разрешенные типом.
Классы и объекты впервые появились в программировании в языке Симула 67. Произошло это спустя 10 лет после появления первого алгоритмического языка Фортран. Определение класса наряду с описанием данных содержало четкое определение операций или методов, применимых к данным. Объекты - экземпляры класса являются обобщением понятия переменной. Сегодня определение класса в C# и других объектных языках, аналогично определению типа в CTS, содержит:
- данные, задающие свойства объектов класса;
- методы, определяющие поведение объектов класса;
- события, которые могут происходить с объектами класса.
Так есть ли различие между этими двумя основополагающими понятиями - типом и классом? На первых порах можно считать, что класс - это хорошо определенный тип данных, объект - хорошо определенная переменная. Понятия фактически являются синонимами, какое из них употреблять лишь дело вкуса. Встроенные типы, такие как integer или string, предпочитают называть по-прежнему типами, а их экземпляры - переменными. Что же касается абстракции данных, описывающей служащих и названной, например, Employee, то естественнее называть ее классом, а ее экземпляры - объектами. Такой взгляд на типы и классы довольно полезен, но он не является полным. Позже при обсуждении классов и наследования постараемся более четко определить принципиальные различия в этих понятиях.
Объектно-ориентированное программирование, доминирующее сегодня, построено на классах и объектах. Тем не менее, понятия типа и переменной все еще остаются центральными при описании языков программирования, что характерно и для языка C#. Заметьте, что и в Framework .Net предпочитают говорить о системе типов, хотя все типы библиотеки FCL являются классами.
Типы данных принято разделять на простые и сложные в зависимости от того, как устроены их данные. У простых (скалярных) типов возможные значения данных едины и неделимы. Сложные типы характеризуются способом структуризации данных - одно значение сложного типа состоит из множества значений данных, организующих сложный тип.
Есть и другие критерии классификации типов. Так, типы разделяются на встроенные типы и типы, определенные программистом (пользователем). Встроенные типы изначально принадлежат языку программирования и составляют его базис. В основе системы типов любого языка программирования всегда лежит базисная система типов, встроенных в язык. На их основе программист может строить собственные, им самим определенные типы данных. Но способы (правила) создания таких типов являются базисными, встроенными в язык.
Типы данных разделяются также на статические и динамические. Для данных статического типа память отводится в момент объявления, требуемый размер данных (памяти) известен при их объявлении. Для данных динамического типа размер данных в момент объявления обычно неизвестен и память им выделяется динамически по запросу в процессе выполнения программы.
Еще одна важная классификация типов - это их деление на значимые и ссылочные. Для значимых типов значение переменной (объекта) является неотъемлемой собственностью переменной (точнее, собственностью является память, отводимая значению, а само значение может изменяться). Для ссылочных типов значением служит ссылка на некоторый объект в памяти, расположенный обычно в динамической памяти - "куче". Объект, на который указывает ссылка, может быть разделяемым. Это означает, что несколько ссылочных переменных могут указывать на один и тот же объект и разделять его значения. Значимый тип принято называть развернутым, подчеркивая тем самым, что значение объекта развернуто непосредственно в памяти, отводимой объекту. О ссылочных и значимых типах еще предстоит обстоятельный разговор.
Для большинства процедурных языков, реально используемых программистами - Паскаль, C++, Java, Visual Basic, C#, - система встроенных типов более или менее одинакова. Всегда в языке присутствуют арифметический, логический (булев), символьный типы. Арифметический тип всегда разбивается на подтипы. Всегда допускается организация данных в виде массивов и записей (структур). Внутри арифметического типа всегда допускаются преобразования, всегда есть функции, преобразующие строку в число и обратно. Так что, мой читатель, Ваше знание, по крайней мере, одного из процедурных языков позволяет построить общую картину системы типов и для языка C#. Отличия будут в нюансах, которые и придают аромат и неповторимость языку.
Поскольку язык C# является непосредственным потомком языка C++, то и системы типов этих двух языков близки и совпадают вплоть до названия типов и областей их определения. Но отличия, в том числе принципиального характера, есть и здесь.