17.1 — Wprowadzenie do std::array

W lekcji 16.1 — Wprowadzenie do kontenerów i tablic, wprowadziliśmy kontenery i tablice. Podsumowując:

  • Kontenery zapewniają przechowywanie kolekcji nienazwanych obiektów (zwanych elementami).
  • Tablice przydzielają swoje elementy w sposób ciągły w pamięci i umożliwiają szybki, bezpośredni dostęp do dowolnego elementu poprzez indeks dolny.
  • C++ ma trzy różne typy tablic, które są powszechnie używane: std::vector, std::array i tablice w stylu C.

W lekcji 16.10 -- std::vector zmiany rozmiaru i pojemności, wspomnieliśmy, że tablice dzielą się na dwie kategorie:

  • Tablice o stałym rozmiarze (zwane także tablicami o stałej długości) wymagają, aby długość tablicy była znana w momencie tworzenia instancji i tej długości nie można później zmienić. Tablice typu C i std::array są tablicami o stałym rozmiarze.
  • Rozmiar tablic dynamicznych można zmieniać w czasie wykonywania. std::vector jest tablicą dynamiczną.

W poprzednim rozdziale skupiliśmy się na std::vector, ponieważ jest ona szybka, stosunkowo łatwa w użyciu i wszechstronna. To sprawia, że ​​jest to nasz ulubiony typ, gdy potrzebujemy kontenera tablicy.

Dlaczego więc nie używać tablic dynamicznych do wszystkiego?

Tablice dynamiczne są wydajne i wygodne, ale jak wszystko w życiu wymagają pewnych kompromisów w zamian za oferowane korzyści.

  • std::vector jest nieco mniej wydajne niż tablice o stałym rozmiarze. W większości przypadków prawdopodobnie nie zauważysz różnicy (chyba że napiszesz niechlujny kod, który powoduje wiele niezamierzonych realokacji).
  • std::vector obsługuje constexpr tylko w bardzo ograniczonych kontekstach.

We współczesnym C++ to właśnie ten ostatni punkt jest tak naprawdę znaczący. Tablice Constexpr oferują możliwość pisania kodu, który jest bardziej niezawodny, a także może być lepiej zoptymalizowany przez kompilator. Ilekroć możemy użyć tablicy constexpr, powinniśmy -- a jeśli potrzebujemy tablicy constexpr, std::array jest klasą kontenera, której powinniśmy używać.

Najlepsza praktyka

Użyj std::array dla tablic constexpr i std::vector tablice inne niż constexpr.

Defining a std::array

std::array jest zdefiniowany w nagłówku <array>. Została zaprojektowana tak, aby działać podobnie do std::vector i jak zobaczysz, jest między nimi więcej podobieństw niż różnic.

Jedna różnica polega na tym, jak deklarujemy std::array:

#include <array>  // for std::array
#include <vector> // for std::vector

int main()
{
    std::array<int, 5> a {};  // a std::array of 5 ints

    std::vector<int> b(5);    // a std::vector of 5 ints (for comparison)

    return 0;
}

Nasz std::array deklarację mającą dwa argumenty szablonu. Pierwszy (int) to argument szablonu typu definiujący typ elementu tablicy. Drugi (5) jest całkowitym argumentem szablonu innego niż typ, definiującym długość tablicy.

Powiązana treść

Parametry szablonu innego typu omówimy w lekcji 11.9 — Parametry szablonu inne niż typ.

Długość std::array muszą być wyrażeniem stałym

W przeciwieństwie do a std::vector, którego rozmiar można zmieniać w czasie wykonywania, długość a std::array musi być wyrażeniem stałym. Najczęściej podaną wartością długości będzie literał całkowity, zmienna constexpr lub moduł wyliczający bez zakresu.

#include <array>

int main()
{
    std::array<int, 7> a {}; // Using a literal constant

    constexpr int len { 8 };
    std::array<int, len> b {}; // Using a constexpr variable

    enum Colors
    {
         red,
         green,
         blue,
         max_colors
    };

    std::array<int, max_colors> c {}; // Using an enumerator

#define DAYS_PER_WEEK 7
    std::array<int, DAYS_PER_WEEK> d {}; // Using a macro (don't do this, use a constexpr variable instead)

    return 0;
}

Zauważ, że zmienne inne niż stałe i stałe czasu wykonania nie można użyć do określenia długości:

#include <array>
#include <iostream>

void foo(const int length) // length is a runtime constant
{
    std::array<int, length> e {}; // error: length is not a constant expression
}

int main()
{
    // using a non-const variable
    int numStudents{};
    std::cin >> numStudents; // numStudents is non-constant

    std::array<int, numStudents> {}; // error: numStudents is not a constant expression

    foo(7);

    return 0;
}

Ostrzeżenie

Być może zaskakujące, a std::array można zdefiniować o długości 0:

#include <array>
#include <iostream>

int main()
{
    std::array<int, 0> arr {}; // creates a zero-length std::array
    std::cout << arr.empty();  // true if arr is zero-length

    return 0;
}

Długość zerowa std::array jest klasą specjalnego przypadku, która nie zawiera danych. W związku z tym wywołanie dowolnej funkcji członkowskiej, która uzyskuje dostęp do danych o zerowej długości std::array (w tym operator[]) spowoduje niezdefiniowane zachowanie.

Możesz sprawdzić, czy a std::array ma długość zerową, używając empty() funkcji członkowskiej, która zwraca true jeśli tablica ma zerową długość i false w przeciwnym razie.

Agregowana inicjalizacja a std::array

Być może, co zaskakujące, std::array jest agregatem. Oznacza to, że nie ma konstruktorów i zamiast tego jest inicjowany przy użyciu inicjalizacji agregowanej. Krótko mówiąc, inicjalizacja agregatu pozwala nam bezpośrednio inicjować elementy agregatów. Aby to zrobić, udostępniamy listę inicjalizatorów, która jest ujętą w nawiasy klamrowe listą wartości inicjalizacyjnych oddzielonych przecinkami.

Powiązana treść

Omówiliśmy inicjalizację zbiorczą struktur w lekcji 13.8 -- Inicjowanie agregatu Struct.

#include <array>

int main()
{
    std::array<int, 6> fibonnaci = { 0, 1, 1, 2, 3, 5 }; // copy-list initialization using braced list
    std::array<int, 5> prime { 2, 3, 5, 7, 11 };         // list initialization using braced list (preferred)

    return 0;
}

Każda z tych form inicjalizacji inicjuje elementy tablicy w kolejności, zaczynając od elementu 0.

Jeśli a std::array zdefiniowano bez inicjatora, elementy zostaną zainicjowane domyślnie. W większości przypadków spowoduje to, że elementy pozostaną niezainicjowane.

Ponieważ generalnie chcemy, aby nasze elementy były inicjowane, std::array powinna być wartością inicjowaną (używając pustych nawiasów klamrowych), gdy jest zdefiniowana bez inicjatorów.

#include <array>
#include <vector>

int main()
{
    std::array<int, 5> a;   // Members default initialized (int elements are left uninitialized)
    std::array<int, 5> b{}; // Members value initialized (int elements are zero initialized) (preferred)

    std::vector<int> v(5);  // Members value initialized (int elements are zero initialized) (for comparison)

    return 0;
}

Jeśli na liście inicjatorów podano więcej inicjatorów niż zdefiniowana długość tablicy, kompilator wystąpi błąd. Jeśli na liście inicjatorów znajduje się mniej inicjatorów niż zdefiniowana długość tablicy, pozostałe elementy bez inicjatorów są inicjowane wartościami:

#include <array>

int main()
{
    std::array<int, 4> a { 1, 2, 3, 4, 5 }; // compile error: too many initializers
    std::array<int, 4> b { 1, 2 };          // b[2] and b[3] are value initialized

    return 0;
}

Const i constexpr std::array

A std::array mogą być const:

#include <array>

int main()
{
    const std::array<int, 5> prime { 2, 3, 5, 7, 11 };

    return 0;
}

Nawet jeśli elementy a const std::array nie są jawnie oznaczone jako const, nadal są traktowane jako const (ponieważ cała tablica jest const).

std::array ma także pełne wsparcie dla constexpr:

#include <array>

int main()
{
    constexpr std::array<int, 5> prime { 2, 3, 5, 7, 11 };

    return 0;
}

Ta obsługa constexpr jest kluczowym powodem użycia std::array.

Najlepsza praktyka

Zdefiniuj swój std::array jako constexpr, jeśli to możliwe. Jeśli std::array nie jest constexpr, rozważ użycie std::vector .

dedukcji argumentów szablonu klasy (CTAD) dla std::array C++17

Używania CTAD (dedukcja argumentów szablonu klasy) w C++ 17 możemy pozwolić kompilatorowi wydedukować zarówno typ elementu, jak i długość tablicy a std::array z listy inicjatorów:

#include <array>
#include <iostream>

int main()
{
    constexpr std::array a1 { 9, 7, 5, 3, 1 }; // The type is deduced to std::array<int, 5>
    constexpr std::array a2 { 9.7, 7.31 };     // The type is deduced to std::array<double, 2>

    return 0;
}

Preferujemy tę składnię, gdy jest to praktyczne. Jeśli Twój kompilator nie obsługuje C++17, musisz jawnie podać argumenty szablonu typu i długości.

Najlepsza praktyka

Użyj dedukcji argumentów szablonu klasy (CTAD), aby kompilator wydedukował typ i długość a std::array na podstawie swoich inicjatorów.

CTAD nie obsługuje częściowego pomijania argumentów szablonu (od C++ 23), więc nie ma możliwości użycia funkcji języka podstawowego aby pominąć tylko długość lub tylko typ a std::array:

#include <iostream>

int main()
{
    constexpr std::array<int> a2 { 9, 7, 5, 3, 1 };     // error: too few template arguments (length missing)
    constexpr std::array<5> a2 { 9, 7, 5, 3, 1 };       // error: too few template arguments (type missing)

    return 0;
}

Pomijanie tylko długości tablicy za pomocą std::to_array C++20

Jednak TAD (dedukcja argumentów szablonu, używana do rozpoznawania szablonów funkcji) obsługuje częściowe pominięcie argumentów szablonu. Od C++20 możliwe jest pominięcie długości tablicy a std::array używając std::to_array funkcji pomocniczej:

#include <array>
#include <iostream>

int main()
{
    constexpr auto myArray1 { std::to_array<int, 5>({ 9, 7, 5, 3, 1 }) }; // Specify type and size
    constexpr auto myArray2 { std::to_array<int>({ 9, 7, 5, 3, 1 }) };    // Specify type only, deduce size
    constexpr auto myArray3 { std::to_array({ 9, 7, 5, 3, 1 }) };         // Deduce type and size

    return 0;
}

Niestety użycie std::to_array jest droższe niż bezpośrednie utworzenie std::array ponieważ wiąże się z utworzeniem tymczasowej std::array która jest następnie używana do kopiowania inicjalizacji naszego pożądane std::array. Z tego powodu std::to_array powinno być używane tylko w przypadkach, gdy nie można skutecznie określić typu na podstawie inicjatorów i należy go unikać, gdy tablica jest tworzona wiele razy (np. wewnątrz pętli).

Na przykład, ponieważ nie ma możliwości określenia literału typu short, możesz użyć poniższego, aby utworzyć wartości std::array z short (bez konieczności jawnego określania długość std::array):

#include <array>
#include <iostream>

int main()
{
    constexpr auto shortArray { std::to_array<short>({ 9, 7, 5, 3, 1 }) };
    std::cout << sizeof(shortArray[0]) << '\n';

    return 0;
}

Dostęp do elementów tablicy za pomocą operator[]

Podobnie jak w std::vector, najczęstszym sposobem uzyskiwania dostępu do elementów tablicy std::array jest użycie operatora indeksu dolnego (operator[]):

#include <array> // for std::array
#include <iostream>

int main()
{
    constexpr std::array<int, 5> prime{ 2, 3, 5, 7, 11 };

    std::cout << prime[3]; // print the value of element with index 3 (7)
    std::cout << prime[9]; // invalid index (undefined behavior)

    return 0;
}

Dla przypomnienia, operator[] nie sprawdza granic. Jeśli podany zostanie nieprawidłowy indeks, niezdefiniowane zachowanie zostanie wynik.

W następnej lekcji omówimy kilka innych sposobów indeksowania a std::array .

Czas quizu

Pytanie nr 1

Jakiego rodzaju inicjalizacji używa std::array ?

Pokaż rozwiązanie

Dlaczego powinieneś jawnie inicjować wartość-inicjalizację std::array jeśli nie zapewniasz inicjalizacji wartości?

Pokaż rozwiązanie

Pytanie nr 2

Zdefiniuj std::array które będą utrzymywać wysoką temperaturę dla każdego dnia w roku (z dokładnością do dziesiątej części stopnia).

Pokaż rozwiązanie

Pytanie nr 3

Zainicjuj a std::array za pomocą następujących wartości: „h”, „e”, „l”, „l”, „o”. elementu o indeksie 1.

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
515 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze