Udostępnij za pośrednictwem


<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_offindfind_ifnone_offind_if_notcountany_ofcopy_iffor_eachfor_each_ncount_ifequali .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 , outputjest 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
beginC++20 Pobierz iterator do pierwszego elementu w zakresie.
cbeginC++20 const Pobierz iterator do pierwszego elementu w zakresie.
cendC++20 Pobierz sentinel na końcu -kwalifikowanego constzakresu.
cdataC++20 const Pobierz wskaźnik do pierwszego elementu w ciągłym zakresie.
crbeginC++20 Pobierz iterator odwrotny const na początek zakresu.
crendC++20 Pobierz sentinel na końcu zwracanych crbegin() wartości.
dataC++20 Pobierz wskaźnik do pierwszego elementu w ciągłym zakresie.
emptyC++20 Ustal, czy zakres jest pusty.
endC++20 Pobierz sentinel na końcu zakresu.
rbeginC++20 Pobierz iterator odwrotny na początek zakresu.
rendC++20 Pobierz iterator odwrotny do sentinel na końcu zakresu.
sizeC++20 Pobierz rozmiar zakresu jako wartość niepodpisaną.
ssizeC++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_tC++20 Ustal, czy iterator zwrócił odwołanie range do zakresu, którego okres istnienia zakończył się.
borrowed_subrange_tC++20 Ustal, czy iterator zwrócił subrange odwołanie do podgrupy, której okres istnienia zakończył się.
danglingC++20 Wskazuje, że zwrócony iterator range/subrange cyklu życia range/subrange odwołuje się do niego.
iterator_tC++20 Zwraca typ iteratora określonego typu zakresu.
range_difference_tC++20 Zwraca typ różnicy typu iteratora określonego zakresu.
range_reference_tC++20 Zwraca typ odwołania typu iteratora określonego zakresu.
range_rvalue_reference_tC++20 Zwraca typ odwołania rvalue dla typu iteratora określonego zakresu. Innymi słowy, typ odwołania rvalue elementów zakresu.
range_size_tC++20 Zwraca typ używany do zgłaszania rozmiaru określonego zakresu.
range_value_tC++20 Zwraca typ wartości typu iteratora określonego zakresu. Innymi słowy, typ elementów w zakresie.
sentinel_tC++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