Главная arrow Язык программирования C# arrow C# Полное руководство Герберт Шилдт arrow Практический пример организации управления доступом C# Полное руководство Герберт Шилдт

Практический пример организации управления доступом C# Полное руководство Герберт Шилдт

Для чтобы стали понятнее особенности внутреннего механизма управления доступом, обратимся к конкретному примеру. Одним из самых характерных примеров объектно-ориентированного программирования служит класс, реализующий стек — структуру данных, воплощающую магазинный список, действующий по принципу “последним пришел — первым обслужен”. Свое название он получил по аналогии со стопкой тарелок, стоящих на столе. Первая тарелка в стопке является в то же время последней использовавшейся тарелкой.
Стек служит классическим примером объектно-ориентированного программирования потому, что он сочетает в себе средства хранения информации с методами доступа к ней. Для реализации такого сочетания отлично подходит класс, в котором члены, обеспечивающие хранение информации в стеке, должны быть закрытыми, а методы доступа к ним — открытыми. Благодаря инкапсуляции базовых средств хранения информации соблюдается определенный порядок доступа к отдельным элементам стека из кода, в котором он используется.
Для стека определены две основные операции: поместить данные в стек и извлечь их оттуда. Первая операция помещает значение на вершину стека, а вторая — извлекает значение из вершины стека. Следовательно, операция извлечения является безвозвратной: как только значение извлекается из стека, оно удаляется и уже недоступно в стеке.
В рассматриваемом здесь примере создается класс Stack, реализующий функции стека. В качестве базовых средств для хранения данных в стеке служит закрытый массив. А операции размещения и извлечения данных из стека доступны с помощью открытых методов класса Stack. Таким образом, открытые методы действуют по упомянутому выше принципу “последним пришел — первым обслужен”. Как следует из приведенного ниже кода, в классе Stack сохраняются символы, но тот же самый механизм может быть использован и для хранения данных любого другого типа.

// Класс для хранения символов в стеке.

using System;

class Stack {
   // Эти члены класса являются закрытыми.
   char [ ] stck; // массив, содержащий стек
   int tos;          // индекс вершины стека

   // Построить пустой класс Stack для реализации
   // стека заданного размера.
   public Stack(int size) {
      stck = new char[size]; // распределить память для стека
      tos = 0;
   }

   // Поместить символы в стек.
   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 IsEmptyO {
      return tos==0;
   }

   // Возвратить общую емкость стека.
   public int Capacity () {
      return stck.Length;
   }

   // Возвратить количество объектов, находящихся в
   // данный момент в стеке.
   public int GetNum() {
      return tos;
   }
}


Рассмотрим класс Stack более подробно. В начале этого класса объявляются две следующие переменные экземпляра:
   // Эти члены класса являются закрытыми.
   char[] stck; // массив, содержащий стек
   int tos; // индекс вершины стека

Массив stck предоставляет базовые средства для хранения данных в стеке (в данном случае — символов). Обратите внимание на то, что память для этого массива не распределяется. Это делается в конструкторе класса Stack. А член tos этого класса содержит индекс вершины стека.
Оба члена, tosnstck, являются закрытыми, и благодаря этому соблюдается принцип “последним пришел — первым обслужен”. Если же разрешить открытый доступ к члену stck, то элементы стека окажутся доступными не по порядку. Кроме того, член tos содержит индекс вершины стека, где находится первый обслуживаемый в стеке элемент, и поэтому манипулирование членом tos в коде, находящемся за пределами класса Stack, следует исключить, чтобы не допустить разрушение самого стека. Но в то же время члейы stck и tos доступны пользователю класса Stack косвенным образом с помощью различных отрытых методов, описываемых ниже.
Рассмотрим далее конструктор класса Stack.

// Построить пустой класс Stack для реализации
// стека заданного размера,
   public Stack(int size) {
      stck = new char [size]; // распределить память для стека
      tos = 0;
   }

Этому конструктору передается требуемый размер стека. Он распределяет память для базового массива и устанавливает значение переменной tos в нуль. Следовательно, нулевое значение переменной tos указывает на то, что стек пуст.
Открытый метод Push () помещает конкретный элемент в стек, как показано ниже.

// Поместить символы в стек.
public void Push(char ch) {
   if(tos==stck.Length) {
      Console.WriteLine (" - Стек заполнен.");
      return;
   }

   stck[tos] = ch;
   tos++;
}


Элемент, помещаемый в стек, передается данному методу в качестве параметра ch. Перед тем как поместить элемент в стек, производится проверка на наличие свободного места в базовом массиве, а именно: не превышает ли значение переменной tos длину массива stck. Если свободное место в массиве stck есть, то элемент сохраняется в нем по индексу, хранящемуся в переменной tos, после чего значение этой переменной инкрементируется. Таким образом, в переменной tos всегда хранится индекс следующего свободного элемента массива stck.
Для извлечения элемента из стека вызывается открытый метод Pop (), приведенный ниже.

// Извлечь символ из стека.
public char Рор() {
   if(tos==0) {
      Console .WriteLine (" - Стек пуст.");
      return (char) 0;
   }
   
   tos--;
   return stck[tos];
}


В этом методе сначала проверяется значение переменной tos. Если оно равно нулю, значит, стек пуст. В противном случае значение переменной tos декрементируется, и затем из стека возвращается элемент по указанному индексу.
Несмотря на то что для реализации стека достаточно методов Push () и Pop (), полезными могут оказаться и другие методы. Поэтому в классе Stack определены еще четыре метода: IsFull (), IsEmpty (), Capacity () и GetNum (). Эти методы предоставляют всю необходимую информацию о состоянии стека и приведены ниже.

// Возвратить значение 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;
}


Метод IsFull () возвращает логическое значение true, если стек заполнен, а иначе — логическое значение false. Метод IsEmpty () возвращает логическое значение true, если стек пуст, а иначе — логическое значение false. Для получения общей емкости стека (т.е. общего числа элементов, которые могут в нем храниться) достаточно вызвать метод Capacity (), а для получения количества элементов, хранящихся в настоящий момент в стеке, — метод GetNum (). Польза от этих методов состоит в том, что для получения информации, которую они предоставляют, требуется доступ к закрытой переменной tos. Кроме того, они служат
наглядными примерами организации безопасного доступа к закрытым членам класса с помощью открытых методов.
Конкретное применение класса Stack для реализации стека демонстрируется в приведенной ниже программе.

// Продемонстрировать применение класса Stack.

using System;

// Класс для хранения символов в стеке.
class Stack {
   // Эти члены класса являются закрытыми.
   char [ ] stck; // массив, содержащий стек
   int tos;          // индекс вершины стека

   // Построить пустой класс Stack для реализации
   // стека заданного размера,
   public Stack(int size) {
      stck = new char [size]; // распределить память для стека
      tos =0;
   }

   // Поместить символы в стек.
   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;
   }
}

class StackDemo {
   static void Main() {
      Stack stk1 = new Stack (10);
      Stack stk2 = new Stack (10);
      Stack stk3 = new Stack (10);
      char ch;
      int i;

      // Поместить ряд символов в стек stk1.
      Console.WriteLine("Поместить символы A-J в стек stk1.");
      for(i=0; !stkl.IsFull(); i++)
         stk1.Push((char) ('A' + i) ) ;

      if(stk1.IsFull()) Console.WriteLine ("Стек stk1 заполнен.");

      // Вывести содержимое стека stk1.
      Console.Write("Содержимое стека stk1: ");
      while( !stk1.IsEmpty() ) {
         ch = stk1.Pop();
         Console.Write(ch);
      }

      Console.WriteLine();

      if(stk1.IsEmpty()) Console.WriteLine ("Стек stk1 пуст.\n");

      // Поместить дополнительные символы в стек stk1.
      Console.WriteLine("Вновь поместить символы А-J в стек stk1.");
      for(i=0; !stk1.IsFull(); i++)
         stk1.Push((char) ('A' + i) ) ;

      //А теперь извлечь элементы из стека stk1 и
      // поместить их в стек stk2.
      //В итоге элементы сохраняются в стеке stk2
      //в обратном порядке.
      Console.WriteLine("А теперь извлечь символы из стека stk1\n" +
      "n поместить их в стек stk2.");
      while ( !stk1.IsEmpty() ) {
         ch = stk1.Pop ();
     stk2.Push(ch);
      }

      Console.Write("Содержимое стека stk2: ");
      while( !stk2.IsEmpty() ) {
         ch = stk2.Pop();
         Console.Write(ch);
      }

      Console.WriteLine("\n");

      // Поместить 5 символов в стек.
      Console.WriteLine("Поместить 5 символов в стек stk3.");
      for(i=0; i < 5; i++)
         stk3.Push((char) ('A' + i));

      Console.WriteLine("Емкость стека stk3: " + stk3.Capacity()) ;
      Console.WriteLine("Количество объектов в стеке stk3: " +stk3.GetNum()) ;
   }
}

При выполнении этой программы получается следующий результат:
   Поместить символы A-J в стек stk1.
   Стек stk1 заполнен.
   Содержимое стека stk1: JIHGFEDCBA Стек stk1 пуст.
   
   Вновь поместить символы А-J в стек stk1.
   А теперь извлечь символы из стека stk1 и поместить их в стек stk2.
   Содержимое стека stk2: ABCDEFGHIJ

   Поместить 5 символов в стек stk3.
   Емкость стека stk3: 10
   Количество объектов в стеке stk3: 5