4.1. Eljárások
Ha hosszú programot kell írnunk, érdemes a programot részekre tagolni, így áttekinthetőbb lesz a munkánk, nem kell mindent a Main után bezsúfolni. A program több pontjáról is hivatkozhatunk egy eljárásra, így azt elég egyszer megírni.
Amikor pl. számokat állítunk elő véletlenszerűen, majd kiíratjuk a képernyőre és később rendezve is szeretnénk kiírni, akkor a kiíratást célszerű eljárásban megírni, és hivatkozni rá mindkét esetben. Az eljárások részfeladatokat láthatnak el: kiírást, rendezést, keresést stb. Az eljárásra a programban a nevével hivatkozhatunk, amely után () -eket kell tenni. A zárójelek között lehetnek paraméterek.
Az eljárásnak van:
- neve (ez egy azonosító),
- visszatérési értéke, amely csak
voidlehet, - törzse, amely tartalmazza az utasításokat,
- és lehetnek paraméterei.
Az eljárást a static void Main(string[] args) elé vagy után is tehetjük.
Például:
Az eljárások tehát mindegy milyen sorrendben követik egymást, csak az a fontos, hogy ugyanabba az osztályba (class) kerüljenek. Sőt nem csak a főprogramnak lehet alprogramja (eljárása), hanem eljárásnak is lehet eljárása. Ha tehát a főprogramra úgy tekintünk, mint egy szülőre, akkor az általa használt eljárások vagy függvények az ő gyerekei, ezen alprogramok által használt eljárások, függvények pedig már a főprogram unokái. Természetesen ezek a mélységükben tagozódó eljárások (függvények) tovább fokozhatók, de a mi programjainkhoz legfeljebb "unoka" szintű alprogramokra van szükség.
A változó használata viszont bonyolultabbá válik, mivel az eljárások nem ismerik egymás vagy a főprogram változóit. A változók hatásköre csak az őket tartalmazó eljárásra vonatkozik. Ha azt szeretnénk, hogy egy változót minden eljárás ismerjen, akkor közvetlenül az adott osztály megnevezése után deklaráljuk és írjuk a típus elé: static.
class Program
{
static int a = 0; // Osztályszintű változó
static void Main(string[] args)
{
// Utasítások
Eljaras();
}
static void Eljaras()
{
a = 10;
}
}
Az eljárások alkalmazása során megismerkedünk a dátum (DateTime) típussal is.
34. feladat
Kérjük be a legközelebbi születésnap dátumát (év, hó, nap), majd írjuk ki, hogy hány nap van még a születésnapig és az év végéig! Használjunk eljárásokat az adatbekéréshez és a kiíratáshoz! Név: napok
Megoldás:
namespace napok
{
class Program
{
static DateTime evVege, szulNap;
static int ev, ho, nap;
static void adatbekeres()
{
Console.Write(" év :");
ev = Convert.ToInt32(Console.ReadLine());
Console.Write(" hónap: ");
ho = Convert.ToInt32(Console.ReadLine());
Console.Write(" nap: ");
nap = Convert.ToInt32(Console.ReadLine());
}
static void kiiratas()
{
DateTime maiDatum = DateTime.Now;
evVege = new DateTime(maiDatum.Year, 12, 31);
Console.WriteLine(" A mai dátum és idő: {0}", maiDatum);
Console.WriteLine(" Év végéig {0} nap van még", evVege.Subtract(maiDatum).Days);
Console.WriteLine(" A szülinapig {0} nap van még", szulNap.Subtract(maiDatum).Days);
}
static void Main(string[] args)
{
Console.WriteLine(" Kérem a legközelebbi szülinap dátumát ");
adatbekeres();
szulNap = new DateTime(ev, ho, nap);
kiiratas();
Console.ReadKey();
}
}
}
Azokat a változókat, amelyeket az eljárásokban is szeretnénk használni, már közvetlenül a class után deklaráltuk. Az evVege és a szulNap két dátum típusú változó, amelyet a programban bárhol használhatunk.
A szulNap = new DateTime(ev, ho, nap); segítségével létrehoztuk a változó egy konkrét példányát.
A maiDatum változót csak a kiiratas eljárásban használjuk, ezért itt deklaráltuk, de megtehettük volna a program elején is. A DateTime maiDatum = DateTime.Now; deklarálja a maiDatum nevű változót, és kezdőértékként beállítja a mai nap dátumát a Now-val. Az evVege.Subtract(maiDatum).Days kifejezés megadja a mai dátum és az év vége közti különbséget napokban. Két dátum között eltelt időt számolhatunk órákban (Hours), percekben (Minutes), másodpercekben (Seconds), sőt akár ezredmásodpercekben (Milliseconds) is.
35. feladat
Kérjünk születési dátumot (órára pontosan) majd számoljuk és írjuk ki, hogy hány napos és hány órás az illető! A kiírásnál használjuk a könnyebb olvashatóság miatt a hármas tagolást (ezres csoportosítást)! Név: szülinap
Megoldás:
namespace szulinap
{
class Program
{
static int i = 0;
static string szoveg = "", szoveg1 = "";
static void fordit()
{
szoveg1 = "";
for (i = szoveg.Length - 1; i >= 0; i--)
{
szoveg1 = szoveg1 + Convert.ToString(szoveg[i]);
}
szoveg = szoveg1;
}
static void szokoz()
{
szoveg1 = "";
for (i = 0; i < szoveg.Length; i++)
{
szoveg1 = szoveg1 + Convert.ToString(szoveg[i]);
if ((i + 1) % 3 == 0) szoveg1 = szoveg1 + " ";
}
}
static void visszaForditKiir()
{
for (i = szoveg1.Length - 1; i >= 0; i--)
{
Console.Write(szoveg1[i]);
}
}
static void Main(string[] args)
{
DateTime szulNap = new DateTime();
int ev, ho, nap, ora, perc, mp, oraszam;
Console.Write(" Kérem a születési évet: ");
ev = Convert.ToInt32(Console.ReadLine());
Console.Write(" Kérem a hónapot: ");
ho = Convert.ToInt32(Console.ReadLine());
Console.Write(" Kérem a napot: ");
nap = Convert.ToInt32(Console.ReadLine());
Console.Write(" Kérem az órát: ");
ora = Convert.ToInt32(Console.ReadLine());
perc = 0;
mp = 0;
szulNap = new DateTime(ev, ho, nap, ora, perc, mp);
szoveg = Convert.ToString(DateTime.Now.Subtract(szulNap).Days);
fordit();
szokoz();
Console.Write(" Ennyi napos vagy: ");
visszaForditKiir();
Console.WriteLine();
oraszam = Convert.ToInt32(Math.Round(DateTime.Now.Subtract(szulNap).TotalHours));
szoveg = Convert.ToString(oraszam);
fordit();
szokoz();
Console.Write(" Ennyi órás vagy: ");
visszaForditKiir();
Console.ReadKey();
}
}
}
A főprogramban elvégeztük az adatbekérést, de a percet és másodpercet 0-nak vesszük, mivel nem valószínű, hogy valaki tudja ilyen pontossággal a születési idejét. Dátum típust viszont vagy napra, vagy másodpercre pontosan lehet csak használni a C#-ban, tehát ha az órákat is tudni akarjuk pontosan, akkor fel kell venni a perceket és másodperceket is.
A szoveg = Convert.ToString(DateTime.Now.Subtract(szulNap).Days); sorban szöveggé alakítjuk a jelenlegi időpont (DateTime.Now) és a születés (szulNap) között eltelt időt napokban számolva.
A program nehezebb része azonban a kiíratás tagolásában rejlik. Több módszer közül választhatunk. Az egyik a programban látható: először megfordítjuk a szöveget és hátulról, azaz jobbról balra haladva egy ciklusban minden 3. karakter után beillesztünk egy szóközt. A fordit eljárásban a szoveg1-be kerül a szoveg fordítottja, a szokoz eljárásban pedig beillesztjük a megfelelő helyekre a szóközöket. A visszaForditKiir eljárásban pedig a szoveg1-et írjuk ki fordítva, most már szóközökkel a megfelelő helyeken.
Paraméterátadás
Eddig csak olyan eljárásokkal foglalkoztunk, amelyeknek nem voltak paraméterei, ha viszont az eljárás kimenő adatokat is előállít, akkor szüksége van bemenő adatokra, paraméterekre is. Vannak tehát olyan esetek, amikor szükség van az eljárás és a főprogram, vagy az eljárások között paraméterek, adatok átadására. Ilyenkor a főprogramban az eljárás neve után zárójelben meg kell adni azoknak a változóknak a nevét vesszővel elválasztva, amelyeket paraméterként akarunk átadni. Az eljárásban pedig az eljárás neve után, az eljárás fejrészében zárójelben megadjuk a változók típusát és nevét.
Pl.:
// Formális paraméterlista
static void eljaras(int a, int b)
{
// Eljárás törzse, ahol a-t és b-t használjuk.
}
static void Main(string[] args)
{
// A főprogram, ahonnan az eljárást meghívjuk.
eljaras(a, b); // Aktuális paraméterlista
}
Az eljárásban használt a és b egész típusú változóknak az eljáráson belül más nevet is adhatunk, nem kell megtartani a főprogramban használt neveket. Az eljárásban, alprogramban szereplő változók listáját formális paraméterlistának nevezzük. Az eljárás meghívásának helyén felsorolt változókat pedig aktuális paraméterlistának nevezzük.
Fontos szabály, hogy aktuális és a formális paraméterlistában ugyanannyi adat legyen, tehát annyi adatot adjunk át az eljárásnak, amennyit az vár. Továbbá az átadott adatok típusainak is meg kell egyezni. Illetve az automatikus típuskonverzió miatt az alprogramnak átadott egész típust valósként használhatjuk az alprogram törzsében. C#-ban akár tömböt is átadhatunk paraméterként.
36. feladat
Jók-e az alábbi paraméterlisták?
| Aktuális paraméterlista | Formális paraméterlista | Jó | Nem jó |
|---|---|---|---|
int a=0, f=0; (a,f) |
(int d, int b) |
||
int x=0; double y=0; (x,y) |
(int x, int y) |
||
string a=""; int y=0; (a,y) |
(string nev, int y) |
||
char k=' '; bool v=true; (k,v) |
(string k, bool v) |
||
double x=0; byte y=0; (x,y) |
(int x, byte y) |
||
string x=""; double y=0; (x,y) |
(char x, double y) |
||
int x=0; string szo=""; (x, szo) |
(int a, string szoveg) |
||
bool x=false; double y=0; (x,y) |
(bool x, double z) |
Kattints ide a megoldásért!
| Aktuális paraméterlista | Formális paraméterlista | Jó | Nem jó |
|---|---|---|---|
int a=0, f=0; (a,f) |
(int d, int b) |
[x] | [ ] |
int x=0; double y=0; (x,y) |
(int x, int y) |
[ ] | [x] |
string a=""; int y=0; (a,y) |
(string nev, int y) |
[x] | [ ] |
char k=' '; bool v=true; (k,v) |
(string k, bool v) |
[ ] | [x] |
double x=0; byte y=0; (x,y) |
(int x, byte y) |
[ ] | [x] |
string x=""; double y=0; (x,y) |
(char x, double y) |
[ ] | [x] |
int x=0; string szo=""; (x, szo) |
(int a, string szoveg) |
[x] | [ ] |
bool x=false; double y=0; (x,y) |
(bool x, double z) |
[x] | [ ] |
C#-ban a paraméter átadása kétféle módon történhet:
- Érték szerinti paraméter átadás: Valójában csak az eredeti változó egy másolatát adjuk át. Ilyenkor mindent érték szerint adunk át, ezért nincs külön kulcsszó. Pl.:
eljaras(a,b); - Referencia szerinti paraméter átadás: A
refvagy azoutkulcsszót kell használnunk. Ekkor arefkulcsszót kell a változó neve előtt megadni, ill. az alprogram fejrészében a változó típusa és neve előtt. Ebben az esetben változókat átadhatunk úgy is az alprogramnak, hogy később az itt kiszámolt (előállított) értékeket a főprogramban is tudjuk majd használni.
37. feladat
Számolja ki egy henger felszínét és térfogatát, majd írja ki a képernyőre 3 tizedes pontossággal! A kör területét és kerületét eljárásban számítsa ki, majd adja vissza a kiszámolt értékeket a főprogramnak! Név: Henger
Megoldás:
namespace Henger
{
class Program
{
static void kor(double r, ref double t, ref double k) // (out double t, out double k)
{
// k = 0;
// t = 0;
t = r * r * Math.PI;
k = 2 * r * Math.PI;
}
static void Main(string[] args)
{
double r = 0, m = 0, V = 0, A = 0, t = 0, k = 0; // t, k;
Console.Write("Kérem az alap sugarát: ");
r = Convert.ToDouble(Console.ReadLine());
Console.Write("Kérem a magasságot: ");
m = Convert.ToDouble(Console.ReadLine());
kor(r, ref t, ref k); // kor(r, out t, out k);
V = t * m;
A = 2 * t + k * m;
Console.WriteLine(" A henger térfogata: {0:0.000}", V);
Console.WriteLine(" A henger felszíne: {0:0.000}", A);
Console.ReadKey();
}
}
}
Mint látható, a kör kerületét és területét a főprogramban deklaráltuk, ott adtunk kezdőértéket k-nak és t-nek. A kor nevű eljárás meghívásakor átadjuk a sugarat, de azt nem hozzuk vissza a főprogramba, ezért nem kell elé a ref kulcsszó. A program akkor is helyesen működne, ha a ref helyett az out szerepelne minden esetben, és az eljárás elején adnánk kezdőértéket k-nak és t-nek. Ebben az esetben a főprogram elején nem kell – de lehet – kezdőértéket adni a kerületnek és a területnek. Ez annyiban lenne más, hogy az out-tal jeleznénk: a változó csak kimenő értékkel rendelkezik, és a bemenő értéke lényegtelen. Ha az out-os módot választjuk, akkor a program megfelelő soraiban a // jel utáni részt kell értelemszerűen behelyettesíteni.