W C ++ 11 możesz użyć zakresu opartego na zakresie for
, który działa jak foreach
inne języki. Działa nawet ze zwykłymi tablicami C:
int numbers[] = { 1, 2, 3, 4, 5 };
for (int& n : numbers) {
n *= 2;
}
Skąd wie, kiedy przestać? Czy działa tylko z tablicami statycznymi, które zostały zadeklarowane w tym samym zakresie, w którym for
jest używany? Jak użyłbyś tego for
z dynamicznymi tablicami?
for
. Ale w momencie, gdy tablica rozpada się na wskaźnik, informacja o rozmiarze zostaje utracona.numbers
tosizeof(numbers)/sizeof(int)
na przykład.Odpowiedzi:
Działa dla każdego wyrażenia, którego typem jest tablica. Na przykład:
int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}}; for(int &n : *arraypointer) n *= 2; delete [] arraypointer;
Aby uzyskać bardziej szczegółowe wyjaśnienie, jeśli typ wyrażenia przekazanego po prawej stronie
:
jest typem tablicowym, pętla wykonuje iterację odptr
doptr + size
(ptr
wskazując na pierwszy element tablicy,size
będący liczbą elementów tablicy).Jest to w przeciwieństwie do typów zdefiniowanych przez użytkownika, które działają na zasadzie wyszukiwania w górę
begin
iend
jako członkowie, jeśli przekazujesz obiekt klasy lub (jeśli nie ma tak nazwanych elementów członkowskich) funkcje niebędące członkami. Te funkcje dadzą iteratory początku i końca (wskazujące odpowiednio bezpośrednio po ostatnim elemencie i początku sekwencji).To pytanie wyjaśnia, dlaczego istnieje taka różnica.
źródło
begin
`end. It just happens that
std :: begin`std::end
używa funkcji składowych i zostanie użyte, jeśli lepsze dopasowanie nie jest dostępne.Myślę, że najważniejszą częścią tego pytania jest to, skąd C ++ wie, jaki jest rozmiar tablicy (przynajmniej chciałem to 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
może służyć do uzyskania rozmiaru tablicy:int size1{ std::extent< char[5] >::value }; std::cout << "Array size: " << size1 << std::endl;
Oczywiście nie ma to większego sensu, ponieważ musisz wyraźnie podać rozmiar w pierwszym wierszu, który następnie uzyskasz w drugim wierszu. Ale możesz też użyć,
decltype
a wtedy stanie się bardziej interesujący:char v[] { 'A', 'B', 'C', 'D' }; int size2{ std::extent< decltype(v) >::value }; std::cout << "Array size: " << size2 << std::endl;
źródło
Zgodnie z najnowszą wersją roboczą C ++ (n3376) instrukcja ranged for jest równoważna z następującą:
{ auto && __range = range-init; for (auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin) { for-range-declaration = *__begin; statement } }
Dzięki temu wie, jak zatrzymać w taki sam sposób, jak
for
robi to zwykła pętla przy użyciu iteratorów.Myślę, że możesz szukać czegoś podobnego do poniższego, aby zapewnić sposób użycia powyższej składni z tablicami, które składają się tylko ze wskaźnika i rozmiaru (tablice dynamiczne):
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 klasy może być następnie użyty do utworzenia zakresu, po którym można iterować przy użyciu nowego zakresu dla składni. Używam tego do przeglądania wszystkich obiektów animacji w scenie, która jest importowana przy użyciu biblioteki, która zwraca tylko wskaźnik do tablicy i rozmiar jako oddzielne wartości.
for ( auto pAnimation : Range<aiAnimation*>(pScene->mAnimations, pScene->mNumAnimations) ) { // Do something with each pAnimation instance here }
Moim zdaniem ta składnia jest znacznie jaśniejsza niż to, co można uzyskać za
std::for_each
pomocą zwykłejfor
pętli.źródło
Wie, kiedy przestać, ponieważ zna granice tablic statycznych.
Nie jestem pewien, co masz na myśli przez „tablice dynamiczne”, w każdym razie, jeśli nie iterując po tablicach statycznych, nieformalnie, kompilator wyszukuje nazwy
begin
iend
zakres klasy obiektu, po którym iterujesz, lub przegląda się nabegin(range)
iend(range)
za pomocą odnośnika i używa ich jako argumentu iteratory zależne.Więcej informacji można znaleźć w standardzie C ++ 11 (lub jego publicznej wersji roboczej), „6.5.4 Instrukcja oparta na zakresie
for
”, str.145źródło
new[]
. W takim przypadku masz tylko wskaźnik bez wskazania rozmiaru, więc nie ma możliwości,for
aby działał na podstawie zakresu .Czy należy to rozumieć jako: „ Powiedz mi, co robi ranged-for (z tablicami)? ”
Odpowiem zakładając, że - weź następujący przykład z użyciem zagnieżdżonych tablic:
int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; for (auto &pl : ia)
Wersja tekstowa:
ia
jest tablicą tablic („tablica zagnieżdżona”) zawierającą[3]
tablice, z których każda zawiera[4]
wartości. Powyższy przykład przechodziia
przez swój podstawowy „zakres” ([3]
), a zatem wykonuje pętlę[3]
razy. Każda pętla wytwarza jeden zia
„S[3]
podstawowych wartości, zaczynając od pierwszej a kończąc na ostatnim - tablicę zawierającą[4]
wartości.pl
równa się{1,2,3,4}
- tablicapl
równa się{5,6,7,8}
- tablicapl
równa się{9,10,11,12}
- tablicaZanim wyjaśnimy ten proces, oto kilka przyjaznych przypomnień o tablicach:
pl
musi być odniesieniem, ponieważ nie możemy kopiować tablicn
jest to liczba, toia[n]
jest to samo co*(ia+n)
(Wyłuskujemy adres będącyn
wpisami naprzód) iia+n
jest taki sam jak&ia[n]
(Otrzymujemy adres tego wpisu w tablicy).Oto co się dzieje:
pl
jest ustawiana jako odniesienie doia[n]
, zn
wyrównywaniem bieżącej liczby pętli zaczynając od 0. Tak więcpl
jestia[0]
w pierwszej rundzie, w drugiej toia[1]
i tak dalej. Pobiera wartość poprzez iterację.ia+n
jest krótsza niżend(ia)
.... I to wszystko.
To naprawdę tylko uproszczony sposób pisania tego :
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 twoja tablica nie jest zagnieżdżona, ten proces staje się nieco prostszy , ponieważ 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];
Dodatkowe informacje
A co by było, gdybyśmy nie chcieli używać
auto
słowa kluczowego podczas tworzeniapl
? Jakby to wyglądało?W poniższym przykładzie
pl
odwołuje się do plikuarray of four integers
. Na każdej pętlipl
podawana jest 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 ... Tak to działa, z dodatkowymi informacjami, które pozwolą uniknąć nieporozumień. To tylko „skrócona”
for
pętla, która automatycznie liczy się za Ciebie, ale brakuje jej sposobu na odzyskanie bieżącej pętli bez robienia tego ręcznie.źródło
Przykładowy kod pokazujący różnicę między tablicami na stosie a tablicami na stercie
/** * Question: Can we use range based for built-in arrays * Answer: Maybe * 1) Yes, when array is on the Stack * 2) No, when array is the Heap * 3) Yes, When the array is on the Stack, * but the array elements are on the HEAP */ void testStackHeapArrays() { int Size = 5; Square StackSquares[Size]; // 5 Square's on Stack int StackInts[Size]; // 5 int's on Stack // auto is Square, passed as constant reference for (const auto &Sq : StackSquares) cout << "StackSquare has length " << Sq.getLength() << endl; // auto is int, passed as constant reference // the int values are whatever is in memory!!! for (const auto &I : StackInts) cout << "StackInts value is " << I << endl; // Better version would be: auto HeapSquares = new Square[Size]; Square *HeapSquares = new Square[Size]; // 5 Square's on Heap int *HeapInts = new int[Size]; // 5 int's on Heap // does not compile, // *HeapSquares is a pointer to the start of a memory location, // compiler cannot know how many Square's it has // for (auto &Sq : HeapSquares) // cout << "HeapSquare has length " << Sq.getLength() << endl; // does not compile, same reason as above // for (const auto &I : HeapInts) // cout << "HeapInts value is " << I << endl; // Create 3 Square objects on the Heap // Create an array of size-3 on the Stack with Square pointers // size of array is known to compiler Square *HeapSquares2[]{new Square(23), new Square(57), new Square(99)}; // auto is Square*, passed as constant reference for (const auto &Sq : HeapSquares2) cout << "HeapSquare2 has length " << Sq->getLength() << endl; // Create 3 int objects on the Heap // Create an array of size-3 on the Stack with int pointers // size of array is known to compiler int *HeapInts2[]{new int(23), new int(57), new int(99)}; // auto is int*, passed as constant reference for (const auto &I : HeapInts2) cout << "HeapInts2 has value " << *I << endl; delete[] HeapSquares; delete[] HeapInts; for (const auto &Sq : HeapSquares2) delete Sq; for (const auto &I : HeapInts2) delete I; // cannot delete HeapSquares2 or HeapInts2 since those arrays are on Stack }
źródło