Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2vcTnguyvU

.pdf
Скачиваний:
6
Добавлен:
15.04.2023
Размер:
955.27 Кб
Скачать

// перегрузить оператор >= сравнением стекового

//приоритета текущего объекта и входного приоритета а.

//используется при чтении оператора для определения

//того, следует ли вычислять операторы из стека перед тем,

//как поместить в него новый оператор

int MathOperator::operator>= (MathOperator a) const { return stackprecedence >= a.inputprecedence; }

//вычисляет оператор для текущего объекта. Сначала извлекаются два

//операнда из стека операндов, затем вычисляется результат и снова //помещается в стек операндов

void MathOperator::Evaluate (Stack<float> &OperandStack) { float operand1 = OperandStack.Pop(); // получить левый операнд float operand2 = OperandStack.Pop(); // получить правый операнд

//выполнить оператор и поместить результат в стек

switch (op) { // выбрать операцию case '+' : OperandStack.Push(operand2 + operand1);

break;

case '-': OperandStack.Push(operand2 - operand1); break;

case '*': OperandStack.Push(operand2 * operand1); break;

case '/': OperandStack.Push(operand2 / operand1); break; }

// возвращает операцию ассоциативную с текущим объектом char MathOperator::GetOp(void) {

return op; }

#endif // INFIX_MATH_OPERATIONS

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

Ввод операнда: Поместить операнд в стек операндов

Ввод оператора: Извлечь из стека все операторы, имеющие приоритет больший или равный входному приоритету текущего оператора. Выполнить сравнение, используя метод класса MathOperator">=". Когда операторы будут удалены из стека, выполнить оператор, используя метод Evaluate.

Ввод правой скобки ")" : Извлечь и выполнить все операторы в стеке, имеющие стековый приоритет больший или равный входному приоритету

скобки ")", который равен 0. Стековый приоритет скобки "(" равен -1, так

20

что процесс останавливается , когда встречается скобка "(". Если никакой скобки не обнаружено "(", выражение считается неверным.

• В конце выражения очистить стек операторов. Ранг должен быть 1. Если ранг меньше 1, это означает, что не хватает операнда.

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

//Программа калькулятор

#include <iostream>

 

#include <stdlib.h>

 

#include <ctype.h>

// используется для функции 'isdigit'

#pragma hdrstop

 

#include "tstack.h"

// включить шаблон класса stack

#include "mathop.h"

// определение класса операторов MathOperator

using namespace std;

// проверить: является символ оператором или скобкой int isoperator(char ch) {

if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '(')

return 1;

else

return 0; }

// проверить является символ пробелом или табуляции или переход к новой //строке

int iswhitespace(char ch) {

if (ch == ' ' || ch == '\t' || ch == '\n')

 

return 1;

else

return 0; }

//функция сообщений об ошибках void error(int n) {

// таблица сообщений об ошибках

static char *errormsgs[] = { "Operator expected", "Operand expected", "Missing left parenthesis", "Missing right parenthesis", "Invalid input" };

//”Отсутствует оператор”, “Отсутствует операнд”, “Нет левой скобки” //”Нет правой скобки”, “Неверный ввод”

//параметр n -это индекс ошибки

//печатать сообщение и закончить программу

cerr << errormsgs[n] << endl; exit(1); } int main( ) {

21

//объявить стек операторов с объектами MathOperator Stack<MathOperator> OperatorStack;

//объявить стек операндов

Stack<float> OperandStack;

MathOperator opr1,opr2;

int rank = 0;

 

float number;

 

char ch;

 

// выполнять до знака

'='

while (cin.get(ch) &&

ch != '=') {

// ******** обработка операнда с плавающей точкой ********

if (isdigit(ch) || ch == '.') {

//возвратить знак или '.' и читать число cin.putback(ch);

cin >> number;

//ранг оператора равен 1. суммарный ранг должен быть равен 1 rank++;

if (rank > 1) error(OperatorExpected);

//поместить операнд в стек операндов

OperandStack.Push(number);

}

 

// ********* обработка оператора

**********

 

else if (isoperator(ch))

{

 

 

 

// ранг каждого оператора , отличного от '('

равен -1.

// суммарный ранг должен быть равен 0

 

if (ch != '(') // ранг '('

равен

0

 

 

rank--;

 

 

 

 

if (rank < 0) error(OperandExpected);

 

opr1 = MathOperator(ch);

 

 

 

while(!OperatorStack.StackEmpty() &&

 

(opr2 = OperatorStack.Peek()) >= opr1)

{

opr2 = OperatorStack.Pop();

 

 

opr2.Evaluate(OperandStack);

}

 

OperatorStack.Push(opr1);

}

 

// ********* обработка правой скобки

**********

else if (ch == rightparenthesis)

{

 

// '(' is missing; otherwise, delete '('.

 

 

opr1 = MathOperator(ch);

 

 

while(!OperatorStack.StackEmpty() && (opr2 =

 

OperatorStack.Peek()) >= opr1)

{

 

 

 

 

22

 

 

opr2 = OperatorStack.Pop(); opr2.Evaluate(OperandStack); }

if(OperatorStack.StackEmpty())

error(MissingLeftParenthesis);

opr2 = OperatorStack.Pop( ); }// get rid of '('

// ********* имеем неверный ввод **********

else if (!iswhitespace(ch)) error(InvalidInput); }

//ранг вычисленного выражения должен быть равен 1 if (rank != 1)

error(OperandExpected);

//заполнить стек операторов и завершить вычисление выражения ,

//если найдена левая скобка, а правая отсутствует

while (!OperatorStack.StackEmpty())

{

opr1 = OperatorStack.Pop();

 

if (opr1.GetOp() == leftparenthesis)

 

error(MissingRightParenthesis);

 

opr1.Evaluate(OperandStack); }

 

// значение выражения - в вершине стека операндов

cout << "Значение равно (The value is)" << OperandStack.Pop() << endl; char mn='0';

cout<<"press 1"<<endl; while (mn!='1') cin>>mn; return 0; }

Задание

1

1 .Запустите программу, реализующую вычисление инфиксного выражения. Составьте алгоритм программы. Расширьте программу для операции возведения в степень, которая представлена символом '^'. Возведение в степень является правым ассоциативным. Например:

2 ^ 3=8 //23 =8 или

2 ^ 2 ^ 3 =64

Для вычисления a b

включите математическую библиотеку <math.h> и

используйте функцию pow следующим образом a b = pow(a, b)

2. Напишите шаблонный класс DataStore, который имеет следующие методы:

(a)int Insert(T elt) ;

Вставляет elt в закрытый массив dataElements, имеющий пять элементов типа T. Индекс следующей доступной позиции в dataElements задается данным-членом loc, который также является количеством значений данных в dataElements. Возвращает 0, если больше не остается места в dataElements.

23

int Find (T elt);

Выполняет поиск

элемента

elt

в

dataElements

и

возвращает его индекс, если он найден , и -1, если нет.

 

 

 

 

 

int NumElts( );

Возвращает количество элементов,

сохраняемых

в

dataElements.

 

 

 

 

 

 

 

 

T & GetData(int n);

Возвращает

элемент

в

позиции

n

в

dataElements. Генерирует сообщение об ошибке и выходит, если

n<0 или

n>4.

 

 

 

 

 

 

 

 

3. Напишите перегруженный оператор

“<< “, который

печатает данные

объекта DataStore. Пусть записью будет Person с полями : char name[30];

int age; double balance;

№ 2.

1.Создайте класс контейнера для целых чисел. class vect {

private:

int * p;

int size;

int cur_ind;

 

public:

 

vect();

vect(int n);

array vect (const vect & v); //инициализация от vect vect (const int[], int n); //инициализация от массива ~vect() {delete [] p;}

int ub() {return (size-1); //верхняя граница int & operator [] (int i);

vect & operator=(const vect & v); int & next();

int & current() {return (p[cur_ind]); } void reset_index(int n=0) {cur_ind=n;} };

2. Измените класс vect, введя в класс класс-друг, реализующего функции обращения к элементу контейнера, обход элементов контейнера

class vect_iterator { private:

vect* pv; int cur_ind; public:

vect_iterator(vect & v): cur_ind(0), pv(&v){} int & next();

};

24

3. Выполните параметризацию класса vect и класса vect_iterator

Контрольные вопросы

Объясните, что означает частичная специализация шаблонов.

Найдите ошибочные объявления (или пары объявлений) шаблонов классов:

(а)

template <class Type> class Container1;

 

template <class Type, int size> class Container1;

(b)template <class T, U, class V> class Container2;

(c)

template <class C1, typename C2> class Container3 {};

(d)template <typename myT, class myT> class Container4 {};

(e)template <class Type, int *pi> class Container5;

(f)template <class Type, int val=0> class Container6;

template <class T=complex<double>, int v> class Container6;

Задание для самостоятельной работы

1.Создайте класс Person. Перегрузите оператор “ = = “ для Person так, чтобы он сравнивал поля имен. Напишите программу, которая вставляет элементы данных типа Person до тех пор, пока объект DataStore не будет заполненным. Выполните поиск указанной записи и напечатайте результат поиска.

2.Следующее определение шаблона List некорректно. Как исправить ошибку?

template <class elemenType> class ListItem;

template <class elemType> class List {

public:

List<elemType>() :_at_front( 0 ), _at_end( 0 ), _current( 0 ), _size( 0 ) {} List<elemType>( const List<elemType> & );

List<elemType>& operator=( const List<elemType> & ); ~List();

void insert( ListItem *ptr, elemType value ); int remove( elemType value );

ListItem *find( elemType value ); void display( ostream &os = cout ); int size() { return _size; }

private:

ListItem *_at_front; ListItem *_at_end; ListItem *_current; int _size;

};

25

Глава 6. Наследование

Наследование — механизм создания нового класса из старого. То есть, к существующему классу можно что-то добавить, или изменить его какимто образом для создания нового (порожденного) класса. Этот механизм применяется для повторного использования кода. Наследование позволяет создавать иерархию связанных типов, совместно использующих код и интерфейс.

С++ поддерживает виртуальные (virtual) функции-члены, которые объявлены в основном классе и переопределены в порожденном классе. Иерархия классов, определенная общим наследованием, создает связанный набор пользовательских типов, на которые можно ссылаться с помощью указателя базового класса. При обращении к виртуальной функции через этот указатель в С++ (или ссылку) выбирается соответствующее функциональное определение во время выполнения. Объект, на который указывается, должен содержать в себе информацию о типе, поскольку различие между ними может быть сделано динамически. Каждый объект «знает», как на него должны воздействовать. Эта форма полиморфизма называется чистым полиморфизмом.

С использованием наследования ключевыми элементами методологии ООП становятся:

разработка соответствующего набора типов;

проектирование их возможных связей и применение механизма наследования для совместного использования кода;

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

Производные классы

Для порождения нового класса от существующего может использоваться следующая форма записи:

class имя_класса : (public| protected | private ) имя_базового_класса

{

// объявления членов

}

Одна из особенностей производного класса — видимость унаследованных членов. Для определения доступности членов основного класса порожденному классу используются ключевые слова public, protected и private. Пример производного класса:

enum year { fresh, sops, junior, senior, grad}; class student {

protected:

26

char name[30]; //имя

int student_id; //идентификационный номер студента double gpa; //средний балл

year y; // год обучения public:

student(char * nm, int id, double g, year x); void print(); };

enum support { ta, ra, fellowship, orher }; //вариант финансовой поддержки class grad_student : public student {

protected:

support s; char dept[10]; // кафедра char thesis[80]; //тема диссертации

public:

grad_student (char * nm, int id, double g, year x, support t, char * d, char* th);

void print(); };

Вэтом примере grad_student -производный класс, а student – базовый класс. Использование ключевого слова public с последующим двоеточием в заголовке производного класса означает, что protected и public члены базового класса student должны быть унаследованы как protected и public члены grad_student соответственно. Члены private – недоступны. Общее наследование также означает, что полученный класс grad_student – подтип класса student.

Производный класс представляет собой модификацию основного класса, которая наследует общие и защищенные члены базового класса. Таким образом, в примере grad_student члены student – student_id, gpa, name, year и print унаследованы. Часто производный класс добавляет новые члены к уже существующим членам класса, как в случае класса grad_student, который имеет три новых члена-данных и переопределенную функцию-член print. Функция-член print перекрывается. Это означает то, что производный класс имеет реализацию функции-члена, отличающуюся от базового класса.

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

Вприведенном примере конструктор базового класса выполняет ряд простых инициализаций, используя список инициализаций. Затем он вызывает функцию strcpy() для того, чтобы скопировать имя студента.

27

student::student(char * nm, int id, double g, year x): student_id(id), gra(g), y(x) { strcpy (name, nm); }

Конструктор производного класса:

grad_student::grad_student( char* nm, int id, double g, year x, support t, char* d, char * th):student(nm, id, g, x) , s(t)

{ strcpy(dept, d); strcpy(thesis, th); }

Конструктор для student вызывается как часть списка инициализаторов, так как объект базового класса создается прежде, чем может быть завершен полный объект; grad_student – общий тип, порожденный от базового класса student. В классе student члены student_id и gpa имеют спецификатор области видимости protected. Это делает их видимыми для порожденного класса, но обрабатываемыми не так, как private. Вполне очевидно, что деструкторы должны вызываться в обратном порядке. Поскольку производный класс наследует свойства базового, уничтожение объекта базового класса вызовет уничтожение объекта производного класса. Следовательно, деструктор производного класса должен вызываться до полного уничтожения объекта. Ссылка на порожденный класс может быть неявно преобразована в ссылку на общий базовый класс. Например, в следующем коде

grad_student ga(“Иванов Петя“, 200, 3.2564, grad, ta, “Высшей математики и ПО “, “Исследование передачи данных “);

student rs=&gs;

переменная rs – ссылка на student. Базовый класс для grad_student – student. Следовательно, такое преобразование ссылки допустимо.

Функция-член print – перегружена. Реализуем функции print. void student::print( ) {

cout<< “\n” << name << “ , “<<student_id<< “, “<< y<<”, “ <<gra<<endl; } void grad_student::print( ) {

student::print( );

cout<< dept<<” , “<<s <<”\n”<<thesis<<endl; }

Для того, чтобы grad_student::print вызвала функцию student::print, должен быть использован идентификатор с разрешенной областью student::print. Иначе произойдет бесконечный цикл. Проиллюстрируем некоторые из отношений преобразования между базовым и общим порожденным классами:

# include “student.h” int main() { // ...

student s(“Иванов”, 100, 4.234, fresh), *ps=&s;

grad_student gs(“Сидоров”, 200, 3.246, grad, ta, “АГ“,“Тема 1“), *pgs; ps->print(); //student::print

ps=pgs=&gs;

28

ps->print(); // student::print pgs->print(); // grad_student::print

//...

}

Эта функция объявляет переменные обоих классов и указатели на них. Правило преобразования заключается в том, что указатель на общий порожденный класс может быть неявно преобразован в указатель на базовый класс.

Управление доступом к членам базового класса

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

class имя_производного_класса: уровень_доступа имя_базового_класса

{

// тело класса };

Параметр уровень_доступа определяет статус членов базового класса в производном классе. В качестве этого параметра используются спецификаторы public, private, protected. Если уровень доступа не указан, то для производного класса по умолчанию используется спецификатор private.

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

#include <cstdlib> #include <iostream> using namespace std; class base{

int i,j; public:

void set(int a, int b){i=a; j=b;}

void show(){ cout<<i<<" "<<j<<"\n";} }; class derived: private base{

29

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]