Введение в ООП
Базовые принципы ООП
OOP0Begin1°. Поля и методы класса. Инкапсуляция. Реализовать класс Stack, моделирующий стек целых чисел. Данные стека хранятся в виде односвязной цепочки узлов, имеющих два поля: data (содержит целое число — значение элемента стека) и next (содержит ссылку на следующий элемент стека). Узел реализовать в виде вспомогательного класса StackNode с открытыми полями data и next. Поля должны инициализироваться в конструкторе с помощью его одноименных параметров. Класс Stack включает закрытое поле top типа StackNode, содержащее ссылку на вершину стека (или пустую ссылку, если стек пуст), конструктор без параметров, создающий пустой стек, и набор открытых методов: • Empty (без параметров) — возвращает True, если стек пуст, и False в противном случае; • Push(data) — добавляет в стек элемент со значением data (целочисленный параметр), ничего не возвращает; • Pop (без параметров) — извлекает из стека верхний элемент и возвращает его значение (если стек пуст, то возвращает число 0); • Peek (без параметров) — возвращает значение верхнего элемента, не удаляя его из стека (если стек пуст, то возвращает число 0); • Clear (без параметров) — удаляет из стека все элементы, ничего не возвращает; • ToStr (без параметров) — возвращает строковое представление содержимого стека в формате «v1-v2-…-vN-end», где v1, v2, …, vN — значения элементов стека, начиная с его вершины (например, «12–34–end»); для пустого стека функция должна возвращать только слово «end». Обычно внешняя программа может использовать только методы класса, которые объявляются открытыми (public), в то время как его поля делаются закрытыми (private). Это гарантирует, что внешняя программа не сможет изменить поля неправильным образом и тем самым нарушить нормальную работу объекта. Кроме того, скрытие полей дает возможность в дальнейшем изменить реализацию класса (определяемую его полями), не изменяя его интерфейс (определяемый методами и используемый внешними программами). Так обеспечивается первый принцип ООП: принцип инкапсуляции (сокрытия данных). Тестирование разработанной системы классов. Дана строка Name (имя файла), целое число N и набор из N элементов данных, каждый из которых определяет одну из команд стека. Для команд Empty, Pop, Peek, Clear элемент данных представляет собой строку, совпадающую с именем команды; для команды Push элемент данных включает строку «Push» и целое число, которое требуется добавить в стек; команда ToStr в набор не входит. Создать объект s типа Stack и выполнить для него все команды из исходного набора, записывая информацию о выполнении команд в текстовый файл с именем Name. Все команды для стека должны вызываться во вспомогательной функции StackTest с параметром-ссылкой типа Stack. Информация о каждой команде занимает одну строку файла. Вначале выводится имя команды, двоеточие и пробел. Затем, если команда возвращает целочисленное значение, то выводится это значение, а если команда возвращает логическое значение, то выводится строка «True» в случае значения True и строка «False» в случае значения False. Кроме того, для каждой команды выводится строковое представление стека; для этого после вызова каждой команды дополнительно вызывается метод ToStr для объекта s, и его возвращаемое значение записывается в файл на той же строке, что и предыдущие данные текущей команды, отделяясь от них одним пробелом.
Примечание (C++). Члены класса по умолчанию являются закрытыми (для них можно указывать модификатор private , но это не является обязательным). Чтобы сделать член класса открытым, надо указать модификатор public . Модификатор protected разрешает доступ к члену класса для любых производных классов (если наследование не является закрытым).
[C++] class StackNode
{
public:
int data;
shared_ptr<StackNode> next;
StackNode(int data, shared_ptr<StackNode> next) :
data(data), next(next) {}
};
class Stack
{
shared_ptr<StackNode> top = nullptr;
public:
void Push(int data)
{
top = make_shared<StackNode>(data, top);
}
// Implement other methods of the Stack class
};
void StackTest(shared_ptr<Stack> s)
{
// Implement the StackTest function
}
Примечание (C#). При разработке классов, которые должны использоваться в различных приложениях, их надо снабжать модификатором public . Если класс предназначен только для использования в пределах текущей сборки (как правило, динамической библиотеки), он должен иметь модификатор internal (который можно не указывать); это не позволит другим программам использовать данный класс.
Члены класса по умолчанию являются закрытыми (для них можно указывать модификатор private , но это не является обязательным). Чтобы сделать член класса открытым, надо указать модификатор public . Другие модификаторы доступа для членов класса: internal (член доступен только в пределах текущей сборки) и protected (член доступен для любых производных классов).
[C#] internal class StackNode
{
internal int data;
internal StackNode next;
internal StackNode(int data, StackNode next)
{
this.data = data;
this.next = next;
}
}
public class Stack
{
private StackNode top = null;
public void Push(int data)
{
top = new StackNode(data, top);
}
// Implement other methods of the Stack class
}
static void StackTest(Stack s)
{
// Implement the StackTest function
}
Примечание (Java). При разработке классов, которые должны использоваться в различных приложениях, их надо снабжать модификатором public . Один файл может иметь только один public -класс, и имя файла должно совпадать с именем этого класса.
При описании закрытых членов класса надо использовать модификатор private . Чтобы сделать член класса открытым, надо указать модификатор public . Модификатор protected разрешает доступ к члену класса для любых производных классов и для любых классов того же пакета. Отсутствие модификатора делает возможным доступ к члену класса только в пределах пакета.
[Java] class StackNode {
public int data;
public StackNode next;
public StackNode(int data, StackNode next) {
this.data = data;
this.next = next;
}
}
class Stack {
private StackNode top = null;
public void push(int data) {
top = new StackNode(data, top);
}
// Implement other methods of the Stack class
}
public class MyTask extends PT
{
public static void stackTest(Stack s) throws IOException {
// Implement the stackTest function
}
Примечание (Python). В языке Python поля и методы класса по умолчанию являются открытыми. Если какие-либо поля надо сделать закрытыми, то их имена надо начинать с двух символов подчеркивания. Методы класса тоже можно сделать закрытыми с помощью добавления двух символов подчеркивания, однако это не относится к специальным методам, который начинаются и заканчиваются двойным подчеркиванием; примером является метод __init__ . Метод __init__ в языке Python играет роль конструктора (в нем определяются поля класса и выполняются инициализирующие действия), поскольку «настоящий» конструктор __new__ обычно не переопределяется. При определении метода в классе его первый параметр всегда соответствует тому объекту, для которого вызывается метод. Обычно для этого параметра используется имя self .
[Python] class StackNode:
def __init__(self, data, next):
self.data = data
self.next = next
class Stack:
def __init__(self):
self.__top = None
def push(self,data):
self.__top = StackNode(data, self.__top)
# Implement other methods of the Stack class
def stackTest(s):
pass
# Implement the stackTest function
Примечание 1 (Ruby). В Ruby имена полей классов начинаются с символа @ , и по умолчанию они закрыты. Если требуется обеспечить доступ к какому-либо полю f из внешней программы, то при определении класса можно использовать конструкцию attr_reader :f (для доступа только на чтение) или attr_accessor :f (для доступа на чтение и запись). Методы класса по умолчанию открыты (за исключением некоторых особых методов, например, initialize ). Метод initialize в языке Ruby играет роль конструктора (в нем определяются поля класса и выполняются инициализирующие действия), поскольку «настоящий» конструктор new обычно не переопределяется. Если при определении методов класса указать слова private , public или protected , то методы, указанные после этих слов, будут иметь соответствующий уровень доступа.
Следует иметь в виду, что в языке Ruby уровни доступа private и protected имеют другой смысл, чем в большинстве других объектно-ориентированных языков. Уровень доступа private означает, что метод можно вызвать в другом методе, причем как этого класса, так и производных от него классов, однако этот метод нельзя вызвать для других экземпляров класса, даже если эти экземпляры используются в методе того же класса, в котором определен private-метод. Уровень доступа protected снимает это ограничение: protected-методы можно вызывать для любых экземпляров класса, которые используются в методах исходного и производных классов.
Примечание 2 (Ruby). В Ruby нельзя использовать идентификатор next в качестве имени параметра метода, так как он является служебным словом (аналог continue в других языках программирования), поэтому в конструкторе initialize класса StackNode для второго параметра необходимо использовать другой идентификатор (например, nextNode ). В качестве имени поля класса идентификатор next использовать можно.
[Ruby] class StackNode
def initialize(data, nextNode)
@data = data
@next = nextNode
end
attr_reader :data, :next
end
class Stack
def initialize
@top = nil
end
def push(data)
@top = StackNode.new(data, @top)
end
# Implement other methods of the Stack class
end
def stackTest(s)
# Implement the stackTest function
end
OOP0Begin2°. Наследование. Принцип наследования является вторым принципом ООП; он позволяет создавать иерархии родственных классов. Класс, от которого порождается (наследуется) новый класс, называется классом-предком, или базовым классом, или суперклассом. Порожденный класс называется классом-потомком, или производным классом, или субклассом. Реализовать класс StackC, который является потомком класса Stack (реализованного в задании OOP0Begin1) и обеспечивает дополнительную возможность: определение текущего количества элементов в стеке. Для этого добавить в класс StackC новое закрытое поле cnt целого типа, открытый метод Count без параметров, возвращающий значение поля cnt, и переопределить методы Push, Pop и Clear таким образом, чтобы они, помимо действий, предусмотренных в классе-предке Stack, соответствующим образом корректировали значение поля cnt. Определить конструктор, в котором задается начальное значение поля cnt, равное 0. Кроме того, переопределить метод ToStr, добавив к строке, возвращаемой методом ToStr предка, значение поля cnt в квадратных скобках, например, «12–34–end[2]». Для пустого стека метод ToStr должен возвращать строку «end[0]». Тестирование разработанной системы классов. Дана строка Name (имя файла), целое число N и набор из N элементов данных, каждый из которых определяет одну из команд стека по тем же правилам, что и в задании OOP0Begin1. Команды ToStr и Count в набор не входят. Создать объект s типа StackC и выполнить для него все команды из исходного набора, записывая информацию о выполнении команд в текстовый файл с именем Name по тем же правилам, что и в OOP0Begin1. Как и в задании OOP0Begin1, команды для стека должны вызываться во вспомогательной функции StackTest, которая должна иметь параметр-ссылку типа StackC. За исключением типа своего параметра функция StackTest не должна отличаться от варианта, реализованного в задании OOP0Begin1.
Примечание (C++). В заготовке программы приведен образец наследования производного класса от базового класса. Кроме того, показано, как вызывать методы базового класса из методов производного класса (используется имя базового класса Stack:: ). Заметим, что в описанной реализации класса StackC , а точнее, в реализации системы классов Stack и StackC имеется недочет, который будет исправлен в следующем задании (компилятор C++ этот недочет не выявляет).
[C++] class StackNode
{
public:
int data;
shared_ptr<StackNode> next;
StackNode(int data, shared_ptr<StackNode> next) :
data(data), next(next) {}
};
class Stack
{
shared_ptr<StackNode> top = nullptr;
public:
void Push(int data)
{
top = make_shared<StackNode>(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin1 task
};
class StackC : public Stack {
private:
int cnt = 0;
public:
void Push(int data);
// Implement other methods of the StackC class
};
void StackC::Push(int data) {
Stack::Push(data);
cnt++;
}
void StackTest(shared_ptr<StackC> s)
{
// Use the body of the StackTest function
// from the OOP0Begin1 task
}
Примечание (C#). В заготовке программы приведен образец наследования производного класса от базового класса. Кроме того, показано, как вызывать методы базового класса из методов производного класса (используется ключевое слово base ). При подобном переопределении методов в производном классе компилятор выдает предупреждение вида «StackC.Push(int) hides inherited member Stack.Push(int)». Это свидетельствует о недочете в реализации класса StackC (а точнее, о недочете в реализации системы классов Stack и StackC ), который будет исправлен в следующем задании.
[C#] internal class StackNode
{
internal int data;
internal StackNode next;
internal StackNode(int data, StackNode next)
{
this.data = data;
this.next = next;
}
}
public class Stack
{
private StackNode top = null;
public void Push(int data)
{
top = new StackNode(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin1 task
}
public class StackC: Stack
{
private int cnt = 0;
public void Push(int data)
{
cnt++;
base.Push(data);
}
// Implement other methods of the StackC class
}
static void StackTest(StackC s)
{
// Use the body of the StackTest function
// from the OOP0Begin1 task
}
Примечание (Java). В заготовке программы приведен образец наследования производного класса от базового класса. Кроме того, показано, как вызывать методы базового класса из методов производного класса (используется ключевое слово super ).
[Java] class StackNode {
public int data;
public StackNode next;
public StackNode(int data, StackNode next) {
this.data = data;
this.next = next;
}
}
class Stack {
private StackNode top = null;
public void push(int data) {
top = new StackNode(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin1 task
}
class StackC extends Stack {
private int cnt = 0;
public void push(int data) {
super.push(data);
cnt++;
}
// Implement other methods of the StackC class
}
public class MyTask extends PT
{
public static void stackTest(StackC s) throws IOException {
// Use the body of the stackTest function
// from the OOP0Begin1 task
}
Примечание (Python). В заготовке программы приведен образец наследования производного класса от базового класса. Кроме того, показано, как вызывать методы базового класса из методов производного класса (используется ключевое слово super ).
[Python] class StackNode:
def __init__(self, data, next):
self.data = data
self.next = next
class Stack:
def __init__(self):
self.__top = None
def push(self,data):
self.__top = StackNode(data, self.__top)
# Use the implementation of the Stack class
# from the OOP0Begin1 task
class StackC(Stack):
def __init__(self):
super().__init__()
self.__cnt = 0
def push(self,data):
super().push(data)
self.__cnt += 1
# Implement other methods of the StackC class
def stackTest(s):
pass
# Use the body of the stackTest function
# from the OOP0Begin1 task
Примечание (Ruby). В заготовке программы приведен образец наследования производного класса от базового класса. Кроме того, показано, как вызывать методы базового класса из методов производного класса (используется ключевое слово super ).
[Ruby] class StackNode
def initialize(data, nextNode)
@data = data
@next = nextNode
end
attr_reader :data, :next
end
class Stack
def initialize
@top = nil
end
def push(data)
@top = StackNode.new(data,@top)
end
# Use the implementation of the Stack class
# from the OOP0Begin1 task
end
class StackC < Stack
def initialize
super
@cnt = 0
end
def push(data)
super
@cnt += 1
end
# Implement other methods of the StackC class
end
def stackTest(s)
# Use the body of the stackTest function
# from the OOP0Begin1 task
end
OOP0Begin3°. Полиморфизм. В задании предполагается, что уже реализованы классы Stack и StackC, описанные в заданиях OOP0Begin1 и OOP0Begin2. Изменить, при необходимости, реализацию методов Push, Pop, Clear и ToStr этих классов таким образом, чтобы при вызове их методов выполнялось позднее связывание, (связывание на этапе выполнения), благодаря которому выбор варианта вызываемого метода определяется фактическим типом объекта (указанным при его создании), а не базовым типом, использованным при его описании. Тем самым обеспечивается принцип полиморфизма — третий принцип ООП, позволяющий различным образом обрабатывать объекты разных типов, вызывая для них методы с одинаковыми именами. Для языков с динамической типизацией принцип полиморфизма обеспечивается автоматически для всех классов; для языков со статической типизацией могут потребоваться дополнительные действия при определении тех методов, для которых требуется реализовать позднее связывание (такие методы называются виртуальными). Тестирование разработанной системы классов. Дана строка Class (тип создаваемого объекта), строка Name (имя файла), целое число N и набор из N элементов данных, каждый из которых определяет одну из команд стека по тем же правилам, что и в задании OOP0Begin1. Команды ToStr и Count в набор не входят. Строка Class может принимать одно из двух значений: «Stack» или «StackC». Создать объект s типа, указанного в строке Class, и выполнить для него все команды из исходного набора, записывая информацию о выполнении команд в текстовый файл с именем Name по тем же правилам, что и в OOP0Begin1. Для выполнения всех команд использовать один вызов функции StackTest с параметром требуемого типа; эта функция не должна отличаться от варианта, реализованного в задании OOP0Begin1 (в частности, ее формальный параметр должен быть ссылкой на тип Stack).
Примечание 1 (C++). Для реализации позднего связывания в языке C++ необходимо сделать виртуальными те методы, который по-разному реализованы в классах Stack и StackC : при первом описании этих методов надо в начале их заголовков указать модификатор virtual (для переопределении методов этот модификатор не указывается).
Примечание 2 (C++). Для повышения наглядности и надежности кода при переопределении методов в производных классах рекомендуется указывать в конце их заголовков необязательный модификатор override . В этом случае компилятор проверит наличие данного метода в базовом классе и при его отсутствии выведет сообщение об ошибке.
[C++] class StackNode
{
public:
int data;
shared_ptr<StackNode> next;
StackNode(int data, shared_ptr<StackNode> next) :
data(data), next(next) {}
};
class Stack
{
shared_ptr<StackNode> top = nullptr;
public:
virtual void Push(int data)
{
top = make_shared<StackNode>(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin1 task
// and add the "virtual" keyword to headers
// of the Pop, Clear, and ToStr methods
};
class StackC : public Stack {
private:
int cnt = 0;
public:
void Push(int data) override;
// Use the implementation of the StackC class
// from the OOP0Begin2 task
// and add the "override" keyword to
// the Pop, Clear, and ToStr methods
};
void StackC::Push(int data) {
Stack::Push(data);
cnt++;
}
void StackTest(shared_ptr<Stack> s)
{
// Use the body of the StackTest function
// from the OOP0Begin2 task
}
Примечание (C#). Для реализации позднего связывания в языке C# необходимо сделать виртуальными те методы, который по-разному реализованы в классах Stack и StackC : при их описании надо указывать после слова public модификаторы virtual (для первой реализации метода) или override (при переопределении метода в производных классах).
[C#] internal class StackNode
{
internal int data;
internal StackNode next;
internal StackNode(int data, StackNode next)
{
this.data = data;
this.next = next;
}
}
public class Stack
{
private StackNode top = null;
public virtual void Push(int data)
{
top = new StackNode(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin1 task
// and add the "virtual" keyword to headers
// of the Pop, Clear, and ToStr methods
}
public class StackC: Stack
{
private int cnt = 0;
public override void Push(int data)
{
cnt++;
base.Push(data);
}
// Use the implementation of the StackC class
// from the OOP0Begin2 task
// and add the "override" keyword to headers
// of the Pop, Clear, and ToStr methods
}
static void StackTest(Stack s)
{
// Use the body of the StackTest function
// from the OOP0Begin2 task
}
Примечание 1 (Java). Поскольку в языке Java все методы классов по умолчанию обеспечивают позднее связывание, для решения задачи достаточно использовать систему классов, разработанную в задании OOP0Begin2, не внося в нее никаких изменений.
Примечание 2 (Java). Для повышения наглядности и надежности кода при переопределении методов в производных классах рекомендуется указывать в начале их заголовков необязательный модификатор @Override . В этом случае компилятор проверит наличие данного метода в базовом классе и при его отсутствии выведет сообщение об ошибке.
[Java] class StackNode {
public int data;
public StackNode next;
public StackNode(int data, StackNode next) {
this.data = data;
this.next = next;
}
}
class Stack {
private StackNode top = null;
public void push(int data) {
top = new StackNode(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin1 task
}
class StackC extends Stack {
private int cnt = 0;
@Override
public void push(int data) {
super.push(data);
cnt++;
}
// Use the implementation of the StackC class
// from the OOP0Begin2 task
// and add the "@Override" to
// the pop, clear, and toStr methods
}
public class MyTask extends PT
{
public static void stackTest(Stack s) throws IOException {
// Use the body of the stackTest function
// from the OOP0Begin2 task
}
Примечание (Python). Язык Python является языком с динамической типизацией, поэтому для выполнения этого задания не требуется вносить какие-либо изменения в классы, разработанные в заданиях OOP0Begin1 и OOP0Begin2.
[Python] class StackNode:
def __init__(self, data, next):
self.data = data
self.next = next
class Stack:
def __init__(self):
self.__top = None
def push(self,data):
self.__top = StackNode(data, self.__top)
# Use the implementation of the Stack class
# from the OOP0Begin1 task
class StackC(Stack):
def __init__(self):
super().__init__()
self.__cnt = 0
def push(self,data):
super().push(data)
self.__cnt += 1
# Use the implementation of the StackC class
# from the OOP0Begin2 task
def stackTest(s):
pass
# Use the body of the stackTest function
# from the OOP0Begin2 task
Примечание (Ruby). Язык Ruby является языком с динамической типизацией, поэтому для выполнения этого задания не требуется вносить какие-либо изменения в классы, разработанные в заданиях OOP0Begin1 и OOP0Begin2.
[Ruby] class StackNode
def initialize(data, nextNode)
@data = data
@next = nextNode
end
attr_reader :data, :next
end
class Stack
def initialize
@top = nil
end
def push(data)
@top = StackNode.new(data,@top)
end
# Use the implementation of the Stack class
# from the OOP0Begin1 task
end
class StackC < Stack
def initialize
super
@cnt = 0
end
def push(data)
super
@cnt += 1
end
# Use the implementation of the StackC class
# from the OOP0Begin2 task
end
def stackTest(s)
# Use the body of the stackTest function
# from the OOP0Begin2 task
end
Дополнительные приемы ООП
OOP0Begin4°. Получение информации о типе времени выполнения. Если в программе требуется вызвать метод, отсутствующий в базовом классе, но имеющийся в каком-либо производном классе, то механизм позднего связывания неприменим, и необходимо явным образом анализировать информацию о типе времени выполнения объекта (RTTI — Run-Time Type Information). В данном задании предполагается, что уже реализованы классы Stack и StackC, описанные в заданиях OOP0Begin1–OOP0Begin3. Дана строка Class (тип создаваемого объекта), строка Name (имя файла), целое число N и набор из N элементов данных, каждый из которых определяет одну из команд стека по тем же правилам, что и в задании OOP0Begin1. Команда ToStr в набор не входит, однако команда Count может входить. Строка Class может принимать одно из двух значений: «Stack» или «StackC». Создать объект s типа, указанного в строке Class, и выполнить для него все команды из исходного набора, записывая информацию о выполнении команд в текстовый файл с именем Name по тем же правилам, что и в OOP0Begin1. Команда Count обрабатывается по следующим дополнительным правилам: если объект имеет тип времени выполнения Stack, то в строку файла записывается текст «Count: Method not available», если объект имеет тип времени выполнения StackC, то строка файла должна содержать имя «Count», двоеточие, пробел и значение, которое вернул метод Count (например, «Count: 5»; вызывать метод ToStr и выводить его результат в данном случае не требуется). Для выполнения всех команд использовать один вызов функции StackTest с параметром требуемого типа; эта функция не должна отличаться от варианта, реализованного в задании OOP0Begin3, за исключением фрагмента, обрабатывающего команду Count.
Примечание (C++). Для определения RTTI в языке C++ предусмотрены операции dynamic_cast (для обычных указателей) и dynamic_pointer_cast (для умных указателей). Например, если умный указатель s описан как shared_ptr<Stack> , но фактический тип связанного с ним объекта *s равен StackC (или тип StackC является одним из классов-предков фактического типа объекта *s ), то выражение dynamic_pointer_cast<StackC>(s) вернет тот же умный указатель s , но приведенный к типу shared_ptr<StackC> . Если такое приведение невозможно, то будет возвращен нулевой указатель.
[C++] class StackNode
{
public:
int data;
shared_ptr<StackNode> next;
StackNode(int data, shared_ptr<StackNode> next) :
data(data), next(next) {}
};
class Stack
{
shared_ptr<StackNode> top = nullptr;
public:
virtual void Push(int data)
{
top = make_shared<StackNode>(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin3 task
};
class StackC : public Stack {
private:
int cnt = 0;
public:
void Push(int data) override;
// Use the implementation of the StackC class
// from the OOP0Begin3 task
};
void StackC::Push(int data) {
Stack::Push(data);
cnt++;
}
void StackTest(shared_ptr<Stack> s)
{
// Use the body of the StackTest function
// from the OOP0Begin3 task
// and add a new fragment related
// to the processing of the Count command
}
Примечание (C#). Для определения RTTI в языке C# предусмотрены операции is и as . Например, если объект s описан как Stack , но фактический тип объекта s равен StackC (или тип StackC является одним из классов-предков фактического типа объекта s ), то выражение s is StackC вернет true , а выражение s as StackC вернет тот же объект s , но приведенный к типу StackC . Если такое приведение невозможно, то выражение s is StackC вернет false , а выражение s as StackC — значение null ).
[C#] internal class StackNode
{
internal int data;
internal StackNode next;
internal StackNode(int data, StackNode next)
{
this.data = data;
this.next = next;
}
}
public class Stack
{
private StackNode top = null;
public virtual void Push(int data)
{
top = new StackNode(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin3 task
}
public class StackC: Stack
{
private int cnt = 0;
public override void Push(int data)
{
cnt++;
base.Push(data);
}
// Use the implementation of the StackC class
// from the OOP0Begin3 task
}
static void StackTest(Stack s)
{
// Use the body of the StackTest function
// from the OOP0Begin3 task
// and add a new fragment related
// to the processing of the Count command
}
Примечание (Java). Для определения RTTI в языке Java предусмотрена операция instanceof . Например, если объект s описан как Stack , но его фактический тип равен StackC (или тип StackC является одним из классов-предков фактического типа объекта s ), то выражение s instanceof StackC вернет true , в противном случае оно вернет false . Если будет возвращено значение true , то объект s можно безопасно преобразовать к типу StackC обычной операцией преобразования (StackC)s ; в случае значения false попытка такого преобразования приведет к возбуждению исключения ClassCastException .
[Java] class StackNode {
public int data;
public StackNode next;
public StackNode(int data, StackNode next) {
this.data = data;
this.next = next;
}
}
class Stack {
private StackNode top = null;
public void push(int data) {
top = new StackNode(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin3 task
}
class StackC extends Stack {
private int cnt = 0;
@Override
public void push(int data) {
super.push(data);
cnt++;
}
// Use the implementation of the StackC class
// from the OOP0Begin3 task
}
public class MyTask extends PT
{
public static void stackTest(Stack s) throws IOException {
// Use the body of the stackTest function
// from the OOP0Begin3 task
// and add a new fragment related
// to the processing of the Count command
}
Примечание (Python). В языке Python имеется функция isinstance , позволяющая проверить текущий тип объекта. Например, если тип объекта s равен StackC (или тип StackC является одним из классов-предков типа объекта s ), то выражение isinstance(s, StackC) вернет True , в противном случае оно вернет False . Если функция isinstance вернула значение True , то для объекта можно безопасно вызывать все методы данного класса.
[Python] class StackNode:
def __init__(self, data, next):
self.data = data
self.next = next
class Stack:
def __init__(self):
self.__top = None
def push(self,data):
self.__top = StackNode(data, self.__top)
# Use the implementation of the Stack class
# from the OOP0Begin3 task
class StackC(Stack):
def __init__(self):
super().__init__()
self.__cnt = 0
def push(self,data):
super().push(data)
self.__cnt += 1
# Use the implementation of the StackC class
# from the OOP0Begin3 task
def stackTest(s):
pass
# Use the body of the stackTest function
# from the OOP0Begin3 task
# and add a new fragment related
# to the processing of the Count command
Примечание (Ruby). В языке Ruby для проверки текущего типа объекта следует использовать операцию === , первым операндом которой должен быть проверяемый тип. Например, если тип объекта s равен StackC (или тип StackC является одним из классов-предков типа объекта s ), то выражение StackC === s (порядок операндов важен) вернет true , в противном случае оно вернет false . Если операция === вернула значение true , то для объекта можно безопасно вызывать все методы данного класса.
[Ruby] class StackNode
def initialize(data, nextNode)
@data = data
@next = nextNode
end
attr_reader :data, :next
end
class Stack
def initialize
@top = nil
end
def push(data)
@top = StackNode.new(data,@top)
end
# Use the implementation of the Stack class
# from the OOP0Begin3 task
end
class StackC < Stack
def initialize
super
@cnt = 0
end
def push(data)
super
@cnt += 1
end
# Use the implementation of the StackC class
# from the OOP0Begin3 task
end
def stackTest(s)
# Use the body of the stackTest function
# from the OOP0Begin3 task
# and add a new fragment related
# to the processing of the Count command
end
OOP0Begin5°. Генерация и обработка исключений. Исключения (exceptions) являются универсальным механизмом, который дает возможность объектам стандартным образом оповещать внешнюю программу об особых ситуациях, возникающих при выполнении их методов, а внешней программе — гибко реагировать на возникшие особые ситуации. Как правило, конкретные исключения реализуются в виде иерархии специальных классов, которую можно расширять, наследуя новые классы исключений от существующих. Изменить методы Pop и Peek классов Stack и StackC, описанных в заданиях OOP0Begin1–OOP0Begin3, таким образом, чтобы при попытке их вызова для пустого стека возбуждалось исключение StackException с текстовым сообщением «Empty stack in Pop» для метода Pop и «Empty stack in Peek» для метода Peek. Тестирование разработанной системы классов. Дана строка Class (тип создаваемого объекта), строка Name (имя файла), целое число N и набор из N элементов данных, каждый из которых определяет одну из команд стека по тем же правилам, что и в задании OOP0Begin1. Команды ToStr и Count в набор не входят. Строка Class может принимать одно из двух значений: «Stack» или «StackC». Создать объект s типа, указанного в строке Class, и выполнить для него команды из исходного набора, записывая информацию о выполнении команд в текстовый файл с именем Name по тем же правилам, что и в OOP0Begin1. Если в результате выполнения команды Pop или Peek будет возбуждено исключение, то записать в очередную строку файла текст сообщения вида «Pop: Empty stack in Pop» или «Peek: Empty stack in Peek», закрыть файл и прервать обработку команд. Для выполнения всех команд использовать один вызов функции StackTest (с параметром требуемого типа), модифицировав ее вариант, реализованный в задании OOP0Begin3 или OOP0Begin4.
Примечание (C++). Исключения в языке C++, как правило, являются классами-потомками стандартного класса exception . Чтобы можно было получить дополнительную информацию о возникшем исключении, в классе, производном от exception , надо переопределить виртуальный метод what() . Для возбуждения исключения используется оператор throw , в котором вызывается конструктор требуемого класса-исключения (обычно в классе-исключении определяется конструктор со строковым параметром, который содержит дополнительное описание выявленной особой ситуации).
Для перехвата исключения во внешней программе используется конструкция try-catch . Блок try содержит операторы, в которых может возникнуть исключение, блоки catch (их может быть несколько) содержат обработчики различных исключений (тип обрабатываемого исключения указывается в заголовке блока catch и заключается в круглые скобки). Для перехвата исключения любого типа с возможностью доступа к созданному объекту-исключению ex достаточно использовать блок catch с заголовком catch(exception& ex) .
[C++] class StackNode
{
public:
int data;
shared_ptr<StackNode> next;
StackNode(int data, shared_ptr<StackNode> next) :
data(data), next(next) {}
};
// Implementation of the StackException class
// containing the constructor with a string parameter
// and overriding the what() function
// (you do not need to change this implementation)
class StackException : public exception
{
private:
string msg;
public:
StackException(string msg):msg(msg) {}
const char* what() const throw() override
{
return msg.c_str();
}
};
class Stack
{
shared_ptr<StackNode> top = nullptr;
public:
virtual void Push(int data)
{
top = make_shared<StackNode>(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin4 task
// and change the implementation of Pop and Peek methods
// by adding a throw statement
// to handle the case of an empty stack
};
class StackC : public Stack {
private:
int cnt = 0;
public:
void Push(int data) override;
// Use the implementation of the StackC class
// from the OOP0Begin4 task
};
void StackC::Push(int data) {
Stack::Push(data);
cnt++;
}
void StackTest(shared_ptr<Stack> s)
{
// Use the body of the StackTest function
// from the OOP0Begin4 or OOP0Begin3 task
// and add a try-catch statement to handle
// the case of an empty stack for Pop and Peek commands
}
Примечание (C#). Исключения в языке C# являются классами-потомками стандартного класса Exception . Для возбуждения исключения используется оператор throw , в котором вызывается конструктор требуемого класса-исключения (обычно ему передается строковый параметр с дополнительным описанием выявленной особой ситуации).
Для перехвата исключения во внешней программе используется конструкция try-catch . Блок try содержит операторы, в которых может возникнуть исключение, блоки catch (их может быть несколько) содержат обработчики различных исключений (тип обрабатываемого исключения указывается в заголовке блока catch и заключается в круглые скобки). Для перехвата исключения любого типа с возможностью доступа к созданному объекту-исключению ex достаточно использовать блок catch с заголовком catch(Exception ex) .
После всех блоков catch может располагаться необязательный блок finally , содержащий операторы, которые выполняются после выхода из try -блока в любом случае (даже если в try -блоке было возбуждено исключение, которое не было обработано в блоках catch ).
[C#] internal class StackNode
{
internal int data;
internal StackNode next;
internal StackNode(int data, StackNode next)
{
this.data = data;
this.next = next;
}
}
// Implementation of the StackException class
// containing the constructor with a string parameter
// (you do not need to change this implementation)
public class StackException : Exception
{
public StackException(string message) : base(message)
{ }
}
public class Stack
{
private StackNode top = null;
public virtual void Push(int data)
{
top = new StackNode(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin4 task
// and change the implementation of Pop and Peek methods
// by adding a throw statement
// to handle the case of an empty stack
}
public class StackC: Stack
{
private int cnt = 0;
public override void Push(int data)
{
cnt++;
base.Push(data);
}
// Use the implementation of the StackC class
// from the OOP0Begin4 task
}
static void StackTest(Stack s)
{
// Use the body of the StackTest function
// from the OOP0Begin4 or OOP0Begin3 task
// and add a try-catch statement to handle
// the case of an empty stack for Pop and Peek commands
}
Примечание (Java). Исключения в языке Java являются классами-потомками стандартного класса Exception . Для возбуждения исключения используется оператор throw , в котором вызывается конструктор требуемого класса-исключения (обычно ему передается строковый параметр с дополнительным описанием выявленной особой ситуации).
Для перехвата исключения во внешней программе используется конструкция try-catch . Блок try содержит операторы, в которых может возникнуть исключение, блоки catch (их может быть несколько) содержат обработчики различных исключений (тип обрабатываемого исключения указывается в заголовке блока catch и заключается в круглые скобки). Для перехвата исключения любого типа с возможностью доступа к созданному объекту-исключению ex достаточно использовать блок catch с заголовком catch(Exception ex) .
После всех блоков catch может располагаться необязательный блок finally , содержащий операторы, которые выполняются после выхода из try -блока в любом случае (даже если в try -блоке было возбуждено исключение, которое не было обработано в блоках catch ).
[Java] class StackNode {
public int data;
public StackNode next;
public StackNode(int data, StackNode next) {
this.data = data;
this.next = next;
}
}
// Implementation of the StackException class
// containing the constructor with a string parameter
// (you do not need to change this implementation)
class StackException extends Exception {
public StackException(String s) {
super(s);
}
}
class Stack {
private StackNode top = null;
public void push(int data) {
top = new StackNode(data, top);
}
// Use the implementation of the Stack class
// from the OOP0Begin4 task
// and change the implementation of pop and peek methods
// by adding a throw statement
// to handle the case of an empty stack
}
class StackC extends Stack {
private int cnt = 0;
@Override
public void push(int data) {
super.push(data);
cnt++;
}
// Use the implementation of the StackC class
// from the OOP0Begin4 task
}
public class MyTask extends PT
{
public static void stackTest(Stack s) throws IOException {
// Use the body of the stackTest function
// from the OOP0Begin4 or OOP0Begin3 task
// and add a try-catch statement to handle
// the case of an empty stack for Pop and Peek commands
}
Примечание (Python). Исключения в языке Python являются классами-потомками стандартного класса Exception . Для возбуждения исключения используется оператор raise , в котором вызывается конструктор требуемого класса-исключения (обычно ему передается строковый параметр с дополнительным описанием выявленной особой ситуации). Если в классе-исключении определить функцию __str__ , возвращающую его строковое описание, то для получения этого строкового описания будет достаточно преобразовать исключение к типу str .
Для перехвата исключения во внешней программе используется конструкция try-except . Блок try содержит операторы, в которых может возникнуть исключение, блоки except (их может быть несколько) содержат обработчики различных исключений (тип обрабатываемого исключения указывается в заголовке блока except ). Для перехвата исключения любого типа с возможностью доступа к созданному объекту-исключению ex достаточно использовать блок except с заголовком except Exception as ex .
После всех блоков except может располагаться необязательный блок finally , содержащий операторы, которые выполняются после выхода из try -блока в любом случае (даже если в try -блоке было возбуждено исключение, которое не было обработано в блоках except ).
[Python] class StackNode:
def __init__(self, data, next):
self.data = data
self.next = next
# Implementation of the StackException class
# containing the constructor with a string parameter
# and overriding the __str__ function
# (you do not need to change this implementation)
class StackException(Exception):
def __init__(self, msg):
self.__msg = msg
def __str__(self):
return self.__msg
class Stack:
def __init__(self):
self.__top = None
def push(self, data):
self.__top = StackNode(data,self.__top)
# Use the implementation of the Stack class
# from the OOP0Begin4 task
# and change the implementation of pop and peek methods
# by adding a raise statement
# to handle the case of an empty stack
class StackC(Stack):
def __init__(self):
super().__init__()
self.__cnt = 0
def push(self, data):
super().push(data)
self.__cnt += 1
# Use the implementation of the StackC class
# from the OOP0Begin4 task
def stackTest(s):
pass
# Use the body of the stackTest function
# from the OOP0Begin4 or OOP0Begin3 task
# and add a try-except statement to handle
# the case of an empty stack for Pop and Peek commands
Примечание (Ruby). Исключения в языке Ruby являются классами-потомками стандартного класса StandardError . Для возбуждения исключения предназначен оператор raise , который обычно используется в виде raise ExcepType, msg , где ExcepType — тип возбуждаемого исключения, а msg — строка, содержащая описание возникшей особой ситуации (эта строка будет передана созданному объекту-исключению и будет доступна с помощью его метода message ).
Для перехвата исключения во внешней программе используется конструкция begin-rescue . Блок begin содержит операторы, в которых может возникнуть исключение, блоки rescue (их может быть несколько) содержат обработчики различных исключений (тип обрабатываемого исключения указывается в заголовке блока rescue , например, rescue ExcepType ). В конце заголовка блока rescue можно указать имя переменной, в которой будет сохранен объект-исключение, например, rescue ExcepType => ex ). Для перехвата исключения любого типа с возможностью доступа к созданному объекту-исключению ex достаточно использовать блок rescue с заголовком rescue => ex .
После всех блоков rescue может располагаться необязательный блок ensure , содержащий операторы, которые выполняются после выхода из begin -блока в любом случае (даже если в begin -блоке было возбуждено исключение, которое не было обработано в блоках rescue ).
[Ruby] class StackNode
def initialize(data, nextNode)
@data = data
@next = nextNode
end
attr_reader :data, :next
end
# Implementation of the StackException class
# (you do not need to change this implementation)
class StackException < StandardError
end
class Stack
def initialize
@top = nil
end
def push(data)
@top = StackNode.new(data,@top)
end
# Use the implementation of the Stack class
# from the OOP0Begin4 task
# and change the implementation of pop and peek methods
# by adding a raise statement
# to handle the case of an empty stack
end
class StackC < Stack
def initialize
super
@cnt = 0
end
def push(data)
super
@cnt += 1
end
# Use the implementation of the StackC class
# from the OOP0Begin4 task
end
def stackTest(s)
# Use the body of the stackTest function
# from the OOP0Begin4 or OOP0Begin3 task
# and add a begin-rescue statement to handle
# the case of an empty stack for Pop and Peek commands
end
OOP0Begin6°. Шаблоны функций и шаблоны классов. Шаблонные, или обобщенные классы (templates, generics) имеются в языках со статической типизацией; они, в частности, упрощают реализацию коллекций, позволяющих хранить элементы определенного типа (этот тип при описании класса задается в виде обобщенного параметра T). Наряду с шаблонными классами в таких языках можно использовать шаблонные функции. В языках с динамической типизацией шаблонные классы и функции отсутствуют. Преобразовав при необходимости классы StackNode, Stack и StackC в шаблонные классы (класс StackNode описан в задании OOP0Begin1, классы Stack и StackC описаны, а затем модифицированы, в заданиях OOP0Begin1–OOP0Begin5), обеспечить возможность работы со стеком, содержащим целочисленные, символьные или строковые данные (предполагается, что все элементы стека имеют одинаковый тип). Тестирование разработанной системы классов. Дана строка Class (тип создаваемого объекта), символ Type (тип элементов стека), строка Name (имя файла), целое число N и набор из N элементов данных, каждый из которых определяет одну из команд стека по тем же правилам, что и в задании OOP0Begin1, за исключением того, что после строки «Push» указывается элемент данных типа, определяемого символом Type: • «I» — целочисленный тип, • «C» — символьный тип, • «S» — строковый тип. Команды ToStr и Count в набор не входят; набор команд также не содержит команд Pop и Peek, вызываемых для пустого стека. Строка Class может принимать одно из двух значений: «Stack» или «StackC». Создать объект s типа, указанного в строке Class, с элементами типа, определяемого символом Type, и выполнить для него команды из исходного набора, записывая информацию о выполнении команд в текстовый файл с именем Name по тем же правилам, что и в OOP0Begin1. Для выполнения всех команд использовать один вызов функции StackTest (с параметром требуемого типа), преобразовав при необходимости ее вариант, реализованный в одном из предыдущих заданий, в шаблонную функцию.
Примечание 1 (C++). При реализации метода ToStr следует использовать строковый поток вывода ostringstream . Это позволит единообразно формировать требуемое строковое описание и для числовых, и для символьных, и для строковых элементов стека.
Примечание 2 (C++). Поскольку поток ввода pt автоматически определяет тип вводимых данных, выполнение команды Push для шаблонного класса Stack<T> в шаблонной функции StackTest не будет ничем отличаться от выполнения этой команды для реализованного в предыдущих заданиях стека целых чисел.
[C++] template <typename T>
class StackNode
{
public:
T data;
shared_ptr<StackNode> next;
StackNode(T data, shared_ptr<StackNode> next) :
data(data), next(next) {}
};
template <typename T>
class Stack
{
shared_ptr<StackNode<T> > top = nullptr;
public:
virtual void Push(T data)
{
top = make_shared<StackNode<T> >(data, top);
}
// Similarly, change the implementation of all methods
// of the Stack class from the OOP0Begin5 task
};
template <typename T>
class StackC : public Stack<T> {
private:
int cnt = 0;
public:
void Push(T data) override;
// Similarly, change the implementation of all methods
// of the StackC class from the OOP0Begin5 task
};
template <typename T>
void StackC<T>::Push(T data) {
Stack<T>::Push(data);
cnt++;
}
template <typename T>
void StackTest(shared_ptr<Stack<T> > s)
{
// Change the implementation of the StackTest method
// from the OOP0Begin5 task
}
Примечание (C#). При реализации фрагмента обобщенного метода StackTest<T> , отвечающего за выполнение команды Push , необходимо вызывать различные методы ввода (GetInt , GetChar , GetString ) в зависимости от фактического значения обобщенного параметра T . Для проверки значения параметра T можно использовать условие вида typeof(T) == typeof(<конкретный тип>) , где в качестве конкретного типа указывается int , char или string .
Кроме того, следует учитывать, что вызов метода s.Push(GetInt()) приведет к ошибке компиляции, если объект s описан как объект обобщенного типа Stack<T> (даже если этот вызов выполняется после успешной проверки условия typeof(T) == typeof(int) ). Чтобы избежать ошибки компиляции, необходимо выполнить следующее двойное приведение типа: s.Push((T)(object)GetInt()) (такое приведение типа будет успешным только в случае, если параметр T имеет значение int ).
[C#] internal class StackNode<T>
{
internal T data;
internal StackNode<T> next;
internal StackNode(T data, StackNode<T> next)
{
this.data = data;
this.next = next;
}
}
public class StackException : Exception
{
public StackException(string message) : base(message)
{ }
}
public class Stack<T>
{
private StackNode<T> top = null;
public virtual void Push(T data)
{
top = new StackNode<T>(data, top);
}
// Similarly, change the implementation of all methods
// of the Stack class from the OOP0Begin5 task
}
public class StackC<T>: Stack<T>
{
private int cnt = 0;
public override void Push(T data)
{
cnt++;
base.Push(data);
}
// Similarly, change the implementation of all methods
// of the StackC class from the OOP0Begin5 task
}
static void StackTest<T>(Stack<T> s)
{
// Change the implementation of the StackTest method
// from the OOP0Begin5 task
}
Примечание (Java). При реализации фрагмента обобщенного метода stackTest(Stack<T> s) , отвечающего за выполнение команды Push , необходимо вызывать различные методы ввода (getInt , getChar , getString ) в зависимости от фактического значения параметра T . К сожалению, получить фактическое значение параметра T в Java сложно (это требует применения механизма рефлексии типов). В подобной ситуации можно использовать следующее не слишком красивое, но работающее решение: передавать в обобщенный метод stackTest дополнительный параметр t типа Class , определяющий тип, которым параметризован метод (в нашем случае значениями параметра t могут быть int.class , char.class или String.class ), и в зависимости от значения этого параметра вызывать требуемый метод ввода.
Кроме того, следует учитывать, что вызов метода s.push(getInt()) приведет к ошибке компиляции, если объект s описан как объект обобщенного типа Stack<T> . Чтобы избежать ошибки компиляции, необходимо выполнить следующее двойное приведение типа: s.push((T)(Object)getInt()) . Такое приведение типа будет успешным только в случае, если параметр T имеет значение int (это можно установить в методе stackTest путем проверки равенства t == int.class ).
[Java] class StackNode<T> {
public T data;
public StackNode<T> next;
public StackNode(T data, StackNode<T> next) {
this.data = data;
this.next = next;
}
}
class Stack<T> {
private StackNode<T> top = null;
public void push(T data) {
top = new StackNode<T>(data, top);
}
// Similarly, change the implementation of all methods
// of the Stack class from the OOP0Begin5 task
}
class StackC<T> extends Stack<T> {
private int cnt = 0;
@Override
public void push(T data) {
super.push(data);
cnt++;
}
// Similarly, change the implementation of all methods
// of the StackC class from the OOP0Begin5 task
}
public class MyTask extends PT
{
public static <T> void stackTest(Stack<T> s)
throws IOException {
// Change the implementation of the stackTest method
// from the OOP0Begin5 task
}
Примечание (Python). Язык Python является языком с динамической типизацией, поэтому для выполнения этого задания не требуется вносить какие-либо изменения в классы, разработанные в заданиях OOP0Begin1 и OOP0Begin2.
[Python] class StackNode:
def __init__(self, data, next):
self.data = data
self.next = next
class Stack:
def __init__(self):
self.__top = None
def push(self, data):
self.__top = StackNode(data, self.__top)
# Use the implementation of the Stack class
# from the OOP0Begin1 task
class StackC(Stack):
def __init__(self):
super().__init__()
self.__cnt = 0
def push(self, data):
super().push(data)
self.__cnt += 1
# Use the implementation of the StackC class
# from the OOP0Begin2 task
def stackTest(s):
pass
# Change the implementation of the stackTest method
# from the OOP0Begin3 task
Примечание (Ruby). Язык Ruby является языком с динамической типизацией, поэтому для выполнения этого задания не требуется вносить какие-либо изменения в классы, разработанные в заданиях OOP0Begin1 и OOP0Begin2.
[Ruby] class StackNode
def initialize(data, nextNode)
@data = data
@next = nextNode
end
attr_reader :data, :next
end
class Stack
def initialize
@top = nil
end
def push(data)
@top = StackNode.new(data, @top)
end
# Use the implementation of the Stack class
# from the OOP0Begin1 task
end
class StackC < Stack
def initialize
super
@cnt = 0
end
def push(data)
super
@cnt += 1
end
# Use the implementation of the StackC class
# from the OOP0Begin2 task
end
def stackTest(s)
# Change the implementation of the stackTest method
# from the OOP0Begin3 task
end
|