Zanim jeszcze dotarło do mnie istnienie C++, wymyśliłem ten język na własne potrzeby. Śniłem o nim, nie wiedząc o jego istnieniu. Przy pomocy klasycznego C konstruowałem obiekty używając struktur z dopasowanym do nich zestawem funkcji. Odkrycie C++ wypełniło w moim sercu miejsce z dawna na ten dar przygotowane.
|
dyskretny urok obiektów
W większości popularnych samouczków C++ położono nacisk na dynamiczne kreowanie obiektów z użyciem operatora new. Oczywiście, gdy się powiedziało new, trzeba też powiedzieć delete, a to już nie zawsze jest taka prosta sprawa. Z tego powodu znaczna część czasu przeznaczonego na wykonanie programu upływa na tropieniu niezainicjowanych wskaźników, utraconych dowiązań do obiektów, wycieków pamięci i tym podobnych uciążliwych kłopotów.
Ze zdziwieniem spostrzegłem, że wiele osób tworzy obiekty poprzez new nawet w miejscach, gdzie nie ma to żadnego uzasadnienia. Zapewne pozwolili sobie na ugruntowanie złych przyzwyczajeń, a może nie znają lub nie pamiętają innych sposobów?
Użycie obiektów poprzez deklarowanie ich jako zmiennych daje szereg korzyści, jak: brak dynamicznej alokacji pamięci, automatyczna destrukcja przy opuszczaniu bloku, prostszy i szybszy kod wynikowy...
przykład: strażnik katalogu
Pisząc dziesiąty z kolei program, w którym trzeba było zadbać o powrót do macierzystego folderu po tym, jak radosny użytkownik zawiedzie nas na szary koniec drzewa katalogów, postanowiłem zrobić z tym porządek raz na zawsze. Niby to nic trudnego: zadeklaruj bufor znakowy, zapisz w nim bieżącą ścieżkę, potem w odpowiednim momencie powróć do zapamiętanego miejsca. Ale robienie tego samego po raz kolejny... to nudne i nużące. Najgorsze, że nie zawsze łatwo jest zdefiniować pojęcie "w odpowiednim momencie". Najlepszy byłby koniec bloku, w którym odbywa się wycieczka po katalogach. Ale co zrobić gdy program opuszcza blok w wielu miejscach przez jakieś dzikie break, goto czy return? Trzeba pamiętać o restauracji bieżącego katalogu w każdym z tych miejsc. A mi się zamarzyło, żeby można było na początku powiedzieć "waruj tu" i niczym się więcej nie przejmować. Jak to osiągnąć? Nic prostszego, zawrzyjmy w pliku nagłówkowym deklarację:
#define MAX_DIR 512 class waruj { private: TCHAR cd[MAX_DIR]; public: waruj() { _tgetcwd(cd, MAX_DIR); } ~waruj() { _tchdir(cd); } }; Teraz nasz program mógłby wyglądać np. tak:
in main() { waruj tu; skocz_do("\\gdzie\\tylko\\zapragniesz"); } I program sam powraca na swoje miejsce. Jak to działa? Deklarując zmienną tu typu waruj, powodujemy wykonanie domyślnego konstruktora klasy waruj, który zapamiętuje bieżącą ścieżkę w prywatnym obszarze. Destruktor wywoła się automatycznie, odtwarzając położenie, dokładnie tam gdzie należy, czyli przy wyjściu z bloku.
Dla uproszczenia pominąłem problem istnienia więcej niż jednego dysku, bo myślę, że nawet miernie uzdolniony amator świetnie sobie poradzi z odpowiednią rozbudową przykładu. Pozostawiam to czytelnikom jako element samodoskonalenia.
przykład: rozbiór pliku konfiguracyjnego
Po wielu miesiącach posługiwania się Perlem powróciłem do programowania w C++ i odczułem na własnej skórze, jak szybko się człowiek przyzwyczaja do dobrego.
Podział ciągu znaków na mniejsze elementy zgodnie z regułami prostej składni, który w Perlu realizujemy jednym poleceniem, w C++ przyprawia o ból głowy.
Postanowiłem temu definitywnie zaradzić.
Wyobraźmy sobie tekstowy plik konfiguracyjny, złożony z wierszy wyglądających podobnie do tego:
tune=a:3,b:4,c:2 To definicja prostej melodii: mamy nazwę, a po znaku równości kolejne nutki rozdzielone przecinkami, każdą złożoną z nazwy dźwięku i po dwukropku czasu jego trwania. Program ma za zadanie zagrać te dźwięki, lecz w tym przykładzie, z racji późnej godziny, ograniczę się do ich wypisania.
|
Oto program realizujący to zadanie. Kluczową rolę odgrywają tu obiekty klasy XLIST. Tworzą one coś w rodzaju tablicy, dzieląc podany tekst w miejscach wystąpienia określonego separatora.
W pierwszym kroku separatorem jest znak równości, co dzieli wiersz na nazwę i skojarzoną z nią wartość. Gdy nazwą jest tune, co oznacza melodię, drugą część wiersza można podzielić na poszczególne dźwięki, tym razem w miejscach występowania przecinków. Temu służy zmienna tune, przechowująca listę dźwięków. Z kolei każdy dźwięk można w analogiczny sposób podzielić w miejscu dwukropka na wysokość i czas trwania.
#include <xlist.h> #include <stdio.h> //getchar() TCHAR str[] = __T("tune=a:3,b:4,c:2"); int _tmain(int argc, _TCHAR* argv[]) { XLIST line(str, (TCHAR)'='); if (line.Match(__T("tune")) == 0) { XLIST tune((_TCHAR*)line[1], (TCHAR)','); for (int i = 0; i < tune.Count(); i++) { XLIST snd((_TCHAR*)tune[i],(TCHAR)':'); _tprintf(__T("note %s, %s time units\n"), snd[0], snd[1]); } } getchar(); return 0; } Jeśli program będzie łaskawy, wypisze coś takiego:
note a, 3 time units note b, 4 time units note c, 2 time units Klasę XLIST, zadeklarowaną w pliku xlist.h, cechuje ascetyczna prostota.
#ifndef _xlist_h_ #define _xlist_h_ #include <tchar.h> //----------------------------------------------------- // Comma LIST magic splitter by jbw //----------------------------------------------------- class XLIST { protected: TCHAR *m_pBody; TCHAR *m_pStr; TCHAR m_Sep; int m_Count; int m_Idx; public: XLIST (TCHAR *body, TCHAR sep = (TCHAR)','); int Count (void) { return m_Count; } const TCHAR* operator [] (int i); int Match(const TCHAR *str, bool cases = false); ~XLIST (void); }; #endif Użyty został uniwersalny typ znakowy TCHAR, dzięki czemu funkcje działają poprawnie zarówno dla tradycyjnych bajtowych tekstów jak i dla unikodu.
|
Metody zdefiniowane w pliku xlist.cpp są proste, by nie rzec prymitywne. Podział tekstu następuje poprzez zastąpienie separatora znakiem '\0', tradycyjnie oznaczającym w C/C++ koniec napisu. Zapamiętanie znaku separatora gwarantuje odtworzenie tekstu podczas destrukcji listy. Odrobinę finezji wprowadza zmienna m_Idx, która przechowuje numer ostatnio oglądanego elementu, co pozwala na optymalizację typowych operacji, jak iteracja w porządku rosnącym lub wielokrotne odwołania do tego samego obiektu.
//----------------------------------------------------- // Comma LIST magic splitter by jbw //----------------------------------------------------- #include "xlist.h" XLIST::XLIST (TCHAR *body, TCHAR sep) : m_pBody(body), m_Sep(sep) { TCHAR *p = m_pBody; m_Count = 1; while (p = _tcschr(p, m_Sep)) { ++m_Count; *p++ = '\0'; } m_pStr = m_pBody; m_Idx = 0; } XLIST::~XLIST (void) { TCHAR *p = m_pBody; while (--m_Count) { p += _tcslen(p); *p++ = m_Sep; } } const TCHAR* XLIST::operator [] (int i) { if (i >= 0 && i < m_Count) { if (m_Idx > i) { m_pStr = m_pBody; m_Idx = 0; } while (m_Idx < i) { m_pStr += _tcslen(m_pStr)+1; ++m_Idx; } return m_pStr; } return __T(""); } int XLIST::Match (const TCHAR *str, bool cases) { int (*cmp) (const TCHAR*, const TCHAR*) = (cases) ? _tcscmp : _tcsicmp ; for (int i = 0; i < m_Count; i++) if (cmp((*this)[i], str) == 0) return i; return -1; } Warto zauważyć, że nie są tu tworzone żadne kopie analizowanych tekstów, a cały rozbiór odbywa się "w miejscu", kończąc bez szkody dla oryginalnego napisu.
Przedstawione programy zostały przetestowane w bezpłatnym środowisku Visual Studio Express 2008, i pomyślnie zdały egzamin w komercyjnych zastosowaniach.
|
|