C++/C++11
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
edytujIterowanie 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
edytujW 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:
- Nazwy wartości nie pojawiają się w zakresie definicji typu wyliczeniowego, muszą być zawsze kwalifikowane jego nazwą, np. Nazwa::Wartosc2.
- 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
edytujStandardowe 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 |