Michał Rudowicz
nr indeksu: 171047
Ćwiczenia prowadził: mgr inż. Karol Puchała
Kurs: Programowanie Obiektowe INEW00002
Wykładowca: dr hab. inż., prof. nadzw. Jerzy Kisilewicz
Program składa się z dwóch składników. Główny z nich, rysuj.exe, pozwala na rysowanie wykresów funkcji. Drugi, cli-onp.exe, służy do testowania mechanizmu obliczania wyników wyrażeń.
Program ten uruchamia się w konsoli tekstowej.
Oczekuje na wprowadzenie wyrażenia matematycznego, po czym wypisuje wynik
obliczeń bądź też komunikat błędu, jeśli coś się nie uda.
Po uruchomieniu programu ukazuje się okno wraz z wykresem i kontrolkami
umożliwiającymi rysowanie wykresu.

W pierwszej kontrolce, opisanej jako Wzór należy wpisać wyrażenie matematyczne, według którego program ma narysować
wykres.
Należy pamiętać o ograniczeniach w obecnej wersji programu - liczby ujemne
należy wpisywać nie jako -x, ale 0-x1).
Dodatkowo nie należy stosować skrótów, na przykład 3*x jest poprawne, ale 3x już nie.
Trzeba również stosować pełne nawiasowanie, chociażby cosx zostanie wykryte
przez program jako niepoprawny znak, wobec czego należy stosować cos(x).
Zakres to dwie liczby oznaczające, od którego do którego argumentu program ma rysować wykres.
Rozdzielczość oznacza ilość próbek, które program przeliczy w celu narysowania wykresu. Domyślnie, w celu zachowania gładkości obrazu liczba ta jest ustalona na 500, czyli tyle, ile wynosi szerokość wykresu. Minimalną wartością, którą można tam wpisać, jest 2. Program zawsze liczy o jedną próbkę więcej, niż wynosi wartość w polu Rozdz.
W przypadku wykrycia błędu program w miejscu rysowania wykresu wyświetli stosowny komunikat z żółtym trójkątem w tle, mającym na pierwszy rzut oka ostrzec użytkownika. W chwili obecnej rozpoznawane są 3 rodzaje błędu:
Klasa Znak jest wykorzystywana do przechowywania znaków.
Składa się z dwóch zmiennych typu double: Rodzaj i Wartość.
Rodzaj określa rodzaj znaku (np.
Operator, Funkcja, Nawias, Liczba, Zmienna bądź Błąd), natomiast wartość
może określać wartość liczby bądź np.
rodzaj operatora (mnożenie, dzielenie itp.).
Obiekt tej klasy może mieć następujące znaczenie:
Wartość może przyjmować następujące wartości:
dodać tę tabelę
Możliwe wartości:
dodać tę tabelę
Wszystkie zmienne składające się na tę klasę są publiczne, ponieważ jest ona jedynie „kontenerem” dla nich. Klasa ta posiada również następujące funkcje:
Znak& operator=(const string);
Argument, będący obiektem typu string, jest rozpoznawany i zmienne wchodzące w skład obiektu są odpowiednio ustawiane. Jeśli nie uda się rozpoznać znaku, to wtedy rodzaj jest ustawiany jako -1 (błąd).
unsigned int opWeight(void);
Zwraca wagę operatora na podstawie wartości zmiennej Wartość (czyli nie rozróżnia, czy znak jest w rzeczywistości operatorem, funkcją czy liczbą). Im mniejsza zwracana wartość, tym mniejsza waga operatora.
Klasa ta jest klasą bazową dla klas StosikZnakowy i StosikDouble.
Jest klasą abstrakcyjną, więc nie można tworzyć obiektów tej klasy.
Obiekt ten jest implementacją stosu znanego z Asemblera, pozwalającym przechowyw ać obiekty typu double. Dziedziczy po klasie Stosik. Zawiera jedną prywatną zmienną, będącą wskaźnikiem na double (gdzie tworzona jest dynamiczna tablica).
void push(double); void push(int);
Funkcja ta dodaje na koniec stosu element typu double. Dla wygody użytkownika może przyjąć zarówno liczbę typu double, jak i typu int (która jest automatycznie konwertowana do typu double).
double peek(void);
Pobiera element z wierzchu stosu, ale nie usuwa go (czyli jedynie podgląda, a nie pobiera).
double pop(void);
Pobiera element z wierzchu stosu i usuwa go stamtąd.
Obiekt ten jest implementacją stosu znanego z Asemblera, pozwalającym przechowyw ać obiekty klasy Znak. Dziedziczy po klasie Stosik. Zawiera jedną prywatną zmienną, będącą wskaźnikiem na Znak (gdzie tworzona jest dynamiczna tablica).
void push(Znak); void push(double); void push(int,double);
Funkcja ta dodaje na koniec stosu element typu Znak. Jeśli jako argument dana jest jedynie wartość Double, to przyjmowane jest, że nowy znak jest rodzaju liczbowego. Jeśli jako argument podaje się liczbę int i liczbę double, to tworzony jest znak o rodzaju takim, jak pierwszy argument i wartości takiej, jak drugi argument.
Znak peek(void);
Pobiera element z wierzchu stosu, ale nie usuwa go (czyli jedynie podgląda, a nie pobiera).
Znak pop(void);
Pobiera element z wierzchu stosu i usuwa go stamtąd.
Klasa ta zawiera wskaźnik na znak (wskazujący na dynamiczną tablicę znaków) oraz liczbę typu unsigned int, zawierającą informacje o ilości znaków w tablicy. Tablica zawiera znaki już ułożone w kolejności zgodnej z regułami Odwrotnej Notacji Polskiej.
void dodajZnak(Znak);
Funkcja ta dodaje znak przekazany jako argument na koniec tablicy znajdującej się w klasie. Głównie jest ona wykorzystywana wewnątrz innych metod klasy.
double oblicz(double);
Funkcja ta zwraca wyliczoną wartość wyrażenia. Argument jest używany jako wartość zmiennej w równaniu.
int wczytajInfiks(string*,int);
Funkcja może przyjmować jako argument wskaźnik na obiekt string, pod którym kryje się dynamiczna tablica obiektów typu string i liczbę typu int oznaczającą ilość elementów w tablicy. Zakłada się tutaj, że każdy element tablicy string zawiera tylko jeden znak, to znaczy, że albo zostały od razu oddzielnie podane, albo zostały rozdzielone. Kolejność znaków w tej tablicy jest taka, jak w normalnej, infiksowej notacji, czyli identycznie, jak normalny człowiek zapisuje wyrażenie matematyczne.
Funkcja może zwracać następujące wartości:
dodać tabelkę
Ze względu na konieczność rozdzielania znaków w wyrażeniu istnieje również
przeładowana wersja tej funkcji, przyjmująca jako argument obiekt typu
string.
Dzieli ona wyrażenie zawarte w argumencie i wywołuje oryginalną wersję
funkcji, zwracając taką samą wartość jak ona.
Klasa ta zawiera w sobie dwie tablice dynamiczne zawierające wartości i argumenty funkcji. Na ich podstawie rysowany jest wykres.
Pierwszym argumentem jest tablica double zawietająca wartości funkcji, drugim - tablica double zawierająca argumenty funkcji, a trzecim - liczba elementów w tych tablicach.
Pierwszym argumentem jest wyrażenie matematyczne, kolejne dwa określają dolną i górną granicę zakresu. Czwartym jest rozdzielczość, czyli ilość punktów, które konstruktor wyliczy.
void rysuj(cairo_t* cr, double* kolor);
Funkcja ta rysuje wykres na zadanym w pierwszym argumencie kontekście, używając koloru wyrażonego w tablicy double, wskaźnikiem na którą jest drugi argument. Pierwszym elementem powinien określać kolor czerwony, drugim zielony, a ostatnim - niebieski.
void rysujPochodna(cairo_t* cr, double* kolor);
Funkcji tej używa się tak samo, jak funkcji rysuj, ale rysuje ona nie wykres
samej funkcji, a jej pochodnej.
Poniższy schemat obrazuje sposób działania tej funkcji:
Ze względu na małe możliwości funkcji rysowania dostępnych standardowo wraz z MFC postanowiłem w celu rysowania wykorzystać bibliotekę Cairo. Jest ona rozpowszechniana na zasadach wolnego oprogramowania oraz zapewnia wieloplatformowość. Dodatkowo jest to biblioteka obsługująca grafikę wektorową (w przeciwieństwie do biblioteki dostępnej w MFC, która jest rastrowa), dzięki czemu takie operacje jak skalowanie wykresu (aby w pełni zmieścił się wewnątrz okna) są łatwe do uzyskania za pomocą tej biblioteki. Dodatkowym atutem, docenianym przez końcowego użytkownika aplikacji, jest wygładzanie krawędzi, dzięki czemu wykres jest bardziej estetyczny. Zdecydowaną zaletą jest także wykorzystanie sprzętowej akceleracji graficznej do rysowania obrazu, co pozwala na uzyskanie lepszej wydajności aplikacji. Niestety, biblioteka ta została napisana w C, a nie C++, jednak nie przeszkadza to w użyciu jej w tym programie. Jest to powszechnie stosowana i uznana biblioteka odpowiedzialna za dwuwymiarow ą prezentację graficzną w dużej ilości aplikacji.
W mojej aplikacji wykorzystałem następujące funkcje biblioteki Cairo:
Abstrakcyjny typ płótna Cairo. Dziedziczą po niej inne typy płótna, dzięki czemu do wskaźnika na ten typ można przypisać wynik działania funkcji:
cairo_win32_surface_create(HDC hdc);
dzięki czemu Cairo będzie rysować wewnątrz okna systemu Windows.
Najważniejszy obiekt używany w Cairo, oznaczający kontekst. Zawiera wszystkie informacje dotyczące urządzenia renderującego, w tym współrzędne figur, które mają zostać namalowane. Zmienną tego typu tworzy się za pomocą funkcji cairo_create:
cairo_t* cairo_create (cairo_surface_t *target);
Zmienna ta później musi zostać podana jako argument w praktycznie każdej funkcji z biblioteki Cairo.
void cairo_stroke (cairo_t *cr);
Funkcja ta powoduje narysowanie na urządzeniu renderującym kształtu, który został wcześniej zdefiniowany za pomocą ustawionego rodzaju pędzla.
void cairo_move_to (cairo_t *cr, double x, double y);
Powoduje przesunięcie pędzla do punktu, którego współrzędne są podane w argumencie funkcji, nie powodując jednak rysowania linii.
void cairo_line_to (cairo_t *cr, double x, double y);
Powoduje przesunięcie pędzla tak samo, jak funkcja cairo_move_to, jednak po drodze rysuje linię (która nie ukaże się na ekranie, a jedynie pozostanie w pamięci - aby ją wyświetlić, należy użyć funkcji cairo_stroke)
void cairo_scale (cairo_t *cr, double sx, double sy);
Funkcja transformacji obrazu. Funkcja ta skaluje obraz, w poziomie o sx, a w pionie o sy.
void cairo_translate (cairo_t *cr, double tx, double ty);
Funkcja transformacji obrazu. Przesuwa obraz, w poziomie o tx, a w pionie o ty.
Ponieważ funkcje te same z siebie nie modyfikują obrazu, a jedynie ustawiają w odpowiedni sposób wartości w macierzy transformacji, z której następnie biblioteka korzysta w celu rysowania obrazu, funkcje te powinny być wywołane przed rysowaniem kształtów, które powinny zostać transformowane.
void cairo_set_line_width (cairo_t *cr, double width);
Ustawia grubość pędzla. Warto zauważyć, iż wartości ustawione przez tego rodzaju funkcje są wykorzystywane dopiero przez funkcję cairo_stroke, a nie przez poszczególne funkcje rysujące kształty, więc jedynie ostatnio ustawiona wartość jest brana pod uwagę.
void cairo_set_source (cairo_t *cr, cairo_pattern_t *source);
Ustawia aktualny deseń taki, jak deseń podany w drugim parametrze funkcji.
void cairo_set_source_rgba (cairo_t *cr, double red, double green, double blue, double alpha);
Ustawia kolor jako aktualny deseń. Jako argumenty, poza wskaźnikiem na cairo_t, przyjmuje kolory: czerwony, zielony i niebieski, oraz kanał alpha (przezroczystość). Wartości tych argumentów powinny zawierać się od 0 (brak danego koloru bądź kompletnie przezroczyste) do 1.
void cairo_device_to_user_distance (cairo_t *cr, double *dx, double *dy);
Pozwala na wyliczenie odległości urządzenia od użytkownika. Przydatne, aby tak ustawić grubość pędzla, żeby mimo transformacji zachowywała ona odpowiednią grubość.
typedef struct _cairo_pattern cairo_pattern_t;
Typ deseniu, którego można używać do wypełniania obiektów.
cairo_pattern_t* cairo_pattern_create_linear (double x0, double y0, double x1, double y1);
Funkcja ta zwraca wskaźnik na deseń, będący gradientem liniowym. Argumenty x0 i y0 ustalają położenie punktu początkowego, a argumenty x1 i y1 ustalają położenie końcowego punktu gradientu.
void cairo_pattern_add_color_stop_rgb (cairo_pattern_t *pattern,double offset,double red,double green,double blue);
Funkcja dodaje kolor do deseniu będącego gradientem. Argument offset definiuje miejsce, w którym kolor zostanie umieszczony (od 0 do 1, im większa wartość, tym bliżej punktu końcowego), następne argumenty określają kolor RGB.
void cairo_pattern_add_color_stop_rgba (cairo_pattern_t *pattern,double offset,double red,double green,double blue,double alpha);
Funkcja ta działa tak samo, jak funkcja cairo_patern_add_color_stop_rgba, ale pozwala podać również wartość kanału alpha (czyli przezroczystości).
void cairo_rectangle (cairo_t *cr,double x,double y,double width,ouble height);
Dodaje do obecnej ścieżki prostokąt o podanych wymiarach (width, height) zaczynający się w punkcie o podanych współrzędnych (x, y).
void cairo_fill (cairo_t *cr);
Sprawia, że obecna ścieżka zostaje wypełniona zgodnie z obecnymi zasadami wypełnienia.
void cairo_fill_preserve (cairo_t *cr);
Funkcja ta robi to samo, co cairo_fill, ale nie usuwa obecnej ścieżki z pamięci, dzięki czemu można np. narysować jej obrys.
void cairo_pattern_destroy (cairo_pattern_t *pattern);
Zwalnia pamięć zaalokowaną dla deseniu.
void cairo_show_text (cairo_t *cr, const char *utf8);
Prosta funkcja pozwalająca na wyświetlenie tekstu za pomocą Cairo.
void cairo_text_path(cairo_t *cr, const char *utf8);
Funkcja tworząca ścieżkę o kształcie takim, jak wprowadzony tekst.
void cairo_text_extents (cairo_t *cr, const char *utf8, cairo_text_extents_t *extents);
Funkcja ta podaje wymiary tekstu zgodnie z obecnie wybraną wielkością i krojem pisma. Pozwala precyzyjnie umieścić tekst na ekranie.
void cairo_select_font_face (cairo_t *cr, const char *family, cairo_font_slant_t slant, cairo_font_weight_t weight);
Funkcja ta pozwala na ustawienie kroju pisma, którym programista chce się posłużyć do wyświtlenia tekstu na ekranie za pomocą biblioteki Cairo.
void cairo_set_font_size (cairo_t *cr, double size);
Funkcja ta pozwala ustawić rozmiar pisma, które utworzy Cairo.
Poniżej przedstawione są algorytmy, które wykorzystałem do tworzenia funkcji
wchodzących w skład klasy Rownanie.
Tekst algorytmów pochodzi z Wikipedii2)
i został udostępniony na licencji GNU FDL.
wkleić algorytm z wikipedii