<ranges>
Na wysokim poziomie zakres jest czymś, co można iterować. Zakres jest reprezentowany przez iterator, który oznacza początek zakresu i sentinel, który oznacza koniec zakresu. Sentinel może być taki sam jak iterator początkowy lub może być inny. Kontenery, takie jak vector
i list
, w standardowej bibliotece języka C++ są zakresami. Zakres tworzy abstrakcję iteratorów w sposób, który upraszcza i wzmacnia możliwość korzystania z standardowej biblioteki szablonów (STL).
Algorytmy STL zwykle przyjmują iteratory wskazujące część kolekcji, na której powinny działać. Rozważmy na przykład sposób sortowania vector
za pomocą polecenia std::sort()
. Przekazujesz dwa iteratory, które oznaczają początek i koniec .vector
Zapewnia to elastyczność, ale przekazywanie iteratorów do algorytmu jest dodatkową pracą, ponieważ prawdopodobnie chcesz po prostu posortować całość.
Za pomocą zakresów można wywołać std::ranges::sort(myVector);
metodę , która jest traktowana tak, jakby wywołano std::sort(myVector.begin(), myVector.end());
metodę . W bibliotekach zakresów algorytmy przyjmują zakresy jako parametry (chociaż mogą również przyjmować iteratory, jeśli chcesz). Mogą one działać bezpośrednio na kolekcjach. Przykłady dostępnych algorytmów <algorithm>
zakresu obejmują copy
, , copy_n
, all_of
find
find_if
none_of
find_if_not
count
any_of
copy_if
for_each
for_each_n
count_if
equal
i .mismatch
Ale być może najważniejszą zaletą zakresów jest to, że można tworzyć algorytmy STL, które działają na zakresach w stylu przypominającym programowanie funkcjonalne.
Przykład zakresów
Przed zakresami, jeśli chcesz przekształcić elementy kolekcji, które spełniają określone kryterium, musisz wprowadzić pośredni krok do przechowywania wyników między operacjami. Jeśli na przykład chcesz utworzyć wektor kwadratów z elementów w innym wektorze, który jest podzielny przez trzy, możesz napisać coś takiego:
std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
std::vector<int> intermediate, output;
std::copy_if(input.begin(), input.end(), std::back_inserter(intermediate), [](const int i) { return i%3 == 0; });
std::transform(intermediate.begin(), intermediate.end(), std::back_inserter(output), [](const int i) {return i*i; });
Za pomocą zakresów można wykonać to samo bez konieczności wektora intermediate
:
// requires /std:c++20
std::vector<int> input = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto output = input
| std::views::filter([](const int n) {return n % 3 == 0; })
| std::views::transform([](const int n) {return n * n; });
Oprócz łatwiejszego odczytywania ten kod pozwala uniknąć alokacji pamięci wymaganej dla wektora intermediate
i jego zawartości. Umożliwia również tworzenie dwóch operacji.
W poprzednim kodzie każdy element, który jest podzielny przez trzy, jest połączony z operacją do kwadratu tego elementu. Symbol potoku (|
) łączy operacje i jest odczytywany od lewej do prawej.
Wynik , output
jest sam w sobie rodzajem zakresu nazywanego widokiem.
Widoki
Widok jest lekkim zakresem. Wyświetlanie operacji — takich jak domyślna konstrukcja, przenoszenie konstrukcji/przypisania, kopiowanie konstrukcji/przypisania (jeśli istnieje), zniszczenie, rozpoczęcie i zakończenie — wszystko dzieje się w stałym czasie niezależnie od liczby elementów w widoku.
Widoki są tworzone przez adaptery zakresu, które zostały omówione w poniższej sekcji. Aby uzyskać więcej informacji na temat klas implementujących różne widoki, zobacz Klasy widoków.
Sposób wyświetlania elementów w widoku zależy od adaptera zakresu używanego do tworzenia widoku. W poprzednim przykładzie adapter zakresu przyjmuje zakres i zwraca widok elementów podzielnych przez trzy. Zakres bazowy pozostaje niezmieniony.
Widoki są komponowalne, co jest potężne. W poprzednim przykładzie widok elementów wektorowych, które są podzielne przez trzy, jest połączony z widokiem, który kwadratuje te elementy.
Elementy widoku są oceniane leniwie. Oznacza to, że przekształcenia stosowane do każdego elementu w widoku nie są oceniane, dopóki nie zostanie wyświetlony monit o element. Jeśli na przykład uruchomisz następujący kod w debugerze i umieścisz punkt przerwania w wierszach auto divisible_by_three = ...
i auto square = ...
, zobaczysz, że trafisz divisible_by_three
do punktu przerwania lambda, ponieważ każdy element w input
pliku jest testowany pod kątem widoczności przez trzy. square
Punkt przerwania lambda zostanie trafiony, ponieważ elementy, które są podzielne przez trzy są kwadratowe.
// requires /std:c++20
#include <ranges>
#include <vector>
#include <iostream>
int main()
{
std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto divisible_by_three = [](const int n) {return n % 3 == 0; };
auto square = [](const int n) {return n * n; };
auto x = input | std::views::filter(divisible_by_three)
| std::views::transform(square);
for (int i : x)
{
std::cout << i << '\n';
}
return 0;
}
Aby uzyskać więcej informacji na temat widoków, zobacz <ranges>
klasy widoków.
Adaptery zakresowe
Adaptery zakresowe przyjmują zakres i tworzą widok. Adaptery zakresowe produkują leniwie oceniane widoki. Oznacza to, że nie ponosisz kosztów przekształcania każdego elementu w zakresie w celu utworzenia widoku. Płacisz tylko koszt przetwarzania elementu w widoku, gdy uzyskujesz dostęp do tego elementu.
W poprzednim przykładzie filter
adapter zakresu tworzy widok o nazwie input
zawierający elementy, które są podzielne przez trzy. Adapter transform
zakresu przyjmuje widok elementów podzielnych przez trzy i tworzy widok tych elementów kwadratu.
Adaptery zakresowe można łączyć ze sobą (składać), co jest sercem mocy i elastyczności zakresów. Komponowanie adapterów zakresu pozwala przezwyciężyć problem, że poprzednie algorytmy STL nie są łatwe do komponowania.
Aby uzyskać więcej informacji na temat tworzenia widoków, zobacz Adaptery zakresu.
Algorytmy zakresu
Niektóre algorytmy zakresu przyjmują argument zakresu. Może to być na przykład std::ranges::sort(myVector);
.
Algorytmy zakresu są prawie identyczne z odpowiadającymi im algorytmami iteratora-par w std
przestrzeni nazw. Różnica polega na tym, że mają ograniczenia wymuszane przez koncepcję i akceptują argumenty zakresu lub więcej par argumentów iteratora-sentinel. Mogą one pracować bezpośrednio w kontenerze i można je łatwo połączyć w łańcuch.
<ranges>
, funkcje
Następujące funkcje służą do tworzenia iteratorów i sentinels dla zakresów oraz uzyskiwania rozmiaru zakresu.
Function | opis |
---|---|
begin C++20 |
Pobierz iterator do pierwszego elementu w zakresie. |
cbegin C++20 |
const Pobierz iterator do pierwszego elementu w zakresie. |
cend C++20 |
Pobierz sentinel na końcu -kwalifikowanego const zakresu. |
cdata C++20 |
const Pobierz wskaźnik do pierwszego elementu w ciągłym zakresie. |
crbegin C++20 |
Pobierz iterator odwrotny const na początek zakresu. |
crend C++20 |
Pobierz sentinel na końcu zwracanych crbegin() wartości. |
data C++20 |
Pobierz wskaźnik do pierwszego elementu w ciągłym zakresie. |
empty C++20 |
Ustal, czy zakres jest pusty. |
end C++20 |
Pobierz sentinel na końcu zakresu. |
rbegin C++20 |
Pobierz iterator odwrotny na początek zakresu. |
rend C++20 |
Pobierz iterator odwrotny do sentinel na końcu zakresu. |
size C++20 |
Pobierz rozmiar zakresu jako wartość niepodpisaną. |
ssize C++20 |
Pobierz rozmiar zakresu jako wartość ze znakiem. |
Aby uzyskać więcej informacji, zobacz <ranges>
funkcje.
Pojęcia dotyczące zakresu
Sposób iteracji elementów zakresu zależy od jego bazowego typu iteratora. Zakresy używają pojęć języka C++, które określają, który iterator obsługuje.
W języku C++20, aby powiedzieć, że koncepcja X udoskonala koncepcję Y , oznacza, że wszystko, co spełnia koncepcję Y , spełnia również koncepcję X. Na przykład: samochód, autobus i ciężarówka wszystkie uściślić pojazd.
Niektóre pojęcia dotyczące zakresu odzwierciedlają hierarchię kategorii iteratorów. Poniższa tabela zawiera listę pojęć dotyczących zakresu wraz z typami kontenerów, do których można zastosować.
Koncepcja zakresu | opis | Obsługiwane kontenery |
---|---|---|
std::ranges::output_range |
Może iterować do przodu. | |
std::ranges::input_range |
Może iterować od początku do końca co najmniej raz. | std::forward_list std::unordered_map std::unordered_multimap std::unordered_set std::unordered_multiset basic_istream_view |
std::ranges::forward_range |
Może iterować od początku do końca więcej niż raz. | std::forward_list std::unordered_map std::unordered_multimap std::unordered_set std::unordered_multiset |
std::ranges::bidirectional_range |
Może iterować do przodu i do tyłu więcej niż raz. | std::list std::map std::multimap std::multiset std::set |
std::ranges::random_access_range |
Może uzyskać dostęp do dowolnego elementu (w stałym czasie) przy użyciu [] operatora . |
std::deque |
std::ranges::contiguous_range |
Elementy są przechowywane w pamięci kolejno. | std::array std::string std::vector |
Zobacz <ranges>
pojęcia , aby uzyskać więcej informacji na temat tych pojęć.
<ranges>
szablony aliasów
Następujące szablony aliasów określają typy iteratorów i sentinels dla zakresu:
Szablon aliasu | opis |
---|---|
borrowed_iterator_t C++20 |
Ustal, czy iterator zwrócił odwołanie range do zakresu, którego okres istnienia zakończył się. |
borrowed_subrange_t C++20 |
Ustal, czy iterator zwrócił subrange odwołanie do podgrupy, której okres istnienia zakończył się. |
dangling C++20 |
Wskazuje, że zwrócony iterator range /subrange cyklu życia range /subrange odwołuje się do niego. |
iterator_t C++20 |
Zwraca typ iteratora określonego typu zakresu. |
range_difference_t C++20 |
Zwraca typ różnicy typu iteratora określonego zakresu. |
range_reference_t C++20 |
Zwraca typ odwołania typu iteratora określonego zakresu. |
range_rvalue_reference_t C++20 |
Zwraca typ odwołania rvalue dla typu iteratora określonego zakresu. Innymi słowy, typ odwołania rvalue elementów zakresu. |
range_size_t C++20 |
Zwraca typ używany do zgłaszania rozmiaru określonego zakresu. |
range_value_t C++20 |
Zwraca typ wartości typu iteratora określonego zakresu. Innymi słowy, typ elementów w zakresie. |
sentinel_t C++20 |
Zwraca typ sentinel określonego zakresu. |
Aby uzyskać więcej informacji na temat tych szablonów aliasów, zobacz <ranges>
szablony aliasów.
Zobacz też
<ranges>
, funkcje
<ranges>
Pojęcia
Adaptery zakresowe
Odwołanie do plików nagłówka