Cyklus (programmering)

Den aktuelle version af siden er endnu ikke blevet gennemgået af erfarne bidragydere og kan afvige væsentligt fra den version , der blev gennemgået den 7. februar 2022; checks kræver 3 redigeringer .

En loop  er en slags kontrolstruktur i programmeringssprog på højt niveau , designet til at organisere den gentagne udførelse af et sæt instruktioner . En cyklus kan også kaldes enhver gentagne gange udført sekvens af instruktioner, organiseret på en hvilken som helst måde (for eksempel ved hjælp af et betinget spring ).

Definitioner

En sekvens af instruktioner beregnet til at blive udført gentagne gange kaldes en loop body . En enkelt udførelse af loop-legemet kaldes en iteration . Udtrykket , der bestemmer, om iterationen skal udføres igen, eller om løkken slutter, kaldes udgangsbetingelsen eller slutbetingelsen for løkken (eller fortsættelsesbetingelsen , afhængigt af hvordan dens sandhed fortolkes - som et tegn på behovet for at afslutte eller fortsæt løkken). Variablen , der gemmer det aktuelle iterationsnummer, kaldes loop-iterationstælleren eller blot looptælleren . Sløjfen indeholder ikke nødvendigvis en tæller, tælleren behøver ikke at være én - betingelsen for at forlade sløjfen kan afhænge af flere variabler, der ændres i sløjfen, eller kan bestemmes af eksterne forhold (f.eks. starten af ​​en bestemt tid), i sidstnævnte tilfælde er tælleren muligvis slet ikke nødvendig.

Udførelsen af ​​en hvilken som helst sløjfe inkluderer den indledende initialisering af sløjfevariablerne, kontrol af udgangstilstanden, udførelse af sløjfelegemet og opdatering af sløjfevariablen ved hver iteration. Derudover giver de fleste programmeringssprog midler til tidlig kontrol af sløjfen, for eksempel sløjfetermineringsudsagn, det vil sige exit fra sløjfen uanset sandheden af ​​exitbetingelsen (på C -sprog  - break) og iteration skip-operatorer ( på C-sprog - continue).

Typer af cyklusser

Ubetingede sløjfer

Nogle gange bruger programmer loops, hvis udgang ikke er tilvejebragt af programmets logik. Sådanne cyklusser kaldes ubetingede eller uendelige. På grund af deres atypiske karakter giver programmeringssprog ikke specielle syntaktiske midler til at skabe uendelige sløjfer, derfor oprettes sådanne sløjfer ved hjælp af konstruktioner designet til at skabe almindelige (eller betingede ) sløjfer. For at sikre uendelig gentagelse er betingelseskontrollen i en sådan sløjfe enten fraværende (hvis syntaksen tillader det, som f.eks. i AdaLOOP ... END LOOP sprogsløjfen ), eller erstattet af en konstant værdi ( i Pascal ). C - sproget bruger en loop med tomme sektioner eller en loop . while true do ...for(;;)while (1)

Sløjfe med forudsætning

En løkke med en forudsætning er en løkke, der udføres, mens en betingelse, der er angivet før dens start, er sand. Denne betingelse kontrolleres før udførelsen af ​​loop-kroppen, så kroppen bliver muligvis ikke eksekveret én gang (hvis betingelsen er falsk helt fra begyndelsen). I de fleste proceduremæssige programmeringssprog er det implementeret af while -sætningen , derfor er dets andet navn while-løkken. I Pascal ser en løkke med en forudsætning sådan ud:

mens < betingelse > begynder < loop body > end ; _

C -sprog :

while ( < betingelse > ) { < loop body > }

Sløjfe med postcondition

En løkke med en postbetingelse er en løkke, hvor tilstanden kontrolleres efter udførelsen af ​​løkkelegemet. Det følger heraf, at kroppen altid henrettes mindst én gang. I Pascal implementeres denne løkke af operatøren repeat..until ; i C- do…while.

I Pascal ser en løkke med en postcondition således ud:

gentag < loop body > indtil < exit condition >

På C-sprog:

gør { < loop body > } while ( < loop fortsættelse betingelse > )

Der er forskelle i fortolkningen af ​​loop-betingelsen med en postcondition på forskellige sprog. I Pascal og de sprog, der stammer fra den, behandles tilstanden af ​​en sådan cyklus som en udgangsbetingelse (cyklussen slutter, når betingelsen er sand, i russisk terminologi kaldes sådanne cyklusser også "cyklus indtil"), og i C og dens efterkommere - som en fortsættelsesbetingelse (cyklussen slutter, når betingelsen er falsk, sådanne sløjfer kaldes nogle gange for en while-løkke).

Skift ud af midten

En midterste udgangsløkke er den mest almindelige form for en betinget løkke. Syntaktisk dannes en sådan cyklus ved hjælp af tre konstruktioner: begyndelsen af ​​cyklussen, slutningen af ​​cyklussen og kommandoen til at forlade cyklussen. Startkonstruktionen markerer det punkt i programmet, hvor løkkelegemet begynder, slutkonstruktionen markerer det punkt, hvor kroppen slutter. Inde i kroppen skal der være en udgangskommando fra sløjfen, ved udførelse af hvilken sløjfen ender og styringen overføres til operatøren efter sløjfeendekonstruktionen. For at sløjfen skal udføres mere end én gang, skal exit-kommandoen naturligvis ikke kaldes ubetinget, men kun når betingelsen for at forlade sløjfen er opfyldt.

Den grundlæggende forskel mellem denne type sløjfe og dem, der er betragtet ovenfor, er, at den del af sløjfen, der er placeret efter starten af ​​sløjfen og før exit-kommandoen altid udføres (selvom udgangsbetingelsen fra sløjfen er sand ved den første iteration ), og den del af løkkelegemet, der er placeret efter exit-kommandoen, udføres ikke ved den sidste iteration.

Det er let at se, at man med en middle exit loop nemt kan modellere både en precondition-løkke (ved at placere exit-sætningen i begyndelsen af ​​loop-kroppen) og en postcondition-løkke (ved at placere exit-sætningen i slutningen af ​​løkken) legeme).

Nogle programmeringssprog indeholder specielle konstruktioner til at organisere en løkke med en udgang fra midten. Så på Ada -sproget bruges konstruktionen LOOP ... END LOOPog exit-kommandoen til dette, EXITeller EXIT WHEN:

LOOP ... Loop kropsdel ​​EXIT NÅR < udgangstilstand > ; _ _ ... Loop kropsdel ​​HVIS < udgangstilstand > AFSLUT ; _ _ SLUT ; ... En del af kroppen af ​​END LOOP :

Her, inde i løkken, kan der være et hvilket som helst antal afgangskommandoer af begge typer. Exit-kommandoerne i sig selv adskiller sig ikke fundamentalt, de bruges normalt EXIT WHEN, når kun exit-betingelsen er kontrolleret, men blot EXIT når løkken afsluttes i en af ​​varianterne af en kompleks betinget sætning.

På sprog, hvor sådanne konstruktioner ikke er tilvejebragt, kan en sløjfe med udgang fra midten modelleres ved hjælp af en hvilken som helst betinget sløjfe og en tidlig udgangsoperator fra sløjfen (såsom breaki C, exit i Turbo Pascal osv.), eller en ubetinget operatørovergang goto .

Sløjfe med tæller (eller løkke for)

En løkke med en tæller er en løkke, hvor en variabel ændrer sin værdi fra en given startværdi til en endelig værdi med et eller andet trin, og for hver værdi af denne variabel udføres løkkens krop én gang. I de fleste proceduremæssige programmeringssprog implementeres det af en erklæring for, der specificerer tælleren (den såkaldte "løkkevariabel"), det nødvendige antal gennemløb (eller grænseværdien for tælleren) og muligvis det trin, hvori tælleren ændringer. For eksempel på Oberon-2- sproget ser en sådan cyklus ud som:

FOR v := b TO e BY s DO ... loop krop ENDE

her er v tælleren, b er startværdien af ​​tælleren, e er grænseværdien for tælleren, s er trinnet).

Spørgsmålet om værdien af ​​en variabel i slutningen af ​​en løkke, hvor denne variabel blev brugt som tæller, er tvetydigt. For eksempel, hvis et Pascal-program støder på en konstruktion af formen:

i := 100 ; for i : = 0 til 9 begynder ... loop body end ; k := i ;

spørgsmålet opstår: hvilken værdi vil i sidste ende blive tildelt variablen k : 9, 10, 100, måske en anden? Hvad hvis cyklussen slutter for tidligt? Svarene afhænger af, om værdien af ​​tælleren øges efter den sidste iteration, og om oversætteren ændrer denne værdi yderligere. Et spørgsmål mere: hvad vil der ske, hvis tælleren eksplicit tildeles en ny værdi inde i løkken? Forskellige programmeringssprog håndterer disse problemer på forskellige måder. Hos nogle er tællerens adfærd klart reguleret. I andre, for eksempel i samme Pascal, definerer sprogstandarden hverken den endelige værdi af tælleren eller konsekvenserne af dens eksplicitte ændring i løkken, men den anbefaler ikke at ændre tælleren eksplicit og bruge den til sidst af løkken uden reinitialisering. Et Pascal-program, der ignorerer denne anbefaling, kan give forskellige resultater, når det køres på forskellige systemer og bruger forskellige oversættere.

Problemet er radikalt løst på Ada- og Kotlin -sprogene : tælleren anses for at være beskrevet i loop-headeren og eksisterer simpelthen ikke uden for den. Selvom navnet på tælleren allerede er brugt i programmet, bruges en separat variabel som tæller inde i løkken. Det er forbudt eksplicit at tildele værdier til tælleren, det kan kun ændres af sløjfeoperatørens interne mekanisme.

Som følge heraf har konstruktionen på Ada:

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

Og i Kotlin:

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

udadtil svarende til Pascal-løkken ovenfor, fortolkes den entydigt: Variablen k vil blive tildelt værdien 100, da variablen i brugt uden for denne løkke ikke har noget at gøre med tælleren i , som skabes og ændres inde i løkken . En sådan isolering af tælleren er praktisk og sikker: der kræves ingen separat beskrivelse af den, og sandsynligheden for tilfældige fejl forbundet med utilsigtet ødelæggelse af variabler uden for løkken er minimal. Hvis en programmør skal inkludere en cyklus med en tæller i den færdige kode, så må han ikke kontrollere, om der er en variabel med det navn, han har valgt som tæller, ikke tilføje en beskrivelse af en ny tæller til overskriften på tilsvarende procedure, ikke forsøge at bruge en af ​​de tilgængelige, men i dette øjeblik af "gratis" tællere. Han skriver blot en løkke med en variabel tæller, hvis navn er praktisk for ham, og kan være sikker på, at der ikke vil forekomme navnekollision.

En sløjfe med en tæller kan altid skrives som en betinget sløjfe, før tællerens begyndelse tildeles en startværdi, og udgangsbetingelsen er, at tælleren når slutværdien; samtidig tilføjes en operatør til at ændre tælleren med et givet trin til løkkelegemet. Imidlertid kan specielle operatører af en cyklus med en tæller oversættes mere effektivt, da den formaliserede form af en sådan cyklus tillader brugen af ​​specielle processorinstruktioner til organisering af cyklusser.

Niklaus Wirth kaldte på et tidspunkt løkken med en tæller "marginal" og argumenterede for, at en sådan konstruktion er overflødig og bør udelukkes fra syntaksen for programmeringssprog som ikke-system. I overensstemmelse med denne opfattelse var der ingen cyklus med en tæller i programmeringssproget Oberon . Men i sproget Oberon-2 , skabt af Wirth og Mössenböck i udviklingen af ​​Oberon, dukkede løkken med en tæller FORop igen af ​​hensyn til praktisk anvendelighed [1] .

I nogle sprog, såsom C og andre afledt af det, er løkken for, på trods af den syntaktiske form af en løkke med en tæller, faktisk en løkke med en forudsætning. Det vil sige i C, loop-konstruktionen:

for ( i = 0 ; i < 10 ; ++ i ) { ... loop body }

repræsenterer faktisk en anden form for notation af konstruktionen [2] :

i = 0 _ mens ( i < 10 ) { ... loop body ++ i ; }

Det vil sige, i konstruktionen forskrives først en vilkårlig sætning om initialisering af cyklussen, derefter en fortsættelsesbetingelse og til sidst en operation udført efter hver krop af cyklussen (dette behøver ikke at være en ændring i tælleren ; det kan være redigering af en markør eller en helt uvedkommende handling). For sprog af denne art løses det ovenfor beskrevne problem meget enkelt: tællervariablen opfører sig fuldstændig forudsigeligt og bevarer i slutningen af ​​løkken sin sidste værdi.

Fælles cyklus

En anden variant af løkken er en løkke, der specificerer udførelsen af ​​en operation for objekter fra et givet sæt, uden eksplicit at specificere rækkefølgen, hvori disse objekter er opregnet. Sådanne cyklusser kaldes fælles (såvel som indsamlingscyklusser , visningscyklusser ) og repræsenterer en formel erklæring af formen: "Udfør operation X for alle elementer, der er inkluderet i sættet M". The joint loop, teoretisk set, bestemmer ikke på nogen måde, i hvilken rækkefølge operationen vil blive anvendt på elementerne i sættet, selvom specifikke programmeringssprog selvfølgelig kan specificere en specifik rækkefølge for at iterere over elementerne. Vilkårlighed gør det muligt at optimere udførelsen af ​​cyklussen ved at organisere adgangen ikke i programmørens rækkefølge, men i den mest gunstige rækkefølge. Med mulighed for parallel udførelse af flere operationer er det endda muligt at parallelisere udførelsen af ​​en fælles cyklus, når den samme operation udføres samtidigt på forskellige computermoduler for forskellige objekter, mens programmet forbliver logisk konsistent.

Joint loops er tilgængelige i nogle programmeringssprog ( C# , Eiffel , Java , JavaScript , Perl , Python , PHP , LISP , Tcl osv.) - de giver dig mulighed for at loope gennem alle elementer i en given samling af objekter . I definitionen af ​​en sådan løkke behøver du kun at angive en samling af objekter og en variabel, som i løkkens krop vil blive tildelt værdien af ​​det aktuelt behandlede objekt (eller en reference til det). I forskellige programmeringssprog er operatørsyntaksen forskellig:

C++ :

for ( type & item : set ) //understøttet siden C++11 { //brug element }

C# :

foreach ( skriv element i sæt ) { //using item }

Delphi :

for element i [ 1 .. 100 ] do begin //Using item (Denne kode blev testet i Delphi 2010) end ;

Perl (strengt første til sidste ordre):

foreach ( @set ) { #brug $_ } # eller for ( @sæt ) { #brug $_ } # eller foreach $item ( @sæt ) { #brug $item }

eiffel :

på tværs af sæt som markørløkke -- brug cursor.item end

Java :

for ( skriv element : sæt ) { //using item }

JavaScript :

for ( txtProperty i objObject ) { /* brug: objObject [txtProperty] */ }

PHP :

foreach ( $arr som $item ) { /* brug $item*/ } //eller foreach ( $arr som $key => $value ) { /* brug $keys indeksværdier og $value*/ } //or foreach ( $arr as & $item ) { /*brug $item ved reference*/ }

Visual Basic . net :

For hvert element Som type i sæt 'brug element Næste element

Windows PowerShell :

foreach ($item i $set) { # handlinger på $item }

eller

$sæt | ForEach-Object { # operationer med $_ }

Python

for element i iterator_instance : # brug element

rubin

iterator_instance . hver gør | vare | # brug element slut

Tidlig exit og iterationsspring

Mange programmeringssprog, der har cykliske konstruktioner i deres syntaks, har også specifikke kommandoer, der giver dig mulighed for at overtræde rækkefølgen af ​​disse konstruktioner: kommandoen til at forlade sløjfen tidligt og kommandoen til at springe iteration over.

Tidlig udgang fra cyklussen

Den tidlige exit-kommando bruges, når det er nødvendigt at afbryde udførelsen af ​​en løkke, hvor udgangstilstanden endnu ikke er nået. Dette sker for eksempel, når der opdages en fejl under udførelsen af ​​sløjfelegemet, hvorefter yderligere arbejde med sløjfen ikke giver mening.

En early exit-instruktion kaldes sædvanligvis EXITeller break, og dens effekt ligner virkningen af ​​en ubetinget greninstruktion ( goto) på instruktionen umiddelbart efter løkken, hvori denne instruktion er placeret. Så i C-sproget fungerer følgende to sløjfer nøjagtigt det samme:

// Anvendelse af pausesætningen while ( < betingelse > ) { ... operatører if ( < fejl > ) break ; ... operatører } ... fortsættelse af programmet // Lignende fragment uden pause while ( < betingelse > ) { ... operatører if ( < fejl > ) goto break_label ; ... operatører } break_label : ... fortsættelse af programmet

I begge tilfælde, hvis <fejl>-betingelsen er opfyldt i løkkens brødtekst, vil der blive foretaget en overgang til de udsagn, der er udpeget som "programfortsættelse". Således maskerer loop early exit-operatoren i virkeligheden simpelthen den ubetingede gren, men brugen af ​​break er at foretrække frem for goto, da break-adfærden er klart specificeret af sproget, potentielt mindre farlig (f.eks. er der ingen chance for at lave en fejl med positionen eller navnet på etiketten). Derudover krænker eksplicit tidlig exit fra løkken ikke principperne for struktureret programmering.

En almindelig tidlig exit-erklæring afslutter løkken, hvori den er direkte placeret. I en række programmeringssprog er funktionaliteten af ​​denne operatør udvidet, den giver dig mulighed for at forlade flere indlejrede sløjfer (se nedenfor). I sådanne tilfælde er løkken, der skal forlades, markeret med en etiket, og etiketten er specificeret i den tidlige exit-erklæring.

Spring iteration over

Denne operator bruges, når det i den aktuelle loop-iteration er nødvendigt at springe alle kommandoer over indtil slutningen af ​​loop-kroppen. I dette tilfælde bør selve sløjfen ikke afbrydes, fortsættelses- eller udgangsbetingelserne skal beregnes på sædvanlig måde.

I C og dets efterkommere er kommandoen iteration skip en sætning continuei en loop-konstruktion. Denne operatørs handling ligner et ubetinget hop til linjen inde i løkkelegemet efter dens sidste kommando. For eksempel kan en C-kode, der finder summen af ​​elementerne i et array og summen af ​​alle positive elementer i arrayet, se sådan ud:

int arr [ ARRSIZE ]; ... // Opsummering separat alle og kun positive //elementer af array arr ved at bruge fortsæt. int sum_all = 0 ; int sum_pos = 0 ; for ( int i = 0 ; i < ARRSIZE ; ++ i ) { sum_all += arr [ i ]; if ( arr [ i ] <= 0 ) fortsæt ; sum_pos += arr [ i ]; } // Lignende kode c goto int sum_all = 0 ; int sum_pos = 0 ; for ( int i = 0 ; i < ARRSIZE ; ++ i ) { sum_all += arr [ i ]; if ( arr [ i ] <= 0 ) goto cont_label ; sum_pos += arr [ i ]; forts_label : }

Det andet uddrag viser tydeligt, hvordan det virker continue: det overfører simpelthen kontrol over den sidste kommando i sløjfelegemet og springer udførelsen af ​​summeringskommandoen over, hvis det næste element i arrayet ikke opfylder betingelsen. Således akkumulerer sum_pos summen af ​​kun positive elementer i arrayet.

Nødvendighed

Fra et strukturelt programmeringssynspunkt er sløjfe-exit- og iterationsspring-kommandoerne overflødige, da deres handling let kan modelleres med rent strukturelle midler. Desuden, ifølge en række programmeringsteoretikere (især Edsger Dijkstra), selve det faktum at bruge ikke-strukturelle midler i et program, hvad enten det er et klassisk ubetinget spring eller nogen af ​​dets specialiserede former, såsom pause eller fortsætte, er bevis på en utilstrækkeligt udviklet algoritme til at løse problemet.

Men i praksis er programkoden ofte en registrering af en allerede eksisterende, tidligere formuleret algoritme, som er uhensigtsmæssig at omarbejde af rent tekniske årsager. Et forsøg på at erstatte den tidlige exit-kommando i en sådan kode med strukturelle konstruktioner viser sig ofte at være ineffektivt eller besværligt. For eksempel kunne kodestykket ovenfor med kommandoen breakskrives sådan:

// Tidlig udgang fra løkken uden pause bool flag = falsk ; // flag for tidlig opsigelse while ( < condition > && ! flag ) { ... operatører if ( < fejl > ) { flag = sandt ; } andet { ... operatører } } ... fortsættelse af programmet

Det er nemt at sikre sig, at fragmentet vil fungere på samme måde som de foregående, den eneste forskel er, at i stedet for at forlade sløjfen direkte, sættes flaget for tidlig udgang på stedet for kontrol for en fejl, som kontrolleres senere i den almindelige betingelse for at fortsætte løkken. Men for at annullere den tidlige exit-kommando, skulle der tilføjes en flagbeskrivelse og en anden gren af ​​den betingede operatør til programmet, og programlogikken var "sløret" (beslutningen om at afslutte tidligt tages ét sted, og henrettet i en anden). Som følge heraf er programmet hverken blevet enklere, kortere eller klarere.

Situationen er noget anderledes med kommandoen overspring iteration. Det er som regel meget let og naturligt erstattet af en betinget operatør. For eksempel kunne array-summeringsstykket ovenfor skrives sådan:

int arr [ ARRSIZE ]; ... // Opsummering separat af alle og kun positive //elementer af array arr med erstatning fortsætter int sum_all = 0 ; int sum_pos = 0 ; for ( int i = 0 ; i < ARRSIZE ; ++ i ) { sum_all += arr [ i ]; if ( arr [ i ] > 0 ) // Tilstand omvendt! { sum_pos += arr [ i ]; } }

Som du kan se, var det nok at erstatte den kontrollerede tilstand med den modsatte og placere den sidste del af løkkelegemet i en betinget erklæring. Man kan se, at programmet er blevet kortere (pga. fjernelse af skip iteration-kommandoen) og samtidig mere logisk (det ses direkte af koden, at positive elementer summeres).

Derudover kan brug af iteration skip-kommandoen i en loop med en betingelse (while-loop) også fremkalde en uoplagt fejl: hvis loop-kroppen, som det ofte sker, ender med kommandoer til at ændre loop-variablen(erne), så er iterationen skip-kommando vil også springe disse kommandoer over, som et resultat af hvilket (afhængigt af den betingelse, hvorunder overspringningen sker) kan forekomme en løkke eller en gentagelse af gentagelser, der ikke svarer til algoritmen. Så hvis vi erstatter for-løkken med while i ovenstående eksempel, får vi følgende:

int arr [ ARRSIZE ]; ... int sum_all = 0 ; int sum_pos = 0 ; int i = 0 ; while ( i < ARRSIZE ) // Løkken ser ud som den forrige for ... { sum_all += arr [ i ]; if ( arr [ i ] <= 0 ) fortsæt ; sum_pos += arr [ i ]; ++ i ; // ... men denne kommando vil blive sprunget over, når du fortsætter // og programmet vil loope }

På trods af deres begrænsede anvendelighed og evnen til at erstatte dem med andre sprogkonstruktioner, er skip iterationskommandoer og især tidlig exit fra løkken ekstremt nyttige i nogle tilfælde, hvorfor de er bevaret i moderne programmeringssprog.

Indlejrede sløjfer

Det er muligt at organisere en løkke inde i kroppen af ​​en anden løkke. En sådan løkke vil blive kaldt en indlejret løkke . En indlejret løkke i forhold til den løkke, i hvis krop den er indlejret, vil blive kaldt en indre løkke , og omvendt vil en løkke i kroppen, hvoraf der er en indlejret løkke, blive kaldt ekstern i forhold til den indlejrede. Inde i den indlejrede sløjfe kan der igen indlejres en anden sløjfe, der danner det næste niveau af indlejring og så videre. Antallet af redeniveauer er som regel ikke begrænset.

Det samlede antal henrettelser af kroppen af ​​den indre sløjfe overstiger ikke produktet af antallet af iterationer af den indre og alle ydre sløjfer. Hvis vi for eksempel tager tre sløjfer indlejret inde i hinanden, hver med 10 iterationer, får vi 10 udførelser af kroppen for den ydre sløjfe, 100 for sløjfen på andet niveau og 1000 i den inderste sløjfe.

Et af problemerne forbundet med indlejrede sløjfer er tilrettelæggelsen af ​​tidlig exit fra dem. Mange programmeringssprog har en sløjfetermineringsoperatør ( breaki C, exiti Turbo Pascal, lasti Perl osv.), men som regel giver den kun en udgang fra sløjfen på det niveau, hvorfra det blev kaldt. At kalde det inde fra en indlejret løkke vil kun afslutte den indre løkke, mens den ydre løkke vil fortsætte med at køre. Problemet kan virke langt ude, men det opstår nogle gange ved programmering af kompleks databehandling, når algoritmen kræver en øjeblikkelig afbrydelse under visse forhold, hvis tilstedeværelse kun kan kontrolleres i en dybt indlejret løkke.

Der er flere løsninger på problemet med at forlade indlejrede løkker.

  • Det enkleste er at bruge goto -operatoren til at springe til punktet i programmet umiddelbart efter den indlejrede løkke. Denne variant er kritiseret af strukturerede programmører , ligesom alle konstruktioner, der kræver brug af goto . Nogle programmeringssprog, såsom Modula-2 , har simpelthen ikke en ubetinget filialoperatør, og en sådan konstruktion er ikke mulig i dem.
  • Et alternativ er at bruge de almindelige sløjfetermineringsværktøjer, hvis det er nødvendigt, og sætte specielle flag, der kræver øjeblikkelig afslutning af behandlingen. Ulempen er kodens komplikation, ydeevneforringelse.
  • Placering af en indlejret løkke i en procedure. Tanken er, at al den handling, der muligvis skal afbrydes før tid, præsenteres som en separat procedure, og for tidlig afslutning skal du bruge exit-erklæringen fra proceduren (hvis der er en i programmeringssproget). I C-sproget kan du for eksempel bygge en funktion med en indlejret løkke og organisere udgangen fra den ved hjælp af return-sætningen . Ulempen er, at udvælgelsen af ​​et kodefragment i en procedure ikke altid er logisk begrundet, og ikke alle sprog har regelmæssige midler til tidlig afslutning af procedurer.
  • Udnyt mekanismen til at generere og håndtere undtagelser (ekstraordinære situationer), som nu er tilgængelig på de fleste sprog på højt niveau . I dette tilfælde, i en unormal situation, rejser koden i den indlejrede løkke en undtagelse, og undtagelseshåndteringsblokken, hvori hele den indlejrede løkke er placeret, fanger og behandler den. Ulempen er, at implementeringen af ​​undtagelseshåndteringsmekanismen i de fleste tilfælde er sådan, at programmets hastighed reduceres. Sandt nok er dette ikke særlig vigtigt under moderne forhold: i praksis er ydeevnetabet så lille, at det kun betyder noget for meget få applikationer.
  • Endelig er der særlige sprogfaciliteter til at forlade indlejrede sløjfer. Så i Ada-sproget kan en programmør markere en løkke (det øverste niveau af en indlejret løkke) med en etiket og angive denne etiket i kommandoen til tidlig afslutning af løkken. Udgangen vil ikke ske fra den aktuelle sløjfe, men fra alle indlejrede sløjfer op til den markerede, inklusive [3] . PHP-sproget giver mulighed for at angive antallet af afbrudte cyklusser efter kommandoen break - dette break 2vil afbryde selve cyklussen og den over den, og break 1svarer til blot at skrive kommandoen break[4] .

Sløjfer med flere beskyttede grene

Dijkstras cyklus

I programmeringsteori er der en anden form for cyklisk konstruktion, der er fundamentalt forskellig fra de "klassiske", kaldet Dijkstra-cyklussen, efter Edsger Dijkstra , som først beskrev den. I den klassiske Dijkstra-beskrivelse ser sådan en cyklus sådan ud:

gør P 1 → S 1 , … P n → S n od

Her do er markøren for begyndelsen af ​​løkkekonstruktionen, od er markøren for slutningen af ​​løkkekonstruktionen, P i  er den i - te beskyttelsesbetingelse (et logisk udtryk, der kan være sandt eller falsk), S i  er i -th bevogtede kommando . En løkke består af en eller flere grene ( guarded expressions ), som hver er et par af en guarding condition (eller "vagter" for kort) og en bevogtet kommando (det er klart, at kommandoen i virkeligheden kan være kompleks).

Når Dijkstras loop udføres, beregnes beskyttelsesbetingelserne i hver iteration. Hvis mindst én af dem er sand, udføres den tilsvarende bevogtede kommando, hvorefter en ny iteration begynder (hvis mere end én vagtbetingelse er sand, udføres kun én bevogtet kommando). Hvis alle beskyttelsesbetingelser er falske, afsluttes løkken. Det er let at se, at Dijkstras løkke med én vagttilstand og én vagtkommando i virkeligheden er en almindelig løkke med en forudsætning (”mens”-løkken).

Selvom Dijkstra-løkken blev opfundet tilbage i 1970'erne, er der ingen specielle konstruktioner til at skabe den i programmeringssprog. Den eneste undtagelse var det nyligt oprettede Oberon-07  , det første rigtige programmeringssprog, der eksplicit understøtter en loop med flere beskyttede grene. Dog kan Dijkstras cyklus modelleres uden større besvær ved at bruge de traditionelle konstruktioner af strukturerede programmeringssprog. Her er et eksempel på dets implementering på en af ​​de mulige måder i Ada-sproget:

loop hvis P1 S1 ; ... elsif Pn derefter Sn ; andet exit ; ende hvis ; ende -løkke ;

Her er P1-Pn vagtbetingelserne og S1-Sn er de tilsvarende vagtkommandoer.

Dijkstras loop er nyttig til at implementere nogle specifikke gentagne beregninger, som er ubelejlige at beskrive med mere traditionelle looping-konstruktioner. For eksempel repræsenterer denne cyklus naturligt en endelig automat  - hver gren svarer til en tilstand af automaten, bevogtede forhold er bygget således, at i den aktuelle iteration vælges den gren, der svarer til den aktuelle tilstand af automaten, og koden for den bevogtede instruktion sikrer, at beregninger udføres i den aktuelle tilstand og overgang til den næste (det vil sige en sådan ændring i variabler, hvorefter vagttilstanden for den ønskede gren vil være sand ved næste iteration).

Spider Cycle

Det er let at se, at Dijkstras loop ikke indeholder en eksplicit fortsæt- eller exit-betingelse, som ikke anses for en velsignelse af alle programmeringsteoretikere. Derfor blev der foreslået en kompliceret konstruktion af Dijkstra-cyklussen, kaldet "edderkoppecyklus". I samme notation ser det sådan ud:

gør P1 → S1 , _ … P n →S n ud Q 1 →T 1 , … Q n →T n andet E od

Her efter markøren outtilføjes færdiggørelsesgrene , bestående af udgangsbetingelser Qi og færdiggørelseskommandoer Ti . Derudover er der tilføjet en alternativ færdiggørelsesgren elsemed E-kommandoen .

Spider-løkken udføres således:

  • Bevogtningsforholdene er beregnet. Hvis en ægte vagttilstand eksisterer, udføres den tilsvarende vagtkommando.
  • Udgangsforhold beregnes. Hvis der eksisterer en ægte exit-tilstand, udføres den tilsvarende termineringskommando, hvorefter udførelsen af ​​løkken afsluttes. Hvis alle udgangsbetingelser er falske, begynder den næste iteration, men kun hvis mindst en af ​​beskyttelsesbetingelserne var sand i den aktuelle iteration.
  • Hvis alle guard-betingelser og alle udgangsbetingelser i denne iteration er falske, udføres alt-ende-instruktionen E, hvorefter udførelsen af ​​løkken afbrydes.

Strukturen af ​​'edderkop'-cyklussen tillader ekstremt streng beskrivelse af betingelserne for udførelse af cyklussen. Ifølge teoretiske positioner bør grenen af ​​alternativ fuldførelse ikke bruges som en af ​​mulighederne for korrekt at afslutte løkken (alle sådanne muligheder skal formateres som tilsvarende færdiggørelsesgrene med en eksplicit betingelse), den tjener kun til at spore situationen, når, af en eller anden grund, af en eller anden grund begyndte cyklussen at køre unormalt. Det vil sige, at alt-kommandoen kun kan analysere årsagerne til fejlen og præsentere resultaterne af analysen.

Selvom eksplicit syntaks-niveau understøttelse af denne loop ikke eksisterer i noget programmeringssprog, kan spider loop, ligesom Dijkstras loop, modelleres ved hjælp af traditionelle strukturelle konstruktioner.

Loop optimering metoder

tilsvarende transformationer af kildekoden compiler

Se også

Noter

  1. Oberon er Niklaus Wirths drøm
  2. Strengt taget er identiteten ikke fuldstændig, da fortsæt-sætningen vil fungere anderledes.
  3. Loops/Indlejret ved Rosetta  Code
  4. ↑ PHP Manual , pause 

Links