Ciclu (programare)

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită la 7 februarie 2022; verificările necesită 3 modificări .

O buclă  este un fel de structură de control în limbaje de programare de nivel înalt , concepută pentru a organiza execuția repetată a unui set de instrucțiuni . De asemenea, un ciclu poate fi numit orice secvență de instrucțiuni executată în mod repetat, organizată în orice mod (de exemplu, folosind un salt condiționat ).

Definiții

O secvență de instrucțiuni destinată a fi executată în mod repetat se numește corp de buclă . O singură execuție a corpului buclei se numește iterație . Expresia care determină dacă iterația va fi efectuată din nou sau bucla se va încheia se numește condiția de ieșire sau condiția de final a buclei (sau condiția de continuare , în funcție de modul în care este interpretat adevărul acesteia - ca semn al necesității de a se termina sau continuați bucla). Variabila care stochează numărul curent de iterație se numește contor de iterație în buclă sau pur și simplu contor de buclă . Bucla nu conține neapărat un contor, contorul nu trebuie să fie unul - condiția de ieșire din buclă poate depinde de mai multe variabile modificate în buclă sau poate fi determinată de condiții externe (de exemplu, apariția unui anumit timp), în acest din urmă caz, contorul poate să nu fie deloc necesar.

Execuția oricărei bucle include inițializarea variabilelor buclei, verificarea condiției de ieșire, executarea corpului buclei și actualizarea variabilei buclei la fiecare iterație. În plus, majoritatea limbajelor de programare oferă mijloace pentru controlul timpuriu al buclei, de exemplu, declarațiile de terminare a buclei, adică ieșirea din buclă indiferent de adevărul condiției de ieșire (în limbajul C  - break) și operatorii de ignorare a iterației ( în limbaj C - continue).

Tipuri de cicluri

Bucle necondiționate

Uneori programele folosesc bucle, a căror ieșire nu este asigurată de logica programului. Astfel de cicluri sunt numite necondiționate sau infinite. Datorită atipicității lor, limbajele de programare nu oferă mijloace sintactice speciale pentru crearea de bucle infinite, prin urmare, astfel de bucle sunt create folosind constructe concepute pentru a crea bucle obișnuite (sau condiționate ). Pentru a asigura o repetiție infinită, verificarea condiției într-o astfel de buclă este fie absentă (dacă sintaxa permite, ca, de exemplu, în bucla LOOP ... END LOOPlimbajului Ada ), fie înlocuită cu o valoare constantă ( while true do ...în Pascal ). Limbajul C folosește o buclă for(;;)cu secțiuni goale sau o buclă while (1).

Buclă cu precondiție

O buclă cu o precondiție este o buclă care este executată în timp ce o condiție specificată înainte de începerea acesteia este adevărată. Această condiție este verificată înainte de execuția corpului buclei, astfel încât corpul nu poate fi executat nici măcar o dată (dacă condiția este falsă de la bun început). În majoritatea limbajelor de programare procedurală, este implementat de instrucțiunea while , de aceea al doilea nume este bucla while. În Pascal, o buclă cu o precondiție arată astfel:

while < condiție > do begin < loop body > end ;

În limbajul C :

în timp ce ( < condiție > ) { < corpul buclei > }

Buclă cu postcondiție

O buclă cu o postcondiție este o buclă în care condiția este verificată după execuția corpului buclei. Rezultă că corpul este întotdeauna executat cel puțin o dată. În Pascal, această buclă este implementată de operator repeat..until ; în C - do…while.

În Pascal, o buclă cu o postcondiție arată astfel:

repetați < corpul buclei > până la < condiția de ieșire >

În limbajul C:

face { < corpul buclei > } while ( < condiția de continuare a buclei > )

Există diferențe în interpretarea condiției buclei cu o postcondiție în diferite limbi. În Pascal și în limbile care au coborât din el, condiția unui astfel de ciclu este tratată ca o condiție de ieșire (ciclul se termină atunci când condiția este adevărată, în terminologia rusă astfel de cicluri sunt numite și „ciclul până la”), iar în C și descendenții săi - ca o condiție de continuare (ciclul se termină când condiția este falsă, astfel de bucle sunt uneori numite buclă while).

Ieși din mijloc

O buclă de ieșire din mijloc este cea mai comună formă de buclă condiționată. Sintactic, un astfel de ciclu se formează folosind trei construcții: începutul ciclului, sfârșitul ciclului și comanda de ieșire din ciclu. Construcția de început marchează punctul din program în care începe corpul buclei, construcția de sfârșit marchează punctul în care se termină corpul. În interiorul corpului, trebuie să existe o comandă de ieșire din buclă, la executarea căreia bucla se termină și controlul este transferat operatorului urmând construcția capătului buclei. Desigur, pentru ca bucla să fie executată de mai multe ori, comanda de ieșire nu trebuie apelată necondiționat, ci numai atunci când este îndeplinită condiția de ieșire din buclă.

Diferența fundamentală dintre acest tip de buclă și cele considerate mai sus este că partea din corpul buclei situată după începerea buclei și înainte de comanda de ieșire este întotdeauna executată (chiar dacă condiția de ieșire din buclă este adevărată la prima iterație). ), iar partea din corpul buclei situată după comanda de ieșire nu este executată la ultima iterație.

Este ușor de observat că, cu o buclă de ieșire din mijloc, puteți modela cu ușurință atât o buclă de precondiție (prin plasarea instrucțiunii de ieșire la începutul corpului buclei), cât și o buclă de postcondiție (prin plasarea instrucțiunii de ieșire la sfârșitul buclei). corp).

Unele limbaje de programare conțin construcții speciale pentru organizarea unei bucle cu o ieșire din mijloc. Deci, în limba lui Ada , construcția LOOP ... END LOOPși comanda de ieșire sunt folosite pentru aceasta, EXITsau EXIT WHEN:

LOOP ... buclă parte a corpului EXIT WHEN < condiţie de ieşire > ; ... Partea corpului buclă IF < condiție de ieșire > THEN EXIT ; SFÂRȘIT ; ... O parte a corpului buclei de sfârșit :

Aici, în interiorul buclei, poate exista orice număr de comenzi de ieșire de ambele tipuri. Comenzile de ieșire în sine nu diferă fundamental, ele sunt de obicei EXIT WHENfolosite atunci când este verificată doar condiția de ieșire, ci pur și simplu EXIT când se iese din buclă într-una dintre variantele unei instrucțiuni condiționale complexe.

În limbile în care astfel de construcții nu sunt furnizate, o buclă cu ieșire din mijloc poate fi modelată folosind orice buclă condiționată și un operator de ieșire timpurie din buclă (cum ar fi breakîn C, ieșire în Turbo Pascal etc.) sau un tranziție necondiționată a operatorului goto .

Buclă cu contor (sau buclă pentru)

O buclă cu contor este o buclă în care o variabilă își schimbă valoarea de la o valoare inițială dată la o valoare finală cu un anumit pas, iar pentru fiecare valoare a acestei variabile, corpul buclei este executat o dată. În majoritatea limbajelor de programare procedurală, acesta este implementat printr-o instrucțiune forcare specifică contorul (așa-numita „variabilă buclă”), numărul necesar de treceri (sau valoarea limită a contorului) și, eventual, pasul în care contorul schimbări. De exemplu, în limba Oberon-2 , un astfel de ciclu arată astfel:

PENTRU v := b TO e BY s DO ... corp buclă Sfârşit

aici v este contorul, b este valoarea inițială a contorului, e este valoarea limită a contorului, s este pasul).

Întrebarea despre valoarea unei variabile la sfârșitul unei bucle în care această variabilă a fost folosită ca numărător este ambiguă. De exemplu, dacă un program Pascal întâlnește o construcție de forma:

i := 100 ; for i := 0 to 9 do begin ... loop body end ; k := i ;

apare întrebarea: ce valoare va fi în cele din urmă atribuită variabilei k : 9, 10, 100, poate o altă? Ce se întâmplă dacă ciclul se termină prematur? Răspunsurile depind de dacă valoarea contorului este incrementată după ultima iterație și dacă traducătorul modifică suplimentar această valoare. Încă o întrebare: ce se va întâmpla dacă contorului i se atribuie în mod explicit o nouă valoare în interiorul buclei? Diferite limbaje de programare abordează aceste probleme în moduri diferite. În unele, comportamentul contorului este clar reglementat. În altele, de exemplu, în același Pascal, standardul de limbaj nu definește nici valoarea finală a contorului, nici consecințele modificării sale explicite în buclă, dar nu recomandă schimbarea explicit a contorului și utilizarea lui la sfârșit. a buclei fără reinițializare. Un program Pascal care ignoră această recomandare poate produce rezultate diferite atunci când rulează pe sisteme diferite și utilizează traducători diferiți.

Problema este rezolvată radical în limbajele Ada și Kotlin : contorul este considerat a fi descris în antetul buclei și pur și simplu nu există în afara acestuia. Chiar dacă numele contorului este deja folosit în program, o variabilă separată este folosită ca contor în interiorul buclei. Este interzisă atribuirea explicită a oricăror valori contorului, aceasta putând fi modificată numai prin mecanismul intern al operatorului de buclă.

Ca urmare, construcția de pe Ada:

i := 100 ; for i in ( 0. . 9 ) loop ... bucla body end bucla ; k := i ;

Și în Kotlin:

val i = 100 ; for ( i in 0. . 9 ){ ... loop body } val k = i ;

similar în exterior cu bucla Pascal de mai sus, este interpretată fără ambiguitate: variabilei k i se va atribui valoarea 100, deoarece variabila i folosită în afara acestei bucle nu are nimic de-a face cu contorul i , care este creat și modificat în interiorul buclei . O astfel de izolare a contorului este convenabilă și sigură: nu este necesară o descriere separată pentru aceasta, iar probabilitatea erorilor aleatorii asociate cu distrugerea accidentală a variabilelor externe buclei este minimă. Dacă un programator trebuie să includă un ciclu cu un contor în codul terminat, atunci el poate să nu verifice dacă există o variabilă cu numele pe care a ales-o ca contor, să nu adauge o descriere a unui nou contor la antetul contorului. procedura corespunzatoare, nu incercati sa folositi unul dintre cele disponibile, ci in acest moment de contoare "gratuite". Pur și simplu scrie o buclă cu un numărător de variabile, al cărui nume este convenabil pentru el și poate fi sigur că nu va avea loc nicio coliziune de nume.

O buclă cu contor poate fi întotdeauna scrisă ca o buclă condiționată, înainte de începutul căreia contorului i se atribuie o valoare inițială, iar condiția de ieșire este ca contorul să ajungă la valoarea finală; în același timp, la corpul buclei se adaugă un operator pentru schimbarea contorului cu un pas dat. Cu toate acestea, operatorii speciali ai unui ciclu cu contor pot fi traduși mai eficient, deoarece forma oficializată a unui astfel de ciclu permite utilizarea instrucțiunilor speciale ale procesorului pentru organizarea ciclurilor.

Niklaus Wirth a numit la un moment dat bucla cu un contor „marginal”, argumentând că o astfel de construcție este redundantă și ar trebui exclusă din sintaxa limbajelor de programare ca non-sistem. În conformitate cu acest punct de vedere, nu a existat un ciclu cu un contor în limbajul de programare Oberon . Cu toate acestea, în limbajul Oberon-2 , creat de Wirth și Mössenböck în dezvoltarea lui Oberon, bucla cu un contor a FORapărut din nou în interesul utilizării practice [1] .

În unele limbi, cum ar fi C și altele derivate din acesta, bucla for, în ciuda formei sintactice a unei bucle cu un contor, este de fapt o buclă cu o precondiție. Adică, în C, constructul buclei:

pentru ( i = 0 ; i < 10 ; ++ i ) { ... corpul buclei }

reprezintă de fapt o altă formă de notare a construcției [2] :

i = 0 _ în timp ce ( i < 10 ) { ... corpul buclei ++ i ; }

Adică, în construcția for, în primul rând, se scrie o propoziție arbitrară de inițializare a ciclului, apoi o condiție de continuare și, în sfârșit, o operațiune efectuată după fiecare corp al ciclului (aceasta nu trebuie să fie o modificare a contorului). ; poate fi editarea unui pointer sau a unei operații complet străine). Pentru limbaje de acest fel, problema descrisă mai sus este rezolvată foarte simplu: variabila contor se comportă complet previzibil și, la sfârșitul buclei, își păstrează ultima valoare.

Ciclul comun

O altă variantă a buclei este o buclă care specifică execuția unei operații pentru obiectele dintr-un set dat, fără a specifica în mod explicit ordinea în care sunt enumerate aceste obiecte. Astfel de cicluri se numesc îmbinări (precum și cicluri de colectare , cicluri de vizualizare ) și reprezintă o declarație formală de forma: „Efectuați operația X pentru toate elementele incluse în mulțimea M”. Bucla comună, teoretic, nu determină în niciun fel în ce ordine se va aplica operația elementelor mulțimii, deși limbajele de programare specifice, desigur, pot specifica o anumită ordine de iterare asupra elementelor. Arbitrarul face posibilă optimizarea execuției ciclului prin organizarea accesului nu în ordinea programatorului, ci în cea mai favorabilă ordine. Cu posibilitatea executării în paralel a mai multor operații, este chiar posibilă paralelizarea execuției unui ciclu comun, atunci când aceeași operație este efectuată simultan pe diferite module de calcul pentru diferite obiecte, în timp ce programul rămâne secvenţial logic.

Buclele comune sunt disponibile în unele limbaje de programare ( C# , Eiffel , Java , JavaScript , Perl , Python , PHP , LISP , Tcl , etc.) - vă permit să parcurgeți toate elementele unei colecții date de obiecte . În definirea unei astfel de bucle, trebuie doar să specificați o colecție de obiecte și o variabilă, cărora în corpul buclei li se va atribui valoarea obiectului procesat curent (sau o referință la acesta). În diferite limbaje de programare, sintaxa operatorului este diferită:

C++ :

pentru ( tip & element : set ) // suportat din C++11 { //folosește elementul }

C# :

foreach ( introduceți elementul în set ) { //folosind elementul }

Delphi :

pentru itemul din [ 1 .. 100 ] do begin // Folosind item (Acest cod a fost testat în Delphi 2010) end ;

Perl (strict de la prima până la ultima comandă):

foreach ( @set ) { #use $_ } # sau pentru ( @set ) { #use $_ } # sau foreach $item ( @set ) { #use $item }

eiffel :

peste set ca buclă de cursor -- utilizați cursor.item end

Java :

pentru ( tip element : set ) { //folosind element }

JavaScript :

pentru ( txtProperty în objObject ) { /* utilizare: objObject [txtProperty] */ }

PHP :

foreach ( $arr ca $item ) { /* folosește $item*/ } //sau foreach ( $arr ca $key => $value ) { /* folosește valorile index ale $key-ului și $value*/ } //sau foreach ( $arr as & $item ) { /*utilizați $item prin referință*/ }

Visual Basic . net :

Pentru fiecare articol Ca tip In set 'utilizaţi elementul Următorul articol

Windows PowerShell :

foreach ($articol în $set) { # operațiuni pe $item }

sau

$set | Pentru fiecare obiect { # operațiuni cu $_ }

Piton

pentru item în iterator_instance : # folosește elementul

rubin

iterator_instance . fiecare face | articol | # utilizați sfârșitul articolului

Ieșire timpurie și omitere a iterației

Multe limbaje de programare care au construcții ciclice în sintaxa lor au și comenzi specifice care vă permit să încălcați ordinea de funcționare a acestor construcții: comanda de a ieși din buclă mai devreme și comanda de a sări peste iterație.

Ieșire timpurie din ciclu

Comanda de ieșire timpurie este utilizată atunci când este necesar să se anuleze execuția unei bucle în care condiția de ieșire nu a fost încă atinsă. Acest lucru se întâmplă, de exemplu, atunci când este detectată o eroare în timpul execuției corpului buclei, după care munca ulterioară a buclei nu are sens.

O instrucțiune de ieșire timpurie este de obicei numită EXITsau break, iar efectul ei este similar cu cel al unei instrucțiuni de ramură necondiționată ( goto) asupra instrucțiunii imediat următoare buclei în care se află această instrucțiune. Deci, în limbajul C, următoarele două bucle funcționează exact la fel:

// Aplicarea instrucțiunii break while ( < condiție > ) { ... operatori if ( < eroare > ) break ; ... operatori } ... continuarea programului // Fragment similar fără pauză while ( < condiție > ) { ... operatori if ( < eroare > ) merge la break_label ; ... operatori } break_label : ... continuarea programului

În ambele cazuri, dacă condiția <eroare> este îndeplinită în corpul buclei, se va face o tranziție la instrucțiunile desemnate ca „continuare program”. Astfel, operatorul de ieșire timpurie a buclei, de fapt, maschează pur și simplu ramura necondiționată, cu toate acestea, utilizarea break-ului este de preferat goto, deoarece comportamentul break este clar specificat de limbaj, potențial mai puțin periculos (de exemplu, nu există șansa de a face o greșeală cu poziția sau numele etichetei). În plus, ieșirea anticipată explicită din buclă nu încalcă principiile programării structurate.

O instrucțiune de ieșire anticipată obișnuită încheie bucla în care se află direct. Într-o serie de limbaje de programare, funcționalitatea acestui operator este extinsă, vă permite să ieșiți din mai multe bucle imbricate (vezi mai jos). În astfel de cazuri, bucla de ieșit este marcată cu o etichetă, iar eticheta este specificată în instrucțiunea de ieșire timpurie.

Omite iterația

Acest operator este utilizat atunci când în iterația curentă a buclei este necesar să săriți peste toate comenzile până la sfârșitul corpului buclei. În acest caz, bucla în sine nu trebuie întreruptă, condițiile de continuare sau de ieșire trebuie calculate în mod obișnuit.

În C și descendenții săi, comanda iterație skip este o instrucțiune continueîntr-o construcție buclă. Acțiunea acestui operator este similară cu un salt necondiționat la linia din interiorul corpului buclei după ultima sa comandă. De exemplu, un cod C care găsește suma elementelor unei matrice și suma tuturor elementelor pozitive ale matricei ar putea arăta astfel:

int arr [ ARRSIZE ]; ... // Însumând separat toate și numai elementele pozitive // ​​ale matricei arr folosind continue. int suma_toate = 0 ; int sum_pos = 0 ; pentru ( int i = 0 ; i < ARRSIZE ; ++ i ) { sum_all += arr [ i ]; if ( arr [ i ] <= 0 ) continua ; sum_pos += arr [ i ]; } // Cod similar c goto int sum_all = 0 ; int sum_pos = 0 ; pentru ( int i = 0 ; i < ARRSIZE ; ++ i ) { sum_all += arr [ i ]; if ( arr [ i ] <= 0 ) du- te la cont_label ; sum_pos += arr [ i ]; cont_label : }

Al doilea fragment arată clar cum funcționează continue: pur și simplu transferă controlul asupra ultimei comenzi a corpului buclei, sărind peste executarea comenzii de sumare dacă următorul element al matricei nu satisface condiția. Astfel, sum_pos acumulează suma numai elementelor pozitive ale matricei.

Necesitate

Din punct de vedere al programării structurale, comenzile de ieșire a buclei și de ignorare a iterației sunt redundante, deoarece acțiunea lor poate fi modelată cu ușurință prin mijloace pur structurale. Mai mult decât atât, potrivit unui număr de teoreticieni ai programării (în special, Edsger Dijkstra), însuși faptul de a folosi mijloace nestructurale într-un program, fie că este vorba de un salt necondiționat clasic sau de oricare dintre formele sale specializate, cum ar fi break sau continue, este dovada unui algoritm insuficient dezvoltat pentru rezolvarea problemei.

Cu toate acestea, în practică, codul programului este adesea o înregistrare a unui algoritm deja existent, formulat anterior, care este inutil de reluat din motive pur tehnice. O încercare de a înlocui comanda de ieșire timpurie dintr-un astfel de cod cu constructe structurale se dovedește adesea a fi ineficientă sau greoaie. De exemplu, fragmentul de cod de mai sus cu comanda breakar putea fi scris astfel:

// Ieșire timpurie din buclă fără pauză bool flag = false ; // semnalizare reziliere anticipată while ( < condiție > && ! flag ) { ... operatori dacă ( < eroare > ) { steag = adevărat ; } altfel { ... operatori } } ... continuarea programului

Este ușor să vă asigurați că fragmentul va funcționa similar cu cele anterioare, singura diferență este că, în loc să ieșiți direct din buclă, indicatorul de ieșire timpurie este setat la locul verificării unei erori, care este verificată mai târziu în condiția obișnuită pentru continuarea buclei. Cu toate acestea, pentru a anula comanda de ieșire timpurie, a trebuit să fie adăugate programului o descriere a steagului și o a doua ramură a operatorului condiționat, iar logica programului a fost „încețoșată” (decizia de a ieși mai devreme este luată într-un singur loc, și executat în altul). Drept urmare, programul nu a devenit nici mai simplu, nici mai scurt, nici mai clar.

Situația este oarecum diferită cu comanda skip iterație. Acesta, de regulă, este foarte ușor și natural înlocuit de un operator condiționat. De exemplu, fragmentul de sumare a matricei de mai sus ar putea fi scris astfel:

int arr [ ARRSIZE ]; ... // Însumând separat toate și numai pozitive // ​​​​elementele matricei arr cu înlocuire continue int sum_all = 0 ; int sum_pos = 0 ; pentru ( int i = 0 ; i < ARRSIZE ; ++ i ) { sum_all += arr [ i ]; if ( arr [ i ] > 0 ) // Condiție inversată! { sum_pos += arr [ i ]; } }

După cum puteți vedea, a fost suficient să înlocuiți condiția verificată cu cea opusă și să plasați partea finală a corpului buclei într-o declarație condiționată. Puteți observa că programul a devenit mai scurt (datorită eliminării comenzii skip iterație) și în același timp mai logic (se vede direct din cod că elementele pozitive sunt rezumate).

În plus, utilizarea comenzii iterație skip într-o buclă cu o condiție (while-loop) poate provoca, de asemenea, o eroare neevidentă: dacă corpul buclei, așa cum se întâmplă adesea, se termină cu comenzi pentru schimbarea variabilei buclei (e), atunci iterația comanda skip va omite și aceste comenzi, drept urmare (în funcție de condiția în care are loc saltul) poate apărea o buclă sau o repetare a iterației care nu corespunde algoritmului. Deci, dacă înlocuim bucla for cu while în exemplul de mai sus, obținem următoarele:

int arr [ ARRSIZE ]; ... int suma_toate = 0 ; int sum_pos = 0 ; int i = 0 ; while ( i < ARRSIZE ) // Bucla arată ca cea anterioară pentru ... { sum_all += arr [ i ]; if ( arr [ i ] <= 0 ) continua ; sum_pos += arr [ i ]; ++ i ; // ... dar această comandă va fi omisă când continuați // și programul va fi în buclă }

În ciuda utilității lor limitate și a capacității de a le înlocui cu alte constructe de limbaj, comenzile de iterație și, mai ales, ieșirea timpurie din buclă sunt extrem de utile în unele cazuri, motiv pentru care sunt păstrate în limbajele de programare moderne.

Bucle imbricate

Este posibil să se organizeze o buclă în interiorul corpului altei bucle. O astfel de buclă va fi numită buclă imbricată . O buclă imbricată în raport cu bucla în al cărei corp este imbricată va fi numită buclă interioară , iar invers, o buclă în corpul căreia există o buclă imbricată va fi numită externă în raport cu cea imbricată. În interiorul buclei imbricate, la rândul său, o altă buclă poate fi imbricată, formând următorul nivel de imbricare și așa mai departe. Numărul de niveluri de cuibărit, de regulă, nu este limitat.

Numărul total de execuții ale corpului buclei interioare nu depășește produsul dintre numărul de iterații ale buclei interioare și ale tuturor buclelor exterioare. De exemplu, luând trei bucle imbricate una în alta, fiecare cu 10 iterații, obținem 10 execuții ale corpului pentru bucla exterioară, 100 pentru bucla de al doilea nivel și 1000 în bucla cea mai interioară.

Una dintre problemele asociate buclelor imbricate este organizarea ieșirii timpurii din ele. Multe limbaje de programare au un operator de terminare a buclei ( breakîn C, exitîn Turbo Pascal, lastîn Perl etc.), dar, de regulă, oferă o ieșire numai din bucla nivelului de la care a fost chemat. Apelarea acesteia din interiorul unei bucle imbricate va termina numai acea buclă interioară, în timp ce bucla exterioară va continua să ruleze. Problema poate părea exagerată, dar apare uneori la programarea procesării complexe a datelor, când algoritmul necesită o întrerupere imediată în anumite condiții, a căror prezență poate fi verificată doar într-o buclă profund imbricată.

Există mai multe soluții la problema ieșirii din bucle imbricate.

  • Cel mai simplu este să folosiți operatorul goto pentru a sări la punctul din program imediat după bucla imbricată. Această variantă este criticată de programatorii structurați , la fel ca toate constructele care necesită utilizarea goto . Unele limbaje de programare, cum ar fi Modula-2 , pur și simplu nu au un operator de ramură necondiționat și o astfel de construcție nu este posibilă în ele.
  • O alternativă este utilizarea instrumentelor obișnuite de terminare a buclei, dacă este necesar, setând semnalizatoare speciale care necesită finalizarea imediată a procesării. Dezavantajul este complicația codului, degradarea performanței.
  • Plasarea unei bucle imbricate într-o procedură. Ideea este că toată acțiunea care ar putea trebui întreruptă din timp este prezentată ca o procedură separată, iar pentru terminarea anticipată, utilizați instrucțiunea de ieșire din procedură (dacă există una în limbajul de programare). În limbajul C, de exemplu, puteți construi o funcție cu o buclă imbricată și puteți organiza ieșirea din aceasta folosind instrucțiunea return . Dezavantajul este că selectarea unui fragment de cod într-o procedură nu este întotdeauna justificată din punct de vedere logic și nu toate limbile au mijloace regulate de terminare anticipată a procedurilor.
  • Profitați de mecanismul de generare și de gestionare a excepțiilor (situații excepționale), care este acum disponibil în majoritatea limbilor de nivel înalt . În acest caz, într-o situație anormală, codul din bucla imbricată ridică o excepție, iar blocul de gestionare a excepțiilor, în care este plasată întreaga buclă imbricată, o prinde și o procesează. Dezavantajul este că implementarea mecanismului de gestionare a excepțiilor în majoritatea cazurilor este de așa natură încât viteza programului este redusă. Adevărat, în condițiile moderne acest lucru nu este deosebit de important: în practică, pierderea de performanță este atât de mică încât contează doar pentru foarte puține aplicații.
  • În cele din urmă, există facilități speciale de limbaj pentru ieșirea din bucle imbricate. Deci, în limbajul Ada, un programator poate marca o buclă (nivelul superior al unei bucle imbricate) cu o etichetă și poate indica această etichetă în comanda pentru terminarea timpurie a buclei. Ieșirea nu va avea loc din bucla curentă, ci din toate buclele imbricate până la cea marcată, inclusiv [3] . Limbajul PHP oferă posibilitatea de a specifica numărul de cicluri întrerupte după comandă break - aceasta break 2va întrerupe ciclul în sine și pe cel de deasupra acestuia și break 1este echivalent cu simpla scriere a comenzii break[4] .

Bucle cu mai multe ramuri păzite

Ciclul lui Dijkstra

În teoria programării, există o altă formă de construcție ciclică care este fundamental diferită de cele „clasice”, numită ciclul Dijkstra, după Edsger Dijkstra , care a descris-o primul. În descrierea clasică Dijkstra, un astfel de ciclu arată astfel:

do P 1 → S 1 , … P n → S n od

Aici do , este marcatorul începutului construcției buclei, od este markerul sfârșitului construcției buclei, P i  este condiția i - a de gardă (o expresie logică care poate fi adevărată sau falsă), S i  este i -a comanda păzită . O buclă este formată din una sau mai multe ramuri ( expresii pazite ), fiecare dintre acestea fiind o pereche de o condiție de pază (sau „gărzi” pe scurt) și o comandă de pază (este clar că, în realitate, comanda poate fi complexă).

Când bucla lui Dijkstra este executată, condițiile de gardă sunt calculate în fiecare iterație. Dacă cel puțin una dintre ele este adevărată, se execută comanda de pază corespunzătoare, după care începe o nouă iterație (dacă sunt adevărate mai multe condiții de pază, se execută o singură comandă de pază). Dacă toate condițiile de gardă sunt false, bucla se termină. Este ușor de observat că bucla lui Dijkstra cu o condiție de gardă și o comandă de gardă este, de fapt, o buclă obișnuită cu o precondiție (bucla „while”).

Deși bucla Dijkstra a fost inventată în anii 1970, nu există constructe speciale pentru a o crea în limbaje de programare. Singura excepție a fost recent creat Oberon-07  , primul limbaj de programare real care acceptă în mod explicit o buclă cu mai multe ramuri păzite. Cu toate acestea, ciclul lui Dijkstra poate fi modelat fără prea multe dificultăți folosind constructele tradiționale ale limbajelor de programare structurate. Iată un exemplu de implementare a acestuia într-unul dintre modurile posibile în limbajul Ada:

buclă dacă P1 atunci S1 ; ... elsif Pn apoi Sn ; else iesire ; termina daca ; buclă de capăt ;

Aici P1-Pn sunt condițiile de pază și S1-Sn sunt comenzile de pază corespunzătoare.

Bucla lui Dijkstra este utilă pentru implementarea unor calcule repetitive specifice care sunt incomod de descris cu construcții de buclă mai tradiționale. De exemplu, acest ciclu reprezintă în mod natural un automat finit  - fiecare ramură corespunde unei stări a automatului, condițiile păzite sunt construite astfel încât în ​​iterația curentă să fie selectată ramura corespunzătoare stării curente a automatului și codul celui păzit. instrucțiunea asigură că calculele sunt efectuate în starea curentă și trecerea la următoarea (adică o astfel de modificare a variabilelor, după care condiția de gardă a ramului dorit va fi adevărată la următoarea iterație).

Ciclul păianjenului

Este ușor de observat că bucla lui Dijkstra nu conține o condiție explicită de continuare sau de ieșire, care nu este considerată o binecuvântare de către toți teoreticienii programării. Prin urmare, a fost propusă o construcție complicată a ciclului Dijkstra, numită „ciclul păianjen”. În aceeași notație, arată astfel:

do P 1 →S 1 , … P n →S n afară Q1 → T1 , _ … Q n →T n altfel E od

Aici, după marcaj , outse adaugă ramuri de completare , constând din condițiile de ieșire Q i și comenzile de completare T i . În plus, a fost adăugată o ramură de completare alternativă elsecu comanda E.

Bucla spider se execută astfel:

  • Se calculează condițiile de pază. Dacă există o adevărată condiție de pază, comanda de pază corespunzătoare este executată.
  • Condițiile de ieșire sunt calculate. Dacă există o condiție de ieșire adevărată, se execută comanda de terminare corespunzătoare, după care se încheie execuția buclei. Dacă toate condițiile de ieșire sunt false, începe următoarea iterație, dar numai dacă cel puțin una dintre condițiile de gardă a fost adevărată în iterația curentă.
  • Dacă în această iterație toate condițiile de gardă și toate condițiile de ieșire sunt false, se execută instrucțiunea alt-end E, după care execuția buclei este întreruptă.

Structura ciclului „păianjen” permite descrierea extrem de strictă a condițiilor de execuție a ciclului. Conform pozițiilor teoretice, ramura de completare alternativă nu ar trebui utilizată ca una dintre opțiunile pentru terminarea corectă a buclei (toate astfel de opțiuni ar trebui formate ca ramuri de completare corespunzătoare cu o condiție explicită), ea servește doar pentru a urmări situația când, Din anumite motive, Din anumite motive, ciclul a început să meargă anormal. Adică, comanda alt poate analiza doar cauzele erorii și poate prezenta rezultatele analizei.

Deși suportul explicit la nivel de sintaxă pentru această buclă nu există în niciun limbaj de programare, bucla spider, ca bucla lui Dijkstra, poate fi modelată folosind constructe structurale tradiționale.

Metode de optimizare a buclei

transformări echivalente ale codului sursă compilator

Vezi și

Note

  1. Oberon este visul devenit realitate al lui Niklaus Wirth
  2. Strict vorbind, identitatea nu este completă, deoarece instrucțiunea continue va funcționa diferit.
  3. Loops/Nested at Rosetta  Code
  4. ↑ Manual PHP , pauză 

Link -uri