główna strona  C++
uroda
w prostocie
 
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.
  • Konstruktor dzieli podany tekst, wedle znaku separatora
  • Count() określa liczbę elementów listy
  • operator[] zwraca żądany element listy
  • Match() poszukuje określonego elementu na liście
  • Destruktor przywraca znaki separatora na swoje miejsce
#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.
 
opiekun: Janusz Wiśniewski :: rejestracja odwiedzin 1797 gości
mobi