Интерпретатор математических выражений



Напишем простейший интерпретатор, который будет считать нам какие-нибудь математические выражения, например тригонометрические функции. Скажем, я ввожу в программу sin 15 и она мне должна посчитать. Вводить условимся для удобства в градусах.

#include <iostream>

#include <cmath>

#include <string>

using namespace std;

double rad(double t) { return t*M_PI/180; }

int main () {

           string s;

           double t;

           while(1) {

                           cout << "--> ";

                           cin >> s;

                           if (s=="sin") {

                                           cin>>t;

                                           cout << sin(rad(t)) << endl;

                           }

                           if (s=="cos") {

                                           cin>>t;

                                           cout << cos(rad(t)) << endl;

                           }

                           if (s=="tg") {

                                           cin>>t;

                                           cout << tan(rad(t)) << endl;

                           }

                           cout << endl;

           }

}

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

#include <iostream>

#include <cmath>

#include <string>

using namespace std;

double rad(double t) { return t*M_PI/180; }

double grad(double t) { return t*180/M_PI; }

void count1 (string s) {

           double t;

           if (s=="sin") {

                      cin>>t;

                           cout << sin(rad(t)) << endl;

           }

           if (s=="cos") {

                           cin>>t;

                           cout << cos(rad(t)) << endl;

           }

           if (s=="tg") {

                           cin>>t;

                           cout << tan(rad(t)) << endl;

           }

           if (s=="arcsin") {

                           cin>>t;

                           cout << grad(asin(t)) << endl;

           }

           if (s=="arccos") {

                           cin>>t;

                           cout << grad(acos(t)) << endl;

           }

           if (s=="arctg") {

                           cin>>t;

                           cout << grad(atan(t)) << endl;

           }

           if (s=="ln") {

                           cin>>t;

                           cout << log(t) << endl;

           }

           if (s=="lg") {

                           cin>>t;

                           cout << log10(t) << endl;

           }

           if (s=="lm") {

                           cin>>t;

                           cout << log(t)/log(2) << endl;

           }

           if (s=="exp") {

                           cin>>t;

                           cout << exp(t) << endl;

           }

}

int main () {

           string s;

           while(1) {

                           cout << "--> "; cin >> s; count1(s);

                           cout << endl << endl;

           }

}

Но у нас есть функции также от 2 переменных. Например, логарифм от произвольного основания или возведение в степень. Начинаться пусть они будут с цифры и пусть будут такие операции:

5 ^ 4 = 5 в 4-й степени

7 log 3 = логарифм 7 по основанию 3

127 root 6 = корень 6-й степени из 127

Сложение, вычитание, умножение и деление стандартно. Остаток через mod. Целочисленное деление через div.

...

Функция будет выглядеть так:

void count2 (string s) {

           double a= StringToDoubleFull(s);

           double b; int c;

           cin>>s;

           if(s=="^") {

                           cin>>b;

                           cout << pow(a,b) << endl;

           }

           if(s=="root") {

                           cin>>b;

                           cout << pow(a,1.0/b) << endl;

           }

           if(s=="log") {

                           cin>>b;

                           cout << log(a)/log(b) << endl;

           }

           if(s=="div") {

                           cin>>c;

                           cout << (int)a/c << endl;

           }

           if(s=="mod") {

                           cin>>c;

                           cout << (int)a%c << endl;

           }

           if(s=="+") {

                           cin>>b;

                           cout << a+b << endl;

           }

           if(s=="-") {

                           cin>>b;

                           cout << a-b << endl;

           }

           if(s=="*") {

                           cin>>b;

                           cout << a*b << endl;

           }

           if(s=="/") {

                           cin>>b;

                           cout << a/b << endl;

           }

}

Как понять, какую именну функцию из двух вызывать? Т.к. у нас нет проверки на вещественное число, будет проверять первый символ- если это цифра или минус, тогда значит походу всё слово это вещественное число. И используем вторую функцию.

int main () {

           string s;

           while(1) {

                           cout << "--> "; cin >> s;

                           if(isDigit(s[0])||(s[0]=='-')) count2(s);

                           else count1(s);

                           _(2);

           }

}

В первую программу добавлены возможности вызвать число пи и число е:

           if (s=="e") cout << exp(1) << endl;

           if (s=="pi") cout << M_PI << endl;

Ну и далее эту программу можно ещё дополнять новыми функциями сколько душе угодно, дорабатывать.

Если у нас 1 аргумент, то формат такой: <string> <double>

Если 2 аргумента, то <double> <string> <double>

Если нет аргументов (как с числом пи), то просто: <string>

...

Также можно сделать специальную функцию, которая покажет нам какие действия в программе вообще доступны:

void CountList() {

           __(0);

           cout << "sin 15" << endl;

           cout << "cos 15" << endl;

           cout << "tg 15" << endl;

           cout << "arcsin 0.707" << endl;

           cout << "arccos 0.707" << endl;

           cout << "arctg 0.707" << endl;

           cout << "ln 100" << endl;

           cout << "lg 100" << endl;

           cout << "lm 100" << endl;

           cout << "sqrt 16" << endl;

           cout << "cbrt 27" << endl;

           cout << "abs -5" << endl;

           cout << "exp 3" << endl;

           cout << "e" << endl;

           cout << "pi" << endl;

           _();

           cout << "5 + 3" << endl;

           cout << "5 - 3" << endl;

           cout << "5 * 3" << endl;

           cout << "5 / 3" << endl;

           cout << "5 ^ 3" << endl;

           cout << "5 mod 3" << endl;

           cout << "5 div 3" << endl;

           cout << "5 root 3" << endl;

           cout << "5 log 3" << endl;

           __(2);

}

Вызывается эта функция командой /help через функцию count1():

if(s=="/help") CountList();

 

________________________________________________

РАЗДЕЛ 7

Интересные Дополнения

 

Вектор

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

Для работы с векторами нам нужно подключить соответствующую библиотеку:

#include <vector>

Создаётся вектор так же, как мы делали со стеком или очередью- это шаблон класса, поэтому нужно указывать тип, например: vector <int> x;

Инициализация такого типа: vector <int> v= {1,2,3,4,5}; у меня в devcpp не работала- говорят что нужна версия с++ 11.

Можно инициализировать так:

vector <int> v(3);

v[0]=5; v[1]=7; v[2]=8; cout << v[1] << endl;

Можно работать с векторами как с обычными массивами, но тут есть ряд расширений функционала, сейчас посмотрим ниже.

1. Сравнение векторов- происходит как сравнение обычных переменных:

vector <int> a(3), b(3);

for (int i=0; i<3; i++) {

           a[i]= i*i; b[i]= i*i;

}

if (a==b) cout << "Равны" << endl;

2. Размер вектора: cout << a.size() << endl; // для нашего вектора выведет 3

Можно использовать в цикле:

for (int i=0; i<a.size(); i++) делать что-то там

3. Узнать, пустой ли вектор: a.empty() - логический метод.

4. Добавление элемента в конец вектора:

vector <int> a(3);

for (int i=0; i<a.size(); i++) a[i]= (i+1)*(i+2);

a.push_back(19);

И теперь уже можно выводить элемент a[3], он создался благодаря методу push_back. Также изменился размер вектора, теперь он равен 4.

5. Удаление последней ячейки: a.pop_back(); т.е. это уже наоборот- удалить то что мы только сейчас создали. Теперь размер вектора снова равен 3, но при этом a[3] всё так же доступен и можно вывести, будет 19. Можно обнулить так: a[3]= NULL;

Функции добавления и удаления можно использовать в цикле.

6. Вставка элемента в начало вектора:

a.insert(a.begin(),18);

И можно всё вывести: for (int i=0; i<a.size(); i++) cout << a[i] << " "; // 18 2 6 12 19

А такая команда аналогична push_back: a.insert(a.end(),18);

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

7. Значение первой ячейки: a.front(); Последней- a.back();

8. Как создать вектор векторов:

vector < vector <int> > a;

Т.е. мы создали вектор из векторов типа int.

Если у нас есть некоторый вектор x, т.е. мы можем добавить его в наш вектор векторов:

a.push_back ( vector<int>(x));

Пусть у меня: vector <int> x; x.push_back(3); x.push_back(8);

Можно выводить элементы как у двумерного массива: cout << a[0][1] << endl; // выведет 8

Отсюда следует, что можно создавать "неровные" двумерные массивы благодаря векторам, т.е. у нас будет вектор из векторов разного размера. И это будет нормально восприниматься программой. Ниже пример из вектора 2 векторов, где у первого 2 элемента, а у второго 1:

vector <int> x; x.push_back(3); x.push_back(8);

vector <int> y; y.push_back(6);

vector < vector <int> > a;

a.push_back ( vector<int>(x));

a.push_back ( vector<int>(y));

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

 

Структуры

Для ООП (объектно-ориентированного программирования), как известно, мы используем классы. Но если в языке нет ООП, мы всё равно можем объединять разные типы в одно. В с++ тоже так можно делать, тут это называется структура. В отличие от классов, тут нет инкапсуляции, наследования и полиморфизма. Все поля по умолчанию public, т.е. общедоступны. Вот например структура для точки:

struct point {

double x, y; };

Объявление аналогично как мы делаем в классе. И использовать можно так же:

point M; M.x= 10; и т.д. и т.п.

Также в структурах мы можем использовать и функции (методы), как мы делали в классах.

 

Перечисляемые типы

Перечисляемый тип- ещё один инструмент программирования. На примере:

enum week { mon=1, tue, wed, thu, fri, sat, sun}; // создаём такой тип

cout << mon << endl; // выведет 1

cout << tue << endl; // 2

cout << wed << endl; // 3

cout << thu << endl; // 4

cout << fri << endl; // 5

cout << sat << endl; // 6

cout << sun << endl; // 7

Т.е. первой переменной в перечисляемом типе (mon) мы присваиваем 1, а далее следующие будут просто инкрементироваться. По умолчанию первой переменной задаётся 0.

Или я могу присвоить так: enum week { mon, tue=4, wed, thu, fri=11, sat, sun};

Тогда выведутся числа: 0, 4, 5, 6, 11, 12, 13

Зачем это нужно? Чтобы сделать код более понятным и читаемым для человека.

 

Пространства имён

Пространство имён- это ещё один инструмент, позволяющий нам объединять переменные в единое целое. Может использоваться для большей систематизации переменной в программе. Это больше важно именно при командном программировании, когда каждый создаёт свои переменные и не всегда понятно, кто какие переменные с какими именами уже завёл. Мы уже встречались со встроенным: using namespace std; Но мы можем создавать и свои, например:

namespace cat {

           int age= 6;

           void mrrow (int k) { for(int i=0;i<k;i++)cout << "Mrrow!!!" << endl;}

}

Далее можем использовать: cat::mrrow(5);

Или чтобы не писать постоянно cat:: мы можем написать так: using namespace cat; пишется это после объявления самого пространства имён cat.

#include <iostream>

using namespace std;

namespace cat {

           int age= 6;

           void mrrow (int k) { for(int i=0;i<k;i++)cout << "Mrrow!!!" << endl;}

}

using namespace cat;

int main() {         

           mrrow(5);

           system("pause");

}

 

Встроенные функции

Есть такие функции, которые называются встроенными или inline. Чтобы сделать функцию встроенной, нужно перед её объявлением просто написать inline:

inline double cube (double x) { return x*x*x; }

И дальше работать с ней как с обычной функцией.

В чём же смысл этого? Встроенные функции позволяют увеличить скорость программы, но ценой занимаемого места. Если функция объявлена как встроенная, то компилятор на место вызовов функций вместо ссылок на функцию будет вставлять сам код функции, за счёт чего программа работает быстрее. Но при этом программа будет более громоздкой- и если проект большой, функций много и все они большие и встроенные и вызываются очень много раз- это наоборот может пойти в минус, поэтому лучше использовать встраивание для небольших функций (как в примере выше), которые вызываются много раз.

 


Дата добавления: 2018-09-22; просмотров: 209; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!