2011-10-29 12 views
54

W języku C++ 11 można użyć opartego na zakresach for, który działa jako inny język w postaci foreach. Działa nawet ze zwykłymi tablicami C:Jak działa zakres dla pracy dla zwykłych tablic?

int numbers[] = { 1, 2, 3, 4, 5 }; 
for (int& n : numbers) { 
    n *= 2; 
} 

Skąd wiadomo, kiedy przestać? Czy działa tylko z tablicami statycznymi, które zostały zadeklarowane w tym samym zakresie, w którym używany jest kod for? Jak użyłbyś tego for z dynamicznymi tablicami?

+9

Nie ma tablic "dynamicznych" w C lub C++ per se - istnieją typy tablic, a także wskaźniki, które mogą, ale nie muszą wskazywać na tablicę lub dynamicznie przydzielany blok pamięci, który zachowuje się jak tablica. Dla każdej tablicy typu T [n] jej rozmiar jest zakodowany w typie i można uzyskać do niego dostęp za pomocą 'for'. Ale w momencie, gdy macierz rozpada się na wskaźnik, informacja o rozmiarze jest tracona. – JohannesD

+0

W twoim przykładzie liczba elementów w "liczbach" to na przykład "sizeof (numbers)/sizeof (int)". – JohannesD

Odpowiedz

37

Działa dla każdego wyrażenia, którego typem jest tablica. Np

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}}; 
for(int &n : *arraypointer) 
    n *= 2; 
delete [] arraypointer; 

do bardziej szczegółowego wyjaśnienia, jeśli typ ekspresji przekazywany do prawej : jest typu array, iteracji pętli od ptr do ptr + size (ptr wskazując na pierwszy element array, size będący liczbą elementów tablicy).

Kontrastuje to z typami definiowanymi przez użytkownika, które działają, przeglądając begin i end jako członków, jeśli przekazano obiekt klasy lub (jeśli nie ma żadnych członków nazywanych w ten sposób) funkcji nie będących członkami. Funkcje te dadzą początek i koniec iteratorów (wskazując bezpośrednio po ostatnim elemencie i początku sekwencji).

This question wyjaśnia, dlaczego ta różnica istnieje.

+8

Myślę, że pytanie brzmiało: jak to działa, nie _ kiedy to działa – sehe

+1

@twoje pytanie zawierało wiele "?". Jeden był "Czy to działa z ...?". Wyjaśniłem zarówno * jak * i * kiedy * to działa. –

+7

@JohannesSchaub: Myślę, że problemem "jak" tutaj jest to, jak dokładnie uzyskuje się rozmiar obiektu typu tablicy w pierwszej kolejności (z powodu pomieszania wskaźników i tablic, nie każdy prawie wie, że rozmiar tablicy * jest * dostępne dla programisty.) – JohannesD

2

Wie, kiedy przestać, ponieważ zna granice tablic statycznych.

Nie jestem pewien, co masz na myśli przez „dynamiczne tablice”, w każdym razie, jeśli nie iteracji nad tablic statycznych, nieformalnie, kompilator patrzy nazwami begin i end w zakresie klasy obiektu możesz iterować lub wyszukiwać begin(range) i end(range) przy użyciu wyszukiwania zależnego od argumentów i używa ich jako iteratorów.

Aby uzyskać więcej informacji, w standardzie C++ 11 (lub jego projekt publiczny), "6.5.4 Zakres oparte for stwierdzenie", pg.145

+4

"Tablica dynamiczna" byłaby stworzona z 'new []'. W tym przypadku masz tylko wskaźnik bez wskazania rozmiaru, więc nie ma możliwości, aby "na" oparte na zakresie działało z nim. –

+0

Moja odpowiedź zawiera tablicę dynamiczną, której rozmiar (4) jest znany w czasie kompilacji, ale nie wiem, czy ta interpretacja "tablicy dynamicznej" jest tym, co zamierzał zapytać. –

14

Według najnowszego projektu C++ robocza (n3376) wahała się na rachunku jest równoważna następującej:

{ 
    auto && __range = range-init; 
    for (auto __begin = begin-expr, 
       __end = end-expr; 
      __begin != __end; 
      ++__begin) { 
     for-range-declaration = *__begin; 
     statement 
    } 
} 

więc wie, jak zatrzymać ten sam sposób regularne for pętla wykorzystujące iteratory robi.

myślę, że może być patrząc na coś takiego jak poniżej, aby zapewnić drogę do korzystania z powyższej składni z tablicami, które składa się tylko wskaźnik i rozmiar (dynamicznych tablic):

template <typename T> 
class Range 
{ 
public: 
    Range(T* collection, size_t size) : 
     mCollection(collection), mSize(size) 
    { 
    } 

    T* begin() { return &mCollection[0]; } 
    T* end() { return &mCollection[mSize]; } 

private: 
    T* mCollection; 
    size_t mSize; 
}; 

Ten szablon klasa może następnie należy użyć do utworzenia zakresu, w którym można wykonywać iteracje przy użyciu nowej składni o zakresie. Używam tego do przechodzenia przez wszystkie obiekty animacji w scenie, która jest importowana za pomocą biblioteki, która zwraca tylko wskaźnik do tablicy, a rozmiar jako oddzielne wartości.

for (auto pAnimation : Range<aiAnimation*>(pScene->mAnimations, pScene->mNumAnimations)) 
{ 
    // Do something with each pAnimation instance here 
} 

Ta składnia jest, moim zdaniem, znacznie jaśniejsze niż tego co można dostać za pomocą std::for_each lub zwykły for pętlę.

+0

to fajna sztuczka! –

19

Myślę, że najważniejszą częścią tego pytania jest to, jak C++ wie, co to jest rozmiar tablicy (przynajmniej chciałem wiedzieć, kiedy znalazłem to pytanie).

C++ zna rozmiar tablicy, ponieważ jest częścią definicji tablicy - jest to typ zmiennej. Kompilator musi znać typ.

Ponieważ C++ 11 std::extent mogą być wykorzystywane w celu uzyskania rozmiaru tablicy:

int size1{ std::extent<char[5]>::value }; 
std::cout << "Array size: " << size1 << std::endl; 

oczywiście, to nie ma sensu, bo trzeba wyraźnie podać rozmiar w pierwszym linię, którą następnie uzyskasz w drugiej linii. Ale można też użyć decltype a potem robi się bardziej interesująca:

char v[] { 'A', 'B', 'C', 'D' }; 
int size2{ std::extent< decltype(v) >::value }; 
std::cout << "Array size: " << size2 << std::endl; 
+2

To jest to, o co pierwotnie pytałem. :) –

2

jaki zakres oparte na pracy dla zwykłych tablic?

Czy to czytać jak

będę odpowiedzieć zakładając, że - Weźmy następujący przykład za pomocą zagnieżdżonych tablic „Powiedz mi, co robi dla wahała-(z tablicami)?”:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; 

for (auto &pl : ia) 

Tekst wersja:

ia jest tablicę tablic tablicę ("nested"), zawierające [3] tablic, a każda zawierająca [4] wartości. Powyższy przykład wykonuje pętlę przez ia według jej podstawowego "zakresu" ([3]), a zatem pętle [3] razy. Każda pętla generuje jedną z wartości podstawowych ia, zaczynając od pierwszej i kończąc na ostatniej - tablicą zawierającą wartości [4].

  • pierwszej pętli: pl równa {1,2,3,4} - Tablica
  • Druga pętla: pl równa {5,6,7,8} - Tablica
  • Trzecia pętla: pl równa {9,10,11,12} - Tablica

Zanim wyjaśnimy proces , oto kilka przyjaznych przypomnień o tablicach:

  • Tablice są interpretowane jako wskaźniki do ich pierwszej wartości - Korzystanie z tablicą bez iteracji zwraca adres pierwszej wartości
  • plmusi być punktem odniesienia, ponieważ nie możemy skopiować tablice
  • z tablicami, podczas dodawania numer do samego obiektu tablicowego, przesuwa się do przodu tyle razy i "wskazuje" na równoważny wpis - jeśli numer jest liczbą n, to ia[n] jest tym samym, co *(ia+n) (Wywołujemy adresy o numerach od n) i ia+n jest taka sama jak &ia[n] (Otrzymujemy adres tego wpisu i w tablicy).

Oto co się dzieje:

  • Na każdej pętli pl jest ustawiony jako odniesienia do ia[n] z n równa bieżącej licznika pętli począwszy od 0. Tak więc, pl jest ia[0] w pierwszej rundzie, po drugiej to ia[1] i tak dalej. Pobiera wartość przez iterację.
  • Pętla działa tak długo, jak ia+n jest mniejsza niż end(ia).

... I o to chodzi.

To naprawdę tylko uproszczony sposób napisać ten:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; 
for (int n = 0; n != 3; ++n) 
    auto &pl = ia[n]; 

Jeśli tablica nie jest zagnieżdżona, to proces ten staje się nieco łatwiejsze, że odwołanie nie jest potrzebne, ponieważ iterowana wartość nie jest tablicą, ale raczej "normalną" wartością:

int ib[3] = {1,2,3}; 

// short 
for (auto pl : ib) 
    cout << pl; 

// long 
for (int n = 0; n != 3; ++n) 
    cout << ib[n]; 

Niektóre dodatkowe informacje

Co zrobić, jeśli nie chcemy, aby użyć słowa kluczowego auto podczas tworzenia pl? Jakby to wyglądało?

W poniższym przykładzie pl odnosi się do array of four integers. Na każdej pętli pl jest podana wartość ia[n]:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; 
for (int (&pl)[4] : ia) 

I ... To jak to działa, z dodatkowymi informacjami do sczyszczać wszelkie nieporozumienia. To po prostu "skrócona" pętla, która automatycznie liczy się dla ciebie, ale brakuje jej do odzyskania pętli prądowej bez robienia tego ręcznie.

+0

@Andy 9 na 10 razy * tytuł * to, co pasuje w Google/cokolwiek szuka - Tytuł pyta * jak to działa? *, Nie * kiedy wie, kiedy przestać? *. Mimo to podstawowe pytanie sugerowało * jest * w pewnym stopniu objęte tą odpowiedzią i przechodzi do odpowiedzi dla każdego, kto szuka innej * odpowiedzi *. Pytania składniowe, takie jak te, powinny mieć sformułowane tytuły, aby można było odpowiedzieć na to samo, ponieważ są to wszystkie informacje, które musi wyszukać poszukiwacz. Na pewno nie masz racji - pytanie nie jest zatytułowane tak, jak powinno być. –

Powiązane problemy