Ugrás a tartalomhoz

Adattípus

Ellenőrzött
A Wikipédiából, a szabad enciklopédiából
A Python 3 adattípus-hierarchiája

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és]

A 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és]

Az 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és]

A 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és]

A 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és]

A 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és]

Típusok és a polimorfizmus

[szerkesztés]

Alapvető 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.

Speciális típusok

[szerkesztés]

Kompatibilitás, ekvivalencia, helyettesíthetőség

[szerkesztés]

A 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:

  1. 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
  2. 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]
  1. Parnas, Shore & Weiss 1976.
  2. Shaffer, C. A.. Data Structures & Algorithm Analysis in C++, 3rd, Mineola, NY: Dover (2011). ISBN 978-0-486-48582-9 

Források

[szerkesztés]

Kapcsolódó szócikkek

[szerkesztés]