Adattípus
A számítástechnikában és a számítógépes programozásban az adattípus (vagy egyszerűen típus) adatértékek gyűjteménye vagy csoportosítása, amelyet általában a lehetséges értékek halmaza, az ezeken az értékeken végzett megengedett műveletek halmaza és/vagy ezen értékek megjelenítése határoz meg.[1]
Egy programban az adattípus-specifikáció korlátozza azokat a lehetséges értékeket, amelyeket egy kifejezés, például egy változó vagy egy függvényhívás felvehet. A szó szerinti adatok esetében megmondja a fordítónak vagy interpreternek, hogy a programozó hogyan kívánja használni az adatokat. A legtöbb programozási nyelv támogatja a következő alapvető adattípusokat: egészek (különböző méretűek), lebegőpontos számok (amelyek a valós számokat közelítik), karakterek és booleanek.[2]
A programozási nyelvek explicit vagy implicit módon támogatják az adattípusok használatát, amelyeket statikusan vagy dinamikusan ellenőrizve kényszerítenek, ezzel biztosítják, hogy érvényes, helyes programokat hozzon létre az adott nyelv.
Alapok
szerkesztésA jelentés nélküli bitek csoportjához szemantikai jelentéssel bíró típus hozzárendelése a célja a típus meghatározásnak, idegen szóval típusdeklarációnak. Típust gyakran vagy a memóriaterülethez, vagy egy objektumhoz, illetve változóhoz rendelnek; a számítógép mint hardver önmagában nem tesz különbséget memóriacím, utasításkód, karakter, egész vagy lebegőpontos szám között (Neumann-architektúrák esetén). Az adattípusok tehát elvont, mesterséges kategóriák (ahogyan valójában az adat fogalma is az).
A típusok arról adnak információt a programnak és a programozónak, hogyan kell kezelni az adott bitek csoportját.
A típusok használata leginkább a következő területeken biztosít előnyt:
- Biztonság – A típusok használata biztosítja a fordítóprogramnak, hogy felismerhessen értelmetlen vagy feltehetőleg érvénytelen kifejezéseket. Például, a
"Hello, World" / 3
kifejezést érvénytelennek kell tekinteni, mivel az osztás művelete (általánosan elfogadottan) nem értelmezhető egy string és egy egész között. - Optimalizálás – A statikus típusellenőrzés a fordítóprogram számára hasznos információkat biztosít arra vonatkozóan, hogy milyen utasításkód alkalmazásával lehet hatékony programot előállítani. Egy elem típusából meghatározható például az adott elem helyfoglalási igénye.
- Dokumentálás – A típusok használatával a program dokumentációja egyértelműbbé és érthetőbbé válik. Például ha létrehozunk egy időbélyeg típust, és egy eljárás eredményeként keletkező egész számhoz hozzá is rendeljük ezt a típust, akkor egyértelmű a továbbiakban a dokumentáció olvasója számára, hogy a keletkezett egészet a program a továbbiakban időbélyegnek használja. A típus így járulékos információt szolgáltat, az olvasónak egyértelművé válik az eljárás célja és eredményének további használata.
- Absztrakció (vagy modularitás) – A típusok használatával a programozó lehetőséget kap arra, hogy magasabb szinten gondolkodjon a programról, ne az alacsony szintű megvalósítási kérdések legyenek a meghatározók.
Típusellenőrzés
szerkesztésAz eljárás, ami fordítási időben (statikus ellenőrzés) vagy végrehajtási időben (dinamikus ellenőrzés) ellenőrzi a típuskényszerítés szabályait, és szükség esetén végrehajtja az előírt művelet(ek)et. A típuskényszerítés hatására a program az aktuális változót/memóriacím alatti területet a típusnak megfelelően fogja értelmezni, vagyis ekkor dől el, hogy milyen jellegű adattal is dolgozik.
Statikus típusok
szerkesztésA statikus ellenőrzés elsődlegesen a fordítóprogram feladata. Ha a nyelv kikényszeríti a típushoz tartozó szabályok maradéktalan végrehajtását (ez általában a típuskonverziók végrehajtását, és a típusra vonatkozó megszorításokat jelenti, információveszteség nélkül), akkor a nyelv erősen típusos, ellenkező esetben gyengén típusos.
Dinamikus típusok
szerkesztésA dinamikus típus-hozzárendelés és -ellenőrzés szinte minden esetben a futásidőben történik, mivel az egyes változókhoz ekkor rendelődik hozzá az aktuális típusuk (program végrehajtási ideje alatt). A statikus típusrendszer dinamikus használata általában szükségessé teszi egy olyan rendszer kidolgozását, ami a futási időben képes arra, hogy a kövesse az időben esetleg változó típus-hozzárendelések változásait. Ez az igény egyrészt triviálisnak, de ugyanakkor a helyes működés biztosítása miatt nehézkesnek is tűnik.
A C, C++, Java, ML, és a Haskell statikus típuskezelést és -ellenőrzést használ, míg az Objective-C, Scheme, Lisp, Smalltalk, Perl, PHP, Visual Basic, Ruby, és a Python dinamikus típuskezelést és -ellenőrzést valósítanak meg. A trend azt mutatja, hogy a típuskezelést a fordított nyelvekben használják.
A Kacsa-típusosság (duck typing) egy humoros meghatározása az úgynevezett scriptnyelveknél használatos dinamikus típuskezelésnek, amelyek bizonyos esetekben feltételezésekkel élnek a típusokra: "(értékre hivatkozás esetén) ha valami úgy megy, mint egy kacsa és úgy hápog, mint egy kacsa, akkor az kacsa".
A típusellenőrzés megértéséhez vegyük a következő pszeudokód példát:
var x; // (1) x := 5; // (2) x := "hi"; // (3)
A példában deklaráljuk az x nevet(1); hozzárendeljük az 5 egész értéket az x névhez (2); majd hozzárendeljük a "hi" string értéket az x névhez (3). A legtöbb statikus típuskezelést használó rendszerben a fenti kódtöredék érvénytelen, mivel (2) és (3) esetben az x-hez összeférhetetlen típusokat próbálunk kapcsolni. A nem megfelelő típusok használatát jelző hiba a fordítás alatt megjelenik, nem generálódik futtatható program.
Kontrasztként egy egyszerű dinamikus típuskezelő-rendszer megengedi a program futtatását, mivel az x névvel azonosított hely minden típusa összefér. A megvalósított dinamikus típuskezelő-rendszertől függ, hogy hibajelzést ad-e – "típushiba" –, vagy esetleg hibás kifejezést generál. A lényeg, hogy az esetleges hibajelzés a program futása közben generálódik. Egy tipikus dinamikus típuskezelő-rendszer megvalósítása esetében a program egy "jelzőt" rendel minden értékhez, ahol a jelző az aktuális típust mutatja, és egy művelet végrehajtása előtt ellenőrzésre is kerül.
Például:
var x = 5; // (1) var y = "hi"; // (2) var z = x + y; // (3)
Ebben a kódtöredékben (1) az 5 értéket rendeljük az x-hez x; (2) a "hi" értékelt rendeljük az y-hoz; majd (3) megkíséreljük összeadni x-et és y-t.
Egy dinamikus típuskezelő-rendszert használó nyelvnél minden érték valójában egy típusjelző-érték-párból áll, az (1) értékadás létrehozza a (egész, 5) párt és a (2)-es értékadás a (string, "hi") párt. Amikor a program megkísérli végrehajtani a (3) sort, a nyelv megvalósítása megvizsgálja a párok típusjelzőit – egész és string –, majd felismeri, hogy az + (összeadás) nem értelmezhető művelet erre a két típusra, ezért hibát jelez.
Néhány statikus típuskezelő-rendszert használó nyelv létrehoz egy „hátsó ajtót” a nyelvben, hogy a programozónak lehetősége legyen olyan kódot írni, melyen nem történik statikus típusellenőrzés. Például a C és a Java a típuskonverzió (angolul cast) megoldást alkalmazza.
Statikus és dinamikus típusellenőrzés a gyakorlatban
szerkesztésA választás a két ellenőrzési mód között többnyire kereskedelmi megfontolásokon alapul. A legtöbb programozó erősen kitart az „egy minden felett” elv mellett, mások ezt kimondottan korlátozásnak és nehezen tolerálhatónak tartják. A statikus típusellenőrzés a hibákat még fordítási időben találja meg, ezzel növelhető a program megbízhatósága. Ennek ellenére a programozók tagadják, hogy a típushibák gyakran fordulnának elő, és ezen hibák egy része csak azért van, mert nem vettek figyelembe típusokat. A statikus típusellenőrzés hívei hiszik, hogy a programok megbízhatóbbak, ha a típusokat ellenőrzik, míg a dinamikus típusellenőrzés hívei arra hivatkoznak, hogy a leszállított programok használata önmagában bizonyítja a program megbízhatóságát, és kevés a programhiba.
Az erősen típusos nyelvek (mint az ML és a Haskell) pártfogói szerint minden programhibát úgy kell tekinteni, mint típushiba, ha jól meghatározott típusokat használnak a programozók, és a fordító jól következtet.
A statikus típusok használatának egyik következménye, hogy a fordított kód gyorsabban végrehajtható. Ha ugyanis a fordító pontosan tudja az adattípust, akkor már deklaráláskor lefoglalja a szükséges memóriaterületet, ahonnan az kivételes esetben csordulhat csak ki, és így a gépi kód hatékonyabb lesz. Az újabb statikusan típusos nyelvek fordítói ezt a kézenfekvő lehetőséget ki is használják. Néhány dinamikusan típusos nyelv – mint a CommonLisp – opcionálisan megengedi a típusok meghatározását, éppen az optimalizálás elősegítése miatt . A statikusan típusos nyelvek esetében ez adott. Részletesebben lásd: fordítók optimalizálása.
Azok a statikusan típusos nyelvek, amelyekben hiányzik az interfésztípus – mint a Java –, igénylik a programozóktól, hogy előbb határozzák meg a típusokat, mielőtt azt egy metódus vagy függvény használná. Ez egy kiegészítő információ a program számára, és a fordító nem engedi meg a programozónak, hogy megváltoztassa, vagy ne vegye ezeket figyelembe. Végül is, egy nyelv lehet statikusan típusos anélkül, hogy igényelne típusmeghatározásokat, tehát ez nem következménye a statikus típusosságnak.
Erős és gyenge típusosság
szerkesztésTípusok és a polimorfizmus
szerkesztésAlapvető típusok
szerkesztés- Elemi adattípusok: a legegyszerűbb típusok, amelyek csak individuálisak, egy értéket tartalmazhatnak.
- Egész típusok: az egész számok és a természetes számok típusai
- Racionális típusok: valódi törtek, például 1/3 vagy 3/5 típusa
- Lebegőpontos típusok: a különböző lebegőpontos számábrázolásokhoz (valós számokhoz) tartozó típusok
- Összetett adattípusok: a típus elemi típusokból épül fel, például Rekord. Absztrakt adattípusoknak vannak összetett adattípusú és interfész adattípusú jellemzőik is, attól függően, minek tekintjük őket.
- Altípus
- Származtatott adattípus
- Rekurzív adattípus
- Funkció adattípusok, például bináris funkciók
- Univerzális mennyiségi típusok, például paraméterezett típusok
- Egzisztenciális mennyiségi típusok, például modulok
Speciális típusok
szerkesztés- osztály – az objektumorientált programozásban egy objektum
- interfész – adatfüggőség típusa
- protokoll – egy kommunikációs csatorna típusa
- kind – a típus típusa
- használati eset (use case) – a környezet és a rendszer közötti interakció típusa
- réteg (layer) – egy metódus egy osztályának végrehajtási környezetének típusa az objektumorientált programozásban
Kompatibilitás, ekvivalencia, helyettesíthetőség
szerkesztésA kompatibilitás és ekvivalencia kérdése nagyon bonyolult és ellentmondásos, és közvetlenül kapcsolódik a helyettesíthetőség kérdéséhez; más szavakkal: ha adott A típus és B típus, akkor azok azonos vagy kompatibilis típusúak? Használható-e A értéke helyett B értéke?
Ha A kompatibilis B-vel, akkor A B egyik altípusa (de fordítva ez nem minden esetben igaz!) – mondja ki a Liskov-féle helyettesítési elv.
A típuskonverzió használatával valósítható meg, hogy ha egy típus kompatibilis egy másikkal, akkor behelyettesítjük. A típuskompatibilitás meghatározására két elmélet létezik:
- Elnevezési kompatibilitás: ekkor a két változó csak akkor ekvivalens, ha az egyik megjelenik a másik deklarációiban, és ott azonos típusnevet használnak
- Strukturális kompatibilitás: ekkor a két változó struktúrája identikus, megfeleltethető.
Az ekvivalencia és a kompatibilitás fogalmak azonos értelműek.
A két megközelítésnek különböző konkrét variációi léteznek, a legtöbb (programozási) nyelv ezek különböző kombinációit alkalmazza.
Jegyzetek
szerkesztés- ↑ Parnas, Shore & Weiss 1976.
- ↑ Shaffer, C. A.. Data Structures & Algorithm Analysis in C++, 3rd, Mineola, NY: Dover (2011). ISBN 978-0-486-48582-9
Források
szerkesztés- Wallace Wang: Beginning programming for Dummies, ISBN 978-0-470-10854-3
- P.S. Deshpande-O.G. Kakde: C & Data Structures, ISBN 1-58450-338-6
- Egyszerű adattípusok