Ciklus (programozás)

Az oldal jelenlegi verzióját még nem ellenőrizték tapasztalt közreműködők, és jelentősen eltérhet a 2022. február 7-én felülvizsgált verziótól ; az ellenőrzések 3 szerkesztést igényelnek .

A hurok  egyfajta vezérlőstruktúra a magas szintű programozási nyelvekben , amely egy utasításkészlet ismételt végrehajtásának megszervezésére szolgál . Egy ciklus nevezhető bármilyen ismétlődően végrehajtott utasítássorozatnak is, bármilyen módon szervezve (például feltételes ugrással ).

Definíciók

Az ismétlődően végrehajtandó utasítások sorozatát huroktestnek nevezzük . A ciklustörzs egyszeri végrehajtását iterációnak nevezzük . Azt a kifejezést , amely meghatározza, hogy az iteráció ismét végrehajtódik-e, vagy a ciklus véget ér-e, a ciklus kilépési feltételének vagy befejezési feltételének nevezzük (vagy folytatási feltételnek , attól függően, hogyan értelmezzük igazságát - a befejezés szükségességének jeleként vagy folytassa a hurkot). Az aktuális iterációs számot tároló változót hurokiterációs számlálónak vagy egyszerűen hurokszámlálónak nevezzük . A hurok nem feltétlenül tartalmaz számlálót, a számlálónak nem kell egynek lennie - a hurokból való kilépés feltétele több, a hurokban megváltozott változótól függhet, vagy külső körülmények határozhatják meg (pl. idő), az utóbbi esetben előfordulhat, hogy a számlálóra egyáltalán nincs szükség.

Bármely ciklus végrehajtása magában foglalja a ciklusváltozók kezdeti inicializálását, a kilépési feltétel ellenőrzését, a ciklustörzs végrehajtását és a ciklusváltozó frissítését minden iterációnál. Ezenkívül a legtöbb programozási nyelv lehetőséget biztosít a hurok korai vezérlésére, például cikluslezáró utasításokat, azaz kilépést a hurokból, függetlenül a kilépési feltétel igazságától ( C nyelvben  - break) és az iterációs átugrási operátorokat ( C nyelven - continue).

Ciklusok típusai

Feltétel nélküli hurkok

Néha a programok ciklusokat használnak, amelyekből a kilépést nem a program logikája biztosítja. Az ilyen ciklusokat feltétel nélkülinek vagy végtelennek nevezzük. Atipikusságuk miatt a programozási nyelvek nem biztosítanak speciális szintaktikai eszközöket a végtelen hurkok létrehozásához, ezért az ilyen hurkokat olyan konstrukciók segítségével hozzák létre, amelyek a szokásos (vagy feltételes ) hurkok létrehozására szolgálnak. A végtelen ismétlődés biztosítása érdekében egy ilyen ciklusban a feltétel-ellenőrzés vagy hiányzik (ha a szintaxis megengedi, mint például az AdaLOOP ... END LOOP nyelvi ciklusban ), vagy egy állandó érték helyettesíti ( Pascalban ) . A C nyelv üres szakaszokat tartalmazó ciklust vagy ciklust használ . while true do ...for(;;)while (1)

Hurok előfeltétellel

Az előfeltételes hurok olyan ciklus, amely akkor hajtódik végre, amikor az indítása előtt meghatározott feltétel igaz. Ezt a feltételt a ciklustörzs végrehajtása előtt ellenőrizzük , így előfordulhat, hogy a törzs egyszer sem kerül végrehajtásra (ha a feltétel kezdettől fogva hamis). A legtöbb procedurális programozási nyelvben a while utasítás valósítja meg , ezért a második neve a while ciklus. Pascalban egy előfeltételes ciklus így néz ki:

while < feltétel > do begin < loop body > end ;

C nyelven :

while ( < feltétel > ) { < huroktest > _ }

Hurok utófeltétellel

Az utófeltételes hurok olyan ciklus, amelyben a feltételt a ciklustörzs végrehajtása után ellenőrizzük . Ebből következik, hogy a testet mindig legalább egyszer kivégzik. Pascalban ezt a ciklust az operátor valósítja meg repeat..until ; C-ben do…while.

Pascalban egy utófeltételes ciklus így néz ki:

ismételje meg a < loop body > -t, amíg a < kilépési feltétel >

C nyelven:

do { < huroktest > _ } while ( < hurok folytatási feltétel > )

Különböző nyelveken vannak eltérések az utófeltételes hurokfeltétel értelmezésében. A Pascalban és a belőle származó nyelvekben egy ilyen ciklus feltételét kilépési feltételként kezelik (a ciklus akkor ér véget, ha a feltétel igaz, az orosz terminológiában az ilyen ciklusokat "ciklusig"-nek is nevezik), C és leszármazottai - folytatási feltételként (a ciklus akkor ér véget, ha a feltétel hamis, az ilyen ciklusokat néha while ciklusnak nevezik).

Kerékpár középről

A középső kilépési hurok a feltételes hurok leggyakoribb formája. Szintaktikailag egy ilyen ciklus három konstrukcióval jön létre: a ciklus eleje, a ciklus vége és a ciklusból való kilépés parancsa. A kezdő konstrukció azt a pontot jelöli a programban, ahol a huroktest kezdődik, a végkonstrukció azt a pontot, ahol a test véget ér. A törzsön belül kell lennie egy kilépési parancsnak a hurokból, amelynek végrehajtása után a ciklus véget ér, és a vezérlés a ciklusvégi konstrukciót követő operátorhoz kerül. Természetesen ahhoz, hogy a ciklus többször is végrehajtható legyen, az exit parancsot nem szabad feltétel nélkül meghívni, hanem csak akkor, ha a ciklusból való kilépés feltétele teljesül.

Az alapvető különbség az ilyen típusú ciklusok és a fent említettek között, hogy a ciklustörzsnek a ciklus kezdete utáni és a kilépési parancs előtti része mindig végrehajtásra kerül (még akkor is, ha a ciklusból való kilépési feltétel igaz az első iterációnál ), és a ciklustörzsnek a kilépés parancs utáni része nem kerül végrehajtásra az utolsó iteráció során.

Könnyen belátható, hogy egy középső kilépési hurokkal könnyen modellezhető mind az előfeltétel hurok (az exit utasításnak a ciklustörzs elejére helyezésével), mind az utólagos feltétel hurok (az exit utasítást a ciklus végére helyezve). test).

Egyes programozási nyelvek speciális konstrukciókat tartalmaznak a hurok megszervezésére a középső kijárattal. Tehát az Ada nyelvében erre a build LOOP ... END LOOPés az exit parancsot használják, EXITvagy EXIT WHEN:

LOOP ... Hurok testrész EXIT WHEN < kilépési feltétel > ; _ ... Hurok testrész IF < kilépési feltétel > THEN EXIT ; _ VÉGE ; ... Az END LOOP testének része :

Itt, a cikluson belül, tetszőleges számú kilépési parancs lehet mindkét típusból. Maguk a kilépési parancsok alapvetően nem különböznek egymástól, általában EXIT WHENakkor használatosak, amikor csak a kilépési feltételt ellenőrizzük, hanem egyszerűen akkor, EXIT amikor egy összetett feltételes utasítás valamelyik változatában kilépünk a ciklusból.

Azokon a nyelveken, ahol nem állnak rendelkezésre ilyen konstrukciók, a középről kilépő hurok modellezhető bármilyen feltételes ciklus és egy korai kilépési operátor segítségével a ciklusból (például breakC nyelven, kilépés Turbo Pascalban stb.), vagy egy feltétel nélküli operátor átmenet goto .

Hurok számlálóval (vagy hurok forral)

A számlálós hurok olyan ciklus, amelyben valamilyen változó egy adott kezdeti értékről valamilyen lépéssel végső értékre változtatja az értékét, és ennek a változónak minden egyes értékére a ciklus törzse egyszer végrehajtásra kerül. A legtöbb procedurális programozási nyelvben egy utasítással valósítják meg, foramely megadja a számlálót (az úgynevezett "hurokváltozót"), a szükséges lépések számát (vagy a számláló határértékét), és esetleg azt a lépést, amelyben a számláló változtatások. Például az Oberon-2 nyelven egy ilyen ciklus így néz ki:

FOR v := b TO e BY s DO ... huroktest VÉGE

itt v a számláló, b a számláló kezdőértéke, e a számláló határértéke, s a lépés).

Az a kérdés, hogy mekkora értéke van annak a ciklusnak, amelyben ezt a változót számlálóként használták, nem egyértelmű. Például, ha egy Pascal program az űrlap konstrukciójával találkozik:

i := 100 ; for i := 0 -tól 9 -ig do begin ... ciklus törzs vége ; k := i ;

Felmerül a kérdés: milyen értéket kap végül a k változó : 9, 10, 100, esetleg valami más? Mi van, ha a ciklus idő előtt véget ér? A válaszok attól függnek, hogy az utolsó iteráció után megnő-e a számláló értéke, és hogy a fordító módosítja-e ezt az értéket. Még egy kérdés: mi történik, ha a számlálóhoz kifejezetten új értéket rendelünk a cikluson belül? A különböző programozási nyelvek eltérő módon kezelik ezeket a problémákat. Egyes esetekben a számláló viselkedése egyértelműen szabályozott. Másoknál például ugyanabban a Pascalban a nyelvi szabvány nem határozza meg sem a számláló végső értékét, sem a ciklusban bekövetkezett explicit változásának következményeit, de nem javasolja a számláló explicit megváltoztatását és használatát a végén. újrainicializálás nélkül. Egy Pascal program, amely figyelmen kívül hagyja ezt az ajánlást, eltérő eredményeket produkálhat, ha különböző rendszereken fut és különböző fordítókat használ.

A probléma gyökeresen megoldódott az Ada és a Kotlin nyelvben : a számlálót a ciklusfejlécben leírtnak tekintik, és azon kívül egyszerűen nem létezik. Még akkor is, ha a számláló neve már szerepel a programban, egy külön változót használunk számlálóként a cikluson belül. Tilos bármilyen értéket kifejezetten hozzárendelni a számlálóhoz, azt csak a hurokoperátor belső mechanizmusa módosíthatja.

Ennek eredményeként az építkezés Adán:

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

És Kotlinban:

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

Külsőleg a fenti Pascal ciklushoz hasonlóan értelmezhető: a k változó 100-at kap, mivel a cikluson kívül használt i változónak semmi köze az i számlálóhoz, amely a cikluson belül jön létre és változik . A számláló ilyen leválasztása kényelmes és biztonságos: nincs szükség külön leírásra, és minimális a hurkon kívüli változók véletlen megsemmisüléséhez kapcsolódó véletlenszerű hibák valószínűsége. Ha a programozónak egy számlálós ciklust kell beillesztenie a kész kódba, akkor nem ellenőrizheti, hogy van-e olyan nevű változó, amelyet számlálónak választott, és nem írhatja be egy új számláló leírását a program fejlécébe. megfelelő eljárást, ne a rendelkezésre állók valamelyikét próbálja meg használni, hanem a "szabad" számlálók pillanatában. Egyszerűen ír egy ciklust egy változószámlálóval, aminek a neve kényelmes neki, és biztos lehet benne, hogy nem történik névütközés.

A számlálós ciklus mindig felírható feltételes ciklusként, melynek kezdete előtt a számlálóhoz egy kezdőértéket rendelünk, a kilépési feltétel pedig az, hogy a számláló elérje a végértéket; ezzel egyidejűleg a huroktörzsbe kerül egy operátor a számláló adott lépéssel történő megváltoztatására. A számlálóval ellátott ciklus speciális operátorai azonban hatékonyabban fordíthatók le, mivel egy ilyen ciklus formalizált formája lehetővé teszi speciális processzorutasítások használatát a ciklusok szervezéséhez.

Niklaus Wirth egy időben "marginálisnak" nevezte a számlálóval ellátott hurkot, azzal érvelve, hogy egy ilyen konstrukció redundáns, és ki kell zárni a programozási nyelvek szintaxisából, mint nem rendszeres. Ennek a felfogásnak megfelelően az Oberon programozási nyelvben nem volt számlálós ciklus . A Wirth és Mössenböck által az Oberon fejlesztése során megalkotott Oberon-2FOR nyelvben azonban a gyakorlati használhatóság érdekében ismét megjelent a számlálós hurok [1] .

Egyes nyelvekben, például a C -ben és másokban, amelyek származnak belőle, a ciklus , a számlálós ciklus szintaktikai formájafor ellenére , valójában előfeltételes ciklus. Vagyis C-ben a hurokkonstrukció:

for ( i = 0 ; i < 10 ; ++ i ) { ... huroktörzs } _

valójában a konstrukció egy másik jelölési formáját képviseli [2] :

i = 0_ _ míg ( én < 10 ) { ... huroktest ++ i ; _ }

Azaz a konstrukcióban forelőször egy tetszőleges mondatot írunk a ciklus inicializálásáról, majd egy folytatási feltételt, és végül a ciklus minden egyes törzse után végrehajtott műveletet (ennek nem kell a számláló változásának lennie Ez lehet egy mutató szerkesztése vagy valamilyen teljesen idegen művelet). Az ilyen nyelvek esetében a fent leírt probléma nagyon egyszerűen megoldható: a számlálóváltozó teljesen kiszámíthatóan viselkedik, és a ciklus végén megtartja utolsó értékét.

Közös ciklus

A ciklus egy másik változata egy olyan ciklus, amely egy adott halmaz objektumaihoz bizonyos műveletek végrehajtását határozza meg anélkül, hogy kifejezetten meghatározná az objektumok felsorolásának sorrendjét. Az ilyen ciklusokat együttesnek (valamint gyűjtési ciklusoknak , nézetciklusoknak ) nevezik, és formális nyilatkozatot jelentenek a következő formában: "Végezze el az X műveletet az M halmazban szereplő összes elemre". A kötési ciklus elméletileg semmilyen módon nem határozza meg, hogy a művelet milyen sorrendben kerül alkalmazásra a halmaz elemeire, bár az egyes programozási nyelvek természetesen meghatározhatnak egy meghatározott sorrendet az elemek feletti iterációhoz. Az önkényesség lehetővé teszi a ciklus végrehajtásának optimalizálását azáltal, hogy a hozzáférést nem a programozó, hanem a legkedvezőbb sorrendben szervezi. Több művelet párhuzamos végrehajtásának lehetőségével akár egy közös ciklus végrehajtásának párhuzamosítása is lehetséges, amikor ugyanazt a műveletet egyidejűleg hajtják végre különböző objektumok különböző számítási moduljain, miközben a program logikailag konzisztens marad.

Egyes programozási nyelveken ( C# , Eiffel , Java , JavaScript , Perl , Python , PHP , LISP , Tcl stb.) elérhetők az egyesített hurkok – lehetővé teszik, hogy egy adott objektumgyűjtemény összes elemén keresztül körbefusson . Egy ilyen ciklus definíciójában csak egy objektumgyűjteményt és egy változót kell megadni, amely a ciklus törzsében az éppen feldolgozott objektum értékét (vagy hivatkozását) kapja. A különböző programozási nyelvekben az operátor szintaxisa eltérő:

C++ :

for ( type & item : set ) //támogatott a C++11 óta { //elem használata }

C# :

foreach ( írja be az elemet a készletbe ) { //elem használata }

Delphi :

item in [ 1 .. 100 ] do begin //Az elem használata (Ezt a kódot a Delphi 2010-ben tesztelték) end ;

Perl (szigorú sorrendtől az utolsóig):

foreach ( @set ) { #use $_ } # or for ( @set ) { #use $_ } # or foreach $item ( @set ) { #use $item }

Eiffel :

keresztben beállítva kurzorhurokként -- használja a cursor.item end

Java :

for ( type item : set ) { //az elem használata }

JavaScript :

for ( txtProperty in objObject ) { /* használat: objObject [txtProperty] */ }

PHP :

foreach ( $arr mint $item ) { /* use $item*/ } //vagy foreach ( $arr mint $key => $value ) { /* használja a $kulcs indexértékeit és a $értéket*/ } //vagy foreach ( $arr as & $item ) { /*$item használata hivatkozással*/ }

Visual Basic . net :

Minden elemhez A típushoz beállítva a következő elemet használja _

Windows PowerShell :

foreach ($elem a $készletben) { # művelet a $itemnél }

vagy

$set | ForEach-Object { # művelet a következővel: $_ }

Piton

iterator_instance elemhez : # item használata

rubin

iterator_példány . mindegyik do | tétel | # elem vége

Korai kilépés és iteráció kihagyása

Sok olyan programozási nyelv, amelynek szintaxisa ciklikus konstrukciókat tartalmaz, speciális parancsokkal is rendelkezik, amelyek lehetővé teszik ezen konstrukciók működési sorrendjének megsértését: a ciklusból való korai kilépés parancsa és az iteráció kihagyására vonatkozó parancs.

Korai kilépés a ciklusból

A korai kilépés parancsot akkor használjuk, ha meg kell szakítani egy olyan ciklus végrehajtását, amelyben a kilépési feltétel még nem teljesült. Ez történik például akkor, ha a ciklustörzs végrehajtása során hibát észlelnek, ami után a hurok további munkájának nincs értelme.

A korai kilépési utasítást általában EXITvagy hívják break, és hatása hasonló a feltétel nélküli elágazás ( goto) utasításhoz, amely közvetlenül azt a ciklust követi, amelyben ez az utasítás található. Tehát a C nyelvben a következő két ciklus pontosan ugyanúgy működik:

// A break utasítás alkalmazása while ( < feltétel > ) { ... operátorok if ( < error > ) break ; ... operátorok } ... a program folytatása // Hasonló töredék törés nélkül while ( < feltétel > ) { ... operátorok if ( < error > ) goto break_label ; ... operátorok } break_label : ... a program folytatása

Mindkét esetben, ha az <hiba> feltétel teljesül a ciklus törzsében, akkor át kell térni a „program folytatásának” nevezett utasításokra. Így a ciklus korai kilépési operátora valójában egyszerűen elfedi a feltétel nélküli ágat, azonban a break használata előnyösebb, mint a goto, mivel a break viselkedését a nyelv egyértelműen meghatározza, potenciálisan kevésbé veszélyes (például nincs tévedés esélye a címke pozíciójával vagy nevével). Ezenkívül a ciklusból való explicit korai kilépés nem sérti a strukturált programozás alapelveit.

Egy közönséges korai kilépési utasítás lezárja azt a ciklust, amelyben közvetlenül található. Számos programozási nyelvben ennek az operátornak a funkcionalitása kibővült, lehetővé teszi több beágyazott ciklusból való kilépést (lásd alább). Ilyen esetekben a kilépni kívánt hurkot címkével jelöljük, a címkét pedig a korai kilépési utasításban adjuk meg.

Iteráció kihagyása

Ezt az operátort akkor használjuk, ha az aktuális ciklus iterációjában minden parancsot ki kell hagyni a ciklustörzs végéig. Ebben az esetben magát a hurkot nem szabad megszakítani, a folytatási vagy kilépési feltételeket a szokásos módon kell kiszámítani.

A C-ben és leszármazottaiban az iterációs skip parancs egy cikluskonstrukció utasítása continue. Ennek az operátornak a művelete hasonlít egy feltétlen ugráshoz a huroktörzsben az utolsó parancsot követően. Például egy C kód, amely megtalálja egy tömb elemeinek összegét és a tömb összes pozitív elemének összegét, így nézhet ki:

int arr [ ARRSIZE ]; ... // Az arr tömb összes és csak pozitív elemének külön-külön összegzése a folytatás segítségével. int összes_összeg = 0 ; int összeg_pozíció = 0 ; for ( int i = 0 ; i < ARRSIZE ; ++ i ) { sum_all += arr [ i ]; if ( arr [ i ] <= 0 ) tovább ; sum_pos += arr [ i ]; } // Hasonló kód c goto int sum_all = 0 ; int összeg_pozíció = 0 ; for ( int i = 0 ; i < ARRSIZE ; ++ i ) { sum_all += arr [ i ]; if ( arr [ i ] <= 0 ) goto cont_label ; sum_pos += arr [ i ]; cont_label : }

A második részlet jól mutatja, hogyan működik continue: egyszerűen átadja a vezérlést a ciklustörzs utolsó parancsa felett, kihagyva az összegzési parancs végrehajtását, ha a tömb következő eleme nem felel meg a feltételnek. Így a sum_pos csak a tömb pozitív elemeinek összegét halmozza fel.

Szükségesség

Strukturális programozási szempontból a hurokkilépési és iterációs átugrási parancsok redundánsak, mivel működésük tisztán strukturális eszközökkel könnyen modellezhető. Ezen túlmenően számos programozási teoretikus (különösen Edsger Dijkstra) szerint maga a tény, hogy egy programban nem strukturális eszközöket használnak, legyen az klasszikus feltétel nélküli ugrás vagy annak bármely speciális formája, mint például a szünet vagy a folytatás, a probléma megoldására szolgáló, nem kellően kidolgozott algoritmus bizonyítéka.

A gyakorlatban azonban a programkód gyakran egy már létező, korábban megfogalmazott algoritmus rekordja, amelyet pusztán technikai okok miatt nem célszerű átdolgozni. Az ilyen kódokban a korai kilépési parancs strukturális konstrukciókkal való helyettesítésére tett kísérlet gyakran nem hatékony vagy nehézkes. Például a fenti kódrészlet a paranccsal a breakkövetkezőképpen írható:

// Korai kilépés a ciklusból törés nélkül bool flag = false ; // korai felmondás jelzője while ( < feltétel > && ! flag ) { ... operátorok if ( < hiba > ) { zászló = igaz ; } másik { ... operátorok } } ... a program folytatása

Könnyű megbizonyosodni arról, hogy a töredék az előzőekhez hasonlóan fog működni, csak annyi a különbség, hogy a ciklusból való közvetlen kilépés helyett a hibaellenőrzés helyén a korai kilépés jelzője kerül beállításra, amit a későbbiekben ellenőriznek. a ciklus folytatásának szabályos feltétele. A korai kilépési parancs törléséhez azonban egy zászlóleírást és a feltételes operátor második ágát kellett hozzáadni a programhoz, és a program logikája „elmosódott” (a korai kilépésről szóló döntés egy helyen történik, és egy másikban kivégezték). Ennek eredményeként a program nem lett se egyszerűbb, se rövidebb, se áttekinthetőbb.

Némileg más a helyzet a Skip iteration paranccsal. Általában nagyon könnyen és természetesen helyettesíthető egy feltételes operátorral. Például a fenti tömbösszesítő kódrészlet a következőképpen írható:

int arr [ ARRSIZE ]; ... // Az arr tömb összes és csak pozitív elemeinek külön összegzése // cserével folytatódik int sum_all = 0 ; int összeg_pozíció = 0 ; for ( int i = 0 ; i < ARRSIZE ; ++ i ) { sum_all += arr [ i ]; if ( arr [ i ] > 0 ) // Feltétel megfordítva! { sum_pos += arr [ i ]; } }

Mint látható, elég volt az ellenőrzött feltételt az ellenkezőjére cserélni, és a ciklustörzs utolsó részét egy feltételes utasításba helyezni. Látható, hogy a program rövidebb lett (a skip iteration parancs eltávolítása miatt) és egyben logikusabb is (a kódból közvetlenül látszik, hogy a pozitív elemek összegződnek).

Ezen túlmenően az iterációs skip parancs használata egy ciklusban feltétellel (while-loop) nyilvánvaló hibát is kiválthat: ha a ciklus törzse, mint gyakran előfordul, a ciklusváltozó(k) megváltoztatására szolgáló parancsokkal végződik, akkor az iteráció A skip parancs ezeket a parancsokat is kihagyja, aminek következtében (az átugrás körülményeitől függően) az algoritmusnak nem megfelelő ciklus vagy iterációs ismétlődés léphet fel. Tehát, ha a for ciklust a while-ra cseréljük a fenti példában, a következőket kapjuk:

int arr [ ARRSIZE ]; ... int összes_összeg = 0 ; int összeg_pozíció = 0 ; int i = 0 ; while ( i < ARRSIZE ) // A ciklus úgy néz ki, mint az előző ... { sum_all += arr [ i ]; if ( arr [ i ] <= 0 ) tovább ; sum_pos += arr [ i ]; ++ i ; // ... de ez a parancs kimarad a folytatásnál // és a program ciklusba fog fordulni }

Korlátozott hasznosságuk és más nyelvi konstrukciókkal való helyettesíthetőségük ellenére az iterációs parancsok kihagyása és különösen a ciklusból való korai kilépés bizonyos esetekben rendkívül hasznosak, ezért őrzik meg őket a modern programozási nyelvekben.

Beágyazott hurkok

Lehetőség van egy hurok megszervezésére egy másik hurok törzsében. Az ilyen hurkot beágyazott huroknak nevezzük . A beágyazott hurkot azzal a hurokkal kapcsolatban, amelynek törzsébe be van ágyazva, belső huroknak nevezzük , és fordítva, azt a hurkot, amelynek törzsében van beágyazott hurok, külsőnek nevezzük a beágyazotthoz képest. A beágyazott hurkon belül viszont egy másik hurok is beágyazható, ami a következő beágyazási szintet alkotja , és így tovább. A fészkelő szintek száma általában nincs korlátozva.

A belső hurok törzsének teljes végrehajtási száma nem haladja meg a belső és az összes külső ciklus iterációinak szorzatát. Például, ha három, egymásba ágyazott hurkot veszünk, mindegyik 10 iterációval, a test 10 végrehajtását kapjuk a külső ciklushoz, 100-at a második szintű ciklushoz, és 1000-et a legbelső ciklushoz.

A beágyazott hurkokkal kapcsolatos egyik probléma a beágyazott hurkok korai kilépésének megszervezése. Sok programozási nyelv rendelkezik huroklezáró operátorral ( breakC-ben exit, Turbo Pascal- lastban, Perl -ben stb.), de általában csak abból a ciklusból ad kijáratot, amelyről hívták. Egy beágyazott hurkon belüli meghívása csak a belső ciklust fejezi be, míg a külső ciklus tovább fut. A probléma távolinak tűnhet, de néha felmerül összetett adatfeldolgozás programozása során, amikor az algoritmus bizonyos feltételek mellett azonnali megszakítást igényel, aminek megléte csak egy mélyen beágyazott ciklusban ellenőrizhető.

Számos megoldás létezik a beágyazott hurkok kilépésének problémájára.

  • A legegyszerűbb, ha a goto operátorral a programban közvetlenül a beágyazott ciklust követő pontra ugorhatunk. Ezt a változatot kritizálják a strukturált programozók , csakúgy, mint az összes goto használatát igénylő konstrukciót . Egyes programozási nyelvek, mint például a Modula-2 , egyszerűen nem rendelkeznek feltétel nélküli elágazás operátorral, és ilyen konstrukció nem lehetséges bennük.
  • Alternatív megoldás a szokásos huroklezáró eszközök használata, szükség esetén speciális jelzők beállítása, amelyekhez a feldolgozás azonnali befejezése szükséges. Hátránya a kód bonyolultsága, a teljesítmény romlása.
  • Beágyazott hurok elhelyezése egy eljárásban. Az ötlet az, hogy minden olyan műveletet, amelyet esetleg idő előtt meg kell szakítani, külön eljárásként mutatják be, és a korai befejezéshez használja az eljárásból az exit utasítást (ha van ilyen a programozási nyelvben). A C nyelvben például létrehozhat egy függvényt beágyazott ciklussal, és megszervezheti a kilépést a return utasítás segítségével . Hátránya, hogy egy kódrészletnek az eljárásba való kiválasztása nem mindig logikailag indokolt, és nem minden nyelv rendelkezik rendszeres eszközökkel az eljárások korai befejezésére.
  • Használja ki a kivételek (kivételes helyzetek) generálására és kezelésére szolgáló mechanizmust , amely már elérhető a legtöbb magas szintű nyelven . Ebben az esetben abnormális helyzetben a beágyazott ciklusban lévő kód kivételt vet fel, és a kivételkezelő blokk, amelybe a teljes beágyazott ciklus el van helyezve, elkapja és feldolgozza azt. Hátránya, hogy a kivételkezelési mechanizmus megvalósítása a legtöbb esetben olyan, hogy a program sebessége csökken. Igaz, modern körülmények között ennek nincs különösebb jelentősége: a gyakorlatban a teljesítményveszteség olyan kicsi, hogy csak nagyon kevés alkalmazásnál számít.
  • Végül, vannak speciális nyelvi lehetőségek a beágyazott ciklusokból való kilépéshez. Tehát az Ada nyelvben a programozó megjelölhet egy ciklust (a beágyazott ciklus legfelső szintjét) címkével, és jelezheti ezt a címkét a ciklus korai befejezésének parancsában. A kilépés nem az aktuális ciklusból fog megtörténni, hanem az összes beágyazott ciklusból egészen a megjelöltig, beleértve [3] . A PHP nyelv lehetővé teszi a megszakított ciklusok számának megadását a parancs után break - ez break 2megszakítja magát a ciklust és a felette lévőt, és break 1egyenértékű a parancs egyszerű megírásával break[4] .

Hurok több őrzött ággal

Dijkstra ciklusa

A programozáselméletben létezik a ciklikus konstrukciónak egy másik formája is, amely alapvetően különbözik a "klasszikusoktól", az úgynevezett Dijkstra-ciklus, Edsger Dijkstra után , aki először írta le. A klasszikus Dijkstra leírásban egy ilyen ciklus így néz ki:

csináld P 1 → S 1 , … P n → S n od

Itt do a hurokkonstrukció kezdetének jelzője, a od hurokkonstrukció végének jelzője, P i  az i - edik védőfeltétel (logikai kifejezés, amely lehet igaz vagy hamis), S i  az i -a őrzött parancsnokság . A hurok egy vagy több ágból ( őrzött kifejezések ) áll, amelyek mindegyike egy őrzési feltétel (vagy röviden "őrök") és egy őrzött parancs párja (világos, hogy a parancs valójában összetett is lehet).

Amikor a Dijkstra ciklus végrehajtódik, a védelmi feltételek minden iterációban kiszámításra kerülnek. Ha ezek közül legalább az egyik igaz, akkor a megfelelő őrzött parancs végrehajtásra kerül, ami után egy új iteráció kezdődik (ha egynél több őrzési feltétel igaz, csak egy őrzött parancs kerül végrehajtásra). Ha minden őrzési feltétel hamis, a hurok leáll. Könnyen belátható, hogy Dijkstra ciklusa egy őrfeltétellel és egy őrparanccsal valójában egy közönséges előfeltételes hurok (a "while" hurok).

Bár a Dijkstra hurkot még az 1970-es években találták fel, a programozási nyelvekben nincsenek speciális konstrukciók a létrehozására. Az egyetlen kivétel a nemrégiben létrehozott Oberon-07 volt  , az első igazi programozási nyelv, amely kifejezetten támogatja a több védett ággal rendelkező hurkot. Dijkstra ciklusa azonban különösebb nehézség nélkül modellezhető a strukturált programozási nyelvek hagyományos konstrukcióival. Íme egy példa a megvalósítására az egyik lehetséges módon az Ada nyelven:

hurok ha P1 akkor S1 ; ... elsif Pn then Sn ; különben kilépés ; vége if ; end - loop ;

Itt a P1-Pn az őrzési feltételek, az S1-Sn pedig a megfelelő őrzési parancsok.

A Dijkstra ciklusa hasznos néhány specifikus ismétlődő számítás végrehajtásához, amelyeket kényelmetlen a hagyományos hurokkonstrukciókkal leírni. Például ez a ciklus természetesen egy véges automatát reprezentál  – minden ág az automata egy-egy állapotának felel meg, az őrzött feltételek úgy épülnek fel, hogy az aktuális iterációban az automata aktuális állapotának megfelelő ág kerül kiválasztásra, és az őrzött kódja. utasítás biztosítja, hogy a számítások az aktuális állapotban történjenek, és áttérjenek a következőre (vagyis a változók olyan változása, amely után a következő iterációnál igaz lesz a kívánt ág őrfeltétele).

Spider Cycle

Könnyen belátható, hogy Dijkstra ciklusa nem tartalmaz explicit folytatási vagy kilépési feltételt, amit nem minden programozási teoretikus tekint áldásnak. Ezért javasolták a Dijkstra-ciklus bonyolult felépítését, amelyet "pókciklusnak" neveztek. Ugyanebben a jelölésben ez így néz ki:

csináld P 1 → S 1 , … P n → S n ki Q 1 → T 1 , … Q n → T n más E od

Itt a jelölő után befejezési ágakout kerülnek hozzáadásra , amelyek a Q i kilépési feltételekből és a T i befejezési parancsokból állnak . Ezenkívül egy alternatív befejezési ág is hozzáadásra került az E paranccsal .else

A pókhurok a következőképpen hajtódik végre:

  • Az őrzési feltételek kiszámítva. Ha valódi őrzési feltétel létezik, a megfelelő őrzési parancs végrehajtásra kerül.
  • A kilépési feltételek kiszámításra kerülnek. Valódi kilépési feltétel fennállása esetén a megfelelő befejező parancs végrehajtásra kerül, amely után a ciklus végrehajtása véget ér. Ha minden kilépési feltétel hamis, a következő iteráció kezdődik, de csak akkor, ha az őrzési feltételek közül legalább egy igaz volt az aktuális iterációban.
  • Ha ebben az iterációban az összes őrzési feltétel és minden kilépési feltétel hamis, akkor az alt-end E utasítás végrehajtásra kerül, amely után a ciklus végrehajtása megszakad.

A „pók” ciklus felépítése lehetővé teszi a ciklus végrehajtási feltételeinek rendkívül szigorú leírását. Az elméleti álláspontok szerint az alternatív befejezés ága nem használható a ciklus helyes lezárásának egyik opciójaként (minden ilyen opciót megfelelő befejezési ágként kell formázni, kifejezett feltétellel), csak arra szolgál, hogy nyomon kövesse azt a helyzetet, amikor valamiért, Valamiért rendellenesen kezdett el futni a ciklus. Vagyis az alt parancs csak a hiba okait tudja elemezni és az elemzés eredményeit bemutatni.

Bár ennek a ciklusnak kifejezett szintaktikai szintű támogatása egyetlen programozási nyelvben sem létezik, a pókhurok, akárcsak Dijkstra ciklusa, hagyományos szerkezeti konstrukciók segítségével modellezhető.

Hurokoptimalizálási módszerek

a forráskód egyenértékű átalakításai fordítóprogram

Lásd még

Jegyzetek

  1. Az Oberon Niklaus Wirth álma
  2. Szigorúan véve az identitás nem teljes, mivel a turpināt utasítás másként fog működni.
  3. Hurok/beágyazva a Rosetta  Code -ban
  4. ↑ PHP kézikönyv , szünet 

Linkek