Переделаем нашу предидущую программу, но на этот раз уже напишем её на C++.
Создайте новый каталог проекта kalkulcpp, а в нём три файла: problem.h, problem.cpp, main.cpp.
problem.h
//------------------------------
#ifndef PROBLEM_H_
#define PROBLEM_H_
#include <string>
using namespace std;
class CProblem
{
private:
float Numeral;
float SecondNumeral;
string Operation;
float Result;
string Error;
bool Calculate();
public:
void SetValues();
void Solve();
};
#endif /*PROBLEM_H_*/
problem.cpp
//-------------------------------------------
#include <iostream>
#include <cmath>
#include "problem.h"
using namespace std;
void CProblem::SetValues()
{
cout << "Число: ";
cin >> Numeral;
cout <<"Арифметическое действие (+,-,*,/,pow,sqrt,sin,cos,tan): ";
cin >> Operation;
}
bool CProblem::Calculate()
{
if(Operation == "+")
{
cout << "Второе слагаемое: ";
cin >> SecondNumeral;
Result = Numeral + SecondNumeral;
return true;
}
else if(Operation == "-")
{
cout << "Второе слагаемое: ";
cin >> SecondNumeral;
Result = Numeral - SecondNumeral;
return true;
}
else if(Operation == "*")
{
cout << "Множитель: ";
cin >> SecondNumeral;
Result = Numeral * SecondNumeral;
return true;
}
else if(Operation == "/")
{
cout << "Делитель: ";
cin >> SecondNumeral;
if(SecondNumeral == 0)
{
Error = "Ошибка: деление на ноль.";
return false;
}
else
{
Result = Numeral/SecondNumeral;
return true;
}
}
else if(Operation == "pow")
{
cout << "Степень: ";
cin >> SecondNumeral;
Result = pow(Numeral,SecondNumeral);
return true;
}
else if(Operation == "sqrt")
{
Result = sqrt(Numeral);
return true;
}
else if(Operation == "sin")
{
Result = sin(Numeral);
return true;
}
else if(Operation == "cos")
{
Result = cos(Numeral);
return true;
}
else if(Operation == "tan")
{
Result = tan(Numeral);
return true;
}
else
{
Error = "Ошибка ввода действия.";
return false;
}
}
void CProblem::Solve()
{
if(Calculate() == true)
cout << Result << "\n";
else
cout << Error << "\n";
}
main.cpp
//------------------------------------------
#include <iostream>
#include "problem.h"
using namespace std;
int main(void)
{
CProblem *Problem;
Problem = new CProblem;
Problem->SetValues();
Problem->Solve();
delete Problem;
return(0);
}
Для сборки программ на C++ в наборе GNU имеется компилятор – G++. Он отличается от GCC тем, что по умолчанию подключает
не стандартную библиотеку C, а стандартную библиотеку C++. Все флаги и опции у G++ такие же точно, как и у GCC.
Воспользуемся компилятором G++.
g++ problem.cpp main.cpp -o kalkul
Обратите внимание, что никаких дополнительных библиотек мы не подключали. Это означает, что математические функции входят
в стандартную библиотеку C++. Вообще, в тех случаях, когда программа пишется на C++, рекомендуется использовать именно G++.
Вы уже знаете, что собирать программы таким простым способом можно только в учебных целях.
В профессиональной же работе ваши проекты будут включать большое количество файлов, и вам придётся пользоваться утилитой make.
Создадим Makefile:
kalkul: problem.o main.o
g++ problem.o main.o -o kalkul
problem.o: problem.cpp problem.h
g++ -c problem.cpp
main.o: main.cpp problem.h
g++ -c main.cpp
clean:
rm -f kalkul problem.o main.o
install:
cp kalkul /usr/local/bin/kalkul
uninstall:
rm -f /usr/local/bin/kalkul
И соберём эту же программу снова, но уже «правильным» способом:
make
Инсталлируем её, предварительно зайдя в систему, как суперползователь:
su
make install
exit
Деинсталлируем:
su
make uninstall
exit
И очистим дистрибутив:
make clean
Знакомство с отладчиком gdb
Ошибки, к сожалению, встречаются в любой программе, каким бы крутым профессионалом её разработчик ни был.
Поэтому, нравится это вам или нет, пользоваться отладчиком вам всё равно придётся. Жизнь заставит.
И чем больше времени вы сейчас потратите на изучение работы с ним, тем больше времени это вам сэкономит в дальнейшем.
Мы рассмотрим отладчик GDB, входящий в комплект программ GNU.
Для того, чтобы им пользоваться, нужно сначала скомпилировать программу так, чтобы её двоичный файл
содержал отладочную информацию.
Эта информация включает в себя, в частности, описание соответствий между адресами исполняемого кода
и строками в исходном коде.
Такая компиляция достигается путём добавления флага -g к команде на компиляцию. Н
апример, если бы мы собирали программу kalkul без применения Makefile, мы бы дали такую команду:
g++ main.cpp problem.cpp -o kalkul -g
Если же мы пользуемся командой make, то надо поставить опцию CFLAGS=-g.
Тогда все команды на компиляцию, содержащиеся в Make-файле, автоматически получат флаг -g.
Или измените файл Makefile, добавивь опцию -g (для программы С++):
kalkul: problem.o main.o
g++ -g problem.o main.o -o kalkul
problem.o: problem.cpp problem.h
g++ -g -c problem.cpp
main.o: main.cpp problem.h
g++ -g -c main.cpp
clean:
rm -f kalkul problem.o main.o
install:
cp kalkul /usr/local/bin/kalkul
uninstall:
rm -f /usr/local/bin/kalkul
Или для программы, написанной на C:
kalkul: calculate.o main.o
gcc -g calculate.o main.o -o kalkul -lm
calculate.o: calculate.c calculate.h
gcc -g -c calculate.c
main.o: main.c calculate.h
gcc -g -c main.c
clean:
rm -f kalkul calculate.o main.o
install:
cp kalkul /usr/local/bin/kalkul
uninstall:
rm -f /usr/local/bin/kalkul
Давайте возьмём программу, которую мы создали из файлов main.cpp, problem.cpp и problem.h
(мы тогда называли этот каталог проекта kalkulcpp). У нас Makefile уже сформирован. Воспользуемся им.
Очистим пакет от результатов предыдущей сборки:
make clean
Соберём программу снова, но уже с включением отладочной информации:
make CFLAGS=-g
Или соберем программу с измененным Makefile (рекомендуется этот вариант):
make
Запустим отладчик GDB, загрузив в него нашу программу для отладки:
gdb ./kalkul
Чтобы запустить программу внутри отладчика,даётся команда run:
run
Чтобы посмотреть исходный код, даётся команда list:
list
Если дать эту команду без параметров, то она первые девять строк исходного кода главного файла
(то есть такого, в котором имеется функция main). Чтобы просматривать файл дальше,
надо снова набирать list. Чтобы посмотреть конкретные строки, надо указать два параметра:
с какой строки начинать просмотр, и с какой строки заканчивать.
list 12,15
Чтобы просмотреть другие файлы проекта, надо перед номерами строк указать название нужного файла и отделить
его от номеров строк двоеточием.
list problem.cpp:20,29
Поставим точку останова на строке номер 21.
Точка останова – это метка, указывающая, что программа, дойдя до этого места, должна остановиться.
list problem.cpp:20,27
break 21
Посмотреть, где вы поставили точки останова, можно с помощью команды info breakpoints.
info breakpoints
(При желании можно вместо номера строки указать название функции,тогда программа остановится перед входом в функцию.)
Запустим программу.
run
Введём первое число 5 и знак математического действия « + ». Программа дойдёт до точки останова и остановится,
выведя нам строку, у которой эта точка расположена.
Нам, конечно, интересно знать,в каком именно месте мы остановились, и что программа уже успела выполнить.
Даём команду backtrace.
backtrace
Отладчик выдаёт нам следующую информацию:
#0 CProblem::Calculate (this=0x804b008) at problem.cpp:21
#1 0x08048e00 in CProblem::Solve (this=0x804b008) at problem.cpp:93
#2 0x08048efc in main () at main.cpp:15
Это означается, что мы находимся внутри выполняющейся функции Calculate, являющейся функцией-членом класса CProblem.
Она была вызвана из функции Solve того же класса, а та, в свою очередь, из функции main.
Таким образом, команда backtrace показывает весь стек вызываемых функций от начала программы до текущего места.
Посмотрим, чему же равно на этом этапе значение переменной Numeral.
print Numeral
И нам сразу выводится число 5, которое мы и вводили в программу.
(Значение, введённое нами с клавиатуры, присвоилось именно этой переменной.)
Если мы вместо print будем пользоваться командой display, то величина этой переменной будет показываться каждый раз,
когда программа останавливается, без специального указания.
display Numeral
Добавим ещё одну точку останова на строке 25 файла problem.cpp.
break problem.cpp:25
Продолжим выполнение программы.
continue
Команда Continue продолжает выполнение программы с текущего адреса. Если бы мы набрали run, программа начала
бы выполняться с начала.
Поскольку на строке 24 имеется команда cin >> SecondNumeral, то нам придётся ввести второе слагаемое.
Введём, например,число 2. После этого программа опять остановится на строке 25 (наша вторая точка останова).
Посмотрим, чему равны значения наших переменных Numeral, SecondNumeral и Operation.
Если вы помните, именно такие переменные мы объявляли в классе CProblem.
print Numeral
print Operation
print SecondNumeral
У нас получится 5, « + », 2. Так и должно быть. Но давайте теперь «передумаем» и лучше присвоим переменной
SecondNumeral значение 4.
Отладчик GDB позволяет прямо во время выполнения программы изменить значение любой переменной.
set SecondNumeral=4
Если не верим, что её значение изменилось,можно проверить.
print SecondNumeral
Теперь мы ожидаетм, что результат будет 9. Давайте выполним программу до конца.
continue
Результат действительно равен 9.
Давайте теперь уберём наши точки останова. Мы, кажется, создали две таких точки. Но это можно проверить.
info breakpoints
Удалим их.
delete 1
delete 2
У нас не должно остаться ни одной точки останова. Проверяем.
info breakpoints
Действительно не осталось ни одной.
Теперь давайте пошагово пройдём всю программу (благо, она у нас небольшая).
Поставим точку останова на десятой строке главного файла.
break main.cpp:10
Запустим программу
run
Дойдя до десятой строчки, она остановится. Теперь проходим её, останавливаясь на каждой строчке,
с помощью команды step.
step
Чтобы не набирать каждый раз s-t-e-p, можно просто вводить букву s. Как только программа доходит
до команды Problem->SetValues(), она сразу переходит в файл problem.cpp, где находится определение
функции-члена CProblem::SetValues()
и проходит код этой функции. То же самое, когда она дойдёт до вызова Problem->Solve().
Чтобы при вызове функции, программа не входила в неё, а продолжала дальше выполняться только на текущем уровне стека,
вместо step даётся команда next или просто n.
next
Если мы вошли в функцию, но не хотим дальше проходить её по шагам, а хотим, чтобы она отработала и вернула нас
на предыдущий уровень стека (то есть, обратно в функцию, вызвавшую её), мы пользуемся командой finish.
finish
Таким образом, можно просмотреть, как выполняется вся программа или любой участок программы.
На любом шаге можно проверять значение любой переменной. Чтобы перестать проходить программу
по шагам и запустить её до конца, надо дать команду continue.
Дадим короткий список наиболее часто встречающихся команд отладчика GDB. За более подробной информацией
вы, конечно, всегда можете обратиться к встроенному описанию программы (info gdb) или руководством
по пользованию (man gdb).
Команды отладчика GDB
backtrace – выводит весь путь к текущей точке останова, то есть названия всех функций, начиная от main();
иными словами, выводит весь стек функций;
break – устанавливает точку останова; параметром может быть номер строки или название функции;
clear – удаляет все точки останова на текущем уровне стека (то есть в текущей функции);
continue – продолжает выполнение программы от текущей точки до конца;
delete – удаляет точку останова или контрольное выражение;
display – добавляет выражение в список выражений, значения которых отображаются каждый раз при остановке программы;
finish – выполняет программу до выхода из текущей функции; отображает возвращаемое значение,если такое имеется;
info breakpoints – выводит список всех имеющихся точек останова;
info watchpoints – выводит список всех имеющихся контрольных выражений;
list – выводит исходный код; в качестве параметра передаются название файла исходного кода, затем, через двоеточие,
номер начальной и конечной строки;
next – пошаговое выполнение программы, но, в отличие от команды step, не выполняет пошагово вызываемые функции;
print – выводит значение какого-либо выражения (выражение передаётся в качестве параметра);
run – запускает программу на выполнение;
set – устанавливает новое значение переменной
step – пошаговое выполнение программы;
watch – устанавливает контрольное выражение, программа остановится, как только значение контрольного выражения изменится;
Описание примера
Этот пример мы будем использовать во всех остальных статьях.
Легенда: Слоны на водопое
В большинстве африканских национальных парков
существуют значительные экологические проблемы, вызываемые тем, что плотность
животного населения превышает возможности природной среды на ограниченной
территории. Одним из наиболее сильных факторов, воздействующих на экологию,
являются Слоны. Для соблюдения природного баланса стаду Слонов нужна довольно
большая территория, не совпадающая с границами национальных парков, поэтому
жизнь Слона в национальном парке представляет собой бесконечную цепь проблем,
главная из которых - удовлетворение проблем в воде в засушливый сезон.
Поскольку Слоны - чрезвычайно обаятельные животные с ярко выраженными
индивидуальностями, мы принимаем их проблемы особенно близко к сердцу.
В нашей "легенде" стадо Слонов ищет воду в саванне. Условия поиска и
потребления воды уточняются в каждой задаче отдельно. Каждый Слон
представляется отдельным процессом или нитью, но программы, выполняющиеся в процессах-Слонах, идентичны.
Наряду с процессами-Слонами, есть процесс-монитор, являющийся родительским для всех процессов-Слонов.
Процесс-монитор во всех задачах осуществляет порождение процессов-Слонов, общее управление ими,
а также обеспечение взаимодействия между ними при необходимости.
Таким образом, процесс-монитор представляет собой "коллективный разум" слоновьего стада, мы назвали его
Ганеша.
Каждый отдельный Слон характеризуется такими атрибутами, как:
имя
возраст (от возраста может зависеть, во-первых, время, затрачиваемое
на поиск воды: чем старше Слон, тем быстрее он находит воду, во-вторых,
время потребления воды: чем старше Слон, тем быстрее он пьет);
вес в тоннах (чем тяжелее Слон, тем больше воды ему требуется).
Программное представление Слона описывается в файле elephant.h,
который мы включаем в программы всех наших примеров.
Во всех примерах мы также работаем с одним и тем же стадом - массивом Слонов. Стадо определяется также в файле
elephanth.h, который мы также включаем во все примеры.
Значительная часть программных кодов примеров каждой работы будет переноситься в следующие работы.
Поэтому такие фрагменты кода оформлены как включаемые файлы (.h-файлы).
По смыслу задач "деятельность" Слонов (поиск воды в саванне и насыщение водой) занимает некоторое время.
При этом это время занято реальной деятельностью, а не ожиданием (даже если в программной модели результаты этой деятельности
не рассматриваются). В терминах программирования это означает, что процесс,
выполняющий эту "деятельность", занимает центральный процессор. Поэтому для
моделирования такой "деятельности" не применяются системные вызовы типа
sleep,
nanosleep,
так как они обеспечивают задержку процесса в состоянии ожидания, без использования центрального
процессора. Для "занятого ожидания" нами созданы две функции:
функция a0wait обеспечивает занятое
ожидание в течении заданного интервала времени;
функция a1wait обеспечивает занятое
ожидание в течении случайного интервала времени.
Исходные тексты этих функций доступны для вас в каталоге
/home/OS/Metod/Lab_2010/Part_2/OS_Examples в файле
common/wait.c.
В том же каталоге в файле curtime.c определена вспомогательная
функция curtime, позволяющяя получить
символьное представление текущего времени.