ALGO25 / BLOG

Greșeli cu indicii în for: cum le găsești în C++

Verifică limitele unei bucle, accesul la vecini și parcurgerea inversă. Folosește exemple mici pentru a descoperi erorile de tip off-by-one.

O eroare de tip off-by-one apare când o limită este deplasată cu o poziție: bucla procesează prea mult, prea puțin sau accesează un vecin inexistent. Exemplul cu zece elemente poate părea corect, în timp ce cazul cu unul singur arată imediat problema.

Stabilește intervalul înainte de buclă

Pentru n elemente indexate de la zero, indicii valizi sunt 0, 1, ..., n-1. Intervalul semideschis se scrie [0,n). Condiția de parcurgere este i<n, nu i<=n. Tabloul poate avea capacitatea 1000, dar dacă ai citit n=7, doar primele șapte valori aparțin problemei.

Dacă alegi indexare de la 1, ai nevoie de spațiu și pentru celula de indice n. Un tablou cu 1000 de celule nu permite indicele 1000. Nu combina inițializarea de la 1 cu limita exclusivă n fără să verifici ce element rămâne neprocesat.

Un vector cu patru elemente are indicii 0,1,2,3. Ultima pereche validă de vecini folosește indicii 2 și 3.Un vector cu patru elemente are indicii 0,1,2,3. Ultima pereche validă de vecini folosește indicii 2 și 3.Mărește schema ↗

Accesul la vecini restrânge intervalul

O buclă care citește a[i-1] nu poate începe de la i=0. Pentru comparația cu vecinul din stânga, intervalul natural este i=1..n-1. O buclă care citește a[i+1] trebuie să se oprească înainte de ultimul element, adică să respecte i+1<n.

Pentru n=1, nu există perechi de vecini. O asemenea buclă trebuie să execute zero pași. Dacă ai nevoie de un rezultat pentru elementul unic, îl tratezi prin inițializarea stării, nu prin acces la o poziție imaginară.

Parcurgerea inversă și tipul indicelui

Cu un indice întreg cu semn, poți porni de la n-1 și continua cât timp i>=0. Cu un tip fără semn, condiția i>=0 este mereu adevărată. După zero, decrementarea nu produce -1, ci o valoare mare.

Ai grijă și la expresia size()-1: dacă dimensiunea este zero și tipul este fără semn, scăderea are loc înaintea eventualei conversii. Decide explicit cum tratezi containerul gol. Nu ascunde această decizie într-o conversie plasată prea târziu.

Când vectorul se modifică în timpul parcurgerii

După ștergerea unui element, următorul se mută pe poziția lui. Dacă mărești imediat indicele, poți sări peste el. După inserare, dimensiunea logică crește și trebuie verificată capacitatea înaintea deplasării. Aceste operații au alte invariante decât o simplă citire a tuturor elementelor.

Un exemplu bun pentru ștergerea numerelor pare este 2 4 5: cele două valori de eliminat sunt vecine. Un program care sare peste poziția mutată va păstra din greșeală 4.

Setul minim de teste

Testează n=0 numai dacă este permis, apoi n=1, n=2 și n egal cu limita maximă. Folosește valori diferite și ușor de recunoscut, de exemplu 10, 20, 30. Verifică numărul de elemente procesate și primul/ultimul indice accesat, nu doar rezultatul final.

Pentru aprofundare, citește declararea și parcurgerea vectorilor, inserarea și ștergerea și condițiile buclelor.

Întrebări frecvente

Care sunt indicii valizi pentru n elemente indexate de la zero?

Indicii sunt de la 0 la n-1. O parcurgere completă folosește condiția i < n. Capacitatea mai mare a tabloului nu înseamnă că pozițiile necitite fac parte din date.

Când pot accesa a[i+1] într-o buclă?

Numai când și indicele i+1 este valid, adică i+1 < n pentru n elemente indexate de la zero. Ultimul element nu are un vecin la dreapta în interiorul vectorului.

Ce este o eroare off-by-one?

Este o limită greșită cu o poziție: un element este omis sau este procesată o poziție în plus. Testele cu un singur element și cu soluția la ultimul indice o descoperă frecvent.