Опубликован: 15.09.2010 | Доступ: свободный | Студентов: 4808 / 630 | Оценка: 3.97 / 3.80 | Длительность: 14:45:00
Лекция 5:

Классы: основные понятия

< Лекция 4 || Лекция 5: 1234 || Лекция 6 >

Ключевое слово this

Каждый объект содержит свой экземпляр полей класса. Методы находятся в памяти в единственном экземпляре и используются всеми объектами совместно, поэтому необходимо обеспечить работу методов нестатических экземпляров с полями именно того объекта, для которого они были вызваны. Для этого в любой нестатический метод автоматически передается скрытый параметр this, в котором хранится ссылка на вызвавший функцию экземпляр. Этот процесс иллюстрирует рис. 5.5.

Передача методу скрытого параметра this

Рис. 5.5. Передача методу скрытого параметра this

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

class Demo
    {
        double y;
        public Demo T()         // метод возвращает ссылку на экземпляр
        {
            return this; 
        }
        public void Sety( double y )
        {
            this.y = y;         // полю y присваивается значение параметра y
        }
    }

Конструкторы

Конструктор предназначен для инициализации объекта. Он вызывается автоматически при создании объекта класса с помощью операции new. Имя конструктора совпадает с именем класса. Ниже перечислены свойства конструкторов

  • Конструктор не возвращает значение, даже типа void.
  • Класс может иметь несколько конструкторов с разными параметрами для разных видов инициализации.
  • Если программист не указал ни одного конструктора или какие-то поля не были инициализированы, полям значимых типов присваивается нуль, полям ссылочных типов — значение null.
  • Конструктор, вызываемый без параметров, называется конструктором по умолчанию.

До сих пор мы задавали начальные значения полей класса при описании класса. Это удобно в том случае, когда для всех экземпляров класса начальные значения некоторого поля одинаковы. Если же при создании объектов требуется присваивать полю разные значения, это следует делать в конструкторе. В листинге 5.6 в класс Demo добавлен конструктор, а поля сделаны закрытыми.

using System;
namespace ConsoleApplication1
{   
    class Demo
    {
        public Demo( int a, double y )          // конструктор с параметрами
        {
            this.a = a;
            this.y = y;
        }

        public double Gety()                    // метод получения поля y
        {
            return y; 
        }

        int a;   
        double y;
    }
    
    class Class1
    {   static void Main()
        {
            Demo a = new Demo( 300, 0.002 );     // вызов конструктора
            Console.WriteLine( a.Gety() );       // результат: 0,002
            Demo b = new Demo( 1, 5.71 );        // вызов конструктора
            Console.WriteLine( b.Gety() );       // результат: 5,71
        }
    }
}
Листинг 5.6. Класс с конструктором

Часто бывает удобно задать в классе несколько конструкторов, чтобы обеспечить возможность инициализации объектов разными способами. Все конструкторы должны иметь разные сигнатуры.

Если один из конструкторов выполняет какие-либо действия, а другой должен делать то же самое плюс еще что-нибудь, удобно вызвать первый конструктор из второго. Для этого используется уже известное вам ключевое слово this в другом контексте, например:

class Demo
    {
        public Demo( int a )                         // конструктор 1
        {
            this.a = a;
        }
        public Demo( int a, double y ) : this( a )   // вызов конструктора 1
        {
            this.y = y;
        }
        ...
    }

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

Как вы помните, все классы в C# имеют общего предка — класс object. Конструктор любого класса, если не указан инициализатор, автоматически вызывает конструктор своего предка.

До сих пор речь шла об "обычных" конструкторах, или конструкторах экземпляра. Существует второй тип конструкторов — статические конструкторы, или конструкторы класса. Конструктор экземпляра инициализирует данные экземпляра, конструктор класса — данные класса.

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

Некоторые классы содержат только статические данные и, следовательно, создавать экземпляры таких объектов не имеет смысла. В версию 2.0 введена возможность описывать статический класс, то есть класс с модификатором static. Экземпляры такого класса создавать запрещено, и кроме того, от него запрещено наследовать. Все элементы такого класса должны явным образом объявляться с модификатором static (константы и вложенные типы классифицируются как статические элементы автоматически). В листинге 5.7 приведен пример статического класса.

using System;
namespace ConsoleApplication1
{
    static class D
    {
        static int a = 200;
        static double b = 0.002;

        public static void Print ()
        {
            Console.WriteLine( "a = " + a );
            Console.WriteLine( "b = " + b );
        }
    }

    class Class1
    {   static void Main()
        {
            D.Print();
        }
    }
}
Листинг 5.7. Статический класс (начиная с версии 2.0)

В качестве "сквозного" примера, на котором будет демонстрироваться работа с различными элементами класса, создадим класс, моделирующий персонажа компьютерной игры. Для этого требуется задать его свойства (например, количество щупальцев или наличие гранатомета) и поведение.

using System;
namespace ConsoleApplication1
{
    class Monster
    {
        public Monster()
        {
            this.name   = "Noname";
            this.health = 100;
            this.ammo   = 100;
        }

        public Monster( string name ) : this()
        {
            this.name = name;
        }

        public Monster( int health, int ammo, string name )
        {
            this.name   = name;
            this.health = health;
            this.ammo   = ammo;
        }

        public string GetName()
        {
            return name;
        }

        public int GetHealth()
        {
            return health;
        }

        public int GetAmmo() 
        {
            return ammo; 
        }

        public void Passport()
        {
            Console.WriteLine( "Monster {0} \t health = {1} ammo = {2}", 
                              name, health, ammo );
        }

        string name;                // закрытые поля
        int health, ammo;
    }

    class Class1
    {
        static void Main()
        {
            Monster X = new Monster();
            X.Passport();
            Monster Vasia = new Monster( "Vasia" );
            Vasia.Passport();
            Monster Masha = new Monster( 200, 200, "Masha" );
            Masha.Passport();
        }
    }
}
Листинг 5.8. Класс Monster

Результат работы программы:

Monster Noname   health = 100 ammo = 100
Monster Vasia    health = 100 ammo = 100
Monster Masha    health = 200 ammo = 200

Свойства

Свойства служат для организации доступа к полям класса. Как правило, свойство связано с закрытым полем класса и определяет методы его получения и установки. Синтаксис свойства:

[ атрибуты ] [ спецификаторы ] тип имя_свойства
{
    [ get код_доступа ]
    [ set код_доступа ]
}

Значения спецификаторов для свойств и методов аналогичны. Чаще всего свойства объявляются со спецификатором public. Код доступа представляет собой блоки операторов, которые выполняются при получении ( get ) или установке ( set ) свойства. Может отсутствовать либо часть get, либо set, но не обе одновременно.

Если отсутствует часть set, свойство доступно только для чтения (read-only), если отсутствует часть get, свойство доступно только для записи (write-only). В версии C# 2.0 введена возможность задавать разный уровень доступа для частей get и set.

Пример описания свойств:

public class Button: Control  
{
    private string caption;     // закрытое поле, с которым связано свойство 
    public string Caption {     // свойство
       get {                    // способ получения свойства
          return caption;
       }
       set {                      // способ установки свойства
          if (caption != value) {
             caption = value;
          }
       }
    }
    ...
}

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

В программе свойство выглядит как поле класса, например:

Button ok = new Button();
ok.Caption = "OK";              // вызывается метод установки свойства  
string s = ok.Caption;          // вызывается метод получения свойства

При обращении к свойству автоматически вызываются указанные в нем методы чтения и установки.

Синтаксически чтение и запись свойства выглядят почти как методы. Метод get должен содержать оператор return. В методе set используется параметр со стандартным именем value, который содержит устанавливаемое значение.

Добавим в класс Monster, описанный в листинге 5.8, свойства, позволяющие работать с закрытыми полями этого класса. Код класса несколько разрастется, зато упростится его использование.

using System;
namespace ConsoleApplication1
{
    class Monster
    {   public Monster()
        {
            this.health = 100;
            this.ammo   = 100;
            this.name   = "Noname";
        }

        public Monster( string name ) : this()
        {
            this.name = name;
        }

        public Monster( int health, int ammo, string name )
        {
            this.health = health;
            this.ammo   = ammo;
            this.name   = name;
        }

        public int Health          // свойство Health связано с полем health
        {
            get 
            {
                return health;
            }
            set 
            {
                if (value > 0) health = value;
                else           health = 0;
            }
        }

        public int Ammo                // свойство Ammo связано с полем ammo
        {
            get 
            {
                return ammo;
            }
            set 
            {
                if (value > 0) ammo = value;
                else           ammo = 0;
            }
        }

        public string Name             // свойство Name связано с полем name
        {
            get 
            {
                return name;
            }
        }

        public void Passport()
        {
            Console.WriteLine("Monster {0} \t health = {1} ammo = {2}", 
                              name, health, ammo);
        }

        string name;                                        // закрытые поля
        int health, ammo;
    }
    
    class Class1
    {   static void Main()
        {
            Monster Masha = new Monster( 200, 200, "Masha" );
            Masha.Passport();
            --Masha.Health;              // использование свойств
            Masha.Ammo += 100;           // использование свойств
            Masha.Passport();
        }
    }
}
Листинг 5.9. Класс Monster со свойствами

Результат работы программы:

Monster Masha    health = 200 ammo = 200
Monster Masha    health = 199 ammo = 300

Вопросы и задания для самостоятельной работы студента

  1. Перечислите и опишите элементы класса в C#.
  2. Опишите способы передачи параметров в методы.
  3. Запишите алгоритм вычисления чисел Фибоначчи с помощью рекурсии.
  4. Для чего в классе может потребоваться несколько конструкторов?
  5. Как можно вызвать один конструктор из другого? Зачем это нужно?
  6. Что такое this? Что в нем хранится, как он используется?
  7. Что такое деструктор? Гарантирует ли среда его выполнение?
  8. Какие действия обычно выполняются в части set свойства?
  9. Может ли свойство класса быть не связанным с его полями?
  10. Можно ли описать разные спецификаторы доступа к частям get и set свойства?

Лабораторные работы

Лабораторная работа № 4. Простейшие классы

Разрабатываемый класс должен содержать следующие элементы: скрытые поля, конструкторы с параметрами и без параметров, методы, свойства. Методы и свойства должны обеспечивать непротиворечивый, полный, минимальный и удобный интерфейс класса. При возникновении ошибок должны выбрасываться исключения.

Задание

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

Написать программу, демонстрирующую все разработанные элементы класса.

< Лекция 4 || Лекция 5: 1234 || Лекция 6 >