Създаване на заглавни
файлове.
* Стекът е линейна структура от данни, която позволява добавяне и отстраняване
на елемент само от единия си край, наречен връх на стека. Нека функцията
push
добавя елемент в стека
s,
а функцията pop изважда елемент
от стека s.
Изпълнение на функция | Състояние на стека s |
s.push("Able") | "Able" |
s.push("Backer") |
"Backer"
"Able" |
s.push("Charlie") |
"Charlie"
"Backer" "Able" |
s.pop() -> "Charlie" |
"Backer"
"Able" |
s.push("Delta") |
"Delta"
"Backer" "Able" |
s.pop() -> "Delta" |
"Backer"
"Able" |
s.pop() -> "Backer" | "Able" |
s.pop() -> "Able" |
* Създаване на клас за стек от низове.
class Stack {
public:
Stack();
void push(string s); /* добавя нова
стойност в стека */
string pop();
/* изважда стойност от стека */
bool is_empty() const;
private:
vector<string> data; /* съхранява
данните на стека */
int top;
/* сочи върха на стека */
};
Stack::Stack()
{ top = 0; }
/* създава празен стек */
void Stack::push(string s)
{ if (top < data.size())
data[top]
= s; /* има празна позиция във вектора data */
else data.push_back(s); /* векторът
data увеличава размера си */
top++;
}
string Stack::pop()
{ assert(top > 0);
/* стекът не е празен */
top--;
return data[top];
}
bool Stack::is_empty() const
{ return top == 0; }
* Разделяне на информацията между stack.h
и stack.cpp.
Заглавният файл
stack.h
съдържа дефиницията на класа и се включва във всеки файл, който използва
стек.
// stack.h
#ifndef STACK_H
#define STACK_H
#include <vector>
#include <string>
using namespace std;
class Stack {
public:
Stack();
void push(string );
string pop();
bool is_empty() const;
private:
vector<string> data;
int top;
};
#endif
Може да се наложи един и същи заглавен файл да се
включи два пъти в текста на една програма - тогава компилаторът ще даде
синтактична грешка. За да се избегне тази ситуация се използва механизмът
на предпазителите - чрез директивите на препроцесора ifndef,
define
и endif, както е показано
на примера.
Дефинициите на член-функциите на класа Stack
са във файл stack.cpp.
// stack.cpp
#include <cassert>
using namespace std;
#include "stack.h"
Stack::Stack()
{ top = 0; }
void Stack::push(string s)
{ if (top < data.size()) data[top]
= s;
else data.push_back(s);
top++;
}
string Stack::pop()
{ assert(top > 0);
top--;
return data[top];
}
bool Stack::is_empty() const
{ return top == 0; }
// end stack.cpp
Елемент
от низа |
2 | * | ( | 1 | + | 2 | ^ | 2 | * | 3 | ) | + | 2 | ^ | 10 | = |
"пресмятане" |
2^2
=4 |
4*3
=12 1+12 =13 |
2*13
=26 |
2^10
=1024 26+1024 =1050 |
||||||||||||
num_s | 2 | 2 | 2 |
2
1 |
2
1 |
2
1 2 |
2
1 2 |
2
1 2 2 |
2
1 4 |
2
1 4 3 |
2
13 |
26 |
26
2 |
26
2 |
26
2 10 |
1050 |
op_s | * |
*
( |
*
( |
*
( + |
*
( + |
*
( + ^ |
*
( + ^ |
*
( + * |
*
( + * |
* | + | + |
+
^ |
+
^ |
Текстът на програмата за пресмятане на аритметични изрази е разделен
на няколко файла, връзките между които са описани в следната таблица:
Файл (модул) | #include | компилация |
stack.h | - | - |
stack.cpp | stack.h | stack.obj |
error.h | - | - |
error.cpp | error.h | error.obj |
input.h | - | - |
input.cpp |
error.h
input.h |
input.obj |
eval.h | stack.h | - |
eval.cpp |
stack.h
error.h eval.h |
eval.obj |
calc.cpp |
stack.h
input.h eval.h error.h |
calc.exe |
// error.h
#ifndef ERROR_H
#define ERROR_H
#include <string>
using namespace std;
void error(string );
#endif
// input.h
#ifndef INPUT_H
#define INPUT_H
#include <string>
using namespace std;
bool is_operator(string);
string next_token();
#endif
// eval.h
#ifndef EVAL_H
#define EVAL_H
#include <string>
#include "stack.h"
using namespace std;
int precedence(string);
void evaluate(Stack &, string);
#endif
// error.cpp
#include <iostream>
#include <string>
#include "error.h"
using namespace std;
void error(string message)
{ cout << "ERROR: " << message
<< ".\n";
exit(1);
}
// end error.cpp
// eval.cpp
#include <string>
#include <sstream>
#include <cmath>
using namespace std;
#include "stack.h"
#include "error.h"
#include "eval.h"
int precedence(string s)
{ if (s == "+" or s == "-")
return 1;
else if (s == "*" or s == "/")
return 2;
else if (s == "^")
return 3;
else
return 0;
}
double string_to_double(string s)
{ istringstream instr(s);
double x;
instr >> x;
return x;
}
string double_to_string(double x)
{ ostringstream outstr;
outstr << x;
return outstr.str();
}
void evaluate(Stack &num, string op)
{ if (num.is_empty()) error("Syntax error");
double y = string_to_double(num.pop());
if (num.is_empty()) error("Syntax
error");
double x = string_to_double(num.pop());
double z;
if (op == "^") z = pow(x, y);
else if (op == "*") z = x * y;
else if (op == "/")
{ if (y == 0) error("Divide by
0");
else z = x / y;
}
else if (op == "+") z = x + y;
else if (op == "-") z = x - y;
else error("Syntax error");
num.push(double_to_string(z));
}
// end eval.cpp
// input.cpp
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
#include "error.h"
#include "input.h"
bool is_operator(string s)
{ return s == "+" or s == "-" or
s == "*" or s == "/" or s == "^";
}
void skip_whitespace()
{ char ch;
do
{ cin.get(ch);
if (cin.fail()) return;
}
while (isspace(ch));
cin.unget();
}
string next_number()
{ string r;
bool more = true;
while (more)
{ char ch;
cin.get(ch);
if (cin.fail())
more = false;
else if (isdigit(ch))
r = r + ch;
else
{ cin.unget();
more =
false;
}
}
return r;
}
string next_token()
{ skip_whitespace();
char ch;
cin.get(ch);
if (cin.fail()) return "";
if (isdigit(ch)) { cin.unget();
return next_number(); }
else
{ string token;
token = token + ch;
if (is_operator(token)
or token == "(" or
token == ")" or token == "=") return token;
else
{ string message =
"Unexpected input ";
error(message
+ ch);
return
"";
}
}
}
// end input.cpp
// calc.cpp
#include <iostream>
#include <string>
using namespace std;
#include "stack.h"
#include "input.h"
#include "eval.h"
#include "error.h"
int main()
{ Stack numstack;
Stack opstack;
while (true)
{ string s = next_token();
if (is_operator(s))
{ if (opstack.is_empty())
opstack.push(s);
else
{ string
old_op = opstack.pop();
if (precedence(s) > precedence(old_op)) opstack.push(old_op);
else evaluate(numstack, old_op);
opstack.push(s);
}
}
else if (s == "(")
opstack.push(s);
else if (s == ")")
{ bool more = true;
while (more)
{ if (opstack.is_empty())
error("No matching (");
string old_op = opstack.pop();
if (old_op == "(") more = false;
else evaluate(numstack, old_op);
}
}
else if (s == "=")
{ while (not opstack.is_empty())
{ string
old_op = opstack.pop();
if (old_op == "(") error("No matching )");
else evaluate(numstack, old_op);
}
if (numstack.is_empty())
error("Syntax error");
cout <<
numstack.pop() << "\n";
if (not
numstack.is_empty()) error("Syntax error");
}
else if (s == "") return
0;
else numstack.push(s);
}
}
// end calc.cpp
2*(1 + 2^2*3) + 2^10 =
1050 (25/5 + 5)*10 - 4*(5 - 4/(2^2)) - 50 = 34 |
Проекти и make
файлове.
Когато програмата е разделена на няколко модула,
можем да компилираме всеки от тях отделно. Компилаторът създава обектни
файлове за всеки модул. Свързващата програма (link) обработва
всички модули и създава изпълним файл. В интегрирана среда за програмиране,
обединяването на файловете от една програма става със създаване на проекти.
Например в среда на Dev-C++ трябва
да отворим нов проект и да добавим всички файлове от програмата. За програмата
за пресмятане на аритметични изрази, това са файловете:
calc.cpp
stack.cpp
error.cpp
input.cpp
eval.cpp
Самият проект се записва във файла project.dev.
Когато се работи с извикване на компилатора от командния
ред (извън интегрирана среда), най-често се използва специален файл,
наречен make-файл. Той е текстов файл и описва модулите на програмата и
тяхното взаимодействие.
Например програманата среда Dev-C++
създава такъв файл и по-долу е даден този файл за задачата за пресмятане
на аритметични изрази (операционна система Windows 98).
Makefile.win
# Project: Project1
# Makefile created by Dev-C++ 4.9.8.0 CPP = g++.exe
.PHONY: all all-before all-after clean clean-custom all: all-before Pro.exe all-after clean: clean-custom
$(BIN): $(LINKOBJ)
calc.o: calc.cpp
stack.o: stack.cpp
input.o: input.cpp
eval.o: eval.cpp
error.o: error.cpp
|
Поделяне на променливи и константи
между модули.
Функции/класове се използват от няколко модула като
се декларират/дефинират в заглавни файлове и тези файлове се включват с
директивата на препроцесора
#include. Такава техника се
използва и за поделяне на константи и променливи, дефинирани в заглавни
файлове. Когато константа или променлива е дефинирана в някой .cpp
файл и трябва да се използва от други модули, тя се дефинира там като външен
обект (с ключовата дума
extern).
Пример.
// файл first.cpp
...
int global_size;
void f()
{ cout << global_ size;
...
}
// файл second.cpp
...
extern int global_size;
...
int main()
{ global_size = 100;
...
}
Следва разделна компилация на двата файла (модула)
и свързването им в една програма.