Dlaczego wymiar tablicy jest częścią tego typu?

14

Czytając książkę C ++ Primer natrafiłem na następujące stwierdzenie: „Liczba elementów w tablicy jest częścią typu tablicy”. Więc chciałem dowiedzieć się, używając następującego kodu:

#include<iostream>

int main()
{
    char Array1[]{'H', 'e', 'l', 'p'};
    char Array2[]{'P', 'l', 'e', 'a', 's', 'e'};

    std::cout<<typeid(Array1).name()<<std::endl;        //prints  A4_c
    std::cout<<typeid(Array2).name()<<std::endl;        //prints  A6_c

    return 0;
}

Co ciekawe, wynik pisma na dwóch tablicach pokazał, że są one jakoś inne.

  • Co dzieje się za kulisami?
  • Dlaczego konieczne jest, aby tablice miały typ uwzględniający ich rozmiar? Czy tylko dlatego, że jego rozmiar nie powinien się zmienić?
  • Jak wpłynie to na porównywanie tablic?

Po prostu chcę być w stanie głęboko zrozumieć tę koncepcję.

ośmiornica
źródło
3
Nie jest absolutnie konieczne dołączanie informacji o rozmiarze do
tekstu
Każdy samouczek na temat tablic wyjaśni (1). Nie jestem pewien, co masz na myśli przez (3), ponieważ nie ma wbudowanego sposobu porównywania tablic.
HolyBlackCat

Odpowiedzi:

20

Co dzieje się za kulisami?

Niealokowany dynamicznie jest z definicji kontener jednorodnych elementów o stałej wielkości . Tablica Nelementów typu Tjest ułożona w pamięci jako ciągła sekwencja Nobiektów typu T.


Dlaczego konieczne jest, aby tablice miały typ, który obejmuje ich rozmiar?

Nie sądzę, aby „typ” tablicy zawierał jej rozmiar - w rzeczywistości można użyć wskaźnika, aby odnieść się do ciągłej sekwencji Tobiektów. Taki wskaźnik utraciłby informacje o rozmiarze tablicy.

Jest to jednak przydatna rzecz. Poprawia bezpieczeństwo typu i koduje przydatne informacje w czasie kompilacji, których można używać na wiele sposobów. Na przykład można użyć odwołań do tablic w celu przeciążenia tablic o różnych rozmiarach

void foo(int(&array)[4]) { /* ... */ }
void foo(int(&array)[8]) { /* ... */ }

lub obliczyć rozmiar tablicy jako stałe wyrażenie

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

Jak wpłynie to na porównywanie tablic?

Naprawdę nie.

Nie możesz porównywać tablic w stylu C w taki sam sposób, w jaki porównywałbyś dwie liczby (np. intObiekty). Będziesz musiał napisać jakieś porównanie leksykograficzne i zdecydować, co to znaczy dla zbiorów o różnych rozmiarach. std::vector<T>zapewnia to i tę samą logikę można zastosować do tablic.


Bonus: C ++ 11 i wyżej zapewnia std::array, że jest opakowaniem wokół tablicy w stylu C z interfejsem podobnym do kontenera. Powinien być preferowany niż tablice w stylu C, ponieważ jest bardziej spójny z innymi kontenerami (np. std::vector<T>), A także obsługuje porównania leksykograficzne po wyjęciu z pudełka.

Vittorio Romeo
źródło
2
„Musiałbyś napisać jakieś porównanie leksykograficzne i zdecydować, co to znaczy dla kolekcji o różnych rozmiarach”. Możesz po prostu użyć std::equal(przez std::begini std::endktóre są zdefiniowane dla tablic). W takim przypadku tablice o różnych rozmiarach nie są równe.
Stu
3
Warto zauważyć, że w przypadku pętli, których zasięg jest tablicą, konieczne jest odczytanie rozmiaru tablicy w czasie kompilacji - jest to nieco subtelniejsze, ponieważ (na szczęście!) W tym przykładzie nigdy nie zapisuje się typu tablicy, ale wydaje się, że pojawia się o wiele więcej niż przeciążenie na podstawie wielkości.
Milo Brandt,
8

Ilość miejsca przydzielanego do obiektu podczas jego tworzenia zależy całkowicie od jego typu. Alokacja, o której mówię, nie jest alokacją od newlub malloc, ale alokowaną przestrzenią, abyś mógł uruchomić konstruktor i zainicjować obiekt.

Jeśli masz strukturę zdefiniowaną jako (na przykład)

struct A { char a, b; }; //sizeof(A) == 2, ie an A needs 2 bytes of space

Następnie podczas konstruowania obiektu:

A a{'a', 'b'};

Możesz myśleć o procesie konstruowania obiektu jako o procesie:

  • Przydziel 2 bajty miejsca (na stosie, ale gdzie nie ma znaczenia w tym przykładzie)
  • Uruchom konstruktor obiektu (w tym przypadku skopiuj 'a'i 'b'do obiektu)

Należy zauważyć, że 2 bajty potrzebnej przestrzeni są całkowicie zależne od typu obiektu, argumenty funkcji nie mają znaczenia. Tak więc dla tablicy proces jest taki sam, tyle że teraz potrzebna ilość miejsca zależy od liczby elementów w tablicy.

char a[] = {'a'}; //need space for 1 element
char b[] = {'a', 'b', 'c', 'd', 'e'}; //need space for 5 elements

Więc typy ai bmuszą odzwierciedlać fakt, że apotrzebuje wystarczającej ilości miejsca na 1 znak i bpotrzebuje wystarczającej ilości miejsca na 5 znaków. Oznacza to, że rozmiar tych tablic nie może się nagle zmienić, po utworzeniu tablicy 5-elementowej zawsze jest to tablica 5-elementowa. Aby mieć obiekty podobne do „tablicy”, których rozmiar może się różnić, potrzebujesz dynamicznej alokacji pamięci, którą w pewnym momencie powinna obejmować twoja książka.

SirGuy
źródło
0

To z wewnętrznego powodu biblioteki wykonawczej. Jeśli weźmiesz pod uwagę następujące stwierdzenia, na przykład:

unsigned int i;
unsigned int *iPtr;
unsigned int *iPtrArr[2];
unsigned int **iPtrHandle;

Wtedy staje się jasne, na czym polega problem: na przykład adresowanie unsigned int *musi dotyczyć samego sizeof operatorlub adresowania unsigned int.

Istnieje bardziej szczegółowe wyjaśnienie reszty tego, co tu widzisz, ale w dużej mierze jest to podsumowanie tego, co zostało omówione w C Programming Language, 2. wydanie autorstwa Kernighana i Ritchiego, dotyczące programu, który drukuje tekst w prostym języku zadeklarowanego typu strunowy.

Totem CR
źródło