wtorek, 25 marca 2014

Przeciążanie operatorów w C++

Liczba argumentów znajdujących się na liście argumentów przeciążanego operatora zależy od dwóch czynników:
  • czy jest to operator jednoargumentowy czy dwuargumentowy,
  • czy operator jest zdefiniowany jako funkcja globalna (brak wskaźnika "this") czy też jako funkcja składowa:
    • operator jednoargumentowy: jeden argument dla funkcji globalnej i brak argumentu dla funkcji składowej,
    • operator dwuargumentowy: dwa argumenty dla funkcji globalnej i jeden argument dla funkcji składowej.
Przykładowy kod demonstrujący przeciążanie operatorów jednoargumentowych:
#include <iostream>

using namespace std;

// Definicja klasy dla przypadku gdy funkcje operatorowe nie są funkcjami składowymi (dekleracje przyjaźni z funkcjami globalnymi).

class integer {
    int variable;

    integer * that() {
        return this;
    }

public:
    // Konstruktory.

    integer(int variable_constructor = 0) : variable(variable_constructor) { }

    // Funkcje.

    void show_variable() {
        cout << "variable ma wartość = " << variable << endl;
    }

    // Deklaracje przyjaźni (funkcje operatorowe globalne).

    friend const integer & operator+(const integer & argument);
    friend const integer operator-(const integer & argument);
    friend const integer operator~(const integer & argument);
    friend integer * operator~(integer & argument);
    friend int operator!(const integer & argument);
    friend const integer & operator++(integer & argument); // Przedrostkowy.
    friend const integer operator++(integer & argument, int); // Przyrostkowy.
    friend const integer & operator--(integer & argument);
    friend const integer operator--(integer & argument, int);
};

// Globalne funkcje operatorowe.

// Poniższe definicje jako argumenty przyjmują referencje co nie powoduje skutków ubocznych.

const integer & operator+(const integer & argument) {
    return argument; // Jednoargumentowy operator w tym wypadku nic nie zmienia.
}

const integer operator-(const integer & argument) {
    return (-argument.variable);
}

const integer operator~(const integer & argument) {
    return integer(~argument.variable);
}

integer * operator~(integer & argument) {
    return argument.that(); // "& argument" jest rekurencyjne.
}

int operator!(const integer & argument) {
    return !argument.variable;
}

// Teraz definicje funkcji, których argumenty nie są referencjami do stałych.

// Operator przedrostkowy (zwraca wartość po inkrementacji). Kompilator generuje wywołanie funkcji w postaci "operator++(zmienna)".

const integer & operator++(integer & argument) {
    argument.variable++;

    return argument;
}

// Operator przyrostkowy (zwraca wartość przed inkrementacją). Kompilator generuje wywołanie funkcji "operator++(zmienna, int)".

const integer operator++(integer & argument, int) {
    integer before(argument.variable);

    argument.variable++;

    return before;
}

const integer & operator--(integer & argument) { // Przedroskowy.
    argument.variable--;

    return argument;
}

const integer operator--(integer & argument, int) { // Przyrostkowy.
    integer before(argument.variable);

    argument.variable--;

    return before;
}

// Definicja klasy dla przypadku gdy funkcje operatorowe są funkcjami składowymi (niejawny argument "this").

class byte {
    int variable;

public:
    // Konstruktory.

    byte(int variable_constructor = 0) : variable(variable_constructor) { }

    // Stałe funkcje składowe (brak skutków ubocznych).

    const byte & operator+() const {
        return * this;
    }

    const byte operator-() const {
        return byte(-variable);
    }

    const byte operator~() const {
        return byte(~variable);
    }

    byte operator!() const {
        return byte(!variable);
    }

    byte * operator&() {
        return this;
    }

    // Niestałe funkcje składowe (występują skutki uboczne).

    const byte & operator++(); // Przedrostkowo.
    const byte operator++(int); // Przyrostkowo.
    const byte & operator--(); // Przedrostkowo.
    const byte operator--(int); // Przyrostkowo.
};

const byte & byte::operator++() { // Kompilator generuje wywołanie funkcji w postaci "byte::operator++()".
    variable++;

    return * this;
}

const byte byte::operator++(int) { // Kompilator generuje wywołanie funkcji w postaci "byte::operator++(int)".
    byte before(variable);

    variable++;

    return before;
}

const byte & byte::operator--() {
    variable--;

    return * this;
}

const byte byte::operator--(int) {
    byte before(variable);

    variable--;

    return before;
}

int main() {
}

Przykładowy kod prezentujący przeciążanie operatorów dwuargumentowych:
#include <iostream>

using namespace std;

// Definicja klasy dla przypadku gdy funkcje operatorowe nie są funkcjami składowymi (dekleracje przyjaźni z funkcjami globalnymi).

class integer {
    int variable;

public:
    // Konstruktory.

    integer(int variable_constructor = 0) : variable(variable_constructor) { }

    // Deklaracje przyjaźni.

    friend const integer operator+(const integer & left, const integer & right);
    friend const integer operator-(const integer & left, const integer & right);
    friend const integer operator*(const integer & left, const integer & right);
    friend const integer operator/(const integer & left, const integer & right);
    friend const integer operator%(const integer & left, const integer & right);
    friend const integer operator^(const integer & left, const integer & right);
    friend const integer operator&(const integer & left, const integer & right);
    friend const integer operator|(const integer & left, const integer & right);
    friend const integer operator<<(const integer & left, const integer & right);
    friend const integer operator>>(const integer & left, const integer & right);
    friend integer & operator+=(integer & left, const integer & right);
    friend integer & operator-=(integer & left, const integer & right);
    friend integer & operator*=(integer & left, const integer & right);
    friend integer & operator/=(integer & left, const integer & right);
    friend integer & operator%=(integer & left, const integer & right);
    friend integer & operator^=(integer & left, const integer & right);
    friend integer & operator&=(integer & left, const integer & right);
    friend integer & operator|=(integer & left, const integer & right);
    friend integer & operator>>=(integer & left, const integer & right);
    friend integer & operator<<=(integer & left, const integer & right);
    friend int operator==(const integer & left, const integer & right);
    friend int operator!=(const integer & left, const integer & right);
    friend int operator<(const integer & left, const integer & right);
    friend int operator>(const integer & left, const integer & right);
    friend int operator<=(const integer & left, const integer & right);
    friend int operator>=(const integer & left, const integer & right);
    friend int operator&&(const integer & left, const integer & right);
    friend int operator||(const integer & left, const integer & right);
};

// Definicja klasy dla przypadku gdy funkcje operatorowe są funkcjami składowymi (niejawny argument "this").

class byte {
    int variable;

public:
    // Konstruktory.

    byte(int variable_constructor = 0) : variable(variable_constructor) { }

    // Stałe funkcje składowe (brak skutków ubocznych).

    const byte operator+(const byte & right) const {
        return byte(variable + right.variable);
    }

    const byte operator-(const byte & right) const {
        return byte(variable - right.variable);
    }

    const byte operator*(const byte & right) const {
        return byte(variable * right.variable);
    }

    const byte operator/(const byte right) const {
        return byte(variable / right.variable);
    }

    const byte operator%(const byte & right) const {
        return byte(variable % right.variable);
    }

    const byte operator^(const byte & right) const {
        return byte(variable ^ right.variable);
    }

    const byte operator&(const byte & right) const {
        return byte(variable & right.variable);
    }

    const byte operator|(const byte & right) const {
        return byte(variable | right.variable);
    }

    const byte operator<<(const byte & right) const {
        return byte(variable << right.variable);
    }

    const byte operator>>(const byte & right) const {
        return byte(variable >> right.variable);
    }

    // Przypisania modyfikujące i zwracające l-wartość.

    byte & operator=(const byte & right); // Funkcja operator= może być tylko składowa.
    byte & operator+=(const byte & right);
    byte & operator-=(const byte & right);
    byte & operator*=(const byte & right);
    byte & operator/=(const byte & right);
    byte & operator%=(const byte & right);
    byte & operator^=(const byte & right);
    byte & operator&=(const byte & right);
    byte & operator|=(const byte & right);
    byte & operator>>=(const byte & right);
    byte & operator<<=(const byte & right);

    // Operatory warunkowe (zwracające true lub false).

    int operator==(const byte & right) const {
        return (variable == right.variable);
    }

    int operator!=(const byte & right) const {
        return (variable != right.variable);
    }

    int operator<(const byte & right) const {
        return (variable < right.variable);
    }

    int operator>(const byte & right) const {
        return (variable > right.variable);
    }

    int operator<=(const byte & right) const {
        return (variable <= right.variable);
    }

    int operator>=(const byte & right) const {
        return (variable == right.variable);
    }

    int operator&&(const byte & right) const {
        return (variable && right.variable);
    }

    int operator||(const byte & right) const {
        return (variable || right.variable);
    }
};

// Implementacja przeciążonych operatorów dla klasy integer.

const integer operator+(const integer & left, const integer & right) {
    return integer(left.variable + right.variable);
}

const integer operator-(const integer & left, const integer & right) {
    return integer(left.variable - right.variable);
}

const integer operator*(const integer & left, const integer & right) {
    return integer(left.variable * right.variable);
}

const integer operator/(const integer & left, const integer & right) {
    return integer(left.variable / right.variable);
}

const integer operator%(const integer & left, const integer & right) {
    return integer(left.variable % right.variable);
}

const integer operator^(const integer & left, const integer & right) {
    return integer(left.variable ^ right.variable);
}

const integer operator&(const integer & left, const integer & right) {
    return integer(left.variable & right.variable);
}

const integer operator|(const integer & left, const integer & right) {
    return integer(left.variable | right.variable);
}

const integer operator<<(const integer & left, const integer & right) {
    return integer(left.variable << right.variable);
}

const integer operator>>(const integer & left, const integer & right) {
    return integer(left.variable >> right.variable);
}

// Przypisania modyfikujące i zwracające l-wartość (dla klasy integer).

integer & operator+=(integer & left, const integer & right) {
    if (& left == & right) { // Przypisanie do samego siebie.
        left.variable += right.variable;
    }

    return left;
}

integer & operator-=(integer & left, const integer & right) {
    if (& left == & right) { // Przypisanie do samego siebie.
        left.variable -= right.variable;
    }

    return left;
}

integer & operator*=(integer & left, const integer & right) {
    if (& left == & right) { // Przypisanie do samego siebie.
        left.variable *= right.variable;
    }

    return left;
}

integer & operator/=(integer & left, const integer & right) {
    if (& left == & right) { // Przypisanie do samego siebie.
        left.variable /= right.variable;
    }

    return left;
}

integer & operator%=(integer & left, const integer & right) {
    if (& left == & right) { // Przypisanie do samego siebie.
        left.variable %= right.variable;
    }

    return left;
}

integer & operator^=(integer & left, const integer & right) {
    if (& left == & right) { // Przypisanie do samego siebie.
        left.variable ^= right.variable;
    }

    return left;
}

integer & operator&=(integer & left, const integer & right) {
    if (& left == & right) { // Przypisanie do samego siebie.
        left.variable &= right.variable;
    }

    return left;
}

integer & operator|=(integer & left, const integer & right) {
    if (& left == & right) { // Przypisanie do samego siebie.
        left.variable |= right.variable;
    }

    return left;
}

integer & operator>>=(integer & left, const integer & right) {
    if (& left == & right) { // Przypisanie do samego siebie.
        left.variable >>= right.variable;
    }

    return left;
}

integer & operator<<=(integer & left, const integer & right) {
    if (& left == & right) { // Przypisanie do samego siebie.
        left.variable <<= right.variable;
    }

    return left;
}

// Operatory warunkowe zwracające true lub false (dla klasy integer).

int operator==(const integer & left, const integer & right) {
    return left.variable == right.variable;
}

int operator!=(const integer & left, const integer & right) {
    return left.variable != right.variable;
}

int operator<(const integer & left, const integer & right) {
    return left.variable < right.variable;
}

int operator>(const integer & left, const integer & right) {
    return left.variable > right.variable;
}

int operator<=(const integer & left, const integer & right) {
    return left.variable <= right.variable;
}

int operator>=(const integer & left, const integer & right) {
    return left.variable >= right.variable;
}

int operator&&(const integer & left, const integer & right) {
    return left.variable && right.variable;
}

int operator||(const integer & left, const integer & right) {
    return left.variable || right.variable;
}

// Funkcje składowe klasy byte.

byte & byte::operator=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable = right.variable;
    }

    return * this;
}

byte & byte::operator+=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable += right.variable;
    }

    return * this;
}

byte & byte::operator-=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable -= right.variable;
    }

    return * this;
}

byte & byte::operator*=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable *= right.variable;
    }

    return * this;
}

byte & byte::operator/=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable /= right.variable;
    }

    return * this;
}

byte & byte::operator%=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable /= right.variable;
    }

    return * this;
}

byte & byte::operator^=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable ^= right.variable;
    }

    return * this;
}

byte & byte::operator&=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable &= right.variable;
    }

    return * this;
}

byte & byte::operator|=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable |= right.variable;
    }

    return * this;
}

byte & byte::operator>>=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable >>= right.variable;
    }

    return * this;
}

byte & byte::operator<<=(const byte & right) {
    if (this == & right) { // Przypisanie do siebie samego.
        variable <<= right.variable;
    }

    return * this;
}

int main() {
}

Przykład przeciążania operatora przecinkowego:
class after {
public:
    const after & operator,(const after &) const {
        return * this;
    }
};

class before { };

before & operator,(int, before & argument) {
    return argument;
}

Przykład przeciążania operatora wyłuskania wskaźnika do składowej:
#include <iostream>

using std::cout;

using std::endl;

class object {
public:
    typedef int (object::*pmf)(int) const; // "operator->*" musi zwrócić obiekt posiadający "operator()".

    class function_object {
        object * class_object_pointer;
        pmf pointer;

    public:
        // Zapamiętuje wskaźnik obiektu number wskaźnik składowej.

        function_object(object * object_pointer_constructor, pmf pointer_constructor) : class_object_pointer(object_pointer_constructor), pointer(pointer_constructor) {
            cout << "Kontruktor function_object.\n";
        }

        // Wywołanie wykorzystujące wskaźnik obiektu number wskaźnik składowej.

        int operator()(int number) const {
            cout << "Wywolanie funkcji skladowej \"operator()\" klasy function_object klasy object.\n";

            return (class_object_pointer->*pointer)(number); // Wywołanie.
        }
    };

    function_object operator->*(pmf pmf) {
        cout << "Wywolanie \"operator->*\" klasy object." << endl;

        return function_object(this, pmf);
    }

    int action_1(int number) const;
    int action_2(int number) const;
    int action_3(int number) const;
};

int object::action_1(int number) const {
    cout << "Czynnosc nr 1 obiektu.\n";

    return number;
}

int object::action_2(int number) const {
    cout << "Czynnosc nr 2 obiektu.\n";

    return number;
}

int object::action_3(int number) const {
    cout << "Czynnosc nr 3 obiektu.\n";

    return number;
}

int main() {
    object variable;
    object::pmf pmf = &object::action_1;

    cout << "Zwraca " << (variable->*pmf)(1) << "." << endl;

    cout << endl;

    pmf = &object::action_3;

    cout << "Zwraca " << (variable->*pmf)(2) << "." << endl;

    cout << endl;

    pmf = &object::action_2;

    cout << "Zwraca " << (variable->*pmf)(3) << "." << endl;
}

Kod ukazujący przykładowe przeciążenia operatora indeksu tablicy:
#include <iostream>

using std::cout;

using std::endl;

template <typename type, size_t elements> class table {
    type series[elements];
    size_t elements_number;

public:
    table() : elements_number(elements) { }

    type & operator[](size_t elements) {
        return series[elements];
    }

    const type & operator[](size_t elements) const {
        return series[elements];
    }
};

int main() {
    const size_t elements = 5;
    table <short, elements> series;

    for (int i = 0; i < elements; i++) {
        series[i] = i;

        cout << series[i] << endl;
    }
}

Operatory, których nie można przeciążać:
  • operator wyboru składowej (jako "obiekt.składowa"),
  • operator wyłuskania wskaźnika do składowej w postaci ",*",
  • brak operatora potęgowania,
  • nie można samodzielnie tworzyć nowych operatorów,
  • nie można zmieniać reguł dotyczących priorytetów operatorów.
Na ogół jeżeli nie ma to znaczenia to przeciążone operatory powinny być funkcjami składowymi (podkreśla to związek między operatorem i jego klasą). Czasem zachodzi jednak potrzeba aby argument znajdujący się po lewej stronie operatora był obiektem jakiejś innej klasy. Typowym tego przykładem jest przeciążanie operatorów "<<" i ">>" dla operacji związanych ze strumieniami wejścia i wyjścia:
#include <iostream>
#include <sstream>
#include <cstring>
#include <ostream>
#include <istream>

using std::cout;

using std::endl;

using std::ostream;

using std::istream;

using std::stringstream;

class table {
    enum { elements_number = 5 };
    int series[elements_number];

public:
    table() {
        memset(series, 0, (elements_number * sizeof(*series)));
    }

    int & operator[](int index) {
        return series[index];
    }

    friend ostream & operator<<(ostream & stream, const table & argument);
    friend istream & operator>>(istream & stream, table & argument);
};

ostream & operator<<(ostream & stream, const table & argument) {
    for (int iterator = 0; iterator < argument.elements_number; iterator++) {
        stream << argument.series[iterator];

        if (iterator != argument.elements_number - 1)
            stream << ", ";
    }

    stream << endl;

    return stream;
}

istream & operator>>(istream & stream, table & argument) {
    for (int iterator = 0; iterator < argument.elements_number; iterator++)
        stream >> argument.series[iterator];

    return stream;
}

int main() {
    stringstream input("1 2 3 45 88");
    table one;

    input >> one;

    cout << one;
}

Przykład przeciążania operatorów new i delete w obrębie klasy:
#include <iostream>

using std::cout;

using std::cerr;

using std::endl;

using std::bad_alloc;

class object {
    enum { size = 10 };
    char table_char[size]; // Niewykorzystywana (zajmuje tylko pamięć).
    static unsigned char pool[];
    static bool alloc_map[];

public:
    enum { objects_number = 100 }; // Liczba dopuszczalnych obiektów.
    void * operator new (size_t) throw(bad_alloc);
    void operator delete(void *);
};

unsigned char object::pool[objects_number * sizeof(object)];
bool object::alloc_map[objects_number] = { false };

int main() {
    object * one[object::objects_number];

    try {
        for (int iterator = 0; iterator < object::objects_number; iterator++) {
            one[iterator] = new object;
        }

        new object; // Brak pamięci.
    }
    catch (bad_alloc) {
        cerr << "Brak pamieci.\n";
    }

    delete one[10];

    one[10] = 0;

    object * two = new object; // Użycie zwolnionej pamięci.

    delete two;

    for (int iterator = 0; iterator < object::objects_number; iterator++)
        delete one[iterator];
}

// Wielkość jest ignorowana. Zakłada się, zę to obiekt klasy object.

void * object::operator new(size_t) throw(bad_alloc){
    for (int iterator = 0; iterator < objects_number; iterator++) {
        if (!alloc_map[iterator]) {
            cout << "Uzywany blok: " << iterator << ".\n";

            alloc_map[iterator] = true; // Oznaczenie bloku jako używanego.

            return (pool + (iterator * sizeof(object)));
        }
    }

    cerr << "Brak pamieci.\n";

    throw bad_alloc();
}

void object::operator delete(void * memory) {
    if (!memory) { // Sprawdź czy wskaźnik nie jest pusty.
        return;
    }

    // Zakładamy, że obiekt został utworzony w dostępnej puli.

    // Wyznaczanie numeru przydzielonego bloku.

    unsigned long block = (unsigned long)memory - (unsigned long)pool;

    block /= sizeof(object);

    cout << "Zwalnianie bloku: " << block << ".\n";

    alloc_map[block] = false;
}

Przykład przeciążania new i delete w ujęciu globalnym:
#include <cstdlib>
#include <cstdio>

void * operator new(size_t length){
    void * memory = malloc(length);

    if (!memory)
        printf("Brak pamieci."); // Podczas tworzenia obiektu klasy iostream (np. globalne cin, cout oraz cerr) jest wywoływany operator new przydzielający mu pamięć. Użycie funkcji printf nie grozi blokadą systemu gdyż ta funkcja nie wywołuje operatora new podczas swojej inicjalizacji.

    return memory;
}

void operator delete(void * memory) {
    free(memory);
}

Proponuje się następujące zalecenia co do przeciążania operatorów:
  • operatory jednoargumentowe powinny być funkcjami składowymi,
  • funkcje "operator=", "operator()", "operator[]", "operator->" oraz "operator->*" muszą być funkcjami składowymi,
  • funkcje "operator+=",  "operator-=",  "operator/=",  "operator*=",  "operator^= ",  "operator&=, "operator|=",  "operator%=",  "operator>>=" oraz  "operator<<=" powinny być funkcjami składowymi,
  • wszystkie pozostałe operatory dwuargumentowe nie powinny być funkcjami składowymi. 
Źródła:
- Eckel B., Thinking in C++. Edycja polska, Helion SA, 2002,
- Grębosz J., Symfonia C++, EDITION 2000, 2008.

czwartek, 19 grudnia 2013

Co nie co o typach w C++

Definiując daną nazwę mówimy kompilatorowi aby zarezerwował w pamięci miejsce na obiekt danego typu o podanej nazwie.

Różnica między definicją a deklaracją jest taka, iż deklaracja mówi kompilatorowi jaka nazwa reprezentuje dany obiekt konkretnego typu bez rezerwacji miejsca w pamięci, a definicja dodatkowo rezerwuje miejsce w pamięci (powołanie do życia). Przykład:
int zmienna1; // Definicja z deklaracją.
extern int zmienna2; // Deklaracja.

Typ wchar_t może przechowywać rozszerzony zestaw znaków alfanumerycznych.

Zestawienie typów wraz z ich szerokością, zakresem wartości oraz przykładem literału:
- char: 1 bajt , -128-127, 'a' czy też '\x61',
- unsigned char: 1 bajt, 0-255, '!',
- wchar_t: 2 bajty, 0-65535, L'A' czy też L"abcd",
- short: 2 bajty, -32768-32767, -1
- unsigned short: 2 bajty, 0-65535, 2
- int: 4 bajty, -2147483648-2147483647, 0x9FE,
- unsigned int: 4 bajty, 0-4294967295, 10U,
- long: 4 bajty, -2147483648-2147483647, -77L,
- unsigned long: 4 bajty, 0-4294967295, 5UL,
- float: 4 bajty, 3,4e-38-3,4e38, 3.14f
- double: 8 bajtów, 1,7e-308-1,7e308, 1.234,
- long double: 10 bajtów, 3,4e-4932-1,1e4932, 1.234L.
Typ int ma w tym zestawieniu tą samą szerokość co typ long (kompilatory 32 bitowe). Jednak jeżeli kompilator będzie np.16 bitowy to zakres wartości może być mniejszy. Krótko mówiąc; sposób przechowywania liczby może zależeć od typu kompilatora. Możemy to sprawdzić używając operatora sizeof. Stałe całkowite traktuje się jako typ int (np. 1 i 100) natomiast w przypadku bardzo dużych liczb jest to typ long. Z kolei stałe zmiennoprzecinkowe (np. 12.5, 8e2) traktuje się jako typ double.

Niejednokrotnie nasuwa się pytanie dlaczego utrudniać sobie życie i pisać coś w systemach innych niż dziesiątkowy. Niekiedy stosowanie innego zapisu ułatwia nam właśnie pracę np. podczas ustawiania bitów w komórce pamięci. Biorąc pod uwagę ten przykład to przy użyciu zapisu szesnastkowego łatwo wyobrazić sobie pozycje żądanych bitów. Przykładowo jeżeli chodzi o ustawienie samego bitu 9 i 10 to można to zapisać jako "zmienna = 0x300;". Innym argumentem za stosowaniem systemu innego niż dziesiętny jest to, że komputery rozumieją tylko system binarny.

Dlaczego lepiej używać przydomka "const" zamiast dyrektywy "#define"? Używając #define sprawiamy, że kompilator nie jest w stanie skojarzyć co dana nazwa oznacza ponieważ operuje tylko na tekście. Ponadto kompilator nie może sprawdzić czy dana nazwa została użyta w ramach jej zakresu ważności (zakres taki nie występuje). Tym samym nie mamy możliwości śledzenia za pomocą debuggera jak dany obiekt się zmienia.

Przydomek "register" dodany do definicji obiektu mówi kompilatorowi, iż chcemy bardzo często używać tego obiektu i tym samym mieć do niego szybki dostęp. Sprawi to, że zmienna zostanie umieszczona w rejestrze, z którego może być odczytana bardzo szybko. Kompilator jednak może tego nie uwzględnić. Z uwagi na to, że obiekt jest umieszczany w rejestrze to nie możemy dowiedzieć się pod jakim adresem jest przechowywany. Jeżeli spróbujemy to zrobić to zmienna zostanie umieszczona w zwykłej pamięci.

Z kolei specyfikator "volatile" nakazuje ostrożność w posługiwaniu się daną zmienną. Jeżeli nasz obiekt może się w jakiś niezauważalny sposób zmieniać to użycie tego przydomka jest dobrym pomysłem. Kompilator nie będzie wtedy szedł na skróty tylko za każdym razem sprawdzi czy odwołuje się do odpowiedniej komórki pamięci.

Jeżeli chcemy dodać jakąś nazwę do konkretnych wartości opisujących np. oceny to warto jest skorzystać z typu wyliczeniowego enum. Przykład:
#include <iostream>

using namespace std;

int main() {
    enum grades {
        insufficient = 1,
        allowing = 2,
        sufficient = 3,
        good = 4,
        very_good = 5,
        excellent = 6
    };

    enum numbers {
        one, // 0
        two = 5,
        three, // 6
        four = (three + 4) // 10
    };

    enum {
        first = 1,
        second,
        third = 5
    };

    grades primary_school; // Definicja zmiennej, której można podstawić tylko jedną z określonych na liście wyliczeniowej wartości.

    primary_school = allowing;

    primary_school = good;

    cout << primary_school << endl;

    cout << good << endl;

    cout << four << endl;

    cout << second << endl;
}

Źródło: Grębosz J., Symfonia C++, EDITION 2000, 2008

środa, 11 grudnia 2013

Kod C++ - histogram

// Program, który rysuje histogram wystąpień dużych liter w danym pliku tekstowym.

#include <iostream>
#include <fstream>
#include <string>
#include <conio.h>
#include <stdlib.h>

using namespace std;

int main() {
    string filename; // Nazwa pliku, z którego program ma czytać.
    ifstream stream; // Definicja strumienia do pracy z plikiem.
    char alphabet[25]; // Licznik wystąpień znaków.

    // Zerowanie licznika.

    for (int iterator = 0; iterator < 25; iterator++)
        alphabet[iterator] = 0;

    cout << "Podaj sciezke do pliku: ";

    cin >> filename;

    stream.open(filename.c_str(), ios::out);

    if (!stream) {
        cout << "Nie udalo sie otworzyc podanego pliku.";

        exit(-1);
    }

    char actual; // Aktualny znak z pliku wejściowego.

    try {
        while (stream.get(actual)) {
            switch (actual) {
            case 'A':
                alphabet[0]++;

                break;

            case 'B':
                alphabet[1]++;

                break;

            case 'C':
                alphabet[2]++;

                break;

            case 'D':
                alphabet[3]++;

                break;
            
            case 'E':
                alphabet[4]++;

                break;
            
            case 'F':
                alphabet[5]++;

                break;
            
            case 'G':
                alphabet[6]++;

                break;
            
            case 'H':
                alphabet[7]++;

                break;
            
            case 'I':
                alphabet[8]++;

                break;
            
            case 'J':
                alphabet[9]++;

                break;
            
            case 'K':
                alphabet[10]++;

                break;
            
            case 'L':
                alphabet[11]++;

                break;
            
            case 'M':
                alphabet[12]++;

                break;
            
            case 'N':
                alphabet[13]++;

                break;
            
            case 'O':
                alphabet[14]++;

                break;
            
            case 'P':
                alphabet[15]++;

                break;
            
            case 'R':
                alphabet[16]++;

                break;
            
            case 'S':
                alphabet[17]++;

                break;
            
            case 'T':
                alphabet[18]++;

                break;
            
            case 'U':
                alphabet[19]++;

                break;
            
            case 'W':
                alphabet[20]++;

                break;
            
            case 'V':
                alphabet[21]++;

                break;
            
            case 'X':
                alphabet[22]++;

                break;
            
            case 'Y':
                alphabet[23]++;

                break;
            
            case 'Z':
                alphabet[24]++;

                break;
            
            default:
                    break;
            }
        }
    }
    catch (ios::failure const & exception) {
        cout << "Nie udalo sie przeczytac z pliku poniewaz: " << exception.what() << ".";
    }

    // Sprawdz jaka wartość jest najwyższa.

    unsigned int highest;

    highest = alphabet[0];

    for (int iterator = 0; iterator < 25; iterator++) {
        if (alphabet[iterator] > highest)
            highest = alphabet[iterator];
    }

    // Rysowanie histogramu.

    for (int iterator0 = highest; iterator0 >= 1; iterator0--) {
        // Szukamy które litery mają aktualną wartość zmiennej "i".

        for (int iterator1 = 0; iterator1 < 25; iterator1++) {
            if (alphabet[iterator1] >= iterator0)
                cout << "*";
            else
                cout << " ";
        }

        cout << endl;
    }

    cout << "ABCDEFGHIJKLMNOPRSTUWVXYZ" << endl;
}