Standard C++ z roku 2011, w skrócie nazywany C++11, dodał do języka wiele nowości, które powodują, że programy pisane zgodnie z nowym standardem są niekompatybilne ze starymi kompilatorami. W tym rozdziale opiszemy jedynie niektóre zmiany, pozostałe są wyjaśnione w innych rozdziałach.

Iterowanie po kolekcji

edytuj

Iterowanie po kolekcji pętlą for jest możliwe na kilka sposobów.

1. Jeśli kolekcja posiada operator indeksowania i funkcję zwracającą rozmiar można przejść po wszystkich indeksach:

#include <vector>
#include <iostream>

int main() {
    std::vector<int> liczby = {1, 2, 3, 4, 5};

    for (auto i=0u; i < liczby.size(); i++) {
        std::cout << liczby[i] << '\n';   
    }
}

2. Gdy kolekcja wspiera iteratory i metody begin/end (lub ich odpowiedniki):

int main() {
    std::vector<int> liczby = {1, 2, 3, 4, 5};

    for (auto it = liczby.begin(); it != liczby.end(); ++it) {
        std::cout << *it << '\n';   
    }
}

3. Od C++11 możliwe jest skrócenie powyższego zapisu do

int main() {
    std::vector<int> liczby = {1, 2, 3, 4, 5};

    for (auto x: liczby) {
        std::cout << x << '\n';
    }
}

Składnia jest następująca:

for (typ nazwa_zmiennej: nazwa_kolekcji) {
    // działanie na zmiennej
}

Klasa, która implementuje kolekcję musi posiadać metody begin i end, obie muszą zwracać iterator, który implementuje trzy operatory:

  • porównanie (!=)
  • dereferencję (*)
  • preinkrementację (++).

Poniżej kompletny przykład.

#include <iostream>

class Zakres {

    int start, stop;

    class Iterator {
        int liczba;

    public:
        Iterator(int liczba) : liczba(liczba) {}

        bool operator!=(const Iterator& iter) const {
            return liczba != iter.liczba;
        }

        int operator*() const {
            return liczba;
        }

        Iterator operator++() {
            liczba += 1;

            return *this;
        }
    };

public:
    Zakres() : start(0), stop(5) {};

    Iterator begin() const {
        return Iterator(start);
    };

    Iterator end() const {
        return Iterator(stop);
    };
};

int main() {

    Zakres liczby;

    for (auto x: liczby) {
        std::cout << x << '\n';
    }
}

Typy wyliczeniowe

edytuj

W C++, podobnie jak w C, funkcjonują typy wyliczeniowe. Wprowadza się je słowem kluczowym enum, po którym następuje nazwa typu, a w nawiasach klamrowych lista wartości. np.:

enum Kolor {
    Czerwony,
    Zielony,
    Niebieski
};

Problem związany z typami wyliczeniowymi jest dodawanie w zakresie widoczności nazw wszystkich wartości. Jeśli w tym samym zakresie potrzebujemy inny typ wyliczeniowy i powinien on zawierać pewne wartości o tych samych nazwach, kompilator nie dopuści do tego ze względu na powtórzenia nazw. Np. gdybyśmy chcieli typ dla kolorów świateł na skrzyżowaniach, to wartościami powinny być: Czerwony, Zielony, Zolty; dwie pierwsze są już zajęte przez Kolor.

W praktyce rozwiązuje się tego typu konflikty na kilka sposobów:

  • dodając do nazw jakieś prefiksy, np. "kol_Czerwony" i "swiatla_Czerwony";
  • umieszczając typy w przestrzeni nazw lub w klasie.

W C++11 wprowadzono nowy sposób definiowania typów wyliczeniowych enum class, deklaracja jest następująca:

enum class Nazwa {
    Wartosc1,
    Wartosc2,
    ...
    WartoscN
};

lub

enum class Nazwa: typ_wartości {
    Wartosc1,
    Wartosc2,
    ...
    WartoscN
};

Dwie główne różnice:

  1. Nazwy wartości nie pojawiają się w zakresie definicji typu wyliczeniowego, muszą być zawsze kwalifikowane jego nazwą, np. Nazwa::Wartosc2.
  2. Możliwe jest podanie typu, na jakich zapisywane są wartości. W ten sposób można precyzyjnie sterować rozmiarem klas lub struktur.
enum class Kolor {
    Czerwony,
    Zielony,
    Niebieski
};

enum class SygnalizacjaSwietlna {
    Czerwony,
    Zielony,
    Zolty
};

int main() {
    Kolor kolor;
    SygnalizacjaSwietlna sygn;

    // kolor = Czerwony;   // błąd: symbol 'Czerwony' niezadeklarowany
    // kolor = SygnalizacjaSwietlna::Zielony; // błąd: konwersja niemożliwa

    kolor = Kolor::Czerwony;
    sygn  = SygnalizacjaSwietlna::Czerwony;
}

Typy całkowite o określonym rozmiarze

edytuj

Standardowe typy całkowite, tj. int, long, short mogą mieć (w uproszczeniu) dowolny zakres, zależnie od systemu operacyjnego, architektury komputera, a nawet kompilatora. Np. nic nie stoi na przeszkodzie aby wszystkie trzy miały ten sam zakres. Pisząc przenośny program to na programistę spada obowiązek sprawdzenia, z których typów może bezpiecznie korzystać, np. badając stałe w rodzaju MAX_INT.

W C++11, wzorem standardu języka C, dodano typy całkowite o predefiniowanych rozmiarach, dzięki czemu zakresy liczb są gwarantowane. Jednocześnie trzeba mieć na uwadze, że zależnie od architektury procesora obsługa tych typów może być różnie realizowana. Np. na procesorach 32-bitowych wykonywanie działań na 64-bitowych typach wymaga większej liczby instrukcji, co przekłada się negatywnie na wydajność.

typ liczba bitów wartość minimalna wartość maksymalna
uint8_t 8 0 255
uint16_t 16 0 65 535
uint32_t 32 0 4 294 967 295
uint64_t 64 0 18 446 744 073 709 551 615
int8_t 8 -128 127
int16_t 16 -32 768 32 767
int32_t 32 -2 147 483 648 2 147 483 647
int64_t 64 -9 223 372 036 854 775 808 9 223 372 036 854 775 807