tanulas_programozz_c_nyelven - Fehér Krisztián honlapja

Fehér Krisztián weboldala
Fehér Krisztián weboldala
Tartalomhoz ugrás

tanulas_programozz_c_nyelven

Fehér Krisztián: Programozz C nyelven!

Tartalomjegyzék
1 Előszó
2 Ismerkedés a C nyelvvel
2.1 C fejlesztőeszköz beszerzése
2.2 Az első programunk
3 Programozási alapismeretek
3.1 Hogyan készül el egy futtatható állomány?
3.1.1 Az előfeldolgozó
3.2 Megjegyzések a kódban
3.3 Alapvető szintaktika
3.4 Adattípusok, változók
3.4.1 Típuskonverziók
3.4.2 Szöveges változók
3.5 Adatok kiírása a képernyőre
3.6 Adatbeolvasás billentyűzetről
3.6.1 Elemi adatok beolvasása
3.6.2 Komplex szövegek beolvasása
3.6.3 Karakterek beolvasása
3.7 Tömbök
3.7.1 Egydimenziós tömbök
3.7.2 Többdimenziós tömbök
3.7.3 A tömbök anatómiája
3.8 Speciális és saját típusok
3.8.1 Struktúra típus
3.8.2 Union típus
3.8.3 A void típus
3.9 Mutatók
3.10 Operátorok és kifejezések
3.10.1 Klasszikus aritmetikai operátorok
3.10.2 Logikai operátorok
3.10.3 Értékadó operátorok
3.10.4 Precedencia szabály és végrehajtási sorrend
3.10.5 A sizeof operátor
4 Utasítások
4.1 Elágazási szerkezetek
4.2 Ciklusok
5 Függvények, eljárások
5.1 Előrevetett deklaráció, prototípus
5.2 Egyéb tudnivalók
5.3 A main függvény specialitásai
6 Adatkonverziók
6.1 Számok sztringekké alakítása
6.2 Sztringek számokká alakítása
6.3 Példaprogram konvertálásra
7 Dinamikus memóriakezelés, dióhéjban
8 Fájlkezelés
8.1 Szöveges fájlok
8.2 Bináris fájlkezelés
8.3 Pozícionálás a fájlban, fájlvége detektálása
9 Klasszikus rendezési algoritmusok
9.1 Buborék rendezés
9.2 Rendezés cserével
9.3 Közvetlen beszúrásos rendezés
9.4 Shell rendezés



1 Előszó

A programozás korunk írástudása. Egyre többen hangoztatják, hogy a fiataloknak kötelezően tanulniuk kellene számítógépes programozást, legalább alapszinten.
A C programozási nyelv a modern programozási nyelvek alapját képezi. Elsajátítása után a C++, Java, Kotlin, C# és még számos meghatározó, modern programozási nyelv megtanulása gyerekjátékká válik, mivel ezen nyelvek logikája a C nyelven alapul.
A C nyelv szerethető nyelv, hasonlóan a Commodore 64 BASIC-hez, melyre sokan nosztalgiával tekintenek a mai napig. (Ezen sorok írója is a BASIC nyelvet ismerte meg elsőként.) A C nyelv az IBM PC-k térhódításának tanúja, így hosszú múltra is tekint vissza.
A C mégsem elavult nyelv. Napjaink legmodernebb, legnagyobb informatikai kihívását jelentő kutatásainak alapnyelve most is. Ide tartozik a CUDA C, vagy a TensorFlow is, melyek a high-performance computing és a machine learning témakörök éllovasai.
A C nyelv a rendszerközeli, nagyteljesítményű alkalmazások programozási nyelve. Sebességének egyik magyarázata, hogy a C nyelven írt programok közvetlenül gépi kódra fordított kódok lesznek, köztes végrehajtási rétegek nélkül. Így a lehető legnagyobb végrehajtási sebesség érhető el.
Megtanulását elősegíti és legitimálja az is, hogy a C++ objektumorientált világa is teljesen kompatibilis a C nyelvvel, így a megszerzett tudás, algoritmusaink a későbbiekben is könnyen felhasználhatóak lesznek.
C-ben teljesértékű alkalmazásokat írhatunk. Ennek az alapjait lehet megtanulni ebből a leírásból.
A leírás ismeretanyaga elsősorban a szöveges képernyőt használó, ún. konzolalkalmazások elkészítését teszi lehetővé, ami az előszobája a grafikus, ablakozó alkalmazások fejlesztésének. Ez utóbbi kívül esik a leírás keretein.
A C nyelv szabvánnyal rendelkező programozási nyelv, amit az ANSI C foglal magában. Ebben a leírásban elsősorban a Microsoft C implementációját fogjuk alapul venni, ez sok esetben a szabványos függvények továbbfejlesztett, biztonságosabb, kiforrottabb változatait is tartalmazza.
Teljeskörű referencia nyújtása nem célja e leírásnak, a cél a kompakt, gyakorlatias tudás átadása.
A leírás példaprogramjai az alábbi webhelyről tölthetőek le: https://github.com/fkhydra/C_programozas



2 Ismerkedés a C nyelvvel

2.1 C fejlesztőeszköz beszerzése

A C nyelvhez használhatunk minden C++ fordítót, mely a szabványos, azaz ANSI C-t támogatja. A legegyszerűbb a Microsoft Visual Studio Community Edition fejlesztőrendszert beszereznünk, mely támogatja a szabványt és teljesen ingyenes is. Ennek a leírásnak a példái is ezzel készültek. A program az alábbi webhelyről tölthető le:

https://visualstudio.microsoft.com/

A telepítés során arra figyeljünk, hogy  a klasszikus C++-alapú asztali alkalmazás fejlesztőkészletét is feltelepítsük! Másra gyakorlatilag nem is lesz szükség.



2.2 Az első programunk

A legegyszerűbb C nyelvű programot a Visual studio kezdőlapján a ’Create a new project’ lehetőség kiválasztásával tudjuk létrehozni.



Ezután válasszuk ki a ’Console app’ lehetőséget!



Következő lépésként meg kell adnunk projektünk nevét, helyét (amennyiben nem a felajánlott mappába szeretnénk elmenteni a projektfájlokat), illetve az ún. solution (magyarul: megoldás) nevét. Ez utóbbi azért szerepel külön is, mert az egyes programokat egy nagyobb projekt-tárolóba is szervezhetjük, ami több, összetartozó programot tartalmazhat: ez a solution.



Ezt követően megnyílik a kódszerkesztőablak, az alábbi alapértelmezett C++ kóddal.



Ezt a kódot gyorsan ki is cserélhetjük az alábbi C nyelvű kódra (PELDA_01.C), ez lesz az első példaprogramunk. Mint látjuk, mindössze pár sorból áll, mégis egy teljesértékű (konzol-) alkalmazást takar:


#include <stdio.h>

int main()
{
   printf("Hello World!\n");
   return 0;
}


Nézzük meg, pontosan mit is csinál a program, ismerkedjünk meg a forráskód szerkezetével!
Az alábbi sor az ún. szabványos be- és kimenet kezelésére használatos függvényekhez biztosít hozzáférést. A szabványos be- és kimenet alapértelmezetten a képernyő lesz.


#include <stdio.h>


Az stdio.h egy ún. fejlécállomány (angolul: header, lásd később).
Minden C programnak van egy ún. belépési pontja. Ez az a hely a forráskód szintjén, ahol a program végrehajtása ténylegesen elkezdődik.
Konzolalkalmazások esetében ez kötelezően a main függvény (Windows alkalmazások esetében a WinMain).


int main()


Ez a leírás kizárólag konzolalkalmazásokat fog bemutatni.
A két kapcsos zárójel {} a főprogram fő kódblokkjának határát jelöli ki. Minden, amit ezen belülre írunk, a főprogramhoz fog tartozni.
A főprogram mindössze két utasítást tartalmaz: egy ún. függvényhívást tartalmaz, ami a szabványos kimenetre kiírja a ’Hello world!’ szöveget, ill. a main függvényből történő kilépést.


{
   printf("Hello World!\n");
   return 0;
}


A return 0; a program main függvényének az ún. visszatérési értéke lesz. Ennek ezen a helyen elsősorban formai szerepe van, de jó tudni, hogy a 0 értéket általában a „minden oké” eredménynek lehet megfeleltetni.
Fontos megemlíteni, hogy a Visual Studio ikonsorában található futtatási beállításokkal, az ’x64’ lehetőség kiválasztásával 64 bites kódot is generáltathatunk. Amennyiben semmi nem szól ez ellen, használjuk ezt a beállítást! A ’Debug’ lehetőség elsősorban hibakeresésre ajánlott, míg a ’Release’ változattal kisebb méretű és sokkal gyorsabb futási sebességű, „végleges” változatot is előállíthatunk programjainkból.


Válasszuk ki a ’Release’ – ’x64’ lehetőséget, majd a zöld lejátszás szimbólumra kattintva nyomban ki is próbálhatjuk a kész programot. Ekkor egy konzolablak jelenik meg, mely valóban kiírja a ’Hello World!’ üzenetet. A konzolablakot egy tetszőleges billentyű lenyomásával zárhatjuk be.





3 Programozási alapismeretek

3.1 Hogyan készül el egy futtatható állomány?

A begépelt programkódot a számítógép nem képes végrehajtani, azt előbb le kell fordítani ún. gépi kódra. Ezt a folyamatot végzi a fordítóprogram, angol nevén compiler. A folyamat maga a fordítás.

Ez valójában egy többlépcsős folyamat, melynek főbb fázisai:
• előfeldolgozó műveletek (preprocessing)
• tárgykód előállítása
• szerkesztés (linking).

Az ún. előfeldolgozó (preprocesszor) egyszerű szövegszerkesztési műveleteket végez a forráskódon.
Az érdemi fordítás a tárgykód előállítása. Ezek a fájlok .OBJ kiterjesztéssel rendelkeznek. Ez egy köztes állapot, ami rengeteg hivatkozást is tartalmaz.
Utolsó lépésként a szerkesztés során minden hivatkozást fel kell oldani és elkészül a végleges kód, aminek .EXE lesz a kiterjesztése. Ez már közvetlenül futtatható állomány, gépi kódban tárolva.

3.1.1 Az előfeldolgozó

Az előfeldolgozó számára utasításokat, szaknyelven direktívákat adhatunk programjainkban, melyet egy ’#’ jel vezet be. A leggyakrabban használt, legtriviálisabb előfeldolgozó utasítás az #include, mely arra utasítja az előfeldogozót, hogy az utasítás helyére szúrja be egy másik forrásállomány tartalmát.
Első példaprogramunk első sora:

#include <stdio.h>

is ezt a célt szolgálja. Saját fejlécállományok esetén szokás az ”” jelek használata, például:

#include ”sajat.h”

Kis kitérő, de jó tudni, hogy a Visual Studio alapértelmezetten az SDK, azaz a fejlesztőcsomag include könyvtárában keresi a fejlécállományokat. Ha ott nem találja, megpróbálja a projektünk könyvtárában megkeresni. Ezenkívül a projektjeinkhez megadhatunk saját fejlécállományokat tartalmazó könyvtárat is, amit a projekt nevére történő jobb egérgombra kattintással felugró menüben, a ’Properties’ menüpont kiválasztásával megjelenő dialógusablakban tudunk megtenni.



A ’VC++ Directories’ beállításcsoport ’Include Directories’ sorában tudunk megadni egyedi könyvtárakat. Minden új bejegyzést ’;’ jellel zárjunk! Ellenőrizzük, hogy az ablak felső sorában a Configuration/Platform az aktuális futtatási beállításunkra vonatkozik-e! Érdemes lehet az összesre érvényes módon elvégezni a beállításainkat.
Szintén elterjedt előfeldolgozó utasítás a #define, melynek segítségével ún. makrókat használhatunk. A makrók bizonyos műveletek is lehetnek, de alapvetően szöveg helyettesítésre szolgálnak, például kvázi-konstansokat is létehozhatunk velük.
Például a

#define ELETKOR 18

sor után az ELETKOR minden előfordulásakor a megadott érték (18) lesz felhasználva a programkódban. Fontos tudni azonban, hogy ez csak egy egyszerű, statikus szövegcsere lesz, nem pedig igazi változó (lásd később).

3.2 Megjegyzések a kódban

A programkód olvashatóságát, de későbbi újrafelhasználhatóságát is nagyban segíti, ha beleírunk emlékeztető, magyarázó szövegeket. Ezeket nevezik megjegyzéseknek, divatosabb nevükön kommenteknek. Megjegyzéseket egysoros, vagy kétsoros formában is írhatunk a programkódba.
Példák:

//megjegyzés

/*
 megjegyzés
*/

3.3 Alapvető szintaktika

A C kisbetű-nagybetű érzékeny, azaz nem mindegy, hogy egy kifejezést, utasítást, változónevet milyen betűkkel adunk meg. Egy utasítást a ”;” jellel zárunk, így válik egy kifejezés utasítássá.
A kódban kódblokkokat hozhatunk létre a {} jelek segítségével. Ez nem csupán esztétika, hanem nagyban befolyásolja a változók élettartamát és láthatóságát is.
Az alábbi, egymásba ágyazott blokkokban például a valtozo2 nem hivatkozható a befoglaló blokkban, kizárólag a belső kódblokkban hivatkozhatunk rá és csak azon belül „él”:

{
  int valtozo1;
{
 int valtozo2;
}
}

A láthatóság tehát alapesetben „befelé”, az egyre egymásba ágyazott blokkok felé működik.
Kódblokkot használatunk akkor is, ha csak egyetlen utasítás megengedett. Ilyenkor a kódblokk egyetlen logikai utasításnak felel meg. Leggyakoribb példa erre a feltételes operátorok használata:
if( x < 5)
{
utasítás1;
utasítás2;
}

Ne aggódjunk, ha ezen a ponton még nem értünk egy-egy kifejezést, ezekkel a dolgokkal megismerkedünk a továbbiakban!

3.4 Adattípusok, változók

Minden számítógépes program adatokkal dolgozik, melyek elemi adategységei a bonyolultabb műveleteknek és valamilyen tulajdonsággal rendelkeznek. Az adatokat változóknak, a tulajdonságaik összességét típusnak nevezzük. A típus határozza meg, hogy milyen jellegű adatról van szó (egész szám, törtszám) és mennyi helyet foglal a memóriában egyetlen eleme.
A C nyelvben gyakran használt változótípusok:
Típus neve
Értéktartomány
Méret bájtban
bool
false, true
1
char
-128 - 127
1
signed char
-128 - 127
1
unsigned char
0 - 255
1
int
-2147483648 - 2147483647
4
unsigned int
0 - 4294967295
4
short int
-32768 - 32767
2
unsigned short
0 - 65535
2
long int
-2147483648 - 2147483647
4
unsigned long
0 - 4294967295
4
float
3.4E-38 - 3.8E+38
4
double
1.7E-308 - 1.7E+308
8
long double
3.4E-4932 - 3.4E+4932
10
A változókat létrehozásukhoz először is deklarálnunk kell, az alábbi alakban:

TÍPUS NÉV ;

Példa:

int egesz_szam;

A definíció során egyből értéket is adhatunk a változónak:

int egesz_szam = 120;

A kezdőértékadás fontos lépés, ugyanis ha előbb hivatkozunk a változóra, minthogy értéket adnánk neki, akkor általában valamilyen „szemét” lenne a tartalma, ami kerülendő. A modern fordítóprogramok felismerik az ilyen helyzeteket és figyelmeztetést adnak.
Az ún. const típusminőstőt bármelyik változótípus elé odaírhatjuk, ilyenkor egy állandót, konstanst kapunk, melynek tartalmát közvetlenül nem módosíthatjuk.

Példa:

const int allando_szam = 99;

C-ben a szövegek tárolására a char / unsigned char típus használatos, mivel jól lefedi az ANSI karakterkészletet (0-128, ill. 0-255 karatkerkódok).

3.4.1 Típuskonverziók

Felmerül a kérdés, hogy különböző típusú változók interakciója során mi történik. Amennyiben egy kevesebb memóriaterületet igénylő változókkal végzett művelet eredménye (például egy egyszerű értékadás) egy szélesebb értéktartományt felölelő változónak lesz átadva, úgy a kisebb értéktartományú változó automatikusan átkonvertálódik a nagyobbra, értékváltozás nélkül. Fordított esetben viszont értékvesztés léphet fel. Szintén problémákat okozhatnak az egész számokon és a törtszámokon (lebegőpontos számok) végzett műveletek erdményei.
Vegyük az alábbi példát:

int egesz1= 2, egesz2=10;
float eredmeny = egesz1 / egesz2;

2/10 eredménye 0.2, egész számok esetében viszont ilyen nem létezik, hanem kerekítés lesz a dologból, aminek az eredménye bizony 0 lesz, még akkor is, ha az eredményt egy törtszámok tárolására alkalmas változóban tároljuk. Ezt megelőzendő, explicit típuskonverzióval ún. castolással a művelet erejéig lebegőpontos számmá konvertálhatjuk az egész számokat. Az eredmény így már helyes lesz:

float eredmeny = (float)egesz1 / (float)egesz2;

Teljes programkóddal szemléltetve (PELDA_02.C):


#include <stdio.h>
#include <stdlib.h>

int egesz1 = 2, egesz2 = 10;
float eredmeny = (float)egesz1 / (float)egesz2;

int main()
{
printf("%f", eredmeny);
return 0;
}


3.4.2 Szöveges változók

A C nyelvben a szövegeket és a szöveges változókat sztringeknek és karaktersorozatoknak nevezzük.
A karaktersorozatok tulajdonképpen tömbök (lásd hamarosan), melyek char, vagy unsigned char változók sorozatából állnak.
Szöveges változó tipikus deklarációja:

char szoveg[128];

Ezzel egy teljesen üres sztringet kapunk. A szöveges változók utolsó karakere kötelezően a ’0’.
A változónak egyből értéket is adhatunk, sőt, ezt kétféleképpen is megtehetjük.

char szoveg[128] = ”Ez egy szoveg…”

vagy

char szoveg[] = ”Ez egy szoveg…”

A különbség a két megközelítés között az, hogy az első esetben a szting számára 128 karakter lesz lefoglalva, és a megadott szöveg végére kerül egy záró, ún. ’\0’ karakter.
A második esetben a változó mérete pontosan megegyezik a szöveg méretével, plusz a ’\0’ karakter, azaz 17 karakter hosszúságú lesz.

3.4.2.1 Szöveghossz lekérdezése

A szövegek kezelésére a <string.h> fejlécállomány tartalmaz számos függvényt. A legfontosabbakat tekintjük át az alábbiakban.
A strlen függvény a paraméterként megadott szöveges változó hosszát adja vissza, a sorvége ’\0’ karakter nélkül.

size_t strlen(const char *sztring);

3.4.2.2 Szöveg másolása sztringbe

A strcpy függvénnyel egyik sztringet a másikba másolhatunk. Kiválóan alkalmas szöveges változók értékének beállítására is. Fontos, hogy a sorvége ’\0’ karaktert ez a függvény automatikusan elhelyezi a szöveg végére.

strcpy_s(char *cel, rsize_t cel_meret, const char *forras);

3.4.2.3 Két szöveg összekapcsolása

Két szöveget a strcat függvénnyel kapcsolhatunk össze. Ezt szakszóval konkatenálásnak is nevezik.

char *strcat(char *cel, const char *forras);

3.4.2.4 Sztringek összehasonlítása

Ha ellenőrizni szeretnénk két sztring egyezését, a legegyszerűbben az strcmp függvénnyel tehetjük meg ezt.
Visszatérési értéke 0, ha a két szöveg egyezik, egyébként ettől eltérő érték.

int strcmp(const char *sztring1, const char *sztring2);

Példaprogram a fentiek demonstrálására (PELDA_03.C):


#include <stdio.h>
#include <string.h>

char szoveg[] = "Udvozollek, dicso lovag!";
char szoveg2[128];
int szam;

int main()
{
printf("A sztring: %s\n", szoveg);
printf("A szoveg hossza %i\n", (int)strlen(szoveg));
strcpy_s(szoveg2,"Masodik szoveg.");
printf("A masodik sztring: %s\n", szoveg2);
strcat_s(szoveg2," Kiegeszites.");
printf("A masodik sztring kiegeszitve: %s\n", szoveg2);
if(strcmp(szoveg, szoveg2) == 0) printf("A ket sztring egyezik!\n");
else printf("A ket sztring nem egyezik!\n");
return 0;
}


A program kimenete:

A sztring: Udvozollek, dicso lovag!
A szoveg hossza 24
A masodik sztring: Masodik szoveg.
A masodik sztring kiegeszitve: Masodik szoveg. Kiegeszites.
A ket sztring nem egyezik!

3.5 Adatok kiírása a képernyőre

Bár egy program sok mindent tud „csinálni”, azért a legtöbbször valamilyen látható, ember által is értelmezhető eredménye is van a működésüknek.
Ennek egyik fontos eszköze a képernyőre történő kiírás, amire a printf függvény szolgál, melynek általános alakja a következő:

printf( formátumsztring );
printf( formátumsztring, argumentumlista);
int printf(const char *formatumszrting [,argument]...);

A formátumsztring legegyszerűbb esetben csak karaktereket tartalmaz, ahogyan az első példaprogramunkban is láttuk:

printf(”Csak szöveg!”);

Bármit, amit az idézőjelek közé írunk, egyszerűen kiírja a program a képernyőre.
Lehetőség van a kiírt szöveg dinamikussá, a program futásától függővé tenni, például egy számérték aktuális tartalmát is kiírathatjuk vele. Egy egyszerű példa erre:

printf(”A folyamat állapota: %d százalék!”, szamertek);

Ez a felhasználási mód adja a printf függvény igazi rugalmasságát. Ilyenkor a formátumsztring ún. konverziós előírásokat is tartalmaz. Ezek befolyásolhatják a kiírt adatok megjelenését is, illetve ezzel tudjuk jelezni a számítógép számára, hogy milyen típusú adatot szeretnénk megjeleníteni.
A formátumsztring tetszőlegesen tartalmazhat vezérlőkaraktereket, mint például tabulátor- és újsor karaktert. Ezek kódjai ’\t’ és ’\n’.
Az alábbi táblázat tartalmaz néhány gyakori konverziós előírást, a teljesség igénye nélkül.
Kód
Adat típusa
Megjegyzés
%c
int, char
egyetlen karakter
%s
char[]
sztring
%d
int
egész szám
%f
float
egyszeres pontosságú lebegőpontos szám
%lf
double
kétszeres pontosságú lebegőpontos szám
Az alábbi példaprogram (PELDA_04.C) a fentieket mutatja be:


#include <stdio.h>

char szoveg[] = "Udvozollek, dicso lovag! ";
float szam = 1.334;

int main()
{
printf("%s\n", szoveg);
printf("%c\n", szoveg[1]);
printf("%f\n", szam);
printf("\tTabulator.\tItt is egy! Most pedig egy ujsor\nKesz!");
return 0;
}


A program kimenete:

Udvozollek, dicso lovag!
d
1.334000
Tabulator. Itt is egy! Most pedig egy ujsor
Kész!

A printf számos további finomhangolási lehetőséget is kínál, kezdők számára azonban bőségesen elegendő a fentiek megismerése és használata.
Jó tudni viszont a puts függvényről, mely a paraméterként megadott sztringet szintén kiírja a képernyőre, majd mindig sort is emel:

int puts(const char *sztring);

3.6 Adatbeolvasás billentyűzetről

3.6.1 Elemi adatok beolvasása

Elemi adatokat a scanf_s függvénnyel olvastathat be egy program.

scanf_s( formátumsztring, argumentumlista, pufferméret)
int scanf(const char *formatumsztring [,argument]...);

A függvény használata majdnem teljesen megegyezik a printf függvényével, egy lényeges különbség azonban van, mégpedig a tárolás helyének (változó) megadása. A sztringek kivételével ugyanis ilyenkor szerepeltetni kell a ’&’ (címe) operátort is.
Sztringek beolvasásánál meg kell adni a szöveges változó méretét is, bájtban, beleértve a sorvége ’\0’ karaktert is. Számértékek esetén ez a paraméter elhagyható.

Lássunk erre egy példát (PELDA_05.C), egy egész szám beolvasására:


#include <stdio.h>
int szam;

int main()
{
printf("Kerek egy szamot : ");
scanf_s("%d", &szam);

printf("A megadott szam: %d\n", szam);
return 0;
}


A program kimenete:
Kerek egy szamot : 128
A megadott szam: 128

3.6.2 Komplex szövegek beolvasása

A scanf_s függvénnyel sajnos szóközt tartalmazó szöveget nem lehet beolvastatni. Erre szolgál a viszont a gets_s függvény:

char *gets_s(char *buffer, size_t sizeInCharacters);

Ennek szemléltetésére álljon itt ismét egy rövidke példaprogram (PELDA_06.C):


#include <stdio.h>
char szoveg[128];

int main()
{
printf("Szabad a kedves nevet ? ");
gets_s(szoveg,sizeof(szoveg));
printf("Nagyon orvendek, kedves %s!\n", szoveg);
return 0;
}


A program kimenete:

Szabad a kedves nevet? Feher Krisztian
Nagyon orvendek, kedves Feher Krisztian!

3.6.3 Karakterek beolvasása

Egyetlen karaktert a billentyűzetről beolvasni a getchar, míg kiírni putchar függvényekkel tudunk.

int getchar();
int putchar(int c);

Ezt szemlélteti az alábbi példaprogram példát (PELDA_07.C):


#include <stdio.h>

int karakter;

int main()
{
printf(”nyomjon le egy billentyűt!”);
karakter = getchar();
printf("A billentyű kódja: %d\n", karakter);
printf(”Nyomjon le egy billentyűt a befejezéshez!”);
getchar();
return 0;
}


A program kimenete:

Nyomjon le egy billentyut!A
A billentyu kodja: 65
Nyomjon le egy billentyut a befejezeshez!

Mint a fenti  példaprogramban is látható, a getchar függvényt egyszerű utasításként használva várakoztathatjuk a program végrehajtását egy billentyű leütéséig.

3.7 Tömbök

Amennyiben ugyanabból az adattípusból nagy mennyiségű adatot szeretnénk használni, tömböt kell használnunk. Ilyenkor az egyes elemekre nem egyszerűen a tömb nevével, hanem sorszámmal is, az ún. tömbindexszel kell hivatkozni.

3.7.1 Egydimenziós tömbök

A legegyszerűbb tömbök egyetlen ún. dimenziót, azaz kiterjedést mutatnak, ezt felfoghatjuk egyetlen hosszú listának is. Vegyük az alábbi tömböt:

int egesz_tomb[] = {1, 2, 3, 4, 5};

Az egesz_tomb 5 elemet fog tartalmazni. az első és második tömbelemre így hivatkozhatunk: egesz_tomb[0], egesz_tomb[1]. Az indexelés tehát 0-val kezdődik.
A tömb feltöltésére utólag is van lehetőségünk, ilyenkor először meg kell adnunk a tömb méretét, majd az elemeket:

int egesz_tomb[5];
egesz_tomb[0] = 1;
egesz_tomb[1] = 2;

Mint korábban már láthattuk, szövegek tárolása C-ben karaktertömbökkel lehetséges:

char szoveg[] = ’üdvözlet’;

A módszer előnye, hogy a szöveg (sztringek) egyes karaktereit tömbelemekként, indexelve érhetjük el.

3.7.2 Többdimenziós tömbök

Többdimenziós tömböt a legegyszerűbben több szögletes zárójel használatával deklarálhatunk. Például egy kétdimenziós tömböt így hozhatunk létre:

int tomb[10][2];

A tömbök értelmezése, vagyis hogy kétdimenzió esetén például melyik dimenzió a sor és melyik az oszlop, teljes mértékben programfüggő lehet, azaz a programozó határozhatja meg.
A tömbök feldolgozását praktikusan for ciklussal érdemes megoldani.

Egy rövid példaprogram (PELDA_08.C):


#include <stdio.h>

int tomb[5][2];
int x, y;

int main()
{
for (x = 0; x < 5; ++x)
 for (y = 0; y < 2; ++y)
 {
  tomb[x][y] = y * 5 + x;
  printf("tomb[%d][%d] = %d\n",x,y, tomb[x][y]);
 }   

return 0;
}


A program kimenete:

tomb[0][0] = 0
tomb[0][1] = 5
tomb[1][0] = 1
tomb[1][1] = 6
tomb[2][0] = 2
tomb[2][1] = 7
tomb[3][0] = 3
tomb[3][1] = 8
tomb[4][0] = 4
tomb[4][1] = 9

3.7.3 A tömbök anatómiája

A tömbök és a mutatók közötti kapcsolat szoros. A tömb neve egy mutató, mely egy lefoglalt memóriaterületre hivatkozik.
Az alábbi hivatkozás

tomb[2]

például lényegében egyenértékű a

*tomb+2 hivatkozással.

Ez teszi lehetővé például a többdimenziós tömbök egydimenziós tömbben történő „szimulálását” is, mely a dinamikus memóriafoglalásnál jelenthet nagy segítséget.
A függvényeknél majd látni fogjuk, hogy a függvények argumentiumlistáiban a tömbök mutatóként szerepelnek.

3.8 Speciális és saját típusok

A C nyelv lehetővé teszi, hogy a beépített adattípusokat felhasználva összetettebb adattípusokat alkossunk. Ezek jelentősen megnövelik egy program mozgásterét az adatfeldolgozás terén.

3.8.1 Struktúra típus

A meglévő adattípusok különböző kombinációjával speciális, egyedi, komplex típusokat is előállíthatunk.
A struct kulcsszó segtségével például olyan adatszerkezetet is létrehozhatunk, ami alapesetben nem elérhető a C-ben.
Például háromdimenziós vektorszámításra használhatjuk az alábbi típust.

struct vektor {
int x, y, z;
} vektor3d;

Ezután a vektor3d adatszerkezetet felhasználhatjuk rogramjainkban. Az egyes mezőkre a struktúra neve utáni pontot írva hivatkozhatunk, például: vektor3d.x .
Amennyiben több ugyanolyan, de saját típust is használni szeretnénk, érdemes létrehozni egy típusdefiníciót, a typedef kulcsszóval:

typedef struct vektor{
int x, y, z;
};

Ezután a vektort változótípusként használhatjuk:

vektor vektor3d_1, vektor3d_2;

3.8.2 Union típus

Az ún. union típus létrehozása nagyon hasonlít a struct adaszerkezetekéhez, de az adatmezők viselkedése merőben más. Az egyes mezők ugyanis a memóriában átfedésben vannak.
Vegyük az alábbi példát:

typedef union konverter {
unsigned char bajt[2];
unsigned short int integer;
};

konverter bajtkonverter;

A fenti példa során az ’integer’ változó és a kételemes ’bajt’ tömb ugyanazt a memóriaterületet fedi le. A tömb segítségével a kétbájtos egész szám ún. alsó és felső bájtjához is könnyedén hozzáférhetünk. Ehhez hasonló megoldásokkal könnyedén felcserélhetjük egy változó bájtjait. Erre akkor lehet szükség, ha például egy adatfájl tartalma másképpen van kódolva a megszokottnál.

3.8.3 A void típus

A void egyfajta kakukktojás a típusok között, mivel a szó szoros értelmében egy típus nélküli típus. Akkor használjuk, ha a program írásakor még nem ismert a felhasználni kívánt típus. Elsősorban függvények esetén fordul elő, ill. a dinamikus memóriahasználat egyik fontos eszköze (lásd később).

3.9 Mutatók

A mutatók használata a C nyelv egyik jellegzetessége. A mutató egy speciális változó, melynek értéke egy memóriacím, tipikusan egy adat, vagy adattömb kezdőcíme.
Az alábbi példa egy egész számra mutató mutatót és egy numerikus változó használatát szemlélteti.
A mutatókat a deklarációjuk során ’*’ jellel kell jelölnünk. A mutató értelmes értékkel csak azután rendelkezik, hogy átadjuk neki egy típusának megfelelő vátlozó kezdőcímét. Ezt egy értékadáshoz hasonlóan tehetjük meg, de a változó neve elé kell írnunk a ’címe’ jelet, azaz ’&’-t.

int *mutato, szam = 113;
mutato = &szam;
*mutato = 128;

A mutato mint változó ezután egy számértéket fog tartalmazni, ami lényegében egy memóriaterület címe. Az adott memóriaterülettel úgy tudunk dolgozni, hogy a mutató elé írjuk a ’*’ jelet. Ezután ugyanúgy dolgozhatunk vele, mint a ténylegesen a memóriacímen található változóval.
Sőt, a fenti példában a szam változó értékét indirekt módon meg is változtatjuk 128-ra!
Az alábbi példa az ún. dinamikus memóriafoglalásra példa, melynek során 1 millió float típusó számot tartalmazó tömböt foglalunk le dinamikusan, majd felszabadítjuk az adott memóriaterületet:

int *mutato;
mutato = (int*)malloc(1000000 * sizeof(int));
free(mutato);

A dinamikus memóriakezelésre hamarosan még visszatérünk.
Lehetőség van mutatótömböket is létrehozni, így akár többdimenziós tömböket is létrehozhatunk dinamikusan. Ennek ára a kissé bonyolultabb kód, hiszen dimenziónként létre kell hozni a tömbök tárhelyét (lényegében mutatótömbjeink lesznek), amit a memóriaterület felszabadításakor ismét végig kell járni.

3.10 Operátorok és kifejezések

Az operátorok valamilyen adattal csinálnak valamit. Ezek az adatok az ún. operandusok. Az operátorok műveleti jelek.
Megkülönböztetünk egy- és kétoperandusú operátorokat. A legtöbb operátor kétoperandusú, például az alábbi kifejezés:

i + 5

Egyoperandusú az alábbi értéknövelő utasítás:

++i;

3.10.1 Klasszikus aritmetikai operátorok

A klasszikus matematikai műveleteket az alábbi operátorokkal tudjuk elvégezni.
Op.
Jelentés
Példa
+
összeadás
a + b
-
kivonás
a - b
*
szorzás
a * b
/
osztás
a / b
++
érték növelése egy egésszel
++a
--
érték csönkkentése egy egésszel
--a
A logikai operátoroknak elsősorban feltételvizsgálatok alkalmával van jelentősége, például IF .. ELSE szerkezetekben (lásd később).
Op.
Jelentés
Példa
>
nagyobb
a > b
<
kisebb
a < b
==
egyenlő
a == b
>=
nagyobb vagy egyenlő
a >= b
<=
kisebb vagy egyenlő
a <= b
&&
és
(a >5) && (b < 5)
||
vagy
(a >5) || (b < 5)
3.10.3 Értékadó operátorok

Az értékadás az egyik legfontosabb művelet a változókkal végzett munka során, amikor is értéket adunk át, ill. állítunk be egy változónak.
Op.
Jelentés
Példa
=
érték átadása, beállítása
a = 5 vagy a = b
+=
jobbérték hozzáadása balértékhez
a += 5
-=
jobbérték kivonása balértékből
a -= 5
*=
jobbérték szorzása balértékkel
a *= 5
/=
jobbérték kivonása balértékből
a /= 5
3.10.4 Precedencia szabály és végrehajtási sorrend

Precedencia alatt műveletek egymáshoz viszonyított fontosságát, végrehajtási sorrendjét értjük.
Például a szorzás magasabb precedenciájú az összeadásnál, ezért az alábbi művelet

5 * 6 + 1

értéke 31 lesz, nem pedig 35.

A következő oldalon található táblázatban is látszik, hogy a legmagasabb precedenciájú operátor a kapcsos zárójel. Ez azt jelenti, hogy zárójelek alkalmazásával az alapértelmezett precedenciasorrend lényegében felülbírálható.
A zárójelek alkalmazása egyébként is jó programozási gyakorlat, mivel egyértelműen láthatóvá teszi, hogy mely műveletek tartoznak össze és fognak előbb végrehajtódni.
A táblázat a műveleti jeleket csökkenő precedenciasorrendben tartalmazza.
Leírás
Műveleti jel / operátor
Kapcsos zárójel: csoportosítás vagy függvényhívás
Szögletes zárójel
Tag kijelölés objektum névvel
Tag kijelölés mutatóval
Postfix értéknövelés / csökkentés
()

[ ]
.
->
++ --
Prefix értéknövelés / csökkentés
Unáris plusz / mínusz
Logikai tagadás / bitenkénti komplemens
Cast-olás (explizit típuskonverzió)
Közvetlen hivatkozás (mutató)
Operandus címe
Méret meghatározása bájtban
++ --
+ -
! ~

(type)
*
&
sizeof
Szorzás / osztás / maradékos osztás
* / %
Összeadás / kivonás
+ -
Bitenkénti balra, jobbra eltolás
<< >>
Kisebb /kisebb vagy egyenlő
Nagyobb / nagyobb vagy egyenlő
< <=
> >=
Egyenlő / nem egyenlő
== !=
Bitenkénti ÉS
&
Bitenkénti kizáró ( OR )
^
Bitenkénti VAGY
|
Logikai ÉS
&&
Logikai VAGY
| |
Feltételes értékadás
? :
Hozzárendelés
Összeadás/kivonás és hozzárendelés
Szorzás/osztás és
Maradékos osztás / bitenkénti ÉS és hozzárendelés
Bitenkénti kizáró VAGY / VAGY és hozzárendelés
Bitenkénti balra/jobbra eltolás és hozzárendelés
=
+= -=
*= /=
%= &=

^= |=

<<= >>=

Elválasztó (kifejezések elválasztása)
,
A kiértékelések irányát asszociativitásnak nevezik és a precedenciával együtt kell figyelembe venni.
Operátor
Kiértékelés iránya
() [] . ->
balról jobbra
! ~ - ++ -- & * (castolás) sizeof
jobbról balra
* / %
balról jobbra
+ -
balról jobbra
<< >>
balról jobbra
< <= > >=
balról jobbra
== !=
balról jobbra
&
balról jobbra
^
balról jobbra
|
balról jobbra
&&
balról jobbra
||
balról jobbra
?:
jobbról balra
= += -= *= /= %= <<= >>= &= |= ^=
jobbról balra
,
balról jobbra
3.10.5 A sizeof operátor

Speciális szerepet tölt be a sizeof operátor, ez ugyanis a paraméterként megadott változó méretét adja vissza, bájtban.
Példaprogrammal (PELDA_09.C):


#include <stdio.h>
double szam;

int main()
{
printf("A valtozo merete bajtokban : % d\n", sizeof(szam));
return 0;
}


A program kimenete:

A valtozo merete bajtokban :  8



4 Utasítások

Az alábbiakban a legfontosabb programszervező utasításokat és technikákat mutatjuk be, melyek minden programozási nyelvben megtalálhatóak. Ezek segítségével intelligensebbé és nem utolsó sorban átláthatóbbá is tehetjük programjainkat.

4.1 Elágazási szerkezetek

Az if..else, else if feltételvizsgálatokkal egyes programrészek végrehajtását feltételhez köthetjük. Általános alakban:

if (feltétel) utasítás1
else if (feltétel) utasítás2
else utasítás3

Az if kulcsszó után megadott feltétel teljesülése esetén az első utasítás, vagy utasításblokk hajtódik végre.
Amennyiben más, de szintén konkrét feltételhez akarunk kötni bizonyos műveleteket, akkor azt egy else if kifejezéssel tehetjük meg. Fontos, hogy else if csak if után állhat.
Egyik megadott, konkrét feltétel nem teljesülését az else kulcsszó után megadott utasítással, vagy utasításblokkal kezelhetjük.

Példa (PELDA_10.C):


#include <stdio.h>
int erdemjegy;

int main()
{
printf("Hanyas jegyet kaptal? Erdemjegy megadása: ");
scanf_s("%d", &erdemjegy);

if (erdemjegy == 5) printf("Kivalo!");
else if (erdemjegy == 1) printf("Ezen meg dolgozni kell!");
else printf("Szep eredmeny!");

return 0;
}


A program kimenete:

Hanyas jegyet kaptal? Erdemjegy megadasa: 4
Szep eredmeny!

Fontos, hogy feltételként bármi megadható, ami valamilyen értéket szolgáltat, akár egy függvényhívás is!
Amennyiben egyetlen változó, esetleg kifejezés többféle értékét szeretnénk vizsgálni, átláthatóbb megoldáshoz juthatunk az ún. switch – case szerkezettel. Ennek általános alakja:

switch(valtozo) {
case érték1: utasítás1;break;
case érték2: utasítás1;break;
default: utasítás3;break;
}

A vizsgálandó elemet, értéket a switch kifejezés tartalmazza, míg az egyes vizsgálandó értékeket a case kulcsszavak. A default kulcsszó megadásával azt az esetet is kezelhetjük, amikor egyik case értékösszehasonlítás sem érvényes. Vegyük észre az egyes utasítások, ill. utasításblokkok utáni break kulcsszavakat! Ezzel ki lehet és ki is kell úgymond szállni a switch szerkezetből, máskülönben az összes, soron követlező utasítás végrehajtódna. Ez így működik, fogadjuk el…

Példaprogram (PELDA_011.C):


#include <stdio.h>

int erdemjegy;

int main()
{
printf("Hanyas jegyet kaptal? Erdemjegy megadasa: ");
scanf_s("%d", &erdemjegy);

switch (erdemjegy) {
case 5: printf("Kivalo!"); break;
case 4: printf("Szep eredmeny!"); break;
case 3: printf("Elmegy!"); break;
case 2: printf("Legkozelebb jobb is lesz!"); break;
case 1: printf("Ez igy keves lesz!"); break;
default: printf("Ez nem is erdemjegy!"); break;
}

return 0;
}


A program kimenete:

Hanyas jegyet kaptal? Erdemjegy megadasa: 9
Ez nem is erdemjegy!

4.2 Ciklusok

Számos programnál, például játékprogramoknál is, gyakran előfordul, hogy bizonyos műveleteket újra és újra végre kell hajtani, bizonyos számú alkalmommal, vagy akár a „végtelenségig”, egy feltétel teljesüléséig (például a játékból történő kilépésig).

Ezt elegánsan az ún. ciklusokkal lehet megoldani, melyeknek többféle fajtája létezik:
WHILE ciklus: ún. előltesztelő ciklus, mely a ciklus futásának felételét rögtön a ciklus megkezdése előtt már egyszer ellenőrzi és utána is, a ciklus újrakezdésének minden egyes alkalmával.
DO .. WHILE ciklus: ún. hátultesztelő ciklus, mely a ciklus futásának felételét csak a ciklus végén ellenőrzi először, tehát a ciklus műveletei legaláb egyszer  mindenképpen végrehajtódnak.
FOR ciklus: A megadott utasítás(oka)t N alkalommal hajtja végre. Az N elérését egy feltétellel ellenőrizhetjük. Jellemzője ennek a ciklusnak az ún. léptetés, ill. lépték is, mellyel egy változó értékét módosítjuk, melynek értéke végül is a futtatás feltételében is fel lesz használva.


Álalános formájuk:

while (feltétel)
{
utasítások
}

do
{
utasítások
} while (feltétel);

for( inicializálás; feltétel; léptetés)
{
// utasítasok
}

Rövid példaprogram (PELDA_012.C):


#include <stdio.h>
int lepteto;

int main()
{
lepteto = 1;
while (lepteto < 5) printf("A valtozo erteke : % d\n", lepteto++);
printf("\n");

do {
 printf("A valtozo erteke : % d\n", lepteto);
 ++lepteto;
} while (lepteto < 5);

printf("\n");

for (lepteto = 0; lepteto < 5; ++lepteto)
{
 printf("A valtozo erteke : % d\n", lepteto);
}
printf("\n");

return 0;
}


A program kimenete:

A valtozo erteke :  1
A valtozo erteke :  2
A valtozo erteke :  3
A valtozo erteke :  4

A valtozo erteke :  5

A valtozo erteke :  0
A valtozo erteke :  1
A valtozo erteke :  2
A valtozo erteke :  3
A valtozo erteke :  4

A break és continue kulcsszavak segítségével befolyásolható a ciklusok végrehajtása.
A break utasítással azonnal ki lehet lépni a ciklusból és a ciklus törzse utáni következő utasításra ugrik a program végrehajtása.
A continue utasítással átugorható a ciklus soron következő összes utasítása és a vezérlés visszakerül a ciklus elejére.

Példa a használatra (PELDA_013.C):


#include <stdio.h>
int lepteto;

int main()
{
for (lepteto = 0; lepteto < 10; ++lepteto)
{
 if (lepteto == 4) continue;
 printf("A valtozo erteke : % d\n", lepteto);
 if (lepteto == 6) break;
}
printf("\n");

return 0;
}


A program kimenete:

A valtozo erteke :  0
A valtozo erteke :  1
A valtozo erteke :  2
A valtozo erteke :  3
A valtozo erteke :  5
A valtozo erteke :  6



5 Függvények, eljárások

Egy programot esetleg meg lehetne írni úgy is, hogy egyetlen függvénybe, a main függvénybe lenne beleírva az összes utasítás stb. Könnyen belátható azonban, hogy ez rendkívül átláthatatlanná tenné a programkódot, már pár száz sor esetén is.
A C nyelv ún. függvényeken, eljárásokon keresztül teszi lehetővé, hogy bizonyos programfunkciókat különálló logikai egységként lehessen leprogramozni és aktiválni, szakszóval meghívni. Ez az ún. procedurális megközelítés, mely a C nyelv egyik jellemzője..
(A C++ nyelv ezt fejlesztette tovább az objektum fogalmának bevezetésével, mely a logikailag összetartozó függvényeket és adatokat egyetlen logikai egységbe engedi foglalni.)
Tulajdonképpen kétféle függvénytípus létezik: eljárás és „rendes” függvény. A függvény a befejezésekor szolgáltat (visszaaad) valamilyen értéket, melyet a továbbiakban fel lehet használni, míg az eljárás egyszerűen csak lefut, semmilyen értéket nem ad vissza.
Általános formában:

Eljárás

void eljaras (int valtozo)
{
utasítások;
}

Függvény

visszatérési_érték_típusa függvéynnév (int valtozo1, int valtozo2)
{
utasítások;
return visszatérési_érték;
}

A függvények visszatérési értékét a return kulcsszó után adhatjuk meg.
Látható továbbá fentebb, hogy a függvények számára adatokat lehet átadni, melyeket az feldolgozhat. Bármilyen adattípust, akár saját adattípust is megadhatunk. Ezt a függvény fejlécében, az ún. argumentumlistában adhatjuk meg. A függvény használatakor alapértelmezetten a változók (ilyenkor paramétereknek nevezzük őket) csak az értéküket adják át a függvénynek, de az eredeti változóérték nem módosul.
Például ebben a formában:

fuggveny (int valtozo)

Amennyiben lehetővé akarjuk tenni, hogy az átadott paraméter értékét  a függvényen belül is meg lehessen változtatni, akkor mutatókat kell használnunk, tehát

fuggveny (int *valtozo)

Ilyen esteben a függvény meghívásakor használnunk kell a címe (&) operátort.

fuggveny(&valtozo);

Ezután már valóban módosítható a függvényben a paraméterváltozó értéke.

5.1 Előrevetett deklaráció, prototípus

Amennyiben függvényünket már a programunk legelején használni szeretnénk, akkor két választásunk van:
• a függvény egész törzsét a main függvény előtt megadni
• ún. függvény prototípust kell használni a main függvény előtt.

Előbbi logikus ugyan, de kissé átláthatatlanná teszi a programot sok függvény esetén. Az ajánlott megközeltés ezért a prototípusok használata. Ez nem más, mint a függvény fejléce, melyet pontosvesszővel zárunk és a main függvény előtt adjuk meg. Az argumentumlista elemeinek a száma, illetve típusa meg kell hogy egyezzen a tényleges függvnytörzsnél található fejléccel, erre mindig figyelni kell, de adott esetben a fordító, ill. a Visual Studio is jelzi ezt a hiányosságot.

5.2 Egyéb tudnivalók

Amennyiben függvényünk nem használ fel paramétereket, úgy üres argumentumlistát is megadhatunk. Ezt egy üres zárójelpárral, vagy a ’void’ kulcsszónak a zárójelben történő megadásával is megtehetjük, például így:

void fuggveny(void);

A függvények használatával nagyon jól olvashatóvá válhatnak forráskódjaink. Érdemes olyan függvényneveket választanunk, amelyek nem túl hosszúak ugyan, mégis sokmindent elárulnak a függvény működéséről.

Végezetül lássunk egy példaprogramot (PELDA_014.C) a fentiek illusztrálására!


#include <stdio.h>
int valtozo, i;

void hozzaadas(int* szam, int szam2);

int main()
{
valtozo = 1;

for (i = 0; i < 10; ++i)
{
 hozzaadas(&valtozo, 5);
 printf("A valtozo erteke: %d\n", valtozo);
}

return 0;
}

void hozzaadas(int* szam, int szam2)
{
*szam += szam2;
}


A program kimenete:

A valtozo erteke: 6
A valtozo erteke: 11
A valtozo erteke: 16
A valtozo erteke: 21
A valtozo erteke: 26
A valtozo erteke: 31
A valtozo erteke: 36
A valtozo erteke: 41
A valtozo erteke: 46
A valtozo erteke: 51

A void  típussal azt jelezzük, hogy  a függvény nem ad vissza értéket. Ez lényegében megfelel a klasszikus ún. eljárás viselkedésének.

5.3 A main függvény specialitásai

A main függvény a programunk legkülső héját és belépési pontját is jelenti. Paraméterei segítségével elérhetjük, hogy a lefordított programunk indítási parmétereket kapjon.
Ezzel jelentősen növelhető programjaink felhasználhatósága parancssori felhasználás során.
Ilyenkor az alábbi argumentumlistát kell megadnunk:

int main(int argc, char *argv[])

Az argc paraméter a megadott paraméterek számát adja vissza, az argv pedig egy kétdimenziós tömb, ami a programindítási paramétereket tartalmazza, sorrendben.
Ha programunkat például az alábbi módon indítjuk el:

sajat_program.exe par1 par2

akkor a main függvényen beül a megadott paramétereket az argv[1] és argv[2] sztringekkel érhetjük el.
Az argv[0] mindig magának a programfájlnak a nevét tartalmazza.
Az argc segítségével mindig ellenőrizhetjük, annyi paramétert kapott-e meg programunk, amennyire szüksége van.

Az alábbi példa (PELDA_015.C) egyszerűen kiírja a program indítási paramétereit a képernyőre:


#include <stdio.h>

int main(int argc, char* argv[])
{
int i;

if (argc < 2) printf("Nem adott meg extra parametert!");
else
{
 for (i = 0; i < argc; ++i)
  printf("A megadott parameter : % s\n", argv[i]);
}

return 0;
}


A program elindítása és kimenete:

gyakorloprogram.exe elso masodik harmadik

A megadott parameter : gyakorloprogram.exe
A megadott parameter : elso
A megadott parameter : masodik
A megadott parameter : harmadik



6 Adatkonverziók

Gyakori feladat számot szöveggé, vagy szöveget számmá alakítani. Szerencsére az egyes adattípusokhoz rendelkezésre állnak sztenderd könyvtári függvények, ezeket tekintjük most át.
A függvények eléréséhez az stdlib.h fejlécállományra kell hivatkozni programjainkban.

6.1 Számok sztringekké alakítása

A számokat sztringekké, azaz szövegekké alakító függvények mindegyike a konvertálás eredményét jelentő szövegre mutató mutatóval tér vissza.

Egész szám konvertálássa szöveggé, radix számrendszerben (tehát radix = 10 lesz a tizes számrendszer):

char *_itoa_s(int ertek, char *szoveg, int radix);

Hosszú egész számot az alábbi függvény konvertál szöveggé:

char *_ltoa_s(ertek value, char *szoveg, int radix);

Előjel nélküli hosszú egész konvertálása:

char *ultoa(unsigned long ertek, char *szoveg, int radix);

Lebegőpontos szám konvertálása ndec értékes számjegyre, tizedes tört alakban:

char *_gcvt_s(char *szoveg, size_t maxkar, double ertek, int ndec);

6.2 Sztringek számokká alakítása

Szövegek átalakítását végezhetjük el integer, double, long értékekké az alábbi függvényekkel. A függvények visszatérési értékei rendre a konvertált számok lesznek.

int atoi(const char *sztring);
float atof(const char *sztring);
long atol(const char *sztring);

6.3 Példaprogram konvertálásra

Az eddigiek szemléltetésére álljon itt egy példaprogram (PELDA_016.C), mely először számokat konvertál szövegekké, majd az eredményül kapott szövegeket vissza számokká:


#include <stdio.h>
#include <stdlib.h>

int egesz_szam = 15;
long hosszu_szam = 734234798324932;
double lebegopontos_szam = 13.78923213;
char szoveg1[128], szoveg2[128], szoveg3[128];

int main()
{
printf("Szamok szovegge konvertalasa\n");
_itoa_s(egesz_szam,szoveg1,10);
printf("A szam: %s\n", szoveg1);

_ltoa_s(hosszu_szam, szoveg2, 10);
printf("A szam: %s\n", szoveg2);

_gcvt_s(szoveg3, 128,lebegopontos_szam, 5);
printf("A szam: %s\n", szoveg3);

printf("Szovegek szamma konvertalasa\n");
egesz_szam = atoi(szoveg1);
printf("A szam: %d\n", egesz_szam);

hosszu_szam = atol(szoveg2);
printf("A szam: %li\n", hosszu_szam);

lebegopontos_szam = atof(szoveg3);
printf("A szam: %f\n", lebegopontos_szam);

return 0;
}


A program kimenete:

Szamok szovegge konvertalasa
A szam: 15
A szam: 1549139140
A szam: 13.789
Szovegek szamma konvertalasa
A szam: 15
A szam: 1549139140
A szam: 13.789000



7 Dinamikus memóriakezelés, dióhéjban

Elsősorban a tömbök esetén csábító lehet a gondolat, hogy nagyon nagy méretű tömböket is létrehozzunk, például így:

int szamtomb[1800000000];

Ám amint megpróbálunk lefordítani egy ilyen kódot, csalódnunk kell: a fordító hibaüzenett ad, miszerint túlléptük a memóriakeretünket. Ez még akkor is így van, ha számítógépünk bőséges RAM-mal rendelkezik. Hol a hiba? Az előre megadott, statikusan létrehozott adatok tárolására a memóriának van egy dedikált része (ez az ún. heap), ami viszont korlátozott kapacitású és explicit módon is csak kicsit változtatható.
A megoldást a fenti problémára az ún. dinamikus memóriafoglalás jelenti, melynek segítségével memóriaterületeket futási időben hozhatunk létre, ill. szabadíthatunk fel.
Előbbire a malloc, utóbbira a free függvény szolgál:

void *malloc(size_t size);

void free(void *memblock);

Az alábbi példában 5.000.000 float elemet tartalmazó tömböt hozunk létre, dinamikus memóriafoglalással:

float *mutato;
mutato = (float*)malloc(5000000 * sizeof(float));

if(mutato != NULL)
{
//muveletek
}

free(mutato);

Mint látható, mindig egy mutatón keresztül férhetünk hozzá a lefoglalt memóriaterülethez.
A mutató egy castolt malloc függvény hívásával kap értéket, melyben paraméterként meg kell adnunk a lefoglalandó memóriaterület nagyságát. A visszatérési érték NULL lesz, amennyiben valamilyen oknál fogva nem sikerül lefoglalni a memóriaterületet. Ezt érdemes mindig ellenőrizni is.
A lefoglalt memóriaterülettel a mutatóján keresztül ezután ugyanúgy dolgozhatunk, mint egy „sima” tömbbel, azaz egy tömbelemre például így is hivatkozhatunk:

mutato[9] = 112.9;

Miután nincsen szükségünk a lefoglalt memóriaterületre, a free függvénnyel fel kell szabadítanunk azt (lásd fenti példaprogamban).



8 Fájlkezelés

A számítógépes programok egyik legfontosabb tulajdonsága, hogy működésük eredményeit képesek rögzíteni, vagy akár vissza is olvasni. Az informatikában ezeket a műveleteket a fájlkezelés témaköre öleli fel.
A C nyelv a fájlokat kétféleképpen tudja feldolgozni: szöveges fájlokként és bináris adatokként, ún. adatfolyamokként. Ezt a két megközelítési módot fogjuk egészen röviden áttekinteni ebben a fejezetben. A hivatkozott függvények eléréséhez az <stdio.h> fejlácállományra lesz szükség.
A fájlokat minden esetben egy FILE * mutatón keresztül tudjuk kezelni.

A fájlkezelés három alapvető lépése:
• fájl megnyitása
• fájlba írás / olvasás
• fájl lezárása.

A fákjlok megnyitása és lezárása az fopen_s és fclose függvényhívásokon keresztül valósul meg.

errno_t fopen_s(FILE** fajl, const char *fajlnev, const char *mode);

int fclose(FILE *fajl);

A C nyelvben a fájlok megnyitásakor meg kell adnunk a megnyitás módját is (mode paraméter). Ez egy rövid sztring, mely az alábbi értékeket tartalmazhatja:
Megnyitási mód
Működés leírása
”r”
Létező fájl megnyitása olvasásra.
”w”
Új fájl létrehozása és írás engedélyezése. Ha a fájl már létezik, akkor a korábbi tartalma elvész.
”a”
Megnyitás hozzáírásra. A kezdőpozíció a fájl vége. Ha a fájl nem létezett korábban, akkor létre is jön.
”r+”
Létező fájl megnyitása írásra és olvasásra.
”w+”
Fájl létrehozása, valamint olvasás és írás engedélyezése. Ha a fájl már létezik, akkor a korábbi tartalma elvész.
”a+”
Megnyitás hozzáírásra és olvasásra. A kezdőpozíció a fájl vége. Ha a fájl nem létezett korábban, akkor létre is jön.
8.1 Szöveges fájlok

A szöveges fájlok esetében minden adat karakterek sorozatából áll és alapesetben egy karakter egy bájton van eltárolva. Bizonyos rendezettséget szintén feltételezhetünk az adatokról, például vannak benne szóközök, írásjelek stb.
Szöveges fájl megnyitása és lezárása az alábbi módon történik:


FILE* myfile;

// fájl megnyitása
fopen_s(&myfile, "fajl.txt", "rt");

//ellenőrzés, hogy létezik-e a fájl, ill. hozzáférhető-e
if (myfile != NULL)
{
//feldolgozás, utasítások
}

//fájl lezárása
fclose(myfile);


A fájlok kezelése természetesen az operációs rendszeren keresztül valósul meg.
A szöveges fájlok írási és olvasási műveleteit támogató függvények:

Karakter írása és olvasása:

int fputc(int c, FILE *fajl);
int fgetc(FILE *fajl);

Sztring írása és olvasása:

char *fputs(const char *sztring, FILE *fajl);
char *fgets(char *sztring, int n, FILE *fajl);

Összetett, formázott adatok írása és olvasása:

int fprintf_s(FILE *fajl,const char *formatumsztring [,argument_list ]);
int fscanf_s(FILE *fajl,const char *formatumsztring [,argument ]...);

8.2 Bináris fájlkezelés

Bináris fájlokat bájt egységekben lehet kezelni és elsősorban tömör adattárolásra használatosak, ilyenek például a képfájlok.
Az adatok írására és olvasásra egy-egy függvény használatos:

size_t fwrite(const void *buffer,size_t size,size_t count,FILE *fajl);

size_t fread_s(void *buffer, size_t bufferSize, size_t elementSize,
size_t count,FILE *fajl);

Néhány rövid példa, int érték kiírására és beolvasására:

int egesz_szam;

fwrite(egesz_szam, sizeof(egesz_szam), 1, fajl);

fread_s(&egesz_szam, sizeof(egesz_szam), 1, fajl);

Bináris fájl megnyitása az alábbi módon történik:


FILE* myfile;

// fájl megnyitása
fopen_s(&myfile, "fajl.dat", "r");

//ellenőrzés, hogy létezik-e a fájl, ill. hozzáférhető-e
if (myfile != NULL)
{
//feldolgozás, utasítások
}

//fájl lezárása
fclose(myfile);


8.3 Pozícionálás a fájlban, fájlvége detektálása

A fájlok elejére visszapozicionálni legkönyebben a rewind függvénnyel lehetséges:

void rewind(FILE *fajl);

Az fseek függvénnyel bájtra pontos pozicionálást hajthatunk végre relatív, vagy abszolút értelemben:

int fseek(FILE *fajl, long offset, int origin);

Az origin lehetséges értékei (állandókkal):
• SEEK_CUR – az aktuális fájlpozícióhoz képest
• SEEK_END – a file végéhez képest
• SEEK_SET – a fájl elejéhez képest.

Az feof függvény 0 értékkel tér vissza, ha még nem értük el a fájl végét, egyébként ettől eltérő értékkel:

int feof(FILE *fajl);

Az alábbi példaprogram (PELDA_017.C) szöveges fájlok kezelését mutatja be:


#include <stdio.h>
FILE* myfile;
char szoveg[128];

int main(int argc, char* argv[])
{
//kiírás
printf("Szoveg kiirasa...\n");
fopen_s(&myfile, "szovegfajl.txt", "wt");
if (myfile != NULL)
fprintf_s(myfile, "Szovegsor.");
fclose(myfile);

//beolvasás
printf_s("Szoveg visszaolvasasa...\n");
fopen_s(&myfile, "szovegfajl.txt", "rt");
if (myfile != NULL)
fscanf_s(myfile, "%s", szoveg,sizeof(szoveg));
fclose(myfile);
printf("A visszaolvasott szoveg: %s\n", szoveg);

return 0;
}


A program kimenete:

Szoveg kiirasa...
Szoveg visszaolvasasa...
A visszaolvasott szoveg: Szovegsor.



9 Klasszikus rendezési algoritmusok

Az alábbi példában egész számok rendezésére találhatunk példákat. Ezek klasszikus rendezési algoritmusok, melyeket gyakorlásként, de a valóságban, „élesben” is használni lehet.
Az egyes módszerek elsősorban gyorsaságukban, ill. bonyolultságukban térnek el egymástól.

A tömb:

#define N 20
int tomb[N] = {5,6,3,2,7,22,53,45,12,3,8,123,22,
99,0,321231,342,32,1,546};

9.1 Buborék rendezés


void buborek(void)
{
int i, j, csere;
for( i = 0; i<N; i++)
 {
   for (j = N-1; j>i; j--)
   {
     if( tomb[j] < tomb[j-1])
       {
       csere = tomb[j];
       tomb[j] = tomb[j-1];
       tomb[j-1] = csere;}
   }
 }
}
}


9.2 Rendezés cserével


void cserevel(void)
{
 int i,j,csere;
 for (i = 0; i<N-1; i++)
 {
   for (j = i+1; j<N; j++)
   {
     if (tomb[i] > tomb[j])
     {
     csere = tomb[i];
     tomb[i] = tomb[j];
     tomb[j] = csere;
     }
   }
 }  
}


9.3 Közvetlen beszúrásos rendezés


void kozvetlen_beszuras(void)
{
int i, j, csere;
 for(i = 1; i<N; i++)
 {
   csere = tomb [i];
   for(j = i; j>0 && csere < tomb[j-1]; j--)
   tomb[j] = tomb[j-1];
   tomb[j] = csere;  
 }
}


9.4 Shell rendezés


void shell(void)
{
int i,j, csere, k;
for (k = n/2; k>0; k = k/2)
{
  for (i = k; i<N; i++)
  {
    for (j = i-k; j>=0 && tomb[j] > tomb[j+k]; j -= k)
    {
      csere = tomb[j];
      tomb[j] = tomb[j+k];
      tomb[j+k] = csere;
    }
   }
 }   
}

KAPCSOLAT

E-mail:
feher.konyvek@gmail.com
KAPCSOLAT

E-mail:
feher.konyvek@gmail.com
Vissza a tartalomhoz