Iteratori în delphi - void

Iteratorii din Delphi

Se spune documentația. care iterează elementele matricei, caracterele din șir, valorile din set (set) și, cel mai interesant, clasele și înregistrările special pregătite. Cu matrice și simboluri, totul este evident, puteți explica seturile în mai multe detalii:







Acesta este un exemplu de utilizare tipică a iterației peste seturi. Fără iterație, ar trebui să procedați astfel:

Nu numai că acest lucru este incomod, ar fi necesar să adăugați o nouă linie procedurii de fiecare dată când un nou steag este adăugat la set.

Acum despre caracteristicile mai interesante ale iteratorilor. Este permisă crearea de iteratori în propriile clase. De exemplu, o listă personalizată sau un fișier TStringList poate suporta iterația asupra elementelor din listă. Cele mai multe clase standard acceptă:

Puteți să-l adăugați la clasa proprie. Pentru a face acest lucru, avem nevoie de o clasă de ajutor, un iterator (sau un enumerator, așa cum îi place oricui). Clasa principală trebuie să aibă o funcție GetEnumerator. care creează și returnează o instanță a unei clase auxiliare:

Iteratorul în sine trebuie să conțină o funcție MoveNext care returnează False dacă nu mai există elemente și proprietatea curentă care returnează elementul curent:

Deci cel mai simplu iterator este gata. Nu-l pot îmbunătăți într-un fel? De exemplu, orice dolphist experimentat va observa imediat că fiecare utilizare a iteratorului necesită crearea unui obiect, iar crearea obiectelor, amintim, este o operație destul de lentă. Mulțumesc lui Dumnezeu, puteți face un iterator un record. Tot ceea ce trebuie schimbat în codul nostru este de a elimina apelul la crearea moștenită:

Astfel, obținem o repetare destul de rapidă pe un container arbitrar. Puteți face înregistrarea TMyCollection. Nu va oferi un avantaj special în performanță, deoarece este creat o singură dată, dar dacă este necesar pentru alte scopuri - întotdeauna vă rog. Doar în cazul în care, vă reamintesc modul în care Dolphi a implementat referințe încrucișate. Cu clase:

Întrebarea evidentă: este posibil să nu creezi deloc un obiect iterativ sau o înregistrare? Nu putem returna pur și simplu o referire la noi înșine în GetEnumerator dacă suntem siguri că iterația va fi utilizată doar la rândul său?

Răspunsul corect este: nu, nu puteți. Delphi distruge automat iteratorul după utilizare. Dacă în GetEnumerator returnați obiectul principal, acesta va fi distrus. Nu poți face nimic cu asta, nu poți suprascrie Distruge.

Cineva va întreba dacă este posibil să transforme acest truc în înregistrări. Înregistrările nu sunt, de fapt, distruse? Da, înregistrările nu sunt distruse, dar cu ele asemenea trucuri nu au prea mult sens. Nu uitați că înregistrările sunt transmise prin valoare; aceasta înseamnă că funcția GetEnumerator nu întoarce un link la înregistrare, ci un bloc întreg de date, tot conținutul său. Desigur, puteți reveni la obiect însuși:

Aceasta va însemna doar că veți crea o nouă înregistrare pentru TMyRecord și veți copia tot conținutul vechiului conținut în acesta. Această abordare, întâmplător, ar putea fi utilă într-o zi, de exemplu, dacă în interiorul TMyRecord este o cantitate mică de informații critice. Atunci când accesați din mai multe fire, este uneori avantajos să nu blocați obiectul pentru întreaga durată de iterație, ci să copiați informațiile pentru o căutare ulterioară și să o eliberați imediat.

blocarea
Acestea erau aplicații simple ale iteratorilor, iar acum să trecem la cele mai complexe. Cel mai convenabil lucru în iteratori este că acestea vă permit să executați codul arbitrar în momentul enumerării. Acest lucru vom folosi. De exemplu, hai să facem o căutare sigură în flux. Când lucrați cu fire multiple, orice programator caută ceva de genul:







Noi simplificăm acest design!

Uneori această abordare, așa cum am spus, este foarte convenabilă - dar nu întotdeauna. Adesea, pentru a salva o stare, trebuie să copiați întreaga matrice, chiar indicii, și acest lucru este extrem de lung. Ce pacat ca nu putem afla cand Delphi distruge structura ... sau putem?

Ceea ce facem acum este o mică vrăjitorie. Delphi nu provoacă distrugătoare pentru scoruri mari, dar finalizează întregul conținut, inclusiv provocând _Release pentru interfețe. Prin urmare, vom crea o interfață care va elibera blocarea prin _Release.
În primul rând, avem nevoie de o implementare suprasolicitată a IInterface:

RefCnt am plecat numai pentru a nu sparge accidental orice optimizare internă Delphi, care utilizează această valoare. În general, se poate întoarce întotdeauna o unitate. Obiectul nostru nu este distrus cu căderea RefCnt la zero; Durata sa de viață este stabilită, ca și obiectul obișnuit Delphic, prin distrugere manuală. Când capturați o legătură către dvs., acesta blochează obiectul care urmează să fie sortat, după deblocare îl deblochează.

Acum, colecția însăși:

Rețineți că stocăm Gatekeeperul ca obiect. Dacă am fi păstrat-o ca o interfață, ea ar fi în mod constant prinsă. Indicatoarele inteligente la interfețele din Delphi sunt aranjate astfel încât să apeleze automat _AddRef atunci când atribuie valoarea variabilei de tip interfață și să apeleze automat _Release la compensarea acestei valori.

Atunci când întrebăm TMyCollectionEnumerator, folosim această proprietate: vom returna un iterator, în interiorul căruia am pus o variabilă de tipul IInterface. Când ne punem Gatekeeper-ul în el, Dolphi execută automat _AddRef, blocând colecția. Atunci când înregistrarea este distrusă, Dolphi finalizează automat intrarea, șterge câmpul Gatekeeper și, din moment ce era un tip de interfață, o numește _Release - iar colecția este deblocată.

Acest lucru este, fără îndoială, un mod foarte convenabil și rapid. Un obiect al tipului Gatekeeper este suficient pentru orice colecție; Puteți să o utilizați în mai multe iteratoare simultan. Acesta este creat odată, când se creează TMyCollection și aproape că nu se adaugă overhead. Cu toate acestea, aici sunt capcanele lor. Deși Delphi garantează distrugerea iteratorului și distrugerea lui, garantează curățarea interfeței, nu se știe când o va face. În codul atașat, am efectuat unele experimente și a găsit, de exemplu, că, deși funcțiile iterator obișnuite este distrus imediat după ieșirea din „pentru ... în“, în corpul principal al aplicației consola iterators-înregistrările nu sunt distruse deloc. Deci, această tehnică ar trebui utilizată cu prudență.

filtre
O altă aplicație interesantă a iteratorilor este filtrele. În loc de a scrie:

Aș vrea ceva de genul:

Cu iteratori este ușor de făcut, totuși, pentru un preț suplimentar - dacă utilizați înregistrări. Acest preț suplimentar este crearea unei alte înregistrări temporare. Voi demonstra:

Problema aici este că sintaxa dolphi solicită cu tărie ca obiectul din partea dreaptă a "for ... in" să implementeze GetEnumerator. Funcția de filtrare pe care o scriem ar trebui să creeze deja și să returneze un obiect, iar acest obiect va trebui apoi să creeze un alt obiect, enumeratorul. Aș dori să fie în măsură să folosească acest lucru ca un recenzor mai creat FilterKeepalive obiect (la urma urmei, este mai nizachem nu este necesar!). Cu toate acestea, scorurile mari nu va funcționa pentru motivele deja menționate: dacă am rambursare în GetEnumerator „Rezultat: = Auto“, de fapt, vom copia înregistrarea, și nu se îmbunătățește situația.

Un alt lucru - clase. Aici nu se impune nici un cost suplimentar:

Clasa întoarce pur și simplu o referință la sine. Dacă vă amintiți, a fost interzis să faceți acest lucru pentru colecție, deoarece dolphi distruge iteratorul după utilizare. Cu toate acestea, aici nu suntem doar la îndemână, dar este vital: cine altcineva va distruge clasa temporară creată în FilterKeepalive?

generatoare
O altă utilizare interesantă a iteratorilor se datorează faptului că nu trebuie să trecem peste obiectele existente. Iteratorul poate sorta elementele pe care le calculează în zbor. Referința din exemple este generarea numerelor Fibonacci și vom rezolva o problemă mai practică și mai rapid (desigur, fără clase de fabrici și interfețe, Dumnezeu să mă interzică).

Să creăm un iterator care ne va reveni la toate ferestrele de nivel superior din sistem:

Nu există probleme cu acest lucru, dar dați codul. În primul rând, vom crea iteratorul în sine. Este foarte simplu, conține întreaga gamă de ferestre găsite și este umplută când este creată de gazdă:

Evident, avem nevoie de o fabrica care o creeaza:

Asta e tot. Funcția TopLevelWindows returnează fabrica și nu are nevoie să facă deloc operații (înregistrarea automată a înregistrării returnate). Programul atașat sortează prin toate ferestrele și le imprimă pe ecran (nu te ascunde, nu-ți face griji, nu sunt încă nebun).

P.S. În general, cu ferestrele a fost posibil și nu să fie pervertit. Rețelele normale funcționează la fel de bine:

Păi, vrei dosar?

Aici, de asemenea, în liniște puteți gestiona o matrice, dar noi economisim pe faptul că, în cazul în enumeratsii de mijloc vrem să facem o pauză - nu a comis cereri inutile la sistemul de fișiere. Ei bine, în general, matricea încă nu se alocă. Înregistrarea pentru iterație este ceva ce se evidențiază pe stivă, așa cum o înțeleg.







Trimiteți-le prietenilor: