Típusok, változók, objektumok, példányok
Mindenek előtt tisztázzunk pár fogalmat!
Az erősen típusos nyelvekben, így a C#-ban is a típusok határozzák meg, hogy egy adat mit reprezentál és milyen értékeket vehet fel. Eddig mindössze négy alapvető adattípust tanultunk:
int
- Egész számok
float
- Tört számok
bool
- Logikai
string
- Szöveg
A fenti típusok mind a C# nyelv részei, azaz beépített típusok. Más néven primitíveknek is nevezzük őket. Ezek a legelemibb építőköveink ahhoz hogy reprezentáljunk valami komplikáltabb adatot.
Szóval pontosítsunk vagy ahol kell vezessünk be pár fogalmat:
A típus írja le, mit reprezentál egy adott objektum és milyen értékeket vehet fel.
A változó tárol egy adott értéket egy adott típusból.
Ha egy típusból létrehozunk egy konkrét példányt, azt objektumnak nevezzük.
Az objektumokra gondolhatunk úgy is, mint az értékekre, amiket a változók tárolni tudnak. A változó csak egy névvel ellátott tároló, ami benne van az a konkrét létező adat az objektum.
int i = 5; // Új int példányt (5) hozunk létre és eltároljuk az i válltozóban.
float f = 22.1f; // Új float példányt (22.1f) hozunk létre és eltároljuk az f-ben.
bool b = true; // Új bool példányt (true) hozunk létre és eltároljuk az b-ben.
string s = "hello"; // Új string példányt ("hello") hozunk létre és eltároljuk az s-ben.
A fenti példában új objektumokat vagy más szóval új példányokat hoztam létre és megfelelő típusú változókban eltároltam őket.
Primitív típusok
A primitívek vagy más szóval beépített típusok, a legegyszerűbb adatokat reprezentálják egy programozási nyelvben.
Közülük eddig az int
, float
, bool
és string
típusokat vettük, de sok egyéb is létezik, de minden bonyolultabb típus lebontható ezen elemi építőkövekre.
A komplex típusok több nevesített elemből épülnek fel. Ezen elemek lehetnek primitívek vagy egyéb összetett típusok is. Így építkezhetünk felfelé, egyszerűbb típusokból leírva bonyolultakat.
Összetett típusok
Egy típus egy valós létezőt ír le, modellez.
Egy összetett típus alacsonyabb szintű komponenseit a típus field-jeinek vagy mezőinek, nevezzük.
Nézzünk két példát összetett típusra:
Kutya (Összetett típus)
- Név (
string
) - Harap-e? (
bool
) - Sebessége (
float
) - Kora (
int
)
Kobold (Összetett típus)
- Életei száma (
int
) - Ellenfél-e? (
bool
) - Sebzése (
int
) - Lőtáv (
float
)
Vizsgáljuk meg jobban a ezt a kutya típust. Ezt a háziállatot szeretnénk reprezentálni a programunkban, így egy Kutya nevű összetett típust hozunk létre.
Milyen mezők szerepelnek benne? Ez fogós kérdés. Egy kutyát nagyon sok minden írhat le. Az, hogy mi milyen változókat veszünk fel, az attól függ, mire használjuk az típust, a kutya milyen aspektusai érdekelnek minket.
A. Ha például egy Állatorvosi adatbázishoz készült el a kutya összetett típus, akkor valószínűleg szükség lesz olyan mezőkre, hogy mi a kutya neve, ki a gazdája, mikor született és milyen oltásokat kapott.
B. Ha egy játékot írunk, amiben a kutya egy ellenfél, akkor mindezek az információk valószínűleg nem érdekelnek minket, de az igen, hogy milyen gyorsan fut és mennyire kell közel menni, hogy támadjon.
(Persze egy kutya különböző játékokban számtalan módon szerepelhet és mindenhol egészen más és más field-ek írhatják le.)
C. Mi most tételezzük fel, hogy a Kutya típusunk a következő mezőket fogja tartalmazni:
string name; // A kutya neve
int age; // Kora
float speed; // Mekkora a sebessége
bool doesBite; // Harap-e?
Ezután a kutyából számtalan példányt hozhatunk létre különböző beállításokkal.
🐕 Blöki, kora: 5 év, sebessége: 12 m/s, és nem harap.
🐩 Fifi, kora: 3 év, sebessége: 10.5 m/s, és ő viszont harap.
… És így tovább.
A C# összetett típusai
A C#-ban alapvetően 2 fő összetett típus létezik: osztály és struktúra, azaz class és sctuct.
Ezen két típus közti különbségekkel később foglalkozunk majd: Referencia- és értéktípusok. Egyelőre kezeljük úgy őket, mintha pontosan ugyanaz lenne a kettő.
(Létezik még a Recod is mint harmadik fajta összetett típus, ami részben az osztályra hasonlít részben a struktúrára, de ezen a kurzuson nem foglalkozunk vele.)
string name; // A kutya neve
int age; // Kora
float speed; // Mekkora a sebessége
bool doesBite; // Harap-e?
Összetett típus definiálása, példányosítás és field-jeinek elérése
Definiáljuk a kutya struktúrát (struct
kulcsszóval) és a kobold osztályt (class
kulcsszóval)!
struct Dog
{
public string name; // Neve
public int age; // Kora
public float speed; // Sebessége
public bool doesBite; // Harap-e?
}
class Goblin
{
public int health; // Életei
public bool isEnemy; // Ellenfél-e?
public int damage; // Sebzése
public float range; // Lőtáv
}
Egyelőre minden field elé írjuk oda, a public
kulcsszót. Később majd megtanuljuk, mire való:
Tagváltozók és -metódusok láthatósága
Amikor új típusokat definiálunk, azok mindig a legfelső szintű utasítások alá kell kerüljenek vagy külön file-ba.
Szóval a későbbieket a fenti típusdefiníciók FELETTI sorokba írjuk!
Ezután létrehozhatunk kutya és kobold példányokat. Ezt nevezzük a típus példányosításának:
Egy típusból új példányokat a new
kulcsszóval készítünk el. (Kivétel primitívek)
// Objektumok default értékekkel
Dog dog = new Dog();
Goblin goblin = new Goblin();
// Kutya definiált értékekkel:
Dog blöki = new Dog { name = "Blöki", age = 5, speed = 12, doesBite = false };
Dog fifi = new Dog { name = "Fifi", age = 3, speed = 10.5f, doesBite = true };
Az összetett típus (osztály, struktúra) tehát nem egyenlő a konkrét adattal. Az csak egy váz, egy tervrajz, ami leírja milyen adatok kellenek egy fajta létező leírásához.
Az osztály vagy struktúra példánya: az objektum az ami rendelkezik adatokkal.
Az összetett típusú objektum belső elemeit a pont (.) operátorral érjük el.
dog.name = "Toto"; // Kutya objektum nevének beállítása
dog.age = 3; // Kutya objektum korának beállítása
dog.age += 1; // Kutya objektum korának növelése
int age = dog.age; // Kutya objektum korának kiolvasása
Az összetett típusok objektum belső állapota a field-jeinke összessége, ami időben változhat.
Pl.: Egy konkrét kutya objektum kora vagy egy konkrét goblin objektum élete változhat.
Szerializálható osztályok és struktúrák
Ha egy önállóan definiált összetett osztályt szeretnénk használni Egy MonoBehaviour szkript beállításaként, akkor azt szerializálhatóvá kell tennünk a [System.Serializable]
attribútimmal, amit az osztály
vagy struct
kulcsszavak elé írunk.
[System.Serializable] // Kötelező kiírni, ha szeretnénk menteni a típust Scene-be
struct Dog
{
[SerializeField] public string name; // Neve
[SerializeField] public int age; // Kora
[SerializeField] public bool doesBite; // Harap-e?
}
(A [SerializeField]
attribútum kiírása elhagyható lennne publikus mezők esetén.
Bővebben Tagváltozók és -metódusok láthatósága )
Ekkor ha a [SerializeField]
mezőként használjuk a Dog
típust egy MonoBehaviour osztályban, akkor az adott típus is editorfelületről beállítható lesz és ez a beállítás automatikusan mentődik a scene-nel lesz.
class TestMonoBehaviour : MonoBehaviour
{
[SerializeField] Dog dog1;
[SerializeField] Dog dog2;
[SerializeField] Dog dog3;
}
Az Istpector felületen ezek a változók elgördülő menüként jelennek meg:
A szerializáció fogalmáról bővebben.: Játékállapot mentése, Szerializáció