Files
elte-ik-pti-bsc-zarovizsga/10.Programnyelvi alapok/tetel10.tex
2021-06-13 11:18:20 +02:00

775 lines
37 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\documentclass[margin=0px]{article}
\usepackage{listings}
\usepackage[utf8]{inputenc}
\usepackage{graphicx}
\usepackage{float}
\usepackage[a4paper, margin=1in]{geometry}
\usepackage{amsthm}
\usepackage{amssymb}
\renewcommand{\figurename}{ábra}
\makeatletter
\renewcommand\paragraph{%
\@startsection{paragraph}{4}{0mm}%
{-\baselineskip}%
{.5\baselineskip}%
{\normalfont\normalsize\bfseries}}
\makeatother
\renewcommand{\figurename}{ábra}
\newenvironment{tetel}[1]{\paragraph{#1}}{}
% A dokument itt kezdődik
\title{Záróvizsga tételsor \\ \large 10. Programnyelvi alapok}
\date{}
\author{Ancsin Ádám}
\begin{document}
\maketitle
\begin{tetel}{Programnyelvi alapok}
Kifejezések kiértékelésének szabályai. Vezérlési szerkezetek: utasítások, rekurzió. Típusok: tömb, rekord, osztály, öröklődés, statikus és dinamikus kötés, polimorfizmus. Generikusok. Hatókör/láthatóság. Automatikus, statikus és dinamikus élettartam, szemétgyűjtés. Konstruktor, destruktor. Objektumok másolása, összehasonlítása. Alprogramok, paraméterátadás, túlterhelés.
\end{tetel}
\section{Kifejezések kiértékelésének szabályai}
\noindent Fogalmak:
\begin{itemize}
\item Operandusok: Változók, konstansok, függvény- és eljáráshívások.
\item Operátorok: Műveleti jelek, amelyek összekapcsolják egy kifejezésben az operandusokat és valamilyen
műveletet jelölnek.
\item Kifejezés: operátorok és operandusok sorozata
\item Precedencia: A műveletek kiértékelési sorrendjét határozza meg.
\item Asszociativitás iránya: Az azonos precedenciájú operátorokat tartalmazó kifejezésekben a kiértékelés iránya.
Megkülönböztetünk bal-asszociatív és jobb-asszociatív operátorokat.
\end{itemize}
\noindent Az operátorokat háromféleképpen írhatjuk az operandusokhoz képest:
\begin{itemize}
\item Infix : Egy operátort a két operandusa közé kell írni (tehát csak kétoperandusú műveletek operátorait
lehet így írni).
Amikor egy kifejezésben több operátor is szerepel, akkor a különböző operátorok végrehajtási
sorrendjét az operátorok precedenciája dönti el. Amelyik operátor precedenciája magasabb (pl. a
szorzásé magasabb, mint az összeadásé), az általa jelölt műveletet értékeljük ki először. Ugyanazon
operátorok végrehajtási sorrendjét pedig az asszociativitás iránya dönti el (pl. a bal-asszociatív azt
jelenti, hogy balról jobbra haladva kell végrehajtani). Ezeket a (programozási nyelvekbe beépített)
szabályokat zárójelek segítségével lehet felülírni.\\
Példa: A * (B + C) / D
\item Postfix (Lengyelforma) : Az operátorokat az operandusaik mögé írjuk. A kiértékelés sorrendje mindig balról jobbra
történik tehát egy n operandusú operátor a tőle balra levő első n operandusra érvényes.\\
Példa: A B C + * D /\\
Ugyanez zárójelezve (felesleges): ((A (B C +) *) D /)
\item Prefix : Az operátorokat az operandusuk elé írjuk. A kiértékelés sorrendje balról
jobbra történik.\\
Példa: / * A + B C D\\
Ugyanez zárójelezve (felesleges): (/ (* A (+ B C) ) D)\\
Habár a prefix operátorok esetén is balról jobbra történik a kiértékelés, viszont ha egy operátortól
jobbra egy másik operátor következik, akkor értelemszerűen az ehhez az operátorhoz tartozó
műveletet kell először végrehajtani, hogy a bal oldalit is végre tudjuk hajtani. A fenti példában is a
szorzást az osztás előtt, az összeadást pedig a szorzás előtt kell elvégezni.
\end{itemize}
\paragraph{Logikai operátorokat tartalmazó kifejezések kiértékelése}
\noindent Az ilyen kifejezéseknek kétféle kiértékelése létezik:
\begin{itemize}
\item Lusta kiértékelés : Ha az első argumentumból meghatározható a kifejezés értéke, akkor a másodikat
már nem értékeli ki.
\item Mohó kiértékelés : Mindenféleképpen megállapítja mindkét argumentum logikai értékét.
\end{itemize}
\noindent A két kiértékelési módszer bizonyos esetekben különböző eredményt adhat:
\begin{itemize}
\item A 2. argumentum nem mindig értelmes\\
Példa (C++):
\begin{verbatim}
if ((i>=0) && (T[i]>=10))
{
//...
}
\end{verbatim}
Tegyük fel, hogy a T egy \texttt{int} tömb, 0-tól indexelődik.
Itt ha az $i>=0$ hamis, akkor T-t alul indexelnénk. Ez mohó kiértékelés esetén futási idejű hibát okozna.
Lusta kiértékelés esetén (a C++ alapértelmezetten ezt használja) viszont tudhatjuk, hogy a feltétel már nem lehet igaz, emiatt $T[i]>=10$-et már nem kell kiértékelni.
\item A 2. argumentumnak van valamilyen mellékhatása.\\
Példa (C++):
\begin{verbatim}
if ((i>0) || (++j>0))
{
T[j] = 100;
}
\end{verbatim}
Ebben az esetben ha $i>0$ igaz, akkor a feltétel biztosan igaz, viszont a $++j>0$ kifejezés mellékhatásos, növeli j értékét.
Mivel a C++ lusta kiértékelést használ a $||$ operátor esetén ($|$ operátor esetén mohó a kiértékelés), ezért ebben az esetben nem növeli j értékét. (csak akkor, ha $i>0$ hamis).
\end{itemize}
\section{Utasítások, vezérlési szerkezetek}
\subsection{Egyszerű utasítások}
\begin{itemize}
\item Értékadás : Az értékadás bal oldalán egy változó, a jobb oldalán bármilyen kifejezés állhat. Az
értékadással a változóhoz rendeljük a jobb oldali kifejezést. Figyelni kell arra, hogy a bal oldali
változó típusának megfelelő kifejezés álljon a jobb oldalon (vagy létezik implicit konverzió, pl. C++-ban
az egész és logikai típus között). A legtöbb nyelvben az értékadás operátora az \texttt{=} (például C++, Java, C\#),
vagy a \texttt{:=} (például Pascal, Ada).
\item Üres utasítás : Nem mindenhol lehet ilyet írni. A lényege, hogy nem csinál semmit.
Azokban a nyelvekben lehet létjogosultsága, ahol üres blokkot nem írhatunk,
muszáj legalább 1 utasításnak szerepelnie benne. (pl. Ada) Erre szolgál az üres utasítás.
(Ada-ban ez a \texttt{null} utasítás)
\item Alprogramhívás : Alprogramokat nevük és paramétereik megadásával hívhatunk.
Példák:
\begin{itemize}
\item \texttt{System.out.println("Hello”);}
\item \texttt{int x = sum(3,4);}
\end{itemize}
\item Visszatérés utasítás : Az utasítás hatására az alprogram végrehajtása befejeződik. Ha az alprogram egy
függvény, akkor meg kell adni a visszatérési értéket is.
\noindent Példák (C++):
\begin{itemize}
\item Ebben a példában a \texttt{doSomething} egy eljárás, nincs visszatérési értéke. A paraméterül kapott
x változót értékül adjuk a j-nek. Ha ez az érték nem 0, akkor visszatérünk, azaz megszakítjuk az alprogram
végrehajtását (konkrét értéket viszont nem adunk vissza). Ha ez az érték 0, akkor a végrehajtás folytatódik tovább,
meghívjuk a \texttt{doSomethingElse} függvényt a j paraméterrel.
\begin{verbatim}
void doSomething(int x)
{
int j;
if(j=x)
return; // j!=0, do nothing
doSomethingElse(j);
}
\end{verbatim}
\item Ebben a példában az \texttt{isOdd} egy függvény, \texttt{int} visszatérési értékkel. Megmondja a paraméterül kapott
x egész számról, hogy páratlan-e. Ehhez bitenkénti ÉS művelettel "összeéseli" az x-et az 1-gyel (0...01). Ha az eredmény
nem 0, akkor páratlan, visszatérünk igaz értékkel. Különben folytatjuk a működést, majd visszatérünk hamis értékkel.
\begin{verbatim}
int isOdd(int x)
{
if(x & 1) //bitwise AND with 0...01 is not 0...0
return true;
return false;
}
\end{verbatim}
\end{itemize}
\item Utasításblokk: A blokkon belüli utasítások "összetartoznak”.
Ez több esetben is jól alkalmazható nyelvi elem:
\begin{itemize}
\item Vezérlési szerkezetekben: Az adott vezérlési szerkezetekhez tartozó utasításokat különíthetjük el.
\item Az olyan nyelvekben, amelyekben deklaráció csak a program elején található deklarációs
blokkokban lehetséges (pl. Ada), van lehetőség arra, hogy a programkód későbbi részében nyissunk
egy blokkot, ahol deklarációk is szerepelhetnek.
\item Osztályok inicializáló blokkja pl. Java-ban (konstruktor előtt hajtódik végre):
\begin{verbatim}
public class MyClass{
private ResourceSet resourceSet;
{
resourceSet = new ResourceSetImpl();
UMLResourcesUtil.init(resourceSet);
}
/* ... */
}
\end{verbatim}
\item Osztályok statikus inicializáló blokkja pl. Java-ban:
\begin{verbatim}
public class MyClass{
private static ResourceSet resourceSet;
static{
resourceSet = new ResourceSetImpl();
UMLResourcesUtil.init(resourceSet);
}
/* ... */
}
\end{verbatim}
\end{itemize}
\end{itemize}
\subsection{Vezérlési szerkezetek}
\begin{itemize}
\item Elágazás : Az elágazás egy olyan vezérlési szerkezet, amellyel meghatározhatjuk, hogy bizonyos
(blokkban megadott) utasítások csak a megadott feltétellel jöhessenek létre.
Általában több feltételt is megadhatunk egymás után (if L1 then … else if L2 then … else if L3 then …).
Megadhatjuk azt is, hogy mi történjen, ha egyik feltétel sem teljesül (if L then … else …).
Elágazásokat lehet egymásba ágyazni (if … then if ...).
"Csellengő else” ("Dangling else”) probléma: Azokban a nyelvekben lép fel, ahol egy feltétel egy
utasításblokkját nem zárja le külön kódszó (pl. endif). Ekkor abban az esetben, ha elágazásokat
egymásba ágyazunk, a következő probléma léphet fel:\\
Példa: \texttt{if (A>B) then if (C>D) then E:=100; else F:=100;}
A fenti esetben nem lehet megállapítani, hogy a programozó az else kulcsszót melyik elágazásra
értette.
\item Ciklus : Egy utasításblokk (ciklusmag) valahányszori végrehajtását jelenti.
\begin{itemize}
\item Feltétel nélküli ciklus : Végtelen ciklust kódol, kilépni belőle a strukturálatlan utasításokkal lehet
(ld. lentebb), vagy return-nel, esetleg hiba fellépése esetén.
Példa (ADA):
\begin{verbatim}
loop
null;
end loop;
\end{verbatim}
\item Elöltesztelő ciklus : A ciklus a ciklusmag minden végrehajtása előtt megvizsgálja, hogy az adott
feltétel teljesül-e. Ha teljesül, akkor végrehajtja a magot, majd újra ellenőriz. Különben a ciklus
után folytatódik a futás.
Példák:
\begin{itemize}
\item C++:
\begin{verbatim}
int x=0,y=0;
while(x<5 && y<5)
{
x+=y+1;
y=x-1;
}
cout<<x+y<<endl;
\end{verbatim}
\item ADA:
\begin{verbatim}
declare
X,Y: Integer;
begin
X:=0;
Y:=0;
while X<5 and Y<5 loop
X:=X+Y+1;
Y:=X-1;
end loop;
end;
\end{verbatim}
\end{itemize}
\item Számlálásos ciklus : Ebben a vezérlési szerkezetben megadhatjuk, hogy a ciklusmag hányszor
hajtódjon végre.
Példa (ADA):
\begin{verbatim}
for i in 1..10 loop
null;
end loop;
\end{verbatim}
A számlálás úgy történik, hogy egy változóban (ciklusváltozó) tároljuk, hogy "hol tartunk” - ezt hasonlítjuk
össze minden ciklus elején a kifejezéssel, amit meg kell haladnia a változónak (i). Tehát
felfogható egy elöltesztelő ciklusként is, ahol a ciklusfeltétel az \texttt{i<=10} és a ciklusmag végén
növelni kell i-t.
\item Hátultesztelő ciklus : Az a különbség az elöltesztelőhöz képest, hogy a feltételt a ciklusmag
végrehajtása után ellenőrizzük tehát itt a ciklusmag 1-szer mindenképpen lefut.
Példa (C++):
\begin{verbatim}
int i=0;
do
{
++i;
} while(i<5);
\end{verbatim}
Megjegyzés: vannak olyan nyelvek (pl. Pascal), amelyekben a hátultesztelő ciklus feltétele nem bennmaradási, hanem
leállási feltétel. Azaz nem azt adjuk meg, hogy minek kell teljesülnie ahhoz, hogy még egyszer végrehajtásra kerüljön
a ciklusmag, hanem azt, hogy minek kell teljesülnie ahhoz, hogy a ciklusmag ne hajtódjon végre többször.
Az előbbi C++-os példa Pascal-os megfelelője:
\begin{verbatim}
i:=0;
repeat
i:=i+1;
until i=5;
\end{verbatim}
Megjegyzés: ADA-ban nincs igazi hátultesztelős ciklus. Ebben a nyelvben hátultesztelő ciklust úgy írhatunk, hogy ha
írunk egy feltétel nélküli ciklust, amelynek utolsó utasítása egy feltételhez kötött kilépés.
Példa:
\begin{verbatim}
i:=0;
loop
i:=i+1;
exit when i=5;
end loop;
\end{verbatim}
\item foreach : Akkor használatos, ha egy adatszerkezet minden elemére végre akarjuk hajtani a magot.
Tulajdonképpen ez is egy elöltesztelő ciklus (a ciklusfeltétel az, hogy a végére értünk-e az
adatszerkezetnek).
Példa:
\begin{verbatim}
foreach (int v in Vect)
{
++v;
}
\end{verbatim}
Megjegyzések:
\begin{enumerate}
\item Nincs minden programozási nyelvben ilyen ciklus. Leginkább az újabb nyelvekben terjedt el.
\item Nem minden programozási nyelvben a foreach a kulcsszó ehhez a ciklushoz.
Például Java-ban, C++11-ben (régebbi változatokban nincs ilyen) a for ciklust használhatjuk foreach-ként:
\begin{verbatim}
int x=0;
for (int v : vect){
x+=v;
}
\end{verbatim}
\end{enumerate}
\end{itemize}
\end{itemize}
\subsection{Strukturálatlan utasítások}
\begin{itemize}
\item Ciklus megszakítása : A ciklusból való "kiugrásra” (tehát annak azonnali befejezésére) használható.
Gyakran végtelen ciklus megszakítására használjuk, vagy hátultesztelő ciklus kódolására (ahol nincs
erre beépített vezérlési szerkezet, például Ada).
Ilyen utasítás pl. C/C++/C\#-ban, vagy Java-ban a \texttt{break}, illetve Ada-ban az \texttt{exit}.
\item \texttt{goto} utasítás : A programkódban címkéket definiálhatunk, majd a \texttt{goto} utasítással egy
ilyen címkéhez irányíthatjuk a vezérlést. Vezérlési szerkezeteket is lehet vele kódolni. Túlzott
használata olvashatatlan kódhoz vezethet.
\end{itemize}
\subsection{Rekurzió}
Rekurzív alprogram: Olyan alprogram, amelynek kódjában szerepel önmagának a meghívása.
Mindenképpen kell, hogy legyen a rekurziónak megállási feltétele tehát egy olyan feltétel (egy elágazással
együtt), amelynek teljesülése esetén nem történik rekurzív hívás, így az összes, folyamatban levő rekurzív hívás
végre tud hajtódni (ellenkező esetben végtelen rekurzió lép fel, ilyenkor általában előbb-utóbb betelik a stack és leáll a program).\\
\noindent Példák (faktoriális):
\begin{itemize}
\item C++:
\begin{verbatim}
int fact (int n)
{
if(n>0)
return n * fact (n-1);
else
return 1;
}
\end{verbatim}
\item Haskell:
\begin{verbatim}
fact :: (Integral a) => a -> a
fact n
| n>0 = n * fact (n - 1)
| otherwise = 0
\end{verbatim}
\end{itemize}
\section{Típusok}
\subsection{Tömb}
A tömb (angolul array) olyan adatszerkezet, amelyet nevesített elemek csoportja alkot, melyekre sorszámukkal (indexükkel) lehet hivatkozni. Vektornak is nevezik, ha egydimenziós, mátrixnak esetenként, ha többdimenziós. A legtöbb programozási nyelvben minden egyes elemnek azonos adattípusa van és a tömb folytonosan helyezkedik el a számítógép memóriájában. A készítés módja alapján lehet:
\begin{itemize}
\item statikus :a méret fix, deklarációban szabályozott
\item dinamikus tömb: a mérete változik, folyamatosan bővíthető
\end{itemize}
\subsection{Rekord}
A rekord egy összetett értékek leírásához használható konstrukció. Névvel és típussal ellátott összetevői vannak, ezeket mezőknek nevezzük.
Értékhalmaz a mezők értéktípusai által meghatározott alaphalmazok direktszorzata.
\begin{figure}[H]
\centering
\includegraphics[width=0.7\linewidth]{img/rekord}
\caption{A rekord-típuskonstrukciók általános szintaxisa.}
\label{fig:rekord}
\end{figure}
\noindent Műveletek:
\begin{itemize}
\item Szelekciós függvény (.mezőszelektor szintaxisú);
\item konstrukciós függvény (Rekordtípus(mezőértékek) szintaxisú);
\item elképzelhetők transzformációs függvények, amelyek a teljes rekordstruktúrát érintik.
\end{itemize}
\noindent Példa rekordra és használatára C-ben:
\begin{verbatim}
//definition of Point
struct Point
{
int xCoord;
int yCoord;
};
const struct Point ORIGIN = {0,0};
\end{verbatim}
\subsection{Osztály}
Az osztály egy felhasználói típus, amelynek alapján példányok (objektumok)
hozhatók létre. Az osztály alapvetően attribútum és metódus (művelet) definíciókat
tartalmaz. Az osztály írja le az objektum típusát: megadja a tulajdonságait és azok
lehetséges értékeit (azaz a típusértékeket), valamint az objektumon végrehajtható műveleteket (típusműveletek).\\
\noindent Példa C++-ban:
\begin{verbatim}
//definition of Point
class Point {
private:
int xCoord;
int yCoord;
public:
//constructor
Point(int xCoord, int yCoord) : xCoord(xCoord),yCoord(yCoord) {}
void Translate(int dx, int dy) {
xCoord+=dx;
yCoord+=dy;
}
void Translate(Point delta) {
xCoord+=delta.xCoord;
yCoord+=delta.yCoord;
}
int getX() { return xCoord; }
int getY() { return yCoord; }
};
int main(int argc, char* argv[])
{
Point point(0,0);
point.Translate(5,-2);
cout<<point.getX()<<","<<point.getY()<<endl; //5,-2
Point delta(-2,1);
point.Translate(delta);
cout<<point.getX()<<","<<point.getY()<<endl; //3,-1
return 0;
}
\end{verbatim}
Megjegyzés: A nem objektum-orientált nyelvekben nincsenek osztályok, pl. régebbi nyelvekben, mint a C, ADA, Pascal, vagy
funkcionális nyelvekben (Haskell, Clean, stb.).
\subsection{Öröklődés}
Egy osztály legegyszerűbben adattagjainak és metódusainak felsorolásával hozható létre. Azonban az objektum-orientált paradigma lehetőséget ad egy másik, hatékonyabb módszerre is, az öröklődésre. Az öröklődés az újrafelhasználhatóságot szem előtt tartva arra ad lehetőséget, hogy már meglévő (szülő-, ős-) osztályból kiindulva hozzunk létre új (gyermek-, leszármazott-, al-) osztályt. Az öröklés két osztály között fennálló olyan kapcsolat, amely során a leszármazott osztály rendelkezik a szülő osztály majdnem összes tulajdonságával (nem privát adattagjait és metódusait sajátjaként kezeli), s ezeket újabbakkal egészítheti ki. Az így létrehozott osztály is lehet más osztályok őse (kivéve pl. Java-ban a \texttt{final} kulcsszóval ellátott osztályok, ezekből már nem származtathatunk), így ezek az osztályok egy öröklési hierarchiába szerveződnek. Attól függően, hogy egy osztálynak egy- vagy több őse van, beszélünk egyszeres- ill. többszörös öröklődésről. A Java az egyszeres öröklődést támogatja, de pl. C++-ban van többszörös öröklődés.
A Java-ban az osztályhierarchia legfelső eleme az Object osztály, amelyből minden más osztály (közvetve vagy közvetlenül) származik.
Egy alosztály az örökölt metódusokat újraimplementálhatja. Ilyenkor az adott metódus ugyanolyan néven, de más, módosított (alosztályra specifikált) tartalommal kerül megvalósításra. Az ilyen metódusokat polimorfnak nevezzük. Java-ban minden olyan metódust, ami nincs ellátva a \texttt{final} kulcsszóval, újra lehet definiálni. (C++-ban mindent, ami nem privát)\\
\noindent Példák:
\begin{itemize}
\item C++:
\begin{verbatim}
class RegularPolygon
{
protected:
const double radius;
public:
RegularPolygon(double radius) : radius(radius) {}
virtual double area() = 0;
};
class EquilateralTriangle : public RegularPolygon
{
public:
EquilateralTriangle(double radius) : RegularPolygon(radius) {}
virtual double area()
{
return 0.75*sqrt(3.0)*radius*radius;
}
};
\end{verbatim}
\item Java:
\begin{verbatim}
public abstract class RegularPolygon{
protected final double radius;
public RegularPolygon(double radius){
this.radius = radius;
}
public abstract double area();
}
public class EquilateralTriangle extends RegularPolygon{
public EquilateralTriangle(double radius){
super(radius);
}
public double area(){
return 0.75*Math.sqrt(3.0)*radius*radius;
}
}
\end{verbatim}
\end{itemize}
\subsection{Statikus és dinamikus kötés}
\begin{itemize}
\item statikus típus: A változó deklarációjában megadott típus. Fordítás során egyértelműen eldől, nem változhat
futás során. A statikus típus határozza meg, hogy mit szabad csinálni az objektummal (pl. hogy milyen
műveletek hívhatók meg rá).
\item dinamikus típus: A változó által hivatkozott objektum típusa. Vagy a statikus típus leszármazottja, vagy maga
a statikus típus. Futás során változhat.
\end{itemize}
\noindent Példa(Java):
\begin{verbatim}
Object o = new String("Hello”);
\end{verbatim}
Itt az o változó statikus típusa \texttt{Object}, dinamikus típusa pedig \texttt{String}.\\
\noindent Kötések:
\begin{itemize}
\item statikus kötés: A változó statikus típusa szerinti adattagokra lehet hivatkozni.
\item dinamikus kötés: A változó dinamikus típusa szerinti adattagok használhatók.
\end{itemize}
A kötés akkor fontos, ha a hívott művelet, vagy a hivatkozott változó a statikus és a dinamikus típusban
különbözik, vagy esetleg a statikus típusban nem is létezik (ekkor a dinamikus típusban sem hivatkozhatunk
az adattagra).\\
\noindent Többféleképpen lehet a kötéseket meghatározni a különböző nyelvekben:
\begin{itemize}
\item Java : Minden esetben dinamikus kötés van (az örökölt metódusok törzsében is).
\item C++ : A művelet definiálásakor lehet jelezni, ha dinamikus kötést szeretnénk (\texttt{virtual}).
\item Ada : A híváskor lehet jelezni, ha dinamikus kötést szeretnénk.
\end{itemize}
\section{Generikusok}
Generikus programozás: Algoritmusok, adatszerkezetek általánosított, több típusra is működő
leprogramozása (pl. generikus rendezés, generikus verem, …). Ezt az általános kódot nevezzük sablonnak
(template).
Bizonyos nyelvekben (Ada, C++) egy sablont példányosítani kell ekkor a kívánt típusokat, objektumokat
(a sablon definíciónak megfelelően) a sablonnak paraméterként megadva példányosul a szóban forgó sablon.
(Ugyanúgy, mint pl. amikor egy függvénynek megadjuk az aktuális paraméterét.) Ezt nevezzük generatív
programozásnak: a program a megadott sablon alapján létrehoz egy "igazi” programegységet (program
generál programot).
Példa: Egy verem sablon példányosításakor megadjuk, hogy milyen típusú elemeket tároljon a verem (típus
paraméter) és hogy hány elem fér a verembe (objektum paraméter).
C++ és Ada esetén egy sablon paramétere alprogram is lehet.
Példa: Egy rendezésnek megadjuk, hogy milyen művelet alapján rendezzen.
Természetesen lehet alapértelmezett sablonparaméter is.
Egy sablon definiálása esetén természetesen a sablont nem lehet minden típusra használni. Egy sablon attól
lesz sablon, hogy több típusra is működik. Azonban megszorításokat tehetünk (és tennünk is kell) arra, hogy
milyen típusokra lehessen azt használni, hogyan lehessen a sablont példányosítani.
Jó példa erre az Ada nyelv, ahol a sablon specifikációja egy "szerződés” a sablon törzse és a példányosítás
között:
\begin{itemize}
\item A sablon törzse nem használhat mást, csak amit a sablon specifikációja megenged neki. (A törzset
nem feltétlenül kell, hogy ismerjük példányosításkor.)
\begin{verbatim}
generic
type Element_T is private;
with function "*" (X, Y: Element_T) return Element_T is <>;
function Square (X : Element_T) return Element_T;
\end{verbatim}
Itt a \texttt{with function} kezdetű sor végén az \texttt{is <>} azt jelenti, hogy ha az adott típusra már létezik *
művelet (pl. egész számokra), akkor nem kell külön megadni példányosításkor, a program automatikusan azt használja.
A törzs:
\begin{verbatim}
function Square (X: Element_T) return Element_T is
begin
return X * X; -- The formal operator "*".
end Square;
\end{verbatim}
\item A példányosításnak biztosítania kell mindent, amit a sablon specifikációja megkövetel tőle.
A következő példában a négyzetre emelő függvényt mátrixokra alkalmazzuk, feltéve, hogy definiáltuk a
mátrixszorzást.
\begin{verbatim}
with Square;
with Matrices;
procedure Matrix_Example is
function Square_Matrix is new Square
(Element_T => Matrices.Matrix_T, "*" => Matrices.Product);
A : Matrices.Matrix_T := Matrices.Identity;
begin
A := Square_Matrix (A);
end Matrix_Example;
\end{verbatim}
\end{itemize}
Például a C++-ban a sablonszerződés nem így működik. Ott a sablon specifikációja az egész
definíció (emiatt sablonosztályokat csak teljes egészében header fájlokban definiálhatunk).
Példányosításkor ezt is ismerni kell, hogy tudjuk, hogyan példányosíthatunk. Az információelrejtés
elve tehát sérül.
Más nyelvekben (pl. Java, funkcionális nyelvek) nem kell a sablonokat példányosítani mindig ugyanaz a
megírt kód hajtódik végre, csak épp az aktuális paraméterekkel. Pl. Java-ban a típusparaméter fordításkor "elveszik",
csak futási időben derül ki.
\section{Hatókör/láthatóság}
\begin{enumerate}
\item Hatókör: Deklarációkor a programozó összekapcsol egy entitást (például egy változót vagy függvényt) egy névvel. A hatókör alatt a forrásszöveg azt a részét értjük, amíg ez az összekapcsolás érvényben van. Ez általában annak a blokknak a végéig tart, amely tartalmazza az adott deklarációt.
\item A láthatóság a hatókör részhalmaza, a programszöveg azon része, ahol a deklarált névhez a megadott entitás tartozik. Mivel az egymásba ágyazott blokkokban egy korábban már bevezetett nevet más entitáshoz kapcsolhatunk, ezért ilyenkor a külső blokkban deklarált entitás a nevével már nem elérhető. Ezt nevezzük a láthatóság elfedésének.
Egyes nyelvekben (például C++) bizonyos esetekben (például osztályszintű adattagok) a külső blokkban deklarált entitáshoz minősített névvel hozzá lehet férni ekkor is.
\end{enumerate}
\section{Automatikus, statikus és dinamikus élettartam, szemétgyűjtés}
Élettartam: A változók élettartama alatt a program végrehajtási idejének azt a szakaszát értjük, amíg a változó számára lefoglalt tárhely a változóé.
\subsection{Automatikus élettartam}
A blokkokban deklarált lokális változók automatikus élettartamúak, ami azt jelenti, hogy a deklarációtól a tartalmazó blokk végéig tart, azaz egybeesik a hatókörrel. A helyfoglalás számukra a végrehajtási verem aktuális aktivációs rekordjában történik meg.
\subsection{Statikus élettartam}
A globális változók, illetve egyes nyelvekben a statikusként deklarált változók (például C/C++ esetén a \texttt{static} kulcsszóval) statikus élettartamúak. Az ilyen változók élettartama a program teljes végrehajtási idejére kiterjed, számukra a helyfoglalás már a fordítási időben megtörténhet.
\subsection{Dinamikus élettartam}
A dinamikus élettartamú változók esetén a programozó foglal helyet számukra a dinamikus tárterületen (heap), és a programozó feladata gondoskodni arról is, hogy ezt a tárterületet később felszabadítsa. Amennyiben utóbbiról megfeledkezik, azt nevezzük memóriaszivárgásnak (memory leak).
Mint látjuk, a dinamikus élettartam esetén a hatókör semmilyen módon nem kapcsolódik össze az élettartammal, az élettartam szűkebb vagy tágabb is lehet a hatókörnél.
\subsection{Szemétgyűjtő}
A szemétgyűjtő másik neve a hulladékgyűjtő, az angol Garbage Collector név után pedig gyakran csak GC-nek rövidítik. Feladata a dinamikus memóriakezeléshez kapcsolódó tárhelyfelszabadítás automatizálása, és a felelősség levétele a programozó válláról, így csökkentve a hibalehetőséget.
A szemétgyűjtő figyeli, hogy mely változók kerültek ki a hatókörükből, és azokat felszabadíthatóvá nyilvánítja. A módszer hátránya a számításigényessége, illetve a nemdeterminisztikussága. A szemétgyűjtő ugyanis nem szabadítja fel egyből a hatókörükből kikerült változókat, és a felszabadítás sorrendje sem ugyanaz, amilyen sorrendben a változók felszabadíthatóvá váltak.
Azt, hogy a hulladékgyűjtő mikor és mely változót szabadítja fel, egy programozási nyelvenként egyedi, összetett algoritmus határozza meg, amelyben rendszerint szerepet játszik a rendelkezésre álló memória telítettsége, illetve a felszabadításhoz szükséges becsült idő. (Például ha egy objektum rendelkezik destruktorral, akkor általában a GC később szabadítja csak fel.)
Összességében a szemétgyűjtő csak annyit garantál, hogy előbb-utóbb (legkésőbb a program futásának végeztével) minden dinamikusan allokált változót felszabadít.
Szemétgyűjtést használó nyelvek pl. Java, C\#, Ada. C/C++-ban nincs szemétgyűjtés, a programozónak kell gondoskodni a dinamikusan allokált memóriaterületek felszabadításáról.
\section{Konstruktor, destruktor}
\subsection{Konstruktor}
A konstruktor az objektumok inicializáló eljárása, akkor fut le, ha egy osztályból új objektumot példányosítunk. Alapértelmezett konstruktor alatt a paraméter nélküli konstruktort értjük, a legtöbb programozási nyelv esetén ezt a fordítóprogram automatikusan generálja üres törzzsel, amennyiben nem lett megadva egy konstruktor sem egy osztályban.
Többek között a konstruktorban szokás gondoskodni arról, hogy az objektum dinamikus élettartamú változói számára tárhelyet foglaljunk.
\subsection{Destruktor}
A destruktor a konstruktor ellentétes párja, ez az eljárás az objektumok felszabadításakor fut le. Meghívása automatikusan megtörténik, attól függetlenül, hogy az objektum felszabadítása automatikusan történik a hatókör végeztével (lokális objektumok esetén), manuálisan a programozó által (dinamikus élettartamú objektumok esetén) vagy a szemétgyűjtő által (szintén dinamikus élettartamú objektumok esetén).
A desktruktorban szokás többek között az objektum dinamikus helyfoglalású adattagjait és a lefoglalt erőforrásokat felszabadítani.
\section{Objektumok másolása, összehasonlítása}
Az objektumok másolása egy speciális konstruktorral, az úgynevezett másoló konstruktorral (copy constructor) történik. Ez paraméterül az adott osztály egy példányát kapja meg, és azt a programozó által megadott működési logika szerint lemásolja az éppen inicializált objektumba.
Több programozási nyelv (például C++) fordítóprogramja automatikusan elkészít egy másoló konstruktort, ha a programozó nem definiál sajátot. Ez az alapértelmezett másoló konstruktor lemásolja a forrásobjektum összes adattagjának értékét, ezt nevezzük sekély másolatnak (shallow copy). Ha az objektum dinamikus foglalású adattagokat is tartalmaz, akkor azoknak nem az értéke, hanem csak a hivatkozása lesz lemásolva, ami általában nem a kívánt működés. Ez esetben saját másoló konstruktor írása szükséges, ami mély másolatot (deep copy) készít.
\section{Alprogramok, paraméterátadás, túlterhelés}
\subsection{Alprogramok}
Alprogramoknak a függvényeket, eljárásokat és műveleteket nevezzük. Segítségükkel a program feladatonként tagolható, a főprogramból az önálló feladatok kiszervezhetőek.
\subsection{Paraméterátadás}
Az alprogramoknak szüksége lehet bemenő adatokra és vissza is adhat értékeket. Az alprogramokat általános írjuk meg, saját változónevekkel, ezek a formális paraméterek. Az alprogram meghívásakor az átadott aktuális paraméterek alapján a formális paraméterek értéket kapnak. Az, hogy a formális paraméterek értéke mi lesz, a paraméterátadás módjától függ.
\subsubsection{Szövegszerű paraméterátadás}
A makrókban használatosak mind a mai napig. A makró törzsében a formális paraméter helyére beíródik az aktuális paraméter szövege.
\subsubsection{Név szerinti paraméterátadás}
Az aktuális paraméter kifejezést újra és újra kiértékeljük, ahányszor hivatkozás történik a formálisra. A paramétert a törzs kontextusában értékeljük ki, így a formális paraméter különböző előfordulásai mást és mást jelenthetnek az alprogramon belül.
Alkalmazása archaikus (például: Algol 60, Simula 67).
\subsubsection{Érték szerinti paraméterátadás}
Az egyik legelterjedtebb paraméterátadási mód (például: C, C++, Pascal, Ada, Java), bemeneti szemantikájú. A formális paraméter az alprogram lokális változója, híváskor a vermen készül egy másolat az aktuális paraméterről, ez lesz a formális. Az alprogram végén a formális paraméter megszűnik
\subsubsection{Cím szerinti paraméterátadás}
A másik legelterjedtebb paraméterátadási mód (például: Pascal, C++, C\#), be- és kimeneti szemantikájú. A híváskor az aktuális paraméter címe adódik át, azaz a formális és az aktuális paraméter ugyanazt az objektumot jelentik, egy alias jön létre.\\
Megjegyzés: A Java-ban nincs cím szerinti paraméterátadás, csak érték szerinti. A Java ugyanis a primitív típusoknak az értékét tárolja,
objektumok esetén pedig egy referenciát az adott objektumra (mint C++-ban a referencia típus). Objektum átadásakor ez a referencia másolódik le, azaz a referencia adódik át érték szerint.\\
\noindent Például:
\begin{verbatim}
public static void BadSwap(Object x, Object y)
{
Object tmp = x;
x = y;
y = tmp;
}
\end{verbatim}
A fenti függvény nem cseréli ki az x-et és y-t, csak lokálisan (a függvénytörzsön belül), viszont a hívás helyén
x és y is helyben maradnak.
\subsubsection{Eredmény szerinti paraméterátadás}
Kimeneti szemantikájú paraméterátadási mód. A formális paraméter az alprogram lokális változója, az alprogram végén a formális paraméter értéke bemásolódik az aktuálisba. Azonban az alprogram meghívásakor az aktuális értéke nem másolódik be a formálisba. (Használja például az Ada.)
\subsubsection{Érték/eredmény szerinti paraméterátadás}
Az érték és eredmény szerinti paraméterátadás összekombinálása, így egy be- és kimeneti szemantikájú paraméterátadási módot kapunk. (Használja például az Algol-W vagy az Ada.)
\subsubsection{Megosztás szerinti paraméterátadás}
Objektumorientált programozási nyelvek (például: CLU, Eiffel) paraméterátadási módja. Lényege, hogy ha a formális paraméter megváltoztatható és az aktuális paraméter egy megváltoztatható változó, akkor cím szerinti paraméterátadás történik, egyébként pedig érték szerinti. A paraméterátadás módját külön megadni nem lehet.
Megváltoztatható (mutable) objektum alatt azt értjük, hogy tulajdonságai, ezáltal állapota megváltoztatható.
\subsubsection{Igény szerinti paraméterátadás}
Ezt a paraméterátadási módot a lusta kiértékelésű funkcionális nyelvek (például: Clean, Haskell, Miranda) alkalmazzák. Az aktuális paramétert nem híváskor értékeli ki, hanem akkor, amikor először szüksége van rá a számításokhoz.
\subsection{Túlterhelés}
A túlterhelés (overloading) segítségével azonos nevű alprogramokat hozhatunk létre eltérő szignatúrával. A szignatúra a legtöbb programozási nyelvben az alprogram nevét és a formális paraméterek számát és típusát jelenti, de egyes nyelvekben (például Ada) a visszatérési érték típusa is beletartozik. A túlterhelés elsődleges felhasználási területe, hogy ugyanazt a tevékenységet különböző paraméterezéssel is elvégezhessük.
A fordító az alprogramhívásból el tudja dönteni, hogy a túlterhelt változatok közül melyiket kell meghívni. Ha egyik sem illeszkedik vagy több is illeszkedik, akkor fordítási hiba lép fel.
\end{document}