[[oktatas:programozás:C|< C]] ====== C programozási nyelv ====== * **Szerző:** Sallai András * Copyright (c) Sallai András, 2011, 2013, 2014, 2015, 2020, 2023 * Licenc: [[https://creativecommons.org/licenses/by-sa/4.0/|CC Attribution-Share Alike 4.0 International]] * Web: https://szit.hu ===== Bevezetés ===== A C nyelv egy általános célú programozási nyelv. Rendszerprogramozási nyelvként is emlegetik, mivel hatékonyan használható operációs rendszerek írására. Természetesen használható más alkalmazói programok írására is. A nyelvet a BCPL és B nyelvből eredeztetik: | BCPL | -> | B | -> | C | A C nyelvet **Dennis Ritchie** az 1970­-es évek elején fejlesztette ki **Ken Thompson** segítségével. A nyelvet UNIX operációs rendszerre tervezte, amely egy DEC PDF-7 rendszeren futott. Volt egy B nevű nyelv, amely nem lett ismert. A C sok tekintetben örökölte a B nyelv tulajdonságait, azért is lett a neve az ábécé következő betűje. Az 1970­es években a személyi számítógépeken BASIC nyelvet lassan felváltja a C nyelv. 1978-­ban **Dennis Ritchie** és **Brian Kerninghan** nevével fémjelzett „//**A C programozási nyelv**//” című könyv első kiadása megjelenik. 1983 -- ­1989 az Amerikai Nemzeti Szabványügyi Hivatal (angolul: American National Standards Institute, röviden ANSI) szabványnak fogadják el. 1990­-ben megjelenik a szabványnak egy átdolgozott kiadása C99 névvel. {{:oktatas:programozás:c:c_nyelvek_halmaza.png|}} Az ANSI C az eredeti K&R nyelvnek egy kibővített változata. A megvalósítások ehhez még plusz kiterjesztéseket tesznek hozzá. ===== Gyakorlat 001 ===== * Válaszoljon a következő kérdésekre: * Ki kezdte fejleszteni a C nyelvet? * Milyen operációs rendszerre tervezték a C nyelvet eredetileg? * Milyen programozási nyelvből örökölt tulajdonságokat a C nyelv? * Mikor fogadták el a C nyelvet ANSI szabványnak? ===== Helló Világ ===== #include int main() { printf("Helló Világ\n"); } A C nyelv több előre megírt függvénnyel rendelkezik, amit csak meg kell hívni. Ezen előre megírt függvénygyűjteményeket hívjuk sztenderd könyvtáraknak. A sztenderd könyvtárak is több csoportja ismert, amelyeket elneveztünk a rájuk jellemző névvel. Ilyenek a stdio, stdlib, math, stb. A fenti program első sora amely #include-al kezdődik egy szabványos könyvtár használatát teszi lehetővé, amelynek neve stdio. Az stdio programozói könyvtárnevet kötelező kisebb mint és nagyobb mint karakterek közé tenni: #include A programban szereplő printf() utasítás a stdio.h fejállományban vagy programozói könyvtárban található. Ha használni akarjuk a printf() függvényt, mindig szükség van a #include sorra. A C nyelvben, minden utasítást függvényekbe rendezünk. Egy C programban mindig lenni kell egy main nevű függvénynek, a programnak ugyanis ez lesz a belépési pontja. A függvények neve után egy nyitó és egy bezáró zárójelet teszünk. Ezzel is jelezzük, hogy a main valójában függvény: main() A main() tehát egy függvény fejrésze, amely egy névből (esetünkben a "main") és az azt követő zárójelekből áll. Egy függvénynek mindig van törzs része is. A függvény törzsét kapcsos zárójelek írjuk. A függvény törzsében írjuk le mit történjen a programban. A mi programunkban egyeteln utasítás van: printf("Helló Világ\n"); A printf() függvény, illetve utasítás a képernyőre írja a paraméterként megkapott értékeket. Esetünkben ez a "Helló Világ" szöveg. A szöveg végén a "\n" gondoskodik a sortörésről. {{:oktatas:programozas:c:c_fuggveny_reszei.png?400|}} ===== Megjegyzés ===== A C nyelv lehetővé teszi egy és többsoros megjegyzés elhelyezését. // egy soros megjegyzés /* több soros megjegyzés */ ===== Gyakorlat 002 ===== * Írjon programot, ami kiírja a településének a nevét, és irányító számot egy újabb sorba. * Az előző programban, írja többsoros megjegyzésbe saját nevét. ===== Kivitel ===== Ha leírok egy karaktersorozatot, például alma, a printf() függvény a képernyőre írja: #include int main() { printf("alma"); } #include int main() { puts("alma"); } A karaktersorozat persze lehet számok vagy akár más karakterek sorozata is: printf("35"); A számokat csak a formátumuk meghatározásával lehet kiíratni. printf("%d", 35); Előbb írok egy formázó szöveget, majd jöhet a szám. Egész számok esetén a formázó szöveg: "%d". A printf() kiíratandó paramétere, lehet akár egy kifejezés is: printf("%d", 35*2); A kifejezés kiértékelődik, és az eredményt kapjuk vissza. ===== Escape szekvenciák ===== Fentebb láttuk, ha egy karaktersorozatot a képernyőre iratok, annak minden karaktere változtatás nélkül megjelenik a képernyőn. Egyes karaktereknek azonban lehet speciális jelentése is. Fentebb már láttuk, ha az "n" betű elé egy visszaperjelet (\) írunk, akkor annak különleges jelentése lesz. Ahelyett, hogy magát a karaktert kapnánk a képernyőn egy sortörést küld a képernyőre. Ehhez hasonló, például a "\t", amely egy tabulátorjelet küld a képernyőre. Az ilyen karaktereket hívjuk Escape szekvenciáknak. Mivel az idézőjelnek (") is speciális jelentése van, ezért azt is csak escape szekvenciaként tudjuk megjeleníteni: \" A következő táblázat az escape szekvenciák listáját tartalmazza: ^ Escape szekvencia ^ Leírás ^ | \n | Sortörés | | \t | Tabulátor | | \" | Idézőjel | | \% | Százalékjel | ===== Gyakorlat 003 ===== * Írjon programot, ami "Kék János" nevet. * A következő sorba, tabulátorral tagolva a fizetését írja ki, ami 2856000. * A program írja ki a saját (szerző) nevét is, a "szerző: " szöveg után. ===== Változók és adattípusok ===== A C nyelvbe változókat hozhatunk létre, értékek tárolására. A változó deklarálása során meg kell adni milyen típus fogunk benne tárlni, majd megadom a változó nevét. Például egy egész típusú változót szeretnék tárolni, egy szam nevű változóban, akkor: int szam = 25; Ebben a példában rögtön definiáltam a szam nevű változó értékét. A típusok előtt különféle módosítókat adhatunk meg. Ilyen módosító a signed vagy az unsigned. Magyarul előjeles vagy előjel nélküli. A típusok alapértelmezetten előjelesek. Ha nem szeretnék negatív számokat használni, akkor használhatom az unsigned módosítót, így a pozitív számokból nagyobb intervallummal dolgozhatok. Példaként, adjuk meg az int számára unsigned módosítót. Ez azt jelenti, hogy csak előjel nélküli, pozitív számokat adhatunk meg. unsigned int szam = 25; Egy int típusban, a géptől függően 2 vagy 4 bájt nagyságú számot tárolhatunk. Az int számra megadhatunk egy short módosítót. Ekkor maximum 32767 a legnagyobb megadható szám. A legkisebb -32768. short int szam = 25; A legnagyobb megadható szám tárolása: short int szam = 32767; Használhatjuk egyszerre a unsigned és a short módosítót is. Ekkor a legkisebb tárolható szám 0, a legnagyobb, 65535. Például: unsigned short int szam = 63000; {{:oktatas:programozas:c:tipusokmodositok.png?400|}} A következő táblázat tovább módosítókat tartalmaz, és méreteket tartalmaz. ^ Típus ^ Bájt ^ Minimális érték ^ Maximális érték ^ | char, signed char | 1 | -128 | 127 | | unsigned char | 1 | 0 | 255 | | short, short int, \\ signed short int | 2 | -32768 | +32767 | | unsigned short, \\ unsigned short int | 2 | 0 | 65535 | | int, signed int | 2 vagy 4 | short vagy long | short vagy long | | unsigned \\ unsigned int | ugyanígy, de \\ unsigned | ugyanígy, de \\ unsigned | | long, long int, \\ signed long int | 4 | -2147483648 | +2147483647 | | unsigned long, \\ unsigned long int | 4 | 0 | 4294967295 | | char, \\ unsigned char | 1 | 0 | 255 | | signed char | 1 | -128 | 127 | A szabványos limits.h fejállományban megtalálhatók a maximum és minimum értékek. | CHAR_MIN | | CHAR_MAX | | UCHAR_MAX | | SHRT_MIN | | SHRT_MAX | | USHRT_MAX | | INT_MAX | | INT_MIN | | UINT_MAX | | LONG_MAX | | LONG_MIN | | ULONG_MAX | Ha szeretnénk kiíratni a legnagyobb tárolható int típust, írjuk meg a következő programot: #include #include int main() { printf("%d\n", INT_MAX); } ===== Valós típus ===== Legnagyobb tárolható double típus: #include #include int main() { printf("double: %g\n", DBL_MAX); } printf("float : %g\n", FLT_MAX); | FLT_MAX | | DBL_MAX | ===== Gyakorlat 004 ===== * Válaszoljon a következő kérdésekre: * Mi a legnagyobb tárolható szám, ha short típust adok meg? * Mi a legnagyobb tárolható szám, ha char típust adok meg? * Mi annak az állandónak a neve, amiből kideríthető, mi legnagyobb tárolható int típus? ===== Állandók vagy konstansok ===== Az állandó olyan érték, amelyet a továbbiak során nem akarunk megváltoztatni. A program futási ideje alatt ezek az állandók nem változtathatók. Kétfajta állandót használunk: literális és nevesített. {{:oktatas:programozas:c:allandok.png?400|}} ==== Literális állandó ==== Lehet szám, karakter, karaktersorozat, stb. === Számállandó === 1234 formában leírt szám egész szám. Ha long típusú állandót akarok leírni akkor utána kell tennem egy kis l vagy L betűt: 123456789L === Karakteres állandó === Egy vagy több aposztrófok (') közzé írt karakter. Pl.: 'a' vagy 'b' Karakterként nem szerepelhet a ' vagy az újsor. Ezért ezek helyettesítésére ún. escape­sorozatokat (escape szekvencia) használunk: új sor NL \n vízszintes tabulátor HT \t függőleges tabulátor VT \v visszalépés (backspace) BS \b kocsivissza CR \r lapemelés (formfeed) FF \f hangjelzés (bell) BEL \a backslash \ \\ kérdőjel ? \? aposztróf ' \' idézőjel " \" oktális szám ooo \ooo hexadecimális szám hh \xhh === Szöveges állandó === Szokásos elnevezése: „karaktersorozat”. A szöveget úgy adjuk meg, hogy maga a program azon változtatni nem tud a végrehajtás során, vagyis állandó. A C nyelvben a szöveges állandót idézőjelek között adjuk meg. Pl.: "Alma a fa alatt" Vegyük észre! A magyar nyelvtől eltérően a C nyelvben a nyitóidézőjel felül van. ==== Nevesített állandó ==== === #define === Az állandó számára egy azonosító nevet adunk meg. Egy szövegkonstans pl. a „Nagy Péter”. Ennek a konstansnak nevet is adhatunk, pl.: TELJESNEV. Innentől kezdve a TELJESNEV­et bárhova írom oda behelyettesítődik a „Nagy Péter”. C nyelvben ez a következő módon adható meg: #define TELJESNEV ”Nagy Peter” A C nyelvben lehetőség van még egy módon állandó megadására a const módosítóval. Pl.: const int a; Azt vállaljuk,hogy a változó tartalmát sosem változtatjuk meg. Általánosan: #define azonosító helyettesítő–szöveg Egy konkrét példa: #define MAX 5 #define FELSOHATAR 100 #define ALSOHATAR 50 === const === Egy nem módosítható objektumot deklarál. Az azonosítót nem használhatjuk egy egyenlőségjel baloldalán. const int a = 5; #include int main() { const int a = 5; printf("%d\n", a); } #include #define MERET 3 typedef double Ttomb[MERET]; int main() { Ttomb tomb = {3.7, 5.2, 2.8}; printf("%f\n", tomb[0]); } Láthattuk, hogy nevesített állandót két módon hozhatok létre. A define előfordítói utasítással, és a const módosítóval. {{:oktatas:programozas:c:nevesitett_literalis_allando.png?300|}} A const egy módosító. A módosító után áll az állandó típusa. A típus lehet char*, ezzel szöveges állandó adható meg. {{:oktatas:programozas:c:szoveges_allando.png?400|}} A const módosító mondja meg, hogy nevesített állandót szeretnénk, létrehozni. Amikor a programozó állandót hoz létre, azt vállalja, hogy ezt a memória területet a program tovább része alatt nem szeretné megváltoztatni. ===== Gyakorlat 005 ===== * Írjon programot, amely két állandót tartalmaz: * a programban használja a #define utasítást * az egyik szám, egy iskolában adható legkisebb osztályzatot tartalmazza * a másik szám, az iskolában adható legnagyobb osztályzatot tartalmazza * az állandók neveit válassza meg tetszőlegesen ===== Deklarációk ===== A felhasználása előtt minden változót deklarálni kell, bár bizonyos deklarációk a programkörnyezet alapján is létrejöhetnek. A deklaráció egy típust határoz meg, és utána egy vagy több adott típusú változó felsorolása (listája) áll. int also, felso, lepes; char c, sor[1000]; A változók a deklarációk közt tetszőleges módon szétoszthatók, pl. a bal oldali deklarációs listákkal teljesen egyenértékű az int also; int felso; int lepes; char c; char sor[1000]; A változók a deklaráció során kezdeti értéket is kaphatnak: int i = 0; char esc = '\\'; int hatar = MAXSOR+1; float eps = 1.0e-5 Bármely változó deklarációjában alkalmazható a const minősítő. Pl.: const double e = 2.71828182845905; const char uzenet [ ] = ”figyelem:”; int strlen(const char[ ]); ===== Gyakorlat 006 ===== * Válaszoljon a következő kérdésekre: * Milyen nyelvet tanulunk most? * Ki találta ki ezt a nyelvet amit tanulunk? * Mikor let a nyelv kifejlesztve? * Mire használjuk? * Van-e szabványosított verziója? * Mi az az előfordítás? * Mivel kezdjük az előfordítói utasításokat? * Mi az a konstans? * Hogyan hozunk létre C nyelvben konstanst? * Milyen az írásmódja a változóknak és a konstansoknak? * A C nyelv kis és nagybetű érzékeny? * A C nyelv kiírató utasítása? * Hol nem szükséges a C nyelvben az utasítás végére (;) pontosvessző? ===== Operátorok ===== Kétfajta operátort különböztetünk meg: * egy operandusú * két operandusú Egy operandus esetén az operátor lehet prefix és postfix: operátor operandus operandus operátor Két operandusú operátor esetén: operandus1 operátor operandus2 ==== Aritmetikai műveletek ==== + összeadás - kivonás * szorzás / osztás % maradék képzés Példák: int c = 3 + 5; int a = 3; int b = 5; int osszeg = a + b; Maradékképzés: int maradek = 9 % 2; A maradékot a "maradek" nevű változóban tároljuk. ==== Relációs műveletek ==== > < >= <= == Egyenlő != Nem egyenlő Az eredményük vagy 0 (hamis) vagy 1 (igaz) #include int main() { int a = 3; int b = 5; int e = a < b; printf("%d\n", e); } ==== Logikai műveletek ==== && ÉS || VAGY ! NEM Kiértékelés balról jobbra. Ha a kifejezés értéke meghatározható, akkor a kiértékelés leáll. Például: (a < 10) && (b > 5) ==== Értékadás másként ==== a = a + 1 -> a += 1 a = a - 1 -> a -= 1 a = a * 1 -> a *= 1 a = a / 1 -> a /= 1 a = a * (b + 1) -> a *= b + 1 ==== Növelő és csökkentő operátorok ==== ++ -- Az operátorok lehetnek prefix vagy postfix. a++ -> a = a + 1; ++a -> a = a + 1; Ha postfixként használjuk: a = 15; b = a++; // -> b = a; a = a + 1; Ha prefixként használjuk: a = 15; b = ++a; // -> a = a + 1; a = b; ==== Bitenkénti logikai operátorok ==== | & | bitenkénti ÉS | | | | bitenkénti VAGY | | ^ | bitenkénti kizáró vagy | | << | bitléptetés balra | | >> | bitléptetés jobbra | | ~ | egyeskomplemens | Csak egész számokra alkalmazhatók === Példa a bitenkénti balra léptetésre === | 000011 | nem számít mennyit jelent | | 000010 | 3-at jelent, ennyivel léptetünk | | 001100 | eredmény | === Komplemens képzés === | a = 6 | 0000 0110 | | ~a | 1111 1001 | 249 | ==== Precedencia ==== | () | zárójel | | ! ++ -- - | negálás, növelés, csökkentés, előjel | | * / % | szorzás, osztás, maradékképzés | | + - | összeadás, kivonás | | << >> | bitenkénti eltolás balra és jobbra | | < <= >= > | kisebb mint, kisebb vagy egyenlő, nagyobb vagy egyenlő, nagyobb mint | | == =! | egyenlő, nem egyenlő | | & | bitenkénti megengedő (inkluzív) és | | ^ | kizáró (exkluzív) vagy | | | | bitenkénti vagy | | && | és | | || | vagy | Ha a egy művelet azonos szinten van, akkor az operátorok balról jobbra lesznek kiértékelve. Példa a balról jobbra kiértékelésre: 12/2*3 = 18 A zárójel mindig módosítja a kiértékelés sorrendjét. ===== Gyakorlat 007 ===== * Válaszoljon a következő kérdésekre: * Mire való a % operátor? * Mire való a && operátor? * Mire való a || operátor? * Mire való a ++ operátor? ===== Formázott kivitel ===== A printf() függvény formátumozott kivitelt tesz lehetővé számunkra. A formátum kétféle karaktert tartalmazhat: amely kiíródik változás nélkül a kimenetre, és amely a soron következő argumentum konverzióját írja elő. Minden konverziós szakasz a % jellel kezdődik. A % jel és a konverziós karakter között a sorrendben a következők lehetnek: * mínusz jel, ami konvertált argumentumot balra igazítja * egy szám, ami megadja a minimális mezőszélességet * egy pont, ami elválasztja a mezőszélességet a pontosságot megadó számtól * egy szám (pontosság) * karaktersorozatnál a kiírandó karakterek maximális számát * lebegőpontos számok esetén a tizedespont után kiírt számjegyek számát * egész karakterek esetén a kiírt számjegyek maximális számát * egy h betű, ha egy egész számot short típusként vagy egy l betű, ha long típusként írun ki A leggyakrabban használt konverziós karakterek, a d, f, c és s. A d konverziós karaktert egész számok kiíratására használjuk. {{:oktatas:programozas:c:egsz_szam_formazasa.png?200|}} Az f konverziós karaktert valós számok kiíratására használjuk. {{:oktatas:programozas:c:valos_szam_formazasa.png?200|}} A c konverziós karaktert, karakterek kiíratására használjuk. {{:oktatas:programozas:c:karakter_formazasa.png?200|}} Az s konverziós karaktert, karaktersorozatok, vagyis szövegek kiíratására használjuk. {{:oktatas:programozas:c:szoveg_formazasa.png?200|}} Kiíratás 2 tizedesjegy pontossággal: #include int main() { double a = 31.123456; printf("%.2f\n", a); } Kiíratás 20 szélesen: #include int main() { double a = 31.123456; printf("%20.2f\n", a); } Balra igazítás: #include int main() { double a = 31.123456; printf("|%-20.2f|\n", a); } ^ formátum karakter ^ típus ^ Használatra példa ^ | ld | long | | d | int, short, char | | f | float | | c | char, int | | Lg | long double | | % | százalék jelet ír a képernyőre | printf("%%\n"); | ===== Gyakorlat 008 ===== * Válaszoljon a következő kérdésekre: * Milyen formátum karakter szükséges egy egész érték kiíratásához? * Milyen formátum karakter szükséges egy valós érték kiíratásához? * Milyen formátum karakter szükséges egy karakter kiíratásához? * Milyen formátum karakter szükséges egy karaktersorozat kiíratásához? ===== Matematikai függvények ===== A matematikai függvények a math.h nevű fejállományban vannak. ==== Állandók ==== PI értéke M_PI 3.14159265358979323846 #include #include int main() { printf("%f\n", M_PI); } ==== Szinusz ==== A trigonometriai függvények szög helyett radiánban várják az értéküket. A feladatokban általában szögben adják meg az értékeket, amit át kell számolnunk radiánban. Ha például van 30 fokunk, akkor meg kell szorozni PI értékével, majd az eredményt el kell osztani 180-nal. #include #include int main() { printf("%f\n", sin(30 * M_PI / 180)); } Készítsük el példaprogramot, majd fordítsuk és futtassuk. A matematikai függvények használata esetén szükség van egy új kapcsolóra, a -l. A -l értéke egy "m" betű, utalva a math-ra. Egy matematikai függvény használata esetén, tehát így fordítunk egy C forráskódot: cc -l m -o main main.c ==== Függvények ==== double sin (double); double cos (double); double tan (double); double sinh (double); double cosh (double); double tanh (double); double asin (double); double acos (double); double atan (double); double atan2 (double, double); double exp (double); double log (double); double log10 (double); double pow (double, double); double sqrt (double); double ceil (double); // Felfele kerekít double floor (double); // Lelfele kerekít double fabs (double); // Abszolút érték double ldexp (double, int); double frexp (double, int*); double modf (double, double*); double fmod (double, double); #include #include int main() { printf("%f\n", sqrt(9.5)); } #include #include int main() { printf("%f\n", pow(2,8)); } ==== Az abszolút érték egész számokkal ==== Érdekesség, hogy az abs() függvény másik programozói könyvtárban van, a stdlib.h könyvtárban. Szintaxis: int abs(int) //Abszolút érték Használata, például: #include int main() { int szam = -4; int ered = abs(szam); } Ha fordításnál kihagyjuk az stdlib.h programozói könyvtárat, akkor figyelmeztetést kapunk: warning: implicit declaration of function 'abs' [-Wimplicit-function-declaration] int ered = abs(szam); Az újabb GCC 5-s verzióban az alapértelmezetten C szabvány a C11. Ez megköveteli, hogy szerepeltessük az #include fordítási direktívával, a stdlib.h könyvtárat. A fordító rávehető, hogy C89/C90 szabványt használjon, ami nem találja hibának, ha nem fordítjuk az stdlib.h könyvtárat hozzá. cc -std=gnu89 -o main main.c Az alapértelmezett C11 szabványt nem kell megadni, de megadható: cc -std=gnu11 -o main main.c ==== Egész osztás ==== Az stdlib.h fejállományban van egy div() függvény, amellyel egész osztást végezhetünk. A div() függvény egy div_t struktúrát ad vissza. #include #include int main() { div_t a = div(5, 2); printf("%d\n", a); } A struktúra így néz ki: typedef struct { int quot; int rem; } div_t; Mivel az eredményt struktúrában kapjuk, kiírathatjuk külön az osztás eredményét, és a maradékot is: #include #include int main() { div_t a = div(5, 2); printf("%d\n", a.quot); printf("%d\n", a.rem); } ===== Gyakorlat 009 ===== * Válaszoljon a következő kérdésekre: * Mi a math.h? * A maradék képzést milyen operátorral valósítjuk meg? * Mondjon két egy operandusú operátort. * Mondjon négy bitenkénti operátort. * Milyen utasítással írathatunk ki formázott kivitelt. * Formázott kivitel esetén, valós számot milyen formátumkarakterrel tudunk kiíratni? ===== Véletlen szám ===== ==== Függvények ==== Egy számítógépen nem könnyű valódi véletlen számot generálni, mivel minden folyamat jól meghatározott algoritmusok mentén fut. A C nyelvben az stdlib.h fejállományban (programozói könyvtár), van definiálva két függvény, ami véletlen szám generálást, segíti. A következő táblázatban ezeket látjuk: | int rand(void) | Véletlen szám generálás | | void srand(unsigned int) | A véletlen szám generálás előkészítése | A két függvény az ANSI szabvány része. A rand() függvény 0 és egy nagy egész szám között generál véletlen számot. A nagy egész szám a stdlib.h fejállományban van definiálva, neve **RAND_MAX**, ami egy állandó. Ez tartalmazza a legnagyobb generálható egész számot. 64 bites Debian GNU/Linux 10 verzióban ez: **2147483367**. Általában nem ilyen számra van szükség van szükség, helyette valamilyen adott tartományban van véletlen számra szükségünk. Ezért a kapott véletlen számon, elvégzünk egy egész osztást, és annak maradékát vesszük figyelembe. Ha például 0 és 3 közötti számra van szükségünk (ebbe bele értve a 0 és a 3-t is, akkor, 4-gyel osztunk. A kapott maradék 0, 1, 2 vagy 3 lesz. Ez lesz a véletlen számunk. Ha 1, 2, 3 és 4-s számok közül szeretnénk véletlenszerűen számokat kapni, akkor az előző eredményhez adjunk hozzá 1-t. A véletlen szám generálása, ekkor így néz ki: int vel = rand() % 4 + 1; Az srand() függvényre azért van szükségünk, mert nélküle mindig ugyanazt a véletlen számot kapjuk. Paraméterként egy time() függvény szokás futtatni NULL paraméterrel: srand(time(NULL)); int vel1 = rand() % 4 + 1; int vel2 = rand() % 4 + 1; A time() függény a time.h fejállományban található. ==== Maximum kiíratása ==== Írjunk egy programot, ami kiírja a legnagyobb generálható egész számot. #include #include int main() { printf("%d\n", RAND_MAX); } Fordítás és futtatás: cc -o maxrand maxrand.c ./maxrand ==== Példa 1 ==== #include #include #include int main() { printf("Véletlen\n"); srand(time(NULL)); printf("0 és 4 között: %d\n", rand() % 5); printf("20 és 29 között: %d\n", rand() % 10 +20); return 0; } ==== Újabb példa ==== #include #include #include int main() { srand(time(NULL)); int a = rand() % 2; int b = rand() % 2; printf("Elso : %d\n", a); printf("Masodik: %d\n", b); } ==== Valódi véletlen szám ==== Ha több véletlen számot kell generálnunk egymás után, a rand() függvény nem ad mindig megfelelő eredményt. BSD és Linux rendszereken a valódi véletlenszámhoz használjuk a /dev/urandom állományt. #include #include int veletlen(int n) { unsigned int vel; FILE *f; f = fopen("/dev/urandom", "r"); fread(&vel, sizeof(vel), 1, f); fclose(f); return vel % n; } int main() { printf("%d\n", veletlen(6)); } ===== Gyakorlat 010 ===== * Válaszoljon a következő kérdésekre: * Milyen fejállományban van a rand() függvény? * Milyen fejállományban található a time() függvény? * Milyen fejállományban található a RAND_MAX állandó? * Milyen fejállományban található a srand() függvény? * Írjon programot, amely kockadobást szimulál. * Mentés: kocka.c * Írjon programot, amely pókerhez 5 kockadobást szimulál. * Mentés: poker.c ===== Konverzió ===== ==== Típusátalakítások ==== Egy int típusú változó értéke gond nélkül áttehető egy short típusú változóba, amíg az elfér a a short típusban. int a = 35; short b = a; A short típusban a legnagyobb tárolható szám: 32767. Mi történik, akkor, ha 32768 számot tesszük "a" változóba, ami int típusú? int a = 32768; short b = a; Az eredeti szám ilyenkor nem marad meg, értékvesztés történik. Ha valós típusú változó értékét tesszük egész típusú változóba, akkor a törtrészek elvesznek, de a típusátalakítás működik: double a = 35.123; int b = a; ==== Karaktersorozatok és számok közötti konverziók ==== A függvények az stdlib.h programozói könyvtárban találhatók. double atof(const char*) int atoi(const char*) long atol(const char*) int ltoa(int, char*, int) ==== String egész számmá ==== A következő példában egy s változót hozunk létre, amelyben 2 karakter szeretnénk tárolni. A C nyelvben nincs karaktersorozat (string) számára típus. Ezért egy karakter tömböt fogunk használni. A char s[3], egy tömb amiben karaktereket tárolhatunk, összesen 3 darabot. Mi csak két karakter szeretnénk tárolni "15", de a karaktersorozatot lezáró \0 érték számára is helyet kell foglalni, ezért 3 byte számára foglalunk helyet. char s[3]; strcpy(s, "15"); int a = atoi(s); Az **atoi()** függvény a **stdlib.h** programozói könyvtárból érhető el. Az **strcpy()** függvény a **string.h** programozói könyvtárból érhető el. A helyfoglalást, néha a malloc() függvénnyel szokták megoldani, amelynek a használata kicsit összetettebb, de az eredmény egyenértékű a fenti megoldással: char *s = (char*) malloc(5 * sizeof(char)); strcpy(s, "15"); int a = atoi(s); A (char*) típusátalakítás jelent. A malloc() a típusfoglalást után void típust ad vissza, de nekünk char* típusra van szükség. Ha azt írom char s; akkor egyetlen karakter akarok tárlni az s változóban. Ha azt írom char *s; akkor egy karaktersorozatra (string) mutató értéket szeretnék tárolni s-ben. ==== String hosszú egész számmá ==== char *s = (char*) malloc(5 * sizeof(char)); strcpy(s, "15"); long a = atol(s); Az atol() függvény a stdlib.h fejállományból érhető el. ==== String valós számmá ==== char *s = (char*) malloc(5 * sizeof(char)); strcpy(s, "15"); float a = atof(s); Az atof() függvény a stdlib.h fejállományból érhető el. ==== Egész stringgé ==== char *s = (char*) malloc(10 * sizeof(char)); int a = 15; sprintf(s, "%d", a); ==== Egész stringgé ==== char *s = (char*) malloc(10 * sizeof(char)); float a = 15.7; sprintf(s, "%f", a); ==== Karakter stringgé ==== char *s = (char*) malloc(10 * sizeof(char)); char ch = 'a'; sprintf(s, "%c", ch); ==== String karakterré ==== char *s = (char*) malloc(10 * sizeof(char)); strcpy(s, "a"); char ch = s[0]; ===== Gyakorlat 011 ===== * Válaszoljon a következő kérdésekre: * Milyen függvénnyel lehet átkonvertálni egy karaktersorozatot (string), egész számmá? ===== Bevitel ===== ==== Szám bekérése ==== Bekéréshez használhatjuk a scanf() függvényt, ami a stdio.h fejállományban van leírva. A scanf() a printf() függvényhez hasonlóan használható. ^ Formátumkarakterek ^^ ^ Karakter ^ mikor használható ^ | %d | egész szám bekérése | | %f | valós szám bekérése | | %lf | valós szám bekérése double típusú változóba | | %c | karakter bekérése | | %s | egy szó bekérése | A scanf() függvénynek minimum két paramétere van. Az első a formátumstring, utána pedig annyi változó, amennyi a formátumstringben megadtunk, vesszővel tagolva. A formtáumstringbe egy változó leírását a "%" karakterrel kezdjük, és egy vagy két formátumkarakterrel fejezzük be. Egy egész szám bekérése például: scanf("%d", &a); #include int main() { int a; printf("Szam: "); scanf("%d", &a); printf("Ezt írtad: %d\n", a); } Ebben a példában egyetlen egész számot kérünk be az "a" változóba. Vegyük észre az "a" változó előtt a "&" karaktert. Erre azért van szükség, mert a második paraméter nem a változó neve, hanem annak címe kell legyen. Ebből következik, hogy egy változó címét úgy kapjuk meg, hogy "&valtozonev" formát használom. Több változó is bekérhető egyszerre. A következő példában egyszerre két változót kérünk be. #include int main() { int szam1, szam2; printf("Két szám: "); scanf("%d %d", &szam1, &szam2); printf("Ezeket írtad: %d %d\n", szam1, szam2); } A program végrehajtásakor, ilyenkor az első szám után 1 vagy több szóközt, tabulátor vagy sortörést (whitespace karakterek) írok. az első bekért szám után egy Enter is írható, így a második számot a következő sorba írom be. ==== Valós szám bekérése ==== Ha egy float típusú változóba kérünk be számot, a formátumkarakter "f" karakter: #include int main() { float szam1; printf("Valós szám: "); scanf("%f", &szam1); printf("Ezt írtad: %f\n", szam1); } Dupla pontos szám bekérésénél a formátumkarakterből kettő kell: "lf". #include int main() { double szam1; printf("Valós szám: "); scanf("%lf", &szam1); printf("Ezt írtad: %f\n", szam1); } ==== Karakter bekérése ==== Egy karakter bekérése esetén a formátumkarakter egy "c" betű. #include int main() { char ch; printf("Karakter: "); scanf("%c", &ch); printf("Ezt írtad: %c\n", ch); } ==== Szó bekérése ==== A scanf() függvény alkalmas karaktersorozat beolvasására. A scanf() azonban a whitespace karaktereket szeparátornak tekinti, ezért csak szavakat képes beolvasni. Az alábbi program képes beolvasni egy nevet, amiben nincs szóköz vagy tabulátor. #include #include int main() { char *nev; nev = (char*) malloc(100 * sizeof(char)); printf("Név: "); scanf("%s", nev); printf("Ezt írtad: %s\n", nev); } Vegyük észre, hogy a scanf() függvény, második paraméterében, a nev változó neve elé nem tettünk "&" karaktert, pedig azt írtuk, hogy oda cím kell. Azért nem szükséges, mert a nev változót eleve mutató típusnak deklaráltam, ez a "*" karakter jelzi a kódban: char *nev; Ez pedig azt jelenti, hogy eleve a változó címével dolgozunk. A char nev[30] egy karaktertömb, amiben maximum 30 karakter tárolható. Így is használható, így nem szükséges külön malloc() vagy hasonló függvénnyel helyet foglalni a nev változónak: char nev[30] Egy konkrét példa a használatra: #include int main() { char nev[30]; printf("Keresztnév: "); scanf("%s", nev); printf("Ezt írtad: %s\n", nev); } A bekérésnél itt sem kell a "&" karakter. ==== Mondat bekérése ==== Az fgets() függvény az állományok olvasására használható és az stdio.h fejállományban található. Az stdio.h fejlécállományban deklarálva van egy stdin nevű fájl is, amely billentyűzetet szimbolizálja. Így egy valódi állomány helyett a billentyűzetet is olvashatjuk az fgets() függvénnyel. #include int main() { char nev[100]; printf("Név: "); fgets(nev, 100, stdin); printf("Ezt írtad: %s\n", nev); } Az fgets() függvény csak adott számú karaktert kér be, amit a második paraméterben adhatunk meg. Az fgets() függvény beolvassa az Enter billentyűt is. Ha szeretnénk sortörést törölni, ami az Enter hatására kerül az adatszerkezetbe, akkor a következő egyszerű példa erre mutat egy lehetőséget.  A karaktersorozat első elemének címét az „s” mutató tartalmazza: if (s[strlen(s)-1]=='\n') s[strlen(s)-1] = '\0'; A fenti utasítás csak akkor tünteti el az utolsó  karaktert, ha az valóban egy sortörés.  Működése: megnézzük, hogy az utolsó karakter sortörés-e. Ha igen, akkor lecseréljük karaktersorozat végét jelölő null karakterre. #include #include int main() { char *nev; nev = (char*) malloc(100 * sizeof(char)); printf("Név: "); fgets(nev, 100, stdin); printf("Ezt írtad: %s\n", nev); } A Karaktersorozat fejezetben még lesz szó, azok bekéréséről. ===== Gyakorlat 012 ===== * Válaszoljon a következő kérdésekre: * Milyen fejállományban található a scanf() függvény? * Mire használhatjuk a scanf() függvényt? * Melyik függvénnyel lehet mondatokat bekérni? scanf() vagy fgets() ===== Szelekció ===== ==== if ==== Bizonyos utasítások végrehajtását feltételhez köthetjük. #include int main() { int a = 5; if(a > 0) printf("Pozitív szám\n"); } ==== if-else ==== #include int main() { int a = 5; if(a > 0) printf("Pozitív szám\n"); else printf("Nulla vagy negatív\n"); } ==== switch ==== A switch() utasítás paramétereként a C nyelvben csak egész számokat megvalósító típusok adhatok meg. Például int, char. #include int main() { int szam; printf("Szám: "); scanf("%d", &szam); switch(szam) { case 1 : printf("Egy\n"); break; case 2 : printf("Kettő\n"); break; default: printf("Más szám\n"); } } Minden case részt meg kell szakítani egy break utasítással, ha nem szeretnénk a következő case utáni rész is végrehajtani. ===== Gyakorlat 013 ===== * Írjon programot, amely -2 és 2 között generál egy számot. * A lehetséges számok -2, -1, 0, 1, 2 * Ha szám nagyobb mint 0, írja a képernyőre, hogy a következőt: "Pozitív" * Az előző programot, egészítse ki: * 0 vagy negatív szám esetén írja a képernyőre: "Nulla vagy negatív" * Az előző programot, írja át: * 0-nál nagyobb szám esetén kiírja: "Pozitív" * 0 esetén "Nulla" * 0-nál kisebb szám esetén: "Negatív" ===== Iteráció ===== ==== for ==== A for() utasítás, egy növekményes ciklusok számára megvalósított eszköz. Három paramétere van. A paramétereket ";" karakterrel tagoljuk. Az első paraméter a kezdőérték. A második paraméter a feltétel, amely tartalmazza, meddig ismétlődjön a ciklus. Az utolsó paraméter a növekmény. Itt növeljük a i változó tartalmát. for(kezdőérték ; feltétel ; növekmény ) { //ciklus törzse } A ciklus törzsébe írjuk azokat az utasításokat, amelyek szeretnénk minden egyes iterációban végrehajtani. A ciklus törzsét "{" karakterrel kezdjük, és "}" karakterrel zárjuk. Ha csak egyetlen utasításunk van, nem kötelező a kapcsos zárójelek használata. A kód átláthatósága érdekében, azonban ajánlott használni mindig. #include int main() { int i; for(i=0; i<10;i++) printf("%d\n", i); } ==== while ==== A while() ciklusnak egyetlen paramétere van, a feltétel. Ha feltétel igaz, a ciklus törzsében található utasítások végrehajtódnak. Ha hamis, a ciklus véget ér. #include int main() { int i=0; while(i<10) { printf("%d\n", i); i++; } } ==== do-while ==== A do-while, egy hátul tesztelő ciklus. A ciklus törzsét először végrehajtja, és csak ez után vizsgálja meg, hogy a feltétel igaz vagy hamis. Ha feltétel igaz, a ciklus törzsét újra végrehajtja. Ha hamis, a ciklus véget ér. #include int main(){ int i=0; do { printf("%d\n", i); i++; }while(i<10); } ===== Gyakorlat 014 ===== * Írjon programot, amely véletlen számokat generál. * Mentés: nulig.c * A program írja ki a generált véletlen számot. * Ha a szám nem 0, akkor generáljon újabb számot. ===== Összetett típusok ===== ==== enum ==== Felsorolt vagy enum típus. Az enum a felsorolt értékeket egészként tárolja. enum {hetfo, kedd, szerda, csutortok pentek}; A hetfo 0 értékkel egyenlő, a kedd 1 értékkel, a szerda 2, stb. #include int main() { enum het {hetfo, kedd, szerda, csutortok, pentek, szombat, vasarnap}; enum het a; a = hetfo; if(a==hetfo) printf("hétfő\n"); } A fenti példában deklarálunk egy a változót, amely a het napjai értéket veheti fel: enum het a; Az a változóban ez után értékként megadhatjuk a hetfo, kedd, szerda, stb. értékeket. A példában a hetfo értéket adjuk meg: a = hetfo; Ezek után megvizsgálhatjuk, hogy az a változó milyen értéket tartalmaz. A példában az vizsgáljuk értéke egyenlő-e hetfo-vel: if(a==hetfo) Az a változóval persze többet is tehetünk. Például növelhetjük az értéket: a++ Egy ilyen növelés után a tartalma már kedd lesz. Megjegyzés: A későbbiekben még veszünk összetett adattípusokat. Ilyen lesz a tömb meg a struktúra. ===== Gyakorlat 015 ===== * Írjon programot, amelyben az év hónapjait egy enum szerkezetben tárolja. * Mentés: havi.c * Írassa ki a hónapokat az 5-dik hónapot. ===== Karaktersorozat ===== ==== Tárolás ==== A karaktersorozat (angolul string) típus nem létezik a C nyelvben. Ha karaktersorozatot szeretnénk tárolni, akkor egy karakteres mutató típusú változót hozok létre, ahol a mutatótól kezdve a memóriában több karaktert is tárolhatok. A mutató típusú változó a karaktersorozat elejére mutat a memóriában. A sorozat végét egy null karakter adja (nem az ASCII 0-ás karaktere, hanem egy olyan byte aminek az értéke 0. Escape szekvenciával jelölve: '\0' A példa kedvéért legyen a "Hello" szöveg eltárolva a fe1011 memóriacímtől kezdve. Az alábbi táblázat mutatja, mit tartalmaz a memória. ^ betűk | 'H' | 'e' | 'l' | 'l' | 'o' | 0 | ^ memóriacím | fe1011 | fe1012 | fe1013 | fe1014 | fe1015 | fe1016 | Az utolsó tehát nem számjegy, hanem egy 0 értékű bájt. Ezt persze nem kell nekünk a karaktersorozat végére tenni, mert azt a fordító automatikusan megteszi. Egy ilyen mutató létrehozása a következő módon valósítható meg: char *s; A működéshez le kell még foglalni annyi karakter helyet a memóriában, amennyit szeretnénk eltárolni. A helyfoglaláshoz két függvényből egyet szoktunk használni: * malloc() * calloc() Mindkettő az stdlib.h programozói könyvtárakban van. Mi most a malloc() használatát nézzük meg. A malloc() függvénynek egyetlen paramétere van, mégpedig a lefoglalandó byte-ok száma. Ha például 30 karaktert akarunk tárolni, akkor így írhatjuk: malloc(30); Ezzel még nincs kész. Meg kell adni, hogy melyik mutatóhoz rendeljük ezt a 30 byte-ot: s = malloc(30); A malloc() azonban void típust ad vissza, amit át kell alakítanunk char* típusúvá: s = (char*) malloc(30); Ez már egy működő helyfoglalás. A deklarálás és a helyfoglalás együtt ezek után: #include int main() { char *s; s = (char*) malloc(30); } A C nyelven egy karakter eltárolására nem biztos, hogy 1 byte-ot használunk. Ez függhet az adott rendszertől. A helyfoglaló függvényt ezért szokás kibővíteni egy vizsgálattal, amely megmondja mekkora egy char típus az adott rendszeren. Erre a sizeof() operátort használhatjuk. Az általa visszaadott értéket szorozzuk be 30-al: #include int main() { char *s; s = (char*) malloc(30 * sizeof(char)); } Ha szeretnénk megváltoztatni a lefoglalt hely méretét az következő utasítással tehetjük meg: * realloc() A lefoglalt hely felszabadítása: * free() Példa: #include #include #include int main() { char *nev; nev = (char*) malloc(10* sizeof(char)); strcpy(nev, "Lajos"); printf("%s\n", nev); nev = (char*) realloc(nev, 30 * sizeof(char)); strcpy(nev, "Ferdinándházi Lajos"); printf("%s\n", nev); free(nev); } ==== A változó használata ==== Most már használhatjuk a "s" változót. A C nyelvben egy karaktersorozatot nem adhatunk meg egy egyenlőség jel jobboldalán. Ha egy változóba egy karaktersorozatot szeretnénk elhelyezni, akkor arra az strcpy() függvény használható, amely a string.h könyvtárakban találhatók: #include #include int main() { char *gyumolcs; gyumolcs = (char*) malloc(30 * sizeof(char)); strcpy(gyumolcs, "szilva"); } Ha tömböt használunk, akkor nem szükséges külön helyfoglaló utasítást használni, például a malloc(), mivel a eleve helyfoglalás történik. Legyen a példa kedvéért egy "nev" nevű tömb, amiben 30 karakternek foglalok helyet: char nev[30]; Itt lefoglaltam a helyet 30 karakter számára, nem kell külön malloc() függvény. A használatra példa: #include int main() { char gyumolcs[30]; strcpy(gyumolcs, "szilva"); } ==== Kiíratás ==== Ha egy változóban lévő karaktersorozatot akarunk a képernyőre íratni, akkor az "s" formátumkódot kell használnunk. A fenti gyumolcs változó tartalmának képernyőre íratása: #include #include #include int main() { char *gyumolcs; gyumolcs = (char*) malloc(30 * sizeof(char)); strcpy(gyumolcs, "szilva"); printf("%s\n", gyumolcs); } ==== Bekérés ==== A karaktersorozatok bekéréséről már volt szó a Bekérés című fejezetben. Itt most átismételjük a tanultakat. #include #include int main() { char *gyumolcs; gyumolcs = (char*) malloc(30 * sizeof(char)); scanf("%s" gyumolcs); printf("%s\n", gyumolcs); } Ha egy változóba egy értéket szeretnénk beolvasni a scanf() függvénnyel, akkor sosem magát a változó nevét kell megadni, hanem annak címét. Ezt úgy érjük el, hogy a változó név elé egy "&" karaktert teszünk. A gyumolcs változó elé mégsem tettünk, mert az eleve mutató típus, vagyis itt eleve a változó címét kapjuk meg. A scanf() függvény azonban csak egyetlen szó bekérésére alkalmas, mert whitespace karakterig olvas. A szóközök után írt karaktereket már nem tárolja el. Mondatok beolvasását az fgets() függvénnyel valósítjuk meg. Rendelkezésre áll a gets() függvény is, de használata biztonsági kockázatot jelent. Példa az fgets() használatára. #include #include int main() { char *mondat; mondat = (char*) malloc(30 * sizeof(char)); printf("Mondat: "); fgets(mondat, 30, stdin); printf("Ezt írtad: %s\n", mondat); } Az fgets() valójában fájlok olvasására lett kitalálva, de van egy speciális fájl, aminek a neve stdin. Ha ezt a fájlt olvassuk, akkor billentyűzet leütéseit kapjuk meg. A nem ajánlott gets() függvényre példa: #include #include int main() { char *mondat; mondat = (char*) malloc(30 * sizeof(char)); printf("Mondat: "); gets(mondat); printf("Ezt írtad: %s\n", mondat); } ==== Karaktersorozat darabolása ==== A C nyelvben az strtok() függvényt használhatjuk egy karaktersorozat darabolására. Szintaktikája: #include char *strtok( char *str1, const char *str2 ); Az strtok() függvény visszaad egy következő egységet (tokent), a karaktersorozatból. A választott karaktersorozat az első paraméter. Második paraméter az elválasztó. strcpy(str, "alma:körte:szilva:barack"); strtok(str, ":"); Az strtok() NULL értékkel tér vissza ha nem talál több tokent. Az strtok() függvényt úgy használjuk, hogy az első részt kiolvassuk első paraméternek megadva az str változót: strtok(str, ":"); Minden további hívást a NULL értékkel kell végrehajtani: strtok(NULL, ":"); A teljes megvalósítás így nézhet ki: #include #include int main() { char *str = (char*) malloc(30 * sizeof(char)); char *token = NULL; strcpy(str, "alma:körte:szilva:barack"); token = (char*) strtok(str, ":"); while(token != NULL) { printf("%s\n", token); token = (char*) strtok(NULL, ":"); } } Másik példa: #include #include int main() { char *jarmu = (char*) malloc(30 * sizeof(char)); char elvalaszto[] = ":"; char *resz = NULL; strcpy(jarmu, "opel:ford:citroen:mazda"); resz = (char*) strtok(jarmu, elvalaszto); while(resz != NULL) { printf("%s\n", resz); resz = (char*) strtok(NULL, elvalaszto); } } Ugyanaz más szeparátorral: #include #include int main() { char *jarmu = (char*) malloc(30 * sizeof(char)); char elvalaszto[] = "#"; char *resz = NULL; strcpy(jarmu, "opel#ford#citroen#mazda"); resz = (char*) strtok(jarmu, elvalaszto); while(resz != NULL) { printf("%s\n", resz); resz = (char*) strtok(NULL, elvalaszto); } } A szeparátorokaból lehet több is: #include #include int main() { char *jarmu = (char*) malloc(30 * sizeof(char)); char elvalaszto[] = "#:;"; char *resz = NULL; strcpy(jarmu, "opel#ford:citroen;mazda"); resz = (char*) strtok(jarmu, elvalaszto); while(resz != NULL) { printf("%s\n", resz); resz = (char*) strtok(NULL, elvalaszto); } } Lásd még: * [[oktatas:programozás:c:c_példaprogram#sztring_darabolás]] ==== Üres vagy nem üres ==== #include int main () { char szoveg[30]; if(szoveg[0]) { printf("Üres\n"); } } #include #include int main () { char szoveg[30]; if(szoveg && szoveg[0]) { printf("Üres\n"); } } #include #include int main () { char szoveg[30]; strcpy(szoveg, ""); if(szoveg[0] == '\0') { printf("Üres\n"); } } ==== Összefűzés ==== #include #include int main () { char szoveg1[30]; char szoveg2[30]; strcpy(szoveg1, "alma"); strcpy(szoveg2, "körte"); strcat(szoveg1, szoveg2); printf("Szöveg: %s\n", szoveg1); } ==== Összehasonlítás ==== #include #include int main () { char szoveg[30]; strcpy(szoveg, "alma"); if(!strcmp(szoveg, "alma")) { printf("Egyezik\n"); }else { printf("Nem egyezik\n"); } } ===== Gyakorlat 016 ===== * Írjon programot, amelyben bekéri egy dolgozó nevét, települését és fizetését. * Mentés: dole.c ===== Tömb ===== ==== Vektor ==== A tömbök azonos típusú elemek tárolása kitalált adatszerkezet. Például több egész számot is szeretnék eltárolni. A változó deklaráció során jeleznünk kell, hogy most nem egy értékű változóról van szó. ezt a változó neve után tett szögletes zárójelek jelzik. int tomb[4]; Meg kell adnunk azt is hány darab értéket szeretnénk tárolni. A fenti példában négy darab érték tárolásához foglaltunk memóriát, amely mindegyik "int" azaz egész típusú lehet. A tömbnek ez után 4 eleme lehet. Minden elemet egy indexel tudunk azonosítani. Az első index a nulla: 0, majd a többi szám. Ebből következik, hogy egy négy elemű tömbnél a legfelső index 3. A tömb elemeinek úgy adhatunk értéket, hogy leírjuk a tömb nevét, utána szögletes zárójelben megadom, melyik indexű elemet akarom beállítani. int tomb[4]; tomb[0] = 3; tomb[1] = 8; tomb[2] = 5; tomb[3] = 2; Felhasználni ugyanígy tudjuk. Ha szeretném például kiíratni a tömb egyik elemét, azt így tehetem meg: printf("%d\n", tomb[0]); ==== Tömb kezdőértékkel ==== A tömböknek rögtön kezdőértéket adhatunk a deklarációban. Az alábbi példában erre látunk példát. #include int main() { int tomb[] = {3, 5, 6, 7, 8, 9 }; printf("%d\n", tomb[0]); } Megadható a tömb nagysága: int tomb[3] = {3, 5, 6}; De ez nem kötelező. Ha viszont megadjuk akkor annak értéke nem lehet kisebb a tömb kezdőértékeinek a számánál. Ez még helyes. int tomb[5] = {3, 5, 6}; Ez már helytelen: int tomb[2] = {3, 5, 6}; #include #include #define MAX 2 typedef int Ttomb[MAX]; void kiir(int *tomb) { tomb[0]=8; } int main() { Ttomb tomb; tomb[0] = 5; tomb[5] = 3; kiir(tomb); printf("%d\n", tomb[0]); } ==== Tömb bejárása ==== #include int main() { int tomb[] = {3, 8, 5, 6, 2, 9 }; int i; for(i=0; i<6; i++) { printf("%d\n", tomb[i]); } } ==== Tömb mérete ==== int tomb[] = {3, 8, 5, 6, 2, 9 }; int n = sizeof tomb / sizeof tomb[0]; Vagy: int tomb[] = {3, 8, 5, 6, 2, 9 }; size_t meret = sizeof(tomb) / sizeof(tomb[0]); Vagy: int tomb[] = {3, 8, 5, 6, 2, 9 }; int n = sizeof tomb / sizeof *tomb; Komplett példa: #include int main() { int tomb[] = {3, 8, 5, 6, 2, 9 }; int n = sizeof tomb / sizeof *tomb; int i; for(i=0; i ==== Mátrix ==== A tömböknek lehet két kiterjedése is. Ekkor mátrixról beszélünk. A tömb felépítése ekkor hasonlít a matematikában tanult mátrixhoz, amelynek kiterjedése kétirányú. Lássunk egy példát egy mátrixra: | 3 | 12 | 8 | 9 | | 2 | 15 | 17 | 7 | | 11 | 4 | 3 | 18 | === Mátrix létrehozása C nyelven === Az alábbi egyszerű mátrixot szeretnénk megvalósítani: | 3 | 12 | 8 | 9 | | 2 | 15 | 17 | 7 | int tomb[2][4]; Ez két sort és 4 oszlopot jelent. Értékadás: int tomb[2][4]; //Első sor: tomb[0][0] = 3; tomb[0][1] = 12; tomb[0][2] = 8; tomb[0][3] = 9; //Második sor: tomb[1][0] = 2; tomb[1][1] = 15; tomb[1][2] = 17; tomb[1][3] = 7; {{:oktatas:programozas:c:c_ket_dimenzios_tomb.png?400|}} === Mátrix kezdőértéke === int tomb2[2][4] = { {3, 13, 8, 9}, {2, 15, 17, 7} }; ==== Feltöltés ==== GCC-ben: int tomb[10] = {[0 ... 9] = 5}; Egyébként meg egy for ciklussal. ==== Tömb paraméterként ==== Tömb átadása paraméterként: #include void rendez(int *tomb) { if(tomb[0] > tomb[1]) { int tmp = tomb[0]; tomb[0] = tomb[1]; tomb[1] = tmp; } } int main() { int tomb[2]; tomb[0] = 3; tomb[1] = 2; rendez(tomb); printf("%d %d\n", tomb[0], tomb[1]); } Az átadott tömb cím szerint adódik át. ===== Gyakorlat 017 ===== * Írjon programot, amelyben valós számokat tárol. * Mentés: valostomb.c ===== Saját típus ===== ==== A saját típusok alapjai ==== Vannak olyan esetek amelyekben célszerű saját típust létrehozni. Először nézzük meg, hogyan kell saját típust létrehozni. Általánosan: typedef típus újtipusnév; Legyen a példa kedvéért egy új típus amely megegyezik az egészeket tárolni képes int típussal: typedef int TEgesz; Ezek után, például így használhatom: TEgesz szam = 5; A "TEgesz" azonosítót mint típust használtam fel. Ennek persze kevés értelme lehet, de annál több haszna van, tömbtípusok létrehozásánál, különösen ha a tömböket paraméterként szeretném átadni. Lássunk egy tömbtípus létrehozását: typedef int TEgeszTomb[100]; Ezek után így használhatom: TEgeszTomb tomb; tomb[0] = 5; tomb[1] = 8; //... ==== Saját típus a paraméterlistában ==== Még hasznosabb, amikor a saját típust paraméterlistában használjuk. #include typedef TEgeszTomb[100]; int sum(TEgeszTomb ptomb) { int osszeg = 0, i; for(i=0; i<5; i++) osszeg = osszeg + ptomb[i]; return osszeg; } int main() { TEgeszTomb tomb = {3, 5, 8, 2, 7}; printf("%d\n", sum(tomb)); } ===== Fájlkezelés ===== Az állományok írása és olvasása a stdio.h könyvtárak függvényei teszik lehetővé. A C nyelvben egy a fájlváltozó típusa FILE. Mutatóként kell létrehoznunk, ezért fájlmutatóról fogunk beszélni. A fájlokat először mindig meg kell nyitni, használat után pedig bezárni. A megnyitásnak több módja is van, amelyek a következő táblázatban találhatók. ^ Mód ^ Leírás ^ | "r" | Megnyitás olvasásra. | | "w" | Megnyitás írásra. | | "a" | Megnyitás hozzáfűzésre. | | "r+" | Olvasható-írható mód. Írás a fájl elejére. | | "w+" | Olvasható-írható mód. Az fájl tartalma elvész. | | "a+" | Olvasható-írhatód mód. Kezdetben a fájlmutató elöl van. Az írás viszont a fájl végére történik | A megnyitott állományt csatorna névvel is szokás illetni. A megnyitás az fopen() függvénnyel történik. Az fopen() szintaxisa: fájlmutató = fopen(char *állománynév, char *mód) Például: FILE *fp; fp = fopen("adat.txt", "w"); ==== Írás ==== #include int main() { FILE *f; f = fopen("adat.txt","w"); fputs("alma",f); fputs("szilva",f); fputs("barack",f); fclose(f); } ==== Hozzáfűzés ==== #include int main() { FILE *f; f = fopen("adat.txt","a"); fputs("Valami",f); fclose(f); } ==== Olvasás fájlból ==== alma szilva barack #include #include int main() { FILE *fp = fopen("adat.txt", "r"); char *str = (char*) malloc(30 * sizeof(char)); while(!feof(fp)) { fgets(str, 30, fp); if(!feof(fp)) printf("%s", str); } fclose(fp); } A fenti program képes az összes sort gond nélkül kiírni, akár van az utolsó sor végén sortörés, akár nincs. A következő megoldás működik, de csak akkor, ha az utolsó sor után van sortörés: #include #include int main() { FILE *fp = fopen("adat.txt", "r"); char *str = (char*) malloc(30 * sizeof(char)); fgets(str, 30, fp); while(!feof(fp)) { printf("%s", str); fgets(str, 30, fp); } fclose(fp); } Az adat.txt állományhoz írunk áradatokat. alma 250 szilva 300 barack 450 #include #include #include int main() { FILE *fp = fopen("adat2.txt", "r"); char *str = (char*) malloc(30 * sizeof(char)); char *nev = (char*) malloc(30 * sizeof(char)); int ar; while(!feof(fp)) { fgets(str, 30, fp); if(!feof(fp)) { sscanf(str, "%s %d\n", nev,&ar); printf("%s %d\n", nev, ar); } } fclose(fp); } #include #include #include int main() { FILE *fp = fopen("adat2.txt", "r"); char *str = (char*) malloc(30 * sizeof(char)); char *nev = (char*) malloc(30 * sizeof(char)); int ar; while(!feof(fp)) { fgets(str, 30, fp); if(!feof(fp)) { sscanf(str, "%s", nev); sscanf(str + strlen(nev), "%d", &ar); printf("%s %d\n", nev, ar); } } fclose(fp); } ==== Olvasás karakterenként ==== #include #include int main() { FILE *fp = fopen("adat.txt", "r"); char *str = (char*) malloc(30 * sizeof(char)); char ch; while((ch = fgetc(fp)) != -1) printf("%c", ch); } A következő példa megegyezik az előzővel, kivéve, hogy a -1 értéket egy állandóból vesszük, melynek neve EOF. #include #include int main() { FILE *fp = fopen("adat.txt", "r"); char *str = (char*) malloc(30 * sizeof(char)); char ch; while((ch = fgetc(fp)) != EOF) printf("%c", ch); } ==== Hibakezelés ==== Ha az állomány megnyitása során probléma van, akkor NULL értékkel tér vissza. Ennek segítségével vizsgálhatjuk, hogy fájl megnyitás sikeres volt-e. #include #include #include int main() { FILE *fp; if((fp= fopen("adat.txta", "r"))== NULL) { perror("Hiba a fájl megnyitása során\n"); printf("Hibaszám: %d.\n", errno); exit(-1); } char *str = (char*) malloc(30 * sizeof(char)); fgets(str, 30, fp); while(!feof(fp)) { printf("%s", str); fgets(str, 30, fp); } fclose(fp); } ===== Függvények ===== Érték szerint átadott paraméterek: int novel(int szam) { return szam + 1; } Globális paraméterek int szam = 0; void novel() { szam = szam + 1; } Cím szerint átadott paraméterek void novel(int *szam) { *szam = *szam + 1; } int main() { int szam = 0; novel(&szam); } ===== stdlib.h ===== ==== Memóriafoglalás ==== void* calloc(size_t, size_t) void* malloc(size_t) void* realloc(void*, size_t) void free(void*) void abor(void) // Program vége void exit(int) ==== Egyéb ==== int system(const char*) // Egy külső program futtatása char* getenv(const char*) // Környezeti változó lekérdezése void _beep(unsigned int, unsigned int) // hang: frekvencia, másodperc void _sleep(unsigned long) // Várakozás ezredmásodpercig ===== time.h ===== ==== localtime() ==== Szintaxis: #include struct tm *localtime( const time_t *time ); Leírás: A localtime() függvény átkonvertálja a naptári időt helyi időbe. ==== time() ==== Szintaxis #include time_t time( time_t *time ); Leírás: A time() függvény visszatér az aktuális idővel, vagy -1-gyel ha hiba történik. Ha van megadunk egy time paramétert, akkor az aktuális idő eltárolódik ebben a változóban. ==== strftime ==== Az strftime() függvény dátum és időt formáz a time kimenetből egy formátum sztring alapján. A visszatérési érték szintén egy sztring. Az egyes formátumokhoz tartozó formátum kódok a következő táblázat mutatja: ^ Kód ^ Jelentés ^ | %a | a hét napja, rövidítve | | %A | a hét napja, kiírva | | %b | a hónap neve, rövidítve | | %B | a hónap neve, kiírva | | %c | az alapértelmezett dátumformátum | | %d | nap, 2 számjeggyel (01 .. 31) | | %H | óra, 24-órás formátumban, 2 számjeggyel (00 .. 23) | | %I | óra, 12-órás formátumban, 2 számjeggyel (01 .. 12) | | %j | év napja, 3 számjegy (001 .. 366) | | %m | hónap, számmal (01 .. 12) /van kitöltő szóköz/ | | %M | perc, 2 számjegy | | %p | "am" vagy "pm", az adott időpont délelőtt, vagy délután | | %S | másodperc, 2 számjegy | | %U | az aktuális év hányadik hetében járunk, a hét vasárnappal kezdődik, vagyis az "01" hét január első vasárnapján kezdődik. /Az első hét előtti napokat "00" hétnek jelzi/ | | %w | hét napja számmal, a vasárnap a 0 | | %W | év hete, az év első hétfőjén kezdődik az "01" hét | | %x | alapértelmezett dátumformátum idő nélkül | | %X | alapértelmezett időpontformátum dátum nélkül | | %y | év 2 számjeggyel (00 .. 99) | | %Y | év, évszázaddal együtt | | %Z | időzóna neve, 3 betű | | %% | a "%" karakter | ==== Példa ==== time_t datum = time(NULL); strftime(sz, sizeof(sz), "%c", &datum); printf("%s\n", sz); ===== Fogalmak ===== ==== Függvény ==== A C nyelvben ha utasításról beszélek, gyakran azt mondjuk helyette, hogy függvény. Ez azért van mert minden utasítás valójában egy függvényként van megvalósítva. ==== Kimeneti eszköz ==== Mivel számítógépről beszélünk, ezért ez monitor, nyomtató vagy más ehhez hasonló eszköz. ==== Standard Output ==== A Standard Output az alapértelmezett kimeneti eszközt jelenti. Szokásos módon ez a monitor képernyője. Tehát ha azt mondom „A Standard Outputra írok”, ez azt jelenti, hogy a képernyőre írok. ==== Standard Input ==== A Standard Input az alapértelmezett bemeneti eszköz. Szokásos módon ez a billentyűzet. A Standard Inputról olvasás így billentyűzetről bevitt adatokat jelenti. A Standard Outputot és a Standard Inputot együtt röviden stdio néven rövidítjük. ==== Paraméter ==== Amikor írunk egy „saját” függvényt, előfordulhat, hogy a hívó programból adatokat akarunk átadni. Ezeket az adatokat a függvény neve után zárójelbe írjuk és paramétereknek nevezzük. ==== Utasítás ==== Megmondjuk a gépnek mit tegyen. Ezt úgy tesszük, hogy leírjuk a nevét. Valójában ez egy függvény neve. A C nyelvben ha nincs egy utasításnak paramétere akkor is kötelező a zárójelek kitétele. Minden utasítást pontosvessző (;) zár le! Létezik üres utasítás is, ami egy pontosvessző önmagában. ==== Előfordító ==== A C fordító a forráskód lefordítása előtt elvégez egy ún. előfordítást. Az előfordítás az előfordítói utasításokon történik. Az előfordító utasítások „#” jellel kezdődnek. Előfordítással fordított utasítás típusok: * fájlok beépítése * makrók * feltételes fordítás Példák az előfordítói utasításokra: #include #define A 3