Podprogram - Subroutine

V počítačovém programování je podprogram sledem programových instrukcí, které provádějí konkrétní úkol, zabalené jako jednotka. Tuto jednotku lze poté použít v programech, kdekoli by měl být konkrétní úkol proveden.

Podprogramy mohou být definovány v programech nebo samostatně v knihovnách, které může používat mnoho programů. V různých programovacích jazycích může být podprogram nazýván rutina , podprogram , funkce , metoda nebo procedura . Technicky mají všechny tyto termíny různé definice. Někdy se používá obecný zastřešující výraz volatelná jednotka .

Název podprogram naznačuje, že podprogram se chová podobně jako počítačový program, který se používá jako jeden krok ve větším programu nebo jiném podprogramu. Podprogram je často kódován tak, aby jej bylo možné spustit několikrát a z několika míst během jednoho spuštění programu, a to i z jiných podprogramů, a poté se po volání rozvětvit ( vrátit ) na další instrukci , jakmile je úkol podprogramu hotov . Myšlenku podprogramu původně vytvořil John Mauchly během své práce na ENIAC a zaznamenal ji na harvardském sympoziu z ledna 1947 na téma „Příprava problémů pro stroje typu EDVAC“. Maurice Wilkesovi , Davidu Wheelerovi a Stanleymu Gillovi se obecně připisuje formální vynález tohoto konceptu, který nazvali uzavřený podprogram , který je v kontrastu s otevřeným podprogramem nebo makrem . Nicméně, Turing byl projednán podprogramy v dokumentu z roku 1945 o designových návrhů na NPL ACE , jdou tak daleko, že vymyslet koncept zásobníku návratových adres.

Podprogramy jsou účinný programovací nástroj a syntaxe mnoha programovacích jazyků zahrnuje podporu pro jejich psaní a používání. Rozumné používání podprogramů (například prostřednictvím přístupu strukturovaného programování ) často podstatně sníží náklady na vývoj a údržbu rozsáhlého programu a současně zvýší jeho kvalitu a spolehlivost. Podprogramy, často shromažďované do knihoven , jsou důležitým mechanismem pro sdílení a obchodování se softwarem. Disciplína objektově orientovaného programování je založena na objektech a metodách (což jsou podprogramy připojené k těmto objektům nebo třídám objektů ).

V kompilační metodě zvané závitový kód je spustitelný program v podstatě posloupnost volání podprogramů.

Hlavní pojmy

Obsahem podprogramu je jeho tělo, což je část programového kódu, která je spuštěna při vyvolání nebo vyvolání podprogramu.

Podprogram lze zapsat tak, aby očekával získání jedné nebo více hodnot dat z volajícího programu (aby nahradil jeho parametry nebo formální parametry). Volající program poskytuje pro tyto parametry skutečné hodnoty, nazývané argumenty . Různé programovací jazyky mohou pro předávání argumentů používat různé konvence:

Konvence Popis Běžné použití
Volejte podle hodnoty Argument je vyhodnocen a kopie hodnoty je předána podprogramu Výchozí ve většině jazyků podobných Algolu po Algol 60 , jako jsou Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada a mnoho dalších. C, C ++, Java (odkazy na objekty a pole jsou také předávány podle hodnoty)
Volejte podle doporučení Odkaz na argument, obvykle je předána jeho adresa Volitelné ve většině jazyků podobných Algolu po Algol 60 , jako jsou Algol 68, Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada a mnoho dalších. C ++, Fortran, PL/I
Volejte podle výsledku Hodnota parametru se zkopíruje zpět do argumentu při návratu z podprogramu Parametry Ada OUT
Volejte podle hodnoty-výsledku Hodnota parametru se zkopíruje zpět při vstupu do podprogramu a znovu při návratu Algol, parametry Swift in-out
Volejte jménem Jako makro - nahraďte parametry nevyhodnocenými výrazy argumentů Algol, Scala
Volejte podle konstantní hodnoty Stejně jako volání podle hodnoty, kromě toho, že s parametrem je zacházeno jako s konstantou Parametry PL/I NONASSIGNABLE, parametry Ada IN

Podprogram může vrátit vypočítanou hodnotu svému volajícímu (jeho návratová hodnota ) nebo poskytnout různé výsledné hodnoty nebo výstupní parametry. Skutečně, běžné použití podprogramů je implementace matematických funkcí , ve kterých je účelem podprogramu čistě vypočítat jeden nebo více výsledků, jejichž hodnoty jsou zcela určeny argumenty předanými podprogramu. (Jako příklad lze uvést výpočet logaritmus čísla nebo determinant části matrice ). Tento typ se nazývá funkce.

Volání podprogramu může mít také vedlejší účinky, jako je úprava datových struktur v paměti počítače , čtení nebo zápis do periferního zařízení , vytvoření souboru , zastavení programu nebo stroje nebo dokonce zpoždění provádění programu na zadanou dobu. Podprogram s vedlejšími efekty může při každém volání vrátit jiné výsledky, i když je volán se stejnými argumenty. Příkladem je podprogram náhodných čísel dostupný v mnoha jazycích, který při každém volání vrací jiné pseudonáhodné číslo. Široké používání podprogramů s vedlejšími efekty je charakteristické pro imperativní programovací jazyky.

Podprogram může být kódován tak, že se může rekurzivně volat na jednom nebo více místech k provedení svého úkolu. Tato metoda umožňuje přímou implementaci funkcí definovaných matematickou indukcí a rekurzivními algoritmy rozděl a panuj .

Podprogram, jehož účelem je vypočítat jednu booleovskou funkci (tj. Odpovědět na otázku ano/ne), se někdy nazývá predikát. V logických programovacích jazycích se často všechny podprogramy nazývají predikáty, protože primárně určují úspěch nebo neúspěch.

Podprogram, který nevrací žádnou hodnotu nebo vrací hodnotu null, se někdy nazývá procedura. Procedury obvykle mění své argumenty a jsou základní součástí procedurálního programování .

Jazyková podpora

Programovací jazyky na vysoké úrovni obvykle obsahují konkrétní konstrukce pro:

  • Vymezte část programu (tělo), která tvoří podprogram
  • Podprogramu přiřaďte identifikátor (název)
  • Zadejte názvy a datové typy jejích parametrů a návratových hodnot
  • Poskytněte pro své dočasné proměnné rozsah soukromého pojmenování
  • Identifikujte proměnné mimo podprogram, které jsou v něm přístupné
  • Zavolejte podprogram
  • Poskytněte hodnoty jeho parametrům
  • Hlavní program obsahuje adresu podprogramu
  • Podprogram obsahuje adresu další instrukce volání funkce v hlavním programu
  • Zadejte návratové hodnoty z jeho těla
  • Vraťte se do volacího programu
  • Zlikvidujte hodnoty vrácené voláním
  • Zvládněte všechny výjimečné podmínky, se kterými se během hovoru setkáte
  • Zabalte podprogramy do modulu , knihovny , objektu nebo třídy

Některé programovací jazyky , jako Pascal , Fortran , Ada a mnoho dialektů v BASICu , rozlišovat mezi funkce nebo funkčního podprogramů, které poskytují explicitní návratovou hodnotu k volající program a podprogramy nebo postupů, které nemají. V těchto jazycích jsou volání funkcí obvykle vložena do výrazů (např. sqrtFunkci lze nazvat jako y = z + sqrt(x)). Volání procedur se buď chovají syntakticky jako příkazy (např. printProceduru lze volat jako if x > 0 then print(x)nebo ji lze explicitně vyvolat příkazem jako CALLnebo GOSUB(např. call print(x)). Jiné jazyky, například C a Lisp , nerozlišují mezi funkcemi a podprogramy.

V přísně funkčních programovacích jazycích, jako je Haskell , nemohou mít podprogramy žádné vedlejší efekty , což znamená, že se různé vnitřní stavy programu nezmění. Funkce při opakovaném volání se stejnými argumenty vždy vrátí stejný výsledek. Takové jazyky obvykle podporují pouze funkce, protože podprogramy, které nevracejí hodnotu, nemají využití, pokud mohou způsobit vedlejší účinek.

V programovacích jazycích, jako je C , C ++ a C# , podprogramy lze také jednoduše nazývat funkce (nezaměňovat s matematickými funkcemi nebo funkčním programováním , což jsou různé pojmy).

Kompilátor jazyka obvykle překládá volání procedur a vrací se do strojových instrukcí podle přesně definované konvence volání , takže podprogramy lze kompilovat odděleně od programů, které je volají. Sekvence instrukcí odpovídající příkazům volání a návratu se nazývají prolog a epilog procedury .

Výhody

Mezi výhody rozdělení programu na podprogramy patří:

  • Dekompozice složitého programovacího úkolu do jednodušších kroků: toto je jeden ze dvou hlavních nástrojů strukturovaného programování spolu s datovými strukturami
  • Snížení duplicitního kódu v programu
  • Povolení opětovného použití kódu ve více programech
  • Rozdělení velkého programovacího úkolu mezi různé programátory nebo různé fáze projektu
  • Skrytí podrobností implementace před uživateli podprogramu
  • Zlepšení čitelnosti kódu nahrazením bloku kódu voláním funkce, kde popisný název funkce slouží k popisu bloku kódu. Díky tomu je volací kód stručný a čitelný, i když funkce není určena k opětovnému použití.
  • Zlepšení sledovatelnosti (tj. Většina jazyků nabízí způsoby, jak získat trasování hovorů, které obsahuje názvy příslušných podprogramů a možná ještě více informací, jako jsou názvy souborů a čísla řádků); nerozložením kódu na podprogramy by ladění bylo vážně narušeno

Nevýhody

Ve srovnání s použitím in-line kódu vyvolání podprogramu v mechanismu volání přináší určitou výpočetní režii .

Podprogram obvykle vyžaduje standardní úklidový kód - jak na vstupu, tak na výstupu z funkce ( funkce prolog a epilog - obvykle ukládání minimálních registrů pro obecné účely a zpáteční adresy jako minimum).

Dějiny

Myšlenka podprogramu byla zpracována poté, co již nějakou dobu existovaly výpočetní stroje. Aritmetické a podmíněné instrukce skoku byly naplánovány s předstihem a změnily se relativně málo, ale speciální instrukce používané pro volání procedur se za ta léta značně změnily. Nejranější počítače a mikroprocesory, jako Manchester Baby a RCA 1802 , neměly jedinou instrukci volání podprogramu. Mohly být implementovány podprogramy, ale vyžadovaly od programátorů, aby na každém místě volání použili pořadí volání - řadu instrukcí .

Podprogramy byly realizovány v Konrad Zuse je Z4 v roce 1945.

V roce 1945 použil Alan M. Turing výrazy „pohřbít“ a „unbury“ jako prostředek volání a návratu z podprogramů.

V lednu 1947 John Mauchly představil obecné poznámky na „Symposiu velkých digitálních počítacích strojů“ pod společným sponzorstvím Harvard University a Bureau of Ordnance, United States Navy. Zde diskutuje o navrhování sériových a paralelních operací

... struktura stroje nemusí být ani trochu komplikovaná. Jelikož jsou k dispozici všechny logické vlastnosti podstatné pro tento postup, je možné vyvinout kódovací instrukci pro umístění podprogramů do paměti na místa známá stroji, a to takovým způsobem, že je lze snadno zavolat k použití.

Jinými slovy, podprogram A lze označit jako dělení a podprogram B jako komplexní násobení a podprogram C jako vyhodnocení standardní chyby posloupnosti čísel atd. Prostřednictvím seznamu podprogramů potřebných pro konkrétní problém. ... Všechny tyto podprogramy budou poté uloženy ve stroji a vše, co musíte udělat, je stručně na ně odkazovat podle čísla, jak jsou uvedeny v kódování.

Kay McNulty úzce spolupracovala s Johnem Mauchlym na týmu ENIAC a vyvinula nápad na podprogramy pro počítač ENIAC, který programovala během druhé světové války. Ona a další programátoři ENIAC použili podprogramy k výpočtu trajektorií raket.

Goldstine a von Neumann napsali referát ze 16. srpna 1948 o použití podprogramů.

Některé velmi rané počítače a mikroprocesory, jako například IBM 1620 , Intel 4004 a Intel 8008 a mikroprocesory PIC , mají volání podprogramu s jednou instrukcí, které používá vyhrazený zásobník hardwaru k ukládání návratových adres-takový hardware podporuje pouze několik úrovní vnoření podprogramů, ale může podporovat rekurzivní podprogramy. Stroje před polovinou 60. let-například UNIVAC I , PDP-1 a IBM 1130- obvykle používají konvenci volání, která ukládala čítač instrukcí do prvního paměťového umístění volaného podprogramu. To umožňuje libovolně hluboké úrovně vnoření podprogramů, ale nepodporuje rekurzivní podprogramy. PDP-11 (1970) je jedním z prvních počítačů s instrukcí volání podprogramu do komína, tlačení; tato funkce podporuje jak libovolně hluboké vnoření podprogramů, tak také podporuje rekurzivní podprogramy.

Jazyková podpora

Ve velmi raných assemblerech byla podprogramová podpora omezená. Podprogramy nebyly výslovně odděleny jeden od druhého ani od hlavního programu a zdrojový kód podprogramu mohl být proložen s kódem jiných podprogramů. Někteří assembleri by nabízeli předdefinovaná makra ke generování sekvencí volání a návratu. V 60. letech 20. století měli montéři obvykle mnohem sofistikovanější podporu pro podprogramy vložené i samostatně sestavené, které bylo možné spojit dohromady.

Jedním z prvních programovacích jazyků podporujících podprogramy a funkce psané uživateli byl FORTRAN II . Kompilátor IBM FORTRAN II byl vydán v roce 1958. Procedurální programování podporoval také ALGOL 58 a další rané programovací jazyky.

Knihovny podprogramů

I při tomto těžkopádném přístupu se podprogramy velmi osvědčily. Jednak umožnili použití stejného kódu v mnoha různých programech. Paměť byla navíc v raných počítačích velmi vzácným zdrojem a podprogramy umožňovaly značné úspory ve velikosti programů.

Mnoho raných počítačů zavedlo pokyny programu do paměti z děrné papírové pásky . Každý podprogram by pak mohl být zajištěn samostatným kusem pásky, načteným nebo spojeným před nebo za hlavním programem (nebo „hlavní řadou“); a stejnou podprogramovou pásku pak mohlo použít mnoho různých programů. Podobný přístup byl použit v počítačích, které pro svůj hlavní vstup používaly děrné karty . Název knihovna podprogramů původně znamenala knihovnu, v doslovném smyslu, která uchovávala indexované sbírky kazet nebo balíčků karet pro společné použití.

Návrat nepřímým skokem

Aby se odstranila potřeba samočinného modifikace kódu , návrháři počítačů nakonec poskytli instrukci nepřímého skoku , jejíž operand místo samotné návratové adresy bylo umístění proměnné nebo registru procesoru obsahujícího zpáteční adresu.

Na těchto počítačích by místo úpravy návratového skoku podprogramu volající program ukládal zpáteční adresu do proměnné, takže po dokončení podprogramu by provedl nepřímý skok, který by nasměroval provedení na místo dané předdefinovanou proměnnou.

Přejít na podprogram

Dalším pokrokem byla instrukce skok na podprogram , která kombinovala uložení zpáteční adresy s volajícím skokem, čímž se výrazně minimalizovala režie .

Například v systému IBM System/360 by instrukce větve BAL nebo BALR, určené pro volání procedur, uložily zpáteční adresu do registru procesoru uvedeného v instrukci pomocí konvenčního registru 14. Pro návrat musel podprogram pouze provést nepřímá větvová instrukce (BR) prostřednictvím tohoto registru. Pokud podprogram potřeboval tento registr k nějakému jinému účelu (například k volání jiného podprogramu), uložil by obsah registru do umístění v soukromé paměti nebo do zásobníku registrů .

V systémech, jako je HP 2100 , by instrukce JSB prováděla podobný úkol, kromě toho, že zpáteční adresa byla uložena v paměťovém místě, které bylo cílem větve. Provedení postupu by ve skutečnosti začalo na dalším paměťovém místě. V sestavovacím jazyce HP 2100 by člověk například psal

       ...
       JSB MYSUB    (Calls subroutine MYSUB.)
 BB    ...          (Will return here after MYSUB is done.)

pro vyvolání podprogramu s názvem MYSUB z hlavního programu. Podprogram by byl kódován jako

 MYSUB NOP          (Storage for MYSUB's return address.)
 AA    ...          (Start of MYSUB's body.)
       ...
       JMP MYSUB,I  (Returns to the calling program.)

Instrukce JSB umístila adresu instrukce NEXT (jmenovitě BB) na místo určené jako její operand (konkrétně MYSUB), a poté se rozvětvila na místo NEXT (konkrétně AA = MYSUB + 1). Podprogram se pak mohl vrátit do hlavního programu provedením nepřímého skoku JMP MYSUB, I, který se rozvětvil na místo uložené v umístění MYSUB.

Překladače pro Fortran a další jazyky by mohly tyto pokyny snadno využít, jsou -li k dispozici. Tento přístup podporoval více úrovní hovorů; vzhledem k tomu, že návratové adrese, parametrům a návratovým hodnotám podprogramu byla přiřazena pevná paměťová místa, neumožňovalo to rekurzivní volání.

Podobnou metodu mimochodem použil Lotus 1-2-3 na začátku 80. let k odhalení závislostí na přepočtu v tabulce. Totiž v každé buňce bylo vyhrazeno místo pro uložení zpáteční adresy. Vzhledem k tomu, že kruhové odkazy nejsou povoleny pro přirozené pořadí přepočtu, umožňuje to procházku stromem bez rezervace místa pro zásobník v paměti, což bylo u malých počítačů, jako je například IBM PC , velmi omezené .

Zásobník hovorů

Většina moderních implementací volání podprogramu používá k implementaci volání a návratů podprogramů zásobník volání , speciální případ datové struktury zásobníku . Každé volání procedury vytvoří v horní části zásobníku novou položku, nazývanou rámeček zásobníku; když se procedura vrátí, její rámeček zásobníku je ze zásobníku odstraněn a jeho prostor může být použit pro jiná volání procedur. Každý rámec zásobníku obsahuje soukromá data odpovídajícího volání, které obvykle obsahuje parametry procedury a interní proměnné a zpáteční adresu.

Sekvenci volání lze implementovat sekvencí běžných instrukcí (přístup, který se stále používá v architekturách redukované sady instrukčních sad (RISC) a velmi dlouhých instrukčních slov (VLIW)), ale mnoho tradičních strojů navržených od konce 60. let obsahuje speciální instrukce pro ten účel.

Zásobník volání je obvykle implementován jako souvislá oblast paměti. Je libovolnou volbou návrhu, zda spodní část zásobníku je nejnižší nebo nejvyšší adresa v této oblasti, takže se zásobník může v paměti zvětšovat dopředu nebo dozadu; mnoho architektur však zvolilo to druhé.

Některé návrhy, zejména některé implementace Forth , používaly dva oddělené zásobníky, jeden hlavně pro řídicí informace (jako návratové adresy a čítače smyček) a druhý pro data. První z nich byl nebo fungoval jako zásobník volání a byl programátoru přístupný pouze nepřímo prostřednictvím jiných jazykových konstruktů, zatímco druhý byl přístupnější přímo.

Když byla poprvé zavedena volání procedur založená na zásobníku, důležitou motivací bylo ušetřit drahocennou paměť. S tímto schématem kompilátor nemusí rezervovat oddělený prostor v paměti pro soukromá data (parametry, zpáteční adresa a lokální proměnné) každé procedury. V každém okamžiku zásobník obsahuje pouze soukromá data o hovorech, která jsou aktuálně aktivní (tj. Která byla volána, ale ještě se nevrátila). Vzhledem ke způsobům, jakým byly programy obvykle sestavovány z knihoven, nebylo (a stále je) neobvyklé najít programy, které obsahují tisíce podprogramů, z nichž je v daném okamžiku aktivní jen hrstka. U takových programů by mechanismus zásobníku volání mohl ušetřit značné množství paměti. Mechanismus zásobníku volání lze skutečně považovat za nejranější a nejjednodušší metodu automatické správy paměti .

Další výhodou metody zásobníku volání je však to, že umožňuje rekurzivní volání podprogramů , protože každé vnořené volání stejné procedury získá samostatnou instanci svých soukromých dat.

Zpožděné stohování

Jednou nevýhodou mechanismu zásobníku volání jsou zvýšené náklady na volání procedury a její odpovídající návrat. Dodatečné náklady zahrnují zvýšení a snížení ukazatele zásobníku (a v některých architekturách kontrolu přetečení zásobníku ) a přístup k lokálním proměnným a parametrům adresami relativními rámce namísto absolutních adres. Náklady mohou být realizovány ve zvýšené době provádění nebo ve zvýšené složitosti procesoru nebo v obou případech.

Tato režie je nejzjevnější a nejproblematičtější v procedurách listů nebo listových funkcích , které se vracejí, aniž by si samy volaly procedury. Aby se snížila tato režie, mnoho moderních kompilátorů se pokouší odložit použití zásobníku volání, dokud není opravdu potřeba. Například volání procedury P může uložit návratovou adresu a parametry volané procedury do určitých registrů procesoru a přenášet řízení do těla procedury jednoduchým skokem. Pokud se procedura P vrátí bez dalšího volání, zásobník volání se vůbec nepoužije. Pokud P potřebuje zavolat další proceduru Q , pak použije zásobník volání k uložení obsahu všech registrů (jako je zpáteční adresa), které budou potřebné po návratu Q.

Příklady C a C ++

V programovacích jazycích C a C ++ jsou podprogramy označovány jako funkce (dále klasifikované jako členské funkce, pokud jsou přidruženy ke třídě , nebo volné funkce, pokud nejsou). Tyto jazyky používají speciální klíčové slovo voidk označení, že funkce nevrací žádnou hodnotu. Všimněte si, že funkce C/C ++ mohou mít vedlejší efekty, včetně úpravy jakýchkoli proměnných, jejichž adresy jsou předávány jako parametry. Příklady:

void Function1() { /* some code */ }

Funkce nevrací hodnotu a musí být volána jako samostatná funkce, např. Function1();

int Function2() {
  return 5;
}

Tato funkce vrací výsledek (číslo 5) a volání může být součástí výrazu, např. x + Function2()

char Function3(int number) {
  char selection[] = {'S', 'M', 'T', 'W', 'T', 'F', 'S'};
  return selection[number];
}

Tato funkce převádí číslo mezi 0 a 6 na počáteční písmeno příslušného dne v týdnu, konkrétně 0 na 'S', 1 na 'M', ..., 6 na 'S'. Výsledek jeho volání může být přiřazen proměnné, např num_day = Function3(number);.

void Function4(int* pointer_to_var) {
  (*pointer_to_var)++;
}

Tato funkce nevrací hodnotu, ale upravuje proměnnou, jejíž adresa je předána jako parametr; tomu by se říkalo s Function4(&variable_to_increment);.

Malý základní příklad

Example()                               ' Calls the subroutine

Sub Example                             ' Begins the subroutine
    TextWindow.WriteLine("This is an example of a subroutine in Microsoft Small Basic.")  ' What the subroutine does
EndSub                                  ' Ends the subroutine

Ve výše uvedeném příkladu Example()volá podprogram. K definování skutečného podprogramu Submusí být použito klíčové slovo s následným názvem podprogramu Sub. Poté, co obsah následuje, EndSubmusí být zadán.

Příklady jazyka Visual Basic 6

V Visual Basic 6 jazyka, podprogramy jsou označovány jako funkce nebo subs (nebo metody při spojené s třídou). Visual Basic 6 používá různé termíny nazývané typy k definování toho, co se předává jako parametr. Ve výchozím nastavení je nespecifikovaná proměnná zaregistrována jako typ varianty a lze ji předat jako ByRef (výchozí) nebo ByVal . Když je funkce nebo podřízená deklarována, je jí přiděleno veřejné, soukromé nebo přátelské označení, které určuje, zda k ní lze přistupovat mimo modul nebo projekt, ve kterém byla deklarována.

  • Podle hodnoty [ByVal] - způsob předání hodnoty argumentu proceduře předáním kopie hodnoty namísto předání adresy. Výsledkem je, že skutečnou hodnotu proměnné nelze změnit postupem, kterému je předána.
  • Podle odkazu [ByRef] - způsob předání hodnoty argumentu proceduře předáním adresy proměnné místo předání kopie její hodnoty. To umožňuje proceduře přístup ke skutečné proměnné. V důsledku toho lze skutečnou hodnotu proměnné změnit postupem, kterému je předána. Pokud není uvedeno jinak, jsou argumenty předávány odkazem.
  • Veřejné (volitelné) - označuje, že procedura funkce je přístupná všem ostatním procedurám ve všech modulech. Pokud se používá v modulu, který obsahuje možnost Private, není procedura dostupná mimo projekt.
  • Soukromé (volitelné) - označuje, že procedura funkce je přístupná pouze jiným procedurám v modulu, kde je deklarována.
  • Friend (volitelně) - používá se pouze v modulu třídy. Označuje, že procedura Function je viditelná v celém projektu, ale není viditelná pro řadič instance objektu.
Private Function Function1()
    ' Some Code Here
End Function

Funkce nevrací hodnotu a musí být volána jako samostatná funkce, např. Function1

Private Function Function2() as Integer
    Function2 = 5
End Function

Tato funkce vrací výsledek (číslo 5) a volání může být součástí výrazu, např. x + Function2()

Private Function Function3(ByVal intValue as Integer) as String
    Dim strArray(6) as String
    strArray = Array("M", "T", "W", "T", "F", "S", "S")
    Function3 = strArray(intValue)
End Function

Tato funkce převádí číslo mezi 0 a 6 na počáteční písmeno příslušného dne v týdnu, konkrétně 0 na 'M', 1 na 'T', ..., 6 na 'S'. Výsledek jeho volání může být přiřazen proměnné, např num_day = Function3(number).

Private Function Function4(ByRef intValue as Integer)
    intValue = intValue + 1
End Function

Tato funkce nevrací hodnotu, ale upravuje proměnnou, jejíž adresa je předána jako parametr; říkalo by se to s „ Function4(variable_to_increment)“.

Příklad PL/I

V PL/I může být volanému postupu předán deskriptor poskytující informace o argumentu, jako jsou délky řetězců a hranice pole. To umožňuje, aby byl postup obecnější, a eliminuje potřebu programátora předávat takové informace. Standardně PL/I předává argumenty podle odkazu. (Triviální) podprogram pro změnu znaménka každého prvku dvojrozměrného pole může vypadat takto:

  change_sign: procedure(array);
    declare array(*,*) float;
    array = -array;
    end change_sign;

To lze nazvat pomocí různých polí následujícím způsobem:

  /* first array bounds from -5 to +10 and 3 to 9 */
  declare array1 (-5:10, 3:9)float;
  /* second array bounds from 1 to 16 and 1 to 16 */
  declare array2 (16,16) float;
  call change_sign(array1);
  call change_sign(array2);

Příklad Pythonu

V Pythonu se klíčové slovo defpoužívá k definování funkce. Příkazy, které tvoří tělo funkce, musí buď pokračovat na stejném řádku, nebo začínat na dalším řádku a být odsazeny. Následující ukázkový program vytiskne „Hello world!“ následovaný „Wikipedií“ na dalším řádku.

def simple_function():
    print('Hello world!')
    print('Wikipedia')
simple_function()

Lokální proměnné, rekurze a reentrancy

Podprogram může být užitečné využít určité množství místa na škrábnutí ; to znamená paměť používaná během provádění tohoto podprogramu k uchovávání mezivýsledků. Proměnné uložené v tomto stíracím prostoru se nazývají lokální proměnné a stírací prostor se nazývá aktivační záznam . Aktivační záznam má obvykle zpáteční adresu, která mu říká, kam má předat řízení zpět, když podprogram skončí.

Podprogram může mít libovolný počet a povahu míst volání. Pokud je podporována rekurze, může se podprogram dokonce sám volat, což způsobí pozastavení jeho provádění, zatímco dojde k dalšímu vnořenému spuštění stejného podprogramu. Rekurze je užitečný prostředek ke zjednodušení některých složitých algoritmů a k odbourání složitých problémů. Rekurzivní jazyky obecně poskytují novou kopii místních proměnných při každém volání. Pokud si programátor přeje, aby hodnota místních proměnných zůstala mezi hovory stejná, mohou být v některých jazycích deklarovány jako statické nebo lze použít globální hodnoty nebo společné oblasti. Zde je příklad rekurzivního podprogramu v C/C ++ k nalezení Fibonacciho čísel:

int Fib(int n) {
  if (n <= 1) {
    return n;
  }
  return Fib(n - 1) + Fib(n - 2);
}

Rané jazyky jako Fortran zpočátku nepodporovaly rekurzi, protože proměnné byly staticky přidělovány, stejně jako umístění pro zpáteční adresu. Většina počítačů před koncem šedesátých let, například PDP-8, neměla podporu pro registry zásobníků hardwaru.

Moderní jazyky po ALGOLu , jako PL/I a C, téměř vždy používají zásobník, obvykle podporovaný většinou moderních počítačových instrukčních sad, aby poskytly nový aktivační záznam pro každé provedení podprogramu. Tímto způsobem může vnořené provádění libovolně upravovat své lokální proměnné bez obav z vlivu na další probíhající pozastavená spuštění. Jak se vnořené hovory hromadí, vytvoří se struktura zásobníku volání , která se skládá z jednoho aktivačního záznamu pro každý pozastavený podprogram. Ve skutečnosti je tato struktura zásobníku prakticky všudypřítomná, a proto se aktivační záznamy běžně nazývají rámce zásobníků .

Některé jazyky, jako je Pascal , PL/I a Ada, také podporují vnořené podprogramy , což jsou podprogramy, které lze volat pouze v rámci vnějšího (nadřazeného) podprogramu. Vnitřní podprogramy mají přístup k lokálním proměnným vnějšího podprogramu, který je volal. Toho je dosaženo uložením dalších kontextových informací do aktivačního záznamu, nazývaného také zobrazení .

Pokud lze podprogram správně provést, i když již probíhá další provádění stejného podprogramu, říká se, že tento podprogram je reentrantní . Rekurzivní podprogram musí být reentrantní. Reentrantní podprogramy jsou také užitečné ve vícevláknových situacích, protože více vláken může volat stejné podprogram bez strachu z které se navzájem neovlivňují. V systému zpracování transakcí IBM CICS byl kvazi-reentrant o něco méně restriktivní, ale podobný požadavek na aplikační programy, které byly sdíleny mnoha vlákny.

V prostředí s více vlákny je obecně více než jeden zásobník. Prostředí, které plně podporuje korutiny nebo opožděné hodnocení, může k ukládání záznamů o aktivaci používat jiné datové struktury než hromádky.

Přetížení

V silně typovaných jazycích je někdy žádoucí mít řadu funkcí se stejným názvem, ale pracujících na různých typech dat nebo s různými profily parametrů. Například může být definována funkce odmocniny pro provoz na reálných hodnotách, komplexních hodnotách nebo maticích. Algoritmus, který se má použít v každém případě, je jiný a výsledek vrácení se může lišit. Napsáním tří samostatných funkcí se stejným názvem má programátor pohodlí, že si nemusí pamatovat různá jména pro každý typ dat. Dále, pokud lze pro reals definovat podtyp, k oddělení pozitivních a negativních realů lze pro reals zapsat dvě funkce, jednu pro vrácení reálné hodnoty, když je parametr kladný, a druhou pro vrácení komplexní hodnoty, když je parametr záporný.

V objektově orientovaném programování , když řada funkcí se stejným názvem může přijímat různé profily parametrů nebo parametry různých typů, se říká, že každá z funkcí je přetížená .

Zde je příklad přetížení podprogramu v C ++ :

#include <iostream>

double Area(double h, double w) { return h * w; }

double Area(double r) { return r * r * 3.14; }

int main() {
  double rectangle_area = Area(3, 4);
  double circle_area = Area(5);

  std::cout << "Area of a rectangle is " << rectangle_area << std::endl;
  std::cout << "Area of a circle is " << circle_area << std::endl;
}

V tomto kódu existují dvě funkce se stejným názvem, ale mají různé parametry.

Jako další příklad může podprogram zkonstruovat objekt, který bude přijímat směry, a sledovat jeho cestu k těmto bodům na obrazovce. Existuje nepřeberné množství parametrů, které by mohly být předány konstruktoru (barva stopy, počáteční souřadnice x a y, rychlost trasování). Pokud by programátor chtěl, aby byl konstruktor schopen přijmout pouze parametr barvy, mohl by zavolat jiného konstruktora, který přijímá pouze barvu, což zase volá konstruktor se všemi parametry procházejícími v sadě výchozích hodnot pro všechny ostatní parametry ( X a Y by byly obecně soustředěny na obrazovku nebo umístěny na počátku a rychlost by byla nastavena na jinou hodnotu, kterou si kodér zvolí).

PL/I má GENERICatribut definující obecný název pro sadu vstupních odkazů volaných s různými typy argumentů. Příklad:

  DECLARE gen_name GENERIC(
                      name  WHEN(FIXED BINARY),
                      flame  WHEN(FLOAT),
                      pathname OTHERWISE 
                           );

Pro každou položku lze zadat více definic argumentů. Volání „gen_name“ bude mít za následek volání „name“, pokud je argument FIXED BINARY, „plamen“ při FLOAT “atd. Pokud argument odpovídá žádné z voleb, bude zavoláno„ název cesty “.

Uzávěry

Uzávěr je podprogram spolu s hodnotami některých jeho proměnných zachycených z prostředí, ve kterém byl vytvořen. Uzávěry byly pozoruhodnou vlastností programovacího jazyka Lisp, který představil John McCarthy . V závislosti na implementaci mohou uzávěry sloužit jako mechanismus vedlejších účinků.

Konvence

Byla vyvinuta celá řada konvencí pro kódování podprogramů. Pokud jde o jejich pojmenování, mnoho vývojářů přijalo přístup, že název podprogramu by měl být sloveso, když plní určitý úkol, a přídavné jméno, když dělá nějaký dotaz, a podstatné jméno, když se používá k nahrazení proměnných.

Někteří programátoři naznačují, že podprogram by měl provádět pouze jeden úkol, a pokud podprogram provádí více než jeden úkol, měl by být rozdělen do více podprogramů. Tvrdí, že podprogramy jsou klíčovými součástmi údržby kódu a jejich role v programu musí zůstat odlišná.

Zastánci modulárního programování (modularizační kód) prosazují, aby každý podprogram měl minimální závislost na jiných částech kódu. Například použití globálních proměnných obecně zastánci této perspektivy považují za nerozumné, protože přidává těsné propojení mezi podprogramem a těmito globálními proměnnými. Pokud takové propojení není nutné, doporučuje se, aby podprogramy refaktorů místo toho přijaly předané parametry . Zvýšení počtu parametrů předaných podprogramům však může ovlivnit čitelnost kódu.

Návratové kódy

Kromě svého hlavního nebo normálního účinku může podprogram potřebovat informovat volající program o výjimečných podmínkách, které mohly nastat během jeho provádění. V některých jazycích a standardech programování se to často provádí prostřednictvím návratového kódu , celočíselné hodnoty umístěné podprogramem v nějakém standardním umístění, které kóduje normální a výjimečné podmínky.

V systému IBM System/360 , kde byl z podprogramu očekáván návratový kód, byla návratová hodnota často navržena jako násobek 4 - aby jej bylo možné použít jako přímý index tabulky větví do tabulky větví často umístěné bezprostředně po zavolejte instrukci, abyste se vyhnuli dalším podmíněným testům, což dále zvyšuje účinnost. V System / 360 assembleru , by se dalo psát, například:

           BAL  14, SUBRTN01    go to a subroutine, storing return address in R14
           B    TABLE(15)      use returned value in reg 15 to index the branch table, 
*                              branching to the appropriate branch instr.
TABLE      B    OK             return code =00   GOOD                  }
           B    BAD            return code =04   Invalid input         } Branch table
           B    ERROR          return code =08   Unexpected condition  }

Optimalizace volání podprogramů

Při volání podprogramu existuje značná režie za běhu , včetně předávání argumentů, větvení do podprogramu a větvení zpět volajícímu. Režie často zahrnuje ukládání a obnovu určitých registrů procesorů, přidělení a opětovné získání úložiště rámců volání atd. V některých jazycích každé volání podprogramu také znamená automatické testování návratového kódu podprogramu nebo zpracování výjimek , které může vyvolat. Významným zdrojem režie v objektově orientovaných jazycích je intenzivně používané dynamické odesílání pro volání metod.

Existují některé zdánlivě zjevné optimalizace volání procedur, které nelze použít, pokud mohou mít postupy vedlejší účinky. Například ve výrazu musí být (f(x)-1)/(f(x)+1)funkce fvolána dvakrát, protože dvě volání mohou vrátit různé výsledky. Navíc hodnota xmusí být načtena znovu před druhým hovorem, protože první hovor jej mohl změnit. Určení, zda podprogram může mít vedlejší účinek, je velmi obtížné (skutečně nerozhodnutelné na základě Riceovy věty ). Takže zatímco tyto optimalizace jsou v čistě funkčních programovacích jazycích bezpečné, kompilátory typického imperativního programování obvykle musí předpokládat to nejhorší.

Inlining

Metoda použitá k odstranění této režie je vložená expanze nebo vložení těla podprogramu v každém místě volání (oproti rozvětvení na podprogram a zpět). Nejenže se tím vyhnul volání nad hlavou, ale také umožňuje kompilátor pro optimalizaci postupu je tělo efektivněji tím, že vezme v úvahu kontext a argumenty v tomto volání. Vložené tělo může být optimalizováno kompilátorem. Inlining však obvykle zvětší velikost kódu, pokud program neobsahuje pouze jedno volání podprogramu.

Trope v populárních médiích

Pojem „podprogram“ byl od 90. let minulého století v televizi a filmech použit nesčetněkrát. Někdy se odehrává v současnosti, někdy v daleké budoucnosti, jakýkoli dějový prvek zahrnující počítačové programování nebo hacking může koncept evokovat; často se často špatně používá.

Viz také

Reference