17.7 — Wprowadzenie do Tablice w stylu C

Teraz, gdy już to omówiliśmy std::vector i std::array, uzupełnimy nasze omówienie tablic o ostatni typ tablicy: tablice w stylu C.

Jak wspomniano na lekcji 16.1 — Wprowadzenie do kontenerów i tablic, tablice w stylu C zostały odziedziczone z języka C i są wbudowane w rdzeń języka C++ (w przeciwieństwie do pozostałych typów tablic, które są standardowymi klasami kontenerów bibliotek). Oznacza to, że nie musimy #include pliku nagłówkowego, aby z nich skorzystać.

Na marginesie…

Ponieważ są to jedyne typy tablic natywnie obsługiwane przez język, standardowe typy kontenerów tablic bibliotek (np. std::array i std::vector) są zwykle implementowane przy użyciu tablicy w stylu C.

Deklarowanie tablicy w stylu C

Ponieważ są częścią języka podstawowego, tablice w stylu C mają własną składnię deklaracji specjalnych. W deklaracji tablicy w stylu C używamy nawiasów kwadratowych ([]), aby poinformować kompilator, że zadeklarowany obiekt jest tablicą w stylu C. Wewnątrz nawiasów kwadratowych możemy opcjonalnie podać długość tablicy, która jest wartością całkowitą typu std::size_t która mówi kompilatorowi, ile elementów znajduje się w tablicy.

Poniższa definicja tworzy zmienną tablicową w stylu C o nazwie testScore który zawiera 30 elementów typu int:

int main()
{
    int testScore[30] {};      // Definiuje tablicę w stylu C o nazwie testScore, która zawiera 30 elementów int inicjowanych wartością (nie jest wymagane uwzględnianie)

//  std::array<int, 30> arr{}; // Dla porównania oto std::array składający się z 30 elementów int z inicjowaną wartością (wymaga #włączania <array>)

    return 0;
}

Długość tablicy w stylu C musi wynosić co najmniej 1. Kompilator wyświetli błąd, jeśli długość tablicy będzie równa zero, będzie ujemna lub będzie niecałkowita.

Dla zaawansowanych czytelników

Tablice w stylu C dynamicznie przydzielane na stercie mogą mieć długość 0.

Długość tablicy w stylu c musi być wyrażeniem stałym

Po prostu podobnie jak std::array, podczas deklarowania tablicy w stylu C długość tablicy musi być wyrażeniem stałym (typu std::size_t, chociaż zazwyczaj nie ma to znaczenia).

Wskazówka

Niektóre kompilatory mogą umożliwiać tworzenie tablic o długościach innych niż constexpr, w celu zapewnienia zgodności z funkcją C99 zwaną tablicami o zmiennej długości (VLA).

Tablice o zmiennej długości nie są prawidłowymi językami C++ i nie powinny być używane w programach C++. Jeśli twój kompilator pozwala na te tablice, prawdopodobnie zapomniałeś wyłączyć rozszerzenia kompilatora (zobacz 0.10 -- Konfigurowanie kompilatora: Rozszerzenia kompilatora).

Subskrypcja tablicy w stylu C

Podobnie jak w przypadku std::array, tablice w stylu C można indeksować za pomocą operatora indeksu dolnego (operator[]):

#include <iostream>

int main()
{
    int arr[5]; // zdefiniuj tablicę 5 wartości typu int

    arr[1] = 7; // użyj operatora indeksu dolnego do indeksowania elementu tablicy 1
    std::cout << arr[1]; // prints 7

    return 0;
}

W przeciwieństwie do standardowych klas kontenerów bibliotek (które używają indeksów typu bez znaku std::size_t tylko), indeks tablicy w stylu C może być wartością dowolnego typu całkowitego (ze znakiem lub bez znaku) lub wyliczeniem bez zakresu. Oznacza to, że tablice w stylu C nie podlegają wszystkim problemom z indeksowaniem konwersji znaków, które występują w standardowych klasach kontenerów bibliotek!

#include <iostream>

int main()
{
    const int arr[] { 9, 8, 7, 6, 5 };

    int s { 2 };
    std::cout << arr[s] << '\n'; // OK, aby użyć indeksu ze znakiem

    unsigned int u { 2 };
    std::cout << arr[u] << '\n'; // OK, aby użyć indeksu bez znaku

    return 0;
}   

Wskazówka

Tablice w stylu C akceptują indeksy ze znakiem lub bez znaku (lub wyliczenia bez zakresu).

operator[] nie sprawdza żadnych granic, a przekazanie indeksu spoza zakresu spowoduje niezdefiniowane zachowanie.

Na marginesie…

Deklarując tablicę (np. int arr[5]), użycie [] jest częścią składni deklaracji, a nie wywołaniem operatora indeksu dolnego operator[].

Zagregowana inicjalizacja tablic w stylu C

Po prostu podobnie jak std::array, tablice w stylu C są agregowane, co oznacza, że ​​można je inicjować 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ę inicjatorów, która jest ujętą w nawiasy klamrowe listą wartości inicjalizacyjnych oddzielonych przecinkami.

int main()
{
    int fibonnaci[6] = { 0, 1, 1, 2, 3, 5 }; // inicjalizacja listy kopiowania za pomocą nawiasów klamrowych lista
    int prime[5] { 2, 3, 5, 7, 11 };         // inicjalizacja listy przy użyciu listy w nawiasach klamrowych (preferowana)

    return 0;
}

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

Jeśli nie podasz inicjatora dla tablicy w stylu C, elementy zostaną domyślnie zainicjowane. W większości przypadków spowoduje to pozostawienie elementów niezainicjowanych. Ponieważ generalnie chcemy, aby nasze elementy były inicjowane, tablice w stylu C powinny być inicjowane wartościami (przy użyciu pustych nawiasów klamrowych), gdy są zdefiniowane bez inicjatorów.

int main()
{
    int arr1[5];    // Domyślnie inicjowane elementy składowe pozostają niezainicjowane)
    int arr2[5] {}; // Zainicjalizowana wartość elementów (elementy int są niezainicjowane zerem) (preferowane)

    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:

int main()
{
    int a[4] { 1, 2, 3, 4, 5 }; // błąd kompilacji: zbyt wiele inicjatorów
    int b[4] { 1, 2 };          // arr[2] i arr[3] są inicjowanymi wartościami

    return 0;
}

Jedną wadą używania tablicy w stylu C jest to, że typ elementu musi być jawnie określony. CTAD nie działa, ponieważ tablice w stylu C nie są szablonami klas. A użycie auto do próby wydedukowania typu elementu tablicy z listy inicjatorów również nie działa:

int main()
{
    auto squares[5] { 1, 4, 9, 16, 25 }; // błąd kompilacji: nie można użyć dedukcji typu w tablicach w stylu C

    return 0;
}

Pominięta długość

W poniższej definicji tablicy występuje subtelna redundancja. Widzisz to?

int main()
{
    const int prime[5] { 2, 3, 5, 7, 11 }; // liczba pierwsza ma długość 5

    return 0;
}

Wyraźnie mówimy kompilatorowi, że tablica ma długość 5, a następnie inicjujemy ją również za pomocą 5 elementów. Kiedy inicjujemy tablicę w stylu C za pomocą listy inicjatorów, możemy pominąć długość (w definicji tablicy) i pozwolić kompilatorowi wydedukować długość tablicy na podstawie liczby inicjatorów.

Następujące definicje tablic zachowują się identycznie:

int main()
{
    const int prime1[5] { 2, 3, 5, 7, 11 }; // prime1 jawnie zdefiniowana jako długość 5
    const int prime2[] { 2, 3, 5, 7, 11 };  // prime2 wydedukowana przez kompilator jako długość 5

    return 0;
}

Działa to tylko wtedy, gdy inicjatory są jawnie podane dla wszystkich elementów tablicy.

int main()
{
    int bad[] {}; // błąd: kompilator wywnioskuje, że jest to tablica o zerowej długości, co jest niedozwolone!

    return 0;
}

W przypadku używania listy inicjatorów do inicjowania wszystkich elementów tablicy w stylu C, lepiej jest pominąć długość i pozwolić kompilatorowi obliczyć długość tablicy. W ten sposób, jeśli inicjatory zostaną dodane lub usunięte, długość tablicy zostanie automatycznie dostosowana i nie będziemy narażeni na ryzyko niezgodności pomiędzy zdefiniowaną długością tablicy a liczbą dostarczonych inicjatorów.

Najlepsza praktyka

Wolę pomijać długość tablicy w stylu C podczas jawnego inicjowania każdego elementu tablicy wartością.

Tablice w stylu C const i constexpr

Po prostu podobnie jak std::array, tablice w stylu C mogą być const lub constexpr. Podobnie jak inne zmienne const, tablice const muszą zostać zainicjowane i wartości elementów nie można później zmienić.

#include <iostream>

namespace ProgramData
{
    constexpr int squares[5] { 1, 4, 9, 16, 25 }; // tablica constexpr int
}

int main()
{
    const int prime[5] { 2, 3, 5, 7, 11 }; // tablica const int
    prime[0] = 17; // błąd kompilacji: nie można zmienić stałego int

    return 0;
}

Rozmiar tablicy w stylu C

W poprzednich lekcjach używaliśmy operatora sizeof() , aby uzyskać rozmiar obiektu lub wpisać w bajtach. Zastosowany do tablicy w stylu C, sizeof() zwraca liczbę bajtów wykorzystanych przez całą tablicę:

#include <iostream>

int main()
{
    const int prime[] { 2, 3, 5, 7, 11 }; // kompilator wywnioskuje, że liczba pierwsza ma długość 5
    
    std::cout << sizeof(prime); // drukuje 20 (zakładając, że int ma 4 bajty)

    return 0;
}

Zakładając 4-bajtowe wartości całkowite, powyższy program wypisuje 20. Słowo kluczowe prime tablicę zawierającą 5 int elementów po 4 bajty każdy, więc 5 * 4 = 20 bajtów.

Zauważ, że nie ma tu żadnych kosztów ogólnych. Obiekt tablicy w stylu C zawiera swoje elementy i nic więcej.

Uzyskiwanie długości tablicy w stylu C

W C++17 możemy użyć std::size() (zdefiniowanego w nagłówku <iterator>), który zwraca długość tablicy jako wartość całkowitą bez znaku (typu std::size_t). W C++20 możemy także użyć std::ssize(), który zwraca długość tablicy jako wartość całkowitą ze znakiem (dużego typu całkowitego ze znakiem, prawdopodobnie std::ptrdiff_t).

#include <iostream>
#include <iterator> // dla std::size i std::ssize

int main()
{
    const int prime[] { 2, 3, 5, 7, 11 };   // kompilator wywnioskuje, że liczba pierwsza ma długość 5

    std::cout << std::size(prime) << '\n';  // C++17, zwraca wartość całkowitą bez znaku 5
    std::cout << std::ssize(prime) << '\n'; // C++20, zwraca wartość całkowitą ze znakiem 5

    return 0;
}

Wskazówka

Kanoniczny nagłówek definicji std::size() i std::ssize() to <iterator>. Ponieważ jednak te funkcje są tak przydatne, udostępnia je również wiele innych nagłówków, w tym <array> i <vector>. Podczas używania std::size() lub std::ssize() na Tablica w stylu C, być może nie dołączyliśmy jeszcze jednego z pozostałych nagłówków. W takim przypadku nagłówek <iterator> jest tym, który jest zwykle dołączany.

Pełną listę nagłówków definiujących te funkcje można zobaczyć w dokumentacji cppreference dla funkcji size.

Uzyskiwanie długości tablicy w stylu C (C++14 lub starsze)

Przed C++17 nie było standardowej funkcji bibliotecznej umożliwiającej uzyskanie długości tablicy w stylu C.

Jeśli używasz C++11 lub C++14, możesz zamiast tego użyć tej funkcji:

#include <cstddef> // dla std::size_t
#include <iostream>

template <typename T, std::size_t N>
constexpr std::size_t length(const T(&)[N]) noexcept
{
	return N;
}

int main() {

	int array[]{ 1, 1, 2, 3, 5, 8, 13, 21 };
	std::cout << "The array has: " << length(array) << " elements\n";

	return 0;
}

Wykorzystuje szablon funkcji, który przyjmuje tablicę w stylu C przez odniesienie, a następnie zwraca parametr szablonu niebędący typem reprezentujący długość tablicy.

W znacznie starszych bazach kodu możesz zobaczyć długość tablicy w stylu C określoną przez podzielenie rozmiaru całej tablicy przez rozmiar elementu tablicy:

#include <iostream>

int main()
{
    int array[8] {};
    std::cout << "The array has: " << sizeof(array) / sizeof(array[0]) << " elements\n";

    return 0;
}

Wypisuje:

The array has: 8 elements

Jak to działa? Po pierwsze, zauważ, że rozmiar całej tablicy jest równy długości tablicy pomnożonej przez rozmiar elementu. Mówiąc prościej: array size = length * element size.

Korzystając z algebry, możemy zmienić układ tego równania: length = array size / element size. Zwykle używamy sizeof(array[0]) dla rozmiaru elementu. Zatem długość = sizeof(array) / sizeof(array[0]). Czasami możesz zobaczyć tę formułę zapisaną jako sizeof(array) / sizeof(*array), która robi to samo.

Jednakże, jak pokażemy ci w następnej lekcji, ta formuła może dość łatwo zawieść (po przekazaniu zniszczonej tablicy), powodując nieoczekiwane uszkodzenie programu. Obydwa szablony funkcji C++17 std::size() i length() pokazane powyżej będą w tym przypadku powodować błędy kompilacji, więc można ich bezpiecznie używać.

Powiązana treść

Rozpad tablic omówimy w następnej lekcji 17.8 -- Zanikanie tablicy w stylu C.

Tablice w stylu C nie obsługują przypisania

Być może, co zaskakujące, tablice C++ nie obsługują przypisania:

int main()
{
    int arr[] { 1, 2, 3 }; // OK: inicjalizacja jest w porządku
    arr[0] = 4;            // przypisanie do poszczególnych elementów jest w porządku
    arr = { 5, 6, 7 };     // błąd kompilacji: nieprawidłowe przypisanie tablicy

    return 0;
}

Technicznie rzecz biorąc, to nie działa, ponieważ przypisanie wymaga lewy argument za modyfikowalną lwartość, a tablice w stylu C nie są uważane za modyfikowalne lwartości.

Jeśli chcesz przypisać nową listę wartości do tablicy w stylu C, najlepiej użyć zamiast tego std::vector . Alternatywnie możesz przypisać nowe wartości do tablicy w stylu C element po elemencie lub użyć std::copy , aby skopiować istniejącą tablicę w stylu C:

#include <algorithm> // dla std::copy

int main()
{
    int arr[] { 1, 2, 3 };
    int src[] { 5, 6, 7 };

    // Kopiuj src do arr
    std::copy(std::begin(src), std::end(src), std::begin(arr));

    return 0;
}

Czas quizu

Pytanie nr 1

Przekonwertuj następującą std::array definicję na równoważną definicję tablicy w stylu C constexpr:

constexpr std::array<int, 3> a{}; // allocate 3 ints

Pokaż rozwiązanie

Pytanie nr 2

Jakie trzy rzeczy są nie tak z następującym programem?

#include <iostream>

int main()
{
    int length{ 5 };
    const int arr[length] { 9, 7, 5, 3, 1 };
    
    std::cout << arr[length];
    arr[0] = 4;
    
    return 0;
}

Pokaż rozwiązanie

Pytanie nr 3

„Doskonały kwadrat” to liczba naturalna, której pierwiastek kwadratowy jest liczbą całkowitą. Idealne kwadraty możemy uzyskać, mnożąc liczbę naturalną (w tym zero) przez samą siebie. Pierwsze 4 idealne kwadraty to: 0, 1, 4, 9.

Użyj globalnej tablicy constexpr w stylu C, aby przechowywać idealne kwadraty od 0 do 9 (włącznie). Wielokrotnie proś użytkownika o wprowadzenie jednocyfrowej liczby całkowitej lub -1, aby wyjść. Wydrukuj, czy cyfra wprowadzona przez użytkownika jest idealnym kwadratem.

Wyjście powinno być zgodne z poniższymi informacjami:

Enter a single digit integer, or -1 to quit: 4
4 is a perfect square

Enter a single digit integer, or -1 to quit: 5
5 is not a perfect square

Enter a single digit integer, or -1 to quit: -1
Bye

Wskazówki: Użyj pętli for opartej na zakresie, aby przeglądać tablicę w stylu C w celu znalezienia dopasowania.

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:  
326 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze