Книга: C# 4.0: полное руководство

Перегрузка конструкторов

Перегрузка конструкторов

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

// Продемонстрировать перегрузку конструктора.
using System;
class MyClass {
  public int x;
  public MyClass() {
    Console.WriteLine("В конструкторе MyClass()."); x = 0;
  }
  public MyClass(int i) {
    Console.WriteLine("В конструкторе MyClass(int)."); x = i ;
  }
  public MyClass(double d) {
    Console.WriteLine("В конструкторе MyClass(double)."); x = (int) d;
  }
  public MyClass(int i, int j) {
    Console.WriteLine("В конструкторе MyClass(int, int)."); x = i * j;
  }
}
class OverloadConsDemo {
  static void Main() {
    MyClass t1 = new MyClass();
    MyClass t2 = new MyClass(88);
    MyClass t3 = new MyClass(17.23);
    MyClass t4 = new MyClass(2, 4);
    Console.WriteLine("t1.x: " + t1.x);
    Console.WriteLine("t2.х: " + t2.x);
    Console.WriteLine("t3.x: " + t3.x);
    Console.WriteLine("t4.x: " + t4.x);
 }
}

При выполнении этой программы получается следующий результат.

В конструкторе MyClass().
В конструкторе MyClass (int) .
В конструкторе MyClass(double).
В конструкторе MyClass (int, int).
t1.x: 0
t2.x: 88
t3.x: 17
t4.x: 8

В данном примере конструктор MyClass() перегружается четыре раза, всякий раз конструируя объект по-разному. Подходящий конструктор вызывается каждый раз, исходя из аргументов, указываемых при выполнении оператора new. Перегрузка конструктора класса предоставляет пользователю этого класса дополнительные преимущества в конструировании объектов.

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

// Класс для хранения символов в стеке.
using System;
class Stack {
  // Эти члены класса являются закрытыми,
  char[] stck; // массив, содержащий стек
  int tos;    // индекс вершины стека
  // Сконструировать пустой объект класса Stack по заданному размеру стека,
  public Stack(int size) {
    stck = new char[size]; // распределить память для стека
    tos = 0;
  }
  // Сконструировать объект класса Stack из существующего стека,
  public Stack(Stack ob) {
    // Распределить память для стека,
    stck = new char[ob.stck.Length];
    // Скопировать элементы в новый стек,
    for (int i=0; i < ob.tos; i++)
      stck[i] = ob.stck[i];
    // Установить переменную tos для нового стека,
    tos = ob.tos;
  }
  // Поместить символы в стек,
  public void Push(char ch) {
    if(tos==stck.Length) {
      Console.WriteLine(" - Стек заполнен.");
      return;
    }
    stck[tos] = ch;
    tos++;
  }
  // Извлечь символ из стека,
  public char Pop() {
    if(tos==0) {
      Console.WriteLine (" - Стек пуст.");
      return (char) 0;
    }
    tos--;
    return stck[tos];
  }
  // Возвратить значение true, если стек заполнен,
  public bool IsFull() {
    return tos==stck.Length;
  }
  // Возвратить значение true, если стек пуст,
  public bool IsEmpty() {
    return tos==0;
  }
  // Возвратить общую емкость стека,
  public int Capacity()    {
    return stck.Length;
  }
  // Возвратить количество объектов, находящихся в настоящий момент в стеке,
  public int GetNum() {
    return tos;
  }
}
// Продемонстрировать применение класса Stack.
class StackDemo {
  static void Main() {
    Stack stk1 = new Stack(10);
    char ch;
    int i;
    // Поместить ряд символов в стек stk1.
    Console.WriteLine("Поместить символы А-J в стек stk1.");
    for(i=0; !stk1.IsFull(); i++)
      stk1.Push((char) ('A' + i));
    // Создать копию стека stck1.
    Stack stk2 = new Stack(stk1);
    // Вывести содержимое стека stk1.
    Console.Write("Содержимое стека stk1: ");
    while ( !stk1.IsEmpty() ) {
      ch = stk1.Pop();
      Console.Write(ch);
    }
    Console.WriteLine();
    Console.Write("Содержимое стека stk2: ");
    while ( !stk2.IsEmpty() ) {
      ch = stk2.Pop();
      Console.Write(ch);
    }
    Console.WriteLine ("n");
  }
}

Результат выполнения этой программы приведен ниже.

Поместить символы А-J в стек stk1.
Содержимое стека stk1: JIHGFEDCBA
Содержимое стека stk2: JIHGFEDCBA

В классе StackDemo сначала конструируется первый стек (stk1), заполняемый символами. Затем этот стек используется, для конструирования второго стека (stk2). Это приводит к выполнению следующего конструктора класса Stack.
// Сконструировать объект класса Stack из существующего стека,
public Stack(Stack ob) {
  // Распределить память для стека,
  stck = new char[ob.stck.Length];
  // Скопировать элементы в новый стек,
  for (int i=0; i < ob.tos; i++)
    stck[i] = ob.stck[i];
  // Установить переменную tos для нового стека,
  tos = ob.tos;
}

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

Вызов перегружаемого конструктора с помощью ключевого слова this

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

имя_конструктора{список_параметров1) : this (список_параметров2) {
// ... Тело конструктора, которое может быть пустым.
}

В исходном конструкторе сначала выполняется перегружаемый конструктор, список параметров которого соответствует критерию список_параметров2, а затем все остальные операторы, если таковые имеются в исходном конструкторе. Ниже приведен соответствующий пример.

// Продемонстрировать вызов конструктора с помощью ключевого слова this.
using System;
class XYCoord {
  public int x, y;
  public XYCoord() : this(0, 0) {
    Console.WriteLine("В конструкторе XYCoord()");
  }
  public XYCoord(XYCoord obj) : this(obj.x, obj.y) {
    Console.WriteLine("В конструкторе XYCoord(obj)");
  }
  public XYCoord(int i, int j) {
    Console.WriteLine("В конструкторе XYCoord(int, int)"); x = i;
    y = j;
  }
}
class OverloadConsDemo {
  static void Main() {
    XYCoord tl = new XYCoord();
    XYCoord t2 = new XYCoord(8, 9);
    XYCoord t3 = new XYCoord(t2);
    Console.WriteLine("tl.x,tl.y:" + tl.x + ", " + tl.y);
    Console.WriteLine("t2.x,t2.y:" + t2.x + ", " + t2.y);
    Console.WriteLine("t3.x,t3.y:" + t3.x + ", " + t3.y);
  }
}

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

В конструкторе XYCoord(int, int)
В конструкторе XYCoord()
В конструкторе XYCoord(int, int)
В конструкторе XYCoord(int, int)
В конструкторе XYCoord(obj)
tl.x, tl.y: 0, 0
t2.x, t2.у: 8, 9
t3.x, t3.у: 8, 9

Код в приведенном выше примере работает следующим образом. Единственным конструктором, фактически инициализирующим поля х и у в классе XYCoord, является конструктор XYCoord(int, int).А два других конструктора просто вызывают этот конструктор с помощью ключевого слова this. Например, когда создается объект t1, то вызывается его конструктор XYCoord(), что приводит к вызову this(0, 0), который в данном случае преобразуется в вызов конструктора XYCoord(0, 0). То же самое происходит и при создании объекта t2.

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

public XYCoord(int х) : this(х, х) {    }

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

Оглавление книги


Генерация: 4.200. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз