netlib.narod.ru< Назад | Оглавление | Далее >

Переходим к объектам

В большинстве традиционных процедурных языков, таких как Pascal, Fortran, BASIC, PL/I, С и COBOL, мир делится на код и данные. В основном на них пишут код для «перемалывания» данных.

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

Допустим, вы разрабатываете приложение, которое будет работать с датами, в частности, вычислять день года — порядковый номер дня в году. Скажем, для 2 февраля значение дня года равно 33, а для 31 декабря — 366, если год високосный, и 365, если нет. Наверно, вы сочтете разумным описать дату как единую сущность. Например, в С данные, относящиеся к дате, можно описать в структуре с тремя полями:

  struct Date
  {
      int year;
      int month;
      int day;
  };

Затем можно определить переменную типа Date:

  struct Date today;

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

  today.year = 2001;
  today.month = 8;
  today.day = 29;

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

  struct Date birthdate = { 1953, 2, 2 };

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

  int IsLeapYear(int year)
  {
      return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
  }

Ее использует функция вычисления дня года DayOfYear.

  int DayOfYear(struct Date date)
  {
      static int МonthDays[12] = {   0,  31,  59,  90, 120, 151,
                                   181, 212, 243, 273, 304, 334 };
      return MonthDays[date.month - 1] + date.day +
                        ((date.month > 2) && IsLeapYear(date.year));
  }

Обратите внимание, что функция обращается к полям входной структуры, указывая через точку имя поля.

Вот как выглядит полностью работоспособная версия структуры Date и относящихся к ней функций на языке С:

CDate.c

  //--------------------------------------
  // CDate.c (C) 2001 by Charles Petzold
  //--------------------------------------
  #include <stdio.h>

  struct Date
  {
      int year;
      int month;
      int day;
  };
  int IsLeapYear(int year)
  {
      return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
  }
  int DayOfYear(struct Date date)
  {
      static int МonthDays[12] = {   0,  31,  59,  90, 120, 151,
                                   181, 212, 243, 273, 304, 334 };
      return MonthDays[date.month - 1] + date.day +
                        ((date.month > 2) && IsLeapYear(date.year));
  }
  int main(void)
  {
      struct Date mydate;

      mydate.month = 8;
      mydate.day   = 29;
      mydate.year  = 2001;

      printf("Day of year = %f\n", DayOfYear(mydate));

      return 0;
  }

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

Программа на С имеет такой вид, потому что структуры языка С могут содержать только данные. Код и данные существуют отдельно друг от друга. Однако функции IsLeapYear и DayOfYear тесно связаны со структурой Date, поскольку они работают с полями структуры Date. Поэтому имеет смысл объединить эти функции с самой структурой Date. Перемещение функций внутрь структуры превращает программу на С в программу на C++. Вот эта программа на C++:

CppDateStruct.cpp

  //------------------------------------------------
  // CppDateStruct.cpp (C) 2001 by Charles Petzold
  //------------------------------------------------
  #include <stdio.h>

  struct Date
  {
      int year;
      int month;
      int day;

      int IsLeapYear()
      {
          return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
      }
      int DayOfYear()
      {
          static int МonthDays[12] = {   0,  31,  59,  90, 120, 151,
                                       181, 212, 243, 273, 304, 334 };
          return MonthDays[month - 1] + day + ((month > 2) && IsLeapYear());
      }
  };
  int main(void)
  {
      Date mydate;

      mydate.month = 8;
      mydate.day   = 29;
      mydate.year  = 2001;

      printf("Day of year = %f\n", mydate.DayOfYear());

      return 0;
  }

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

Кроме того, в описании переменной mydate функции main исчезло ключевое слово struct. Теперь тип Date выглядит, как обычный тип данных, a mydate — как переменная этого типа. На жаргоне объектно-ориентированного программирования mydate называется объектом типа Date или экземпляром Date. Иногда говорят (в основном любители громких слов), что создается экземпляр (instantiated) объекта Date.

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

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

Однако в самых передовых объектно-ориентированных языках элемент программы, содержащий код и данные, называется не структурой (struct), а классом (ключевое слово class). Для перехода от struct к class в C++ требуется добавить всего одну строку кода: ключевое слово public в начале описания новоиспеченного класса Date.

CppDateClass.cpp

  //------------------------------------------------
  // CppDateClass.cpp (C) 2001 by Charles Petzold
  //------------------------------------------------
  #include <stdio.h>

  class Date
  {
  public:
      int year;
      int month;
      int day;

      int IsLeapYear()
      {
          return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
      }
      int DayOfYear()
      {
          static int МonthDays[12] = {   0,  31,  59,  90, 120, 151,
                                       181, 212, 243, 273, 304, 334 };
          return MonthDays[month - 1] + day + ((month > 2) && IsLeapYear());
      }
  };
  int main(void)
  {
      Date mydate;

      mydate.month = 8;
      mydate.day   = 29;
      mydate.year  = 2001;

      printf("Day of year = %f\n", mydate.DayOfYear());

      return 0;
  }

В C++ и С# классы очень похожи на структуры. И в том, и в другом языке class — это не в точности то же самое, что и struct, но отличия class от struct в этих двух языках разные. Я рассмотрю отличия class и struct в С# в этой главе и подробнее — в главе 3. В C++ все поля и методы struct по умолчанию открытые (public), т.е. к ним можно обращаться из-за пределов структуры. Например, чтобы я мог обратиться к полям и методам Date из функции main, они должны быть открытыми. В классах языка C++ все поля и методы по умолчанию закрытые (private), и, чтобы сделать их открытыми, нужно использовать ключевое слово public.

Этот пример я выполнял в C++, а не в С#, так как C++ разрабатывался как язык, совместимый с С и позволяющий плавно перейти из мира С в мир C++. Теперь пора выполнить этот пример на С#.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz