Wybór rodzaju zmiennych indeksowych

11

Używamy typu Integer reprezentują zmienne indeksowe przez większość czasu. Ale w niektórych sytuacjach jesteśmy zmuszeni dokonać wyboru

std::vector<int> vec;
....

for(int i = 0; i < vec.size(); ++i)
....

Spowoduje to, że kompilator wyświetli ostrzeżenie, że mieszane użycie zmiennych podpisanych / niepodpisanych. jeśli zmienię indeks jako for( size_t i = 0; i < vec.size(); i++ ), (lub an unsigned int), rozwiąże to problemy.

Jeśli chodzi o bardziej specyficzne użycie typów okien, większość interfejsów API systemu Windows obsługuje DWORD (który jest typowany jako długi bez znaku).

Więc kiedy użyję podobnej iteracji, ponownie spowoduje to samo ostrzeżenie. Teraz, jeśli przepisuję to jako

DWORD dwCount;
....

for(DWORD i = 0; i < dwCount; ++i)
....

Uważam to za trochę dziwne. Może to być problem z postrzeganiem.

Zgadzam się, że powinniśmy używać tego samego typu zmiennej indeksowej, aby uniknąć problemów z zasięgiem w przypadku zmiennych indeksowych. Na przykład jeśli korzystamy

_int64 i64Count; // 
....

for(_int64 i = 0; i < i64Count; ++i)
....

Ale w przypadku DWORD lub liczb całkowitych bez znaku występują problemy z przepisaniem go jako

for(int i = 0; (size_t)i < vec.size(); ++i)

Jak większość ludzi pracuje z podobnymi problemami?

sarat
źródło
4
Dlaczego miałbyś używać podpisanej liczby całkowitej do przedstawienia indeksu? To tak, jakby używać wektora liczb całkowitych do przechowywania łańcucha.
Šimon Tóth,
3
@Let_Me_Be: Ponieważ ułatwia testowanie warunków brzegowych. Na przykład przy odliczaniu do zera test mniejszy niż przed wykonaniem treści pętli nie może działać z wartością bez znaku. Podobnie liczenie do maksimum nie działa. Oczywiście w takim przypadku liczba całkowita ze znakiem nie będzie działać (ponieważ nie może reprezentować tak dużej wartości).
Yttrill
„Spowoduje to, że kompilator wyświetli ostrzeżenie, że mieszane użycie zmiennych podpisanych / niepodpisanych.” To tylko jeden z dwóch problemów, z którymi będziesz musiał się zmagać. W wielu przypadkach std::size_tma wyższą rangę niż int (lub nawet długi). Jeśli rozmiar wektora kiedykolwiek przekroczy std::numeric_limits<int>::max(), będziesz żałować, że użyłeś int.
Adrian McCarthy

Odpowiedzi:

11

wektor ma typedef, który mówi prawidłowy typ do użycia: -

for(std::vector<int>::size_type i = 0; i < thing.size(); ++i)
{
}

Prawie zawsze jest zdefiniowane jako size_t, ale nie możesz na tym polegać

JohnB
źródło
8
Naprawdę nie poprawa czytelności, IMHO.
Doc Brown
Nie, ale ponieważ jest to jedyny sposób, aby powiedzieć, jakiego typu należy użyć dla indeksu w wektorze, to tak naprawdę nie ma znaczenia ...
JohnB
4
Te dni w c ++ 11 używają auto
JohnB
6
@JohnB Masz na myśli auto i = 0? To wcale nie pomaga, istań ​​się int.
zenith
1
Czytelność można poprawić za pomocą using index_t = std::vector<int>::size_type;.
Toby Speight
4
std::vector<int> vec;

for(int i = 0; i < vec.size(); ++i)

Użyj do tego iteratora, a nie forpętli.

Dla innych, tak długo, jak typ zmiennej jest tej samej wielkości, static_castpowinna działać dobrze (czyli DWORDdo int16_t)

Demian Brecht
źródło
2
for (std::vector<int>::iterator i = vec.begin(); i != vec.end(); ++i)jest trudny do napisania. Posiadanie for (auto i = vec.begin();...jest o wiele bardziej czytelne. Oczywiście foreachjest również w C ++ 11.
David Thornley,
3

Opisany przypadek jest również jedną z rzeczy, których nie lubię w C ++. Ale nauczyłem się z tym żyć, używając albo

for( size_t i = 0; i < vec.size(); i++ )

lub

for( int i = 0; i < (int)vec.size(); i++ )

(oczywiście ten ostatni tylko wtedy, gdy nie ma ryzyka uzyskania przelewu int).

Doktor Brown
źródło
3

Powodem, dla którego ostrzega przed porównaniem podpisanego i niepodpisanego, jest to, że podpisana wartość prawdopodobnie zostanie przekonwertowana na niepodpisaną, co może nie być tym, czego oczekujesz.

W twoim przykładzie (w porównaniu intdo size_t), intzostanie domyślnie przekonwertowany na size_t(chyba że w intjakiś sposób ma większy zakres niż size_t). Tak więc, jeśli wartość intjest ujemna, prawdopodobnie będzie większa niż wartość, z którą ją porównujesz z powodu zawinięcia. Nie będzie to stanowić problemu, jeśli Twój indeks nigdy nie będzie ujemny, ale nadal otrzymasz to ostrzeżenie.

Zamiast tego należy użyć typ bez znaku (takiego jak unsigned int, size_tlub, jak zaleca John B , std::vector<int>::size_type) dla zmiennej index:

for(unsigned int i = 0; i < vec.size(); i++)

Zachowaj ostrożność podczas odliczania:

for(unsigned int i = vec.size()-1; i >= 0; i--) // don't do this!

Powyższe nie zadziała, ponieważ i >= 0jest zawsze prawdziwe, gdy ijest niepodpisane. Zamiast tego użyj „ operatora strzałki ” dla pętli, które odliczają:

for (unsigned int i = vec.size(); i-- > 0; )
    vec[i] = ...;

Jak wskazują inne odpowiedzi, zwykle chcesz użyć iteratora do przejścia przez vector. Oto składnia C ++ 11:

for (auto i = vec.begin(); i != vec.end(); ++i)
Joey Adams
źródło
1
Wciąż istnieje ryzyko, że unsigned intnie jest wystarczająco duży, aby pomieścić rozmiar.
Adrian McCarthy
2

Nowa opcja dla C ++ 11, możesz wykonywać następujące czynności

for(decltype(vec.size()) i = 0; i < vec.size(); ++i) {...}

i

for(decltype(dWord) i = 0; i < dWord; ++i) {...}

Chociaż powtarza się nieco więcej niż podstawowa pętla for, nie jest ona tak długa, jak w określaniu wartości sprzed 11 lat, a konsekwentne stosowanie tego wzorca będzie działać dla większości, jeśli nie wszystkich, możliwych terminów chcesz porównać z tym, co czyni go doskonałym do refaktoryzacji kodu. Działa nawet w takich prostych przypadkach:

int x = 3; int final = 32; for(decltype(final) i = x; i < final; ++i)

Ponadto, chociaż powinieneś używać, autogdy ustawiasz ijakąś inteligentną wartość (np. vec.begin()), decltypeDziała, gdy ustawiasz na stałą taką jak zero, gdzie auto po prostu to rozwiązuje, intponieważ 0 jest prostym dosłunkiem całkowitym.

Szczerze mówiąc, chciałbym zobaczyć mechanizm kompilatora do rozszerzania autookreślania typu dla inkrementatorów pętli, aby spojrzeć na porównywaną wartość.

Macieja
źródło
1

Używam obsady do int, jak w for (int i = 0; i < (int)v.size(); ++i). Tak, to brzydkie. Obwiniam to za głupi projekt standardowej biblioteki, w której postanowiono użyć liczb całkowitych bez znaku do przedstawienia rozmiarów. (Aby ... co? Rozszerzyć zasięg o jeden bit?)

zvrba
źródło
1
W jakiej sytuacji zbiór negatywny miałby znaczenie? Używanie liczb całkowitych bez znaku dla różnych rozmiarów kolekcji wydaje mi się rozsądnym wyborem. Ostatnio sprawdziłem, biorąc długość sznurka rzadko zwracało wynik ujemny albo ...
CVn
1
W jakiej sytuacji miałoby to znaczenie dla prostego testu, takiego jak if(v.size()-1 > 0) { ... }zwrócenie wartości true dla pustego pojemnika? Problem polega na tym, że rozmiary są również często używane w arytmetyce, szczególnie. z kontenerami opartymi na indeksach, które proszą o problemy, biorąc pod uwagę, że są niepodpisane. Zasadniczo używanie typów niepodpisanych do celów innych niż 1) bitowe manipulacje lub 2) arytmetyka modularna wymaga kłopotów.
zvrba
2
Słuszna uwaga. Chociaż tak naprawdę nie widzę sensu w twoim konkretnym przykładzie (prawdopodobnie po prostu napisałbym, if(v.size() > 1) { ... }ponieważ to wyjaśnia intencję, a jako dodatkowy bonus kwestia podpisania / niepodpisania staje się nieważna), ale widzę, jak w niektórych szczególnych przypadkach sygnatura może być przydatna. Poprawiono mnie.
CVn
1
@Michael: Zgadzam się, że to wymyślony przykład. Mimo to często piszę algorytmy z zagnieżdżonymi pętlami: dla (i = 0; i <v.size () - 1; ++ i) dla (j = i + 1; j <v.size (); ++ j) .. jeśli v jest puste, zewnętrzna pętla wykonuje się (size_t) -1 razy. Więc albo muszę sprawdzić v.empty () przed pętlą, albo rzutować v.size () na podpisany typ, z których oba osobiście uważam za brzydkie obejścia. Wybieram obsadę, ponieważ ma mniej LOC, no if () s => mniej możliwości pomyłki. (Również w 2. uzupełnieniu przepełnienie konwersji zwraca liczbę ujemną, więc pętla w ogóle się nie wykonuje.)
zvrba
Rozszerzenie zakresu o 1 bit było (i nadal jest) bardzo przydatne w systemach 16-bitowych.
Adrian McCarthy