2. gyakorlat
Témák:
- A fordító (compiler) és a fordítás
- Makrók
- Standard könyvtár
- printf
- scanf
- Típusok
A fordító (compiler) és a fordítás
A fordítás művelete
A fordító a C nyelven írt szöveges állományból az alábbi 3 részműveleten keresztül lészít gépi kódot:
- előfeldolgozás (preprocessing)
- fordítás (compiling)
- összefűzés (linking)
Makrók
- Az előfeldolgozást makrókkal befolyásolhatjuk
- A makró azonosítóját (nevét) csupa nagybetűvel szokás írni, hogy megkülönböztessük a C kódtól
#include <header> vagy #include "header"
- Ez a makró másolja be a fejlécállomány tartalmát a kódunkba
<>jelölés esetén a fordító installálásakor elhelyezett helyen keres""jelölés esetén az aktuális munkakönyvtárunkban keres
#define AZONOSÍTÓ érték vagy kifejezés
- Egy konstans értéket vagy kifejezést tudunk definiálni vele
- Ezt a preprocesszor az AZONOSÍTÓ helyére másolja be a kódon belül
Példa:
#undef
- Az AZONOSÍTÓ nevű makrót törli, így az a kód további részében nem lesz elérhető
Példa:
Hibát kapunk: nem találja a HT nevű makrót
#define AZONOSÍTÓ(argumentumok) utasítás
- Így meg tudunk adni utasításokat is
- Matamatikai műveletek esetén javasolt a zárójelek használata, hogy elkerüljük a helytelen számolásokat
- Az utsításmakrók egymásba ágyazhatóak (egyikbe meg lehet hívni a másikat)
#include <stdio.h>
#define SZUM(x,y) x + y
int main()
{
printf("a + b = %d\n", SZUM(1,2));
return 0;
}
if-ek
#ifdef AZONOSÍTÓ
- Megnézi, hogy az AZONOSÍTÓ létezik-e már vagy sem
- Ha igen: a makró törzsében lévő rész bekerül a kódba
- Ha nem: akkor figyelmen kívül hagyja
#include <stdio.h>
#define PRINT
int main()
{
#ifdef PRINT
printf("Hello world!\n");
#endif
return 0;
}
Kiírja: Hello world!
#ifndef AZONOSÍTÓ
- Megnézi, hogy az AZONOSÍTÓ létezik-e már vagy sem
- Viszont ebben az esetben akkor kerül be a makró törzsében lévő rész, ha NINCS definiálva az AZONOSÍTÓ
#include <stdio.h>
#define PRINT
int main()
{
#ifndef PRINT
printf("Hello world!\n");
#else
printf("Goodbye world!\n");
#endif
return 0;
}
Kiírja: Goodbye world!
#elif
- A
#elif-fel további kifejezéseket adhatunk meg
Standard könyvtár
printf
- Függvény, amely kiírja a megadott konstans karakterláncot a standard outputra (terminal)
- Visszatérési értéke a kiírt karakterek száma
- Tetszőleges számú argumentumot is adhatunk neki
- A format argumentumban további speciális karaktereket is megadhatunk, melyeket a % karakterrel jelzünk, folytatva előre meghatározott karakterrel*
* A fontosabbak:
%d- egész szám (pl.: int, short)%f- lebegőpontos szám (pl.: float, double)%c- karakter (pl.: char)%s- karakterlánc (pl.: char tömb)
Még néhány:
%ld- (pl.: long)%u- (pl.: unsigned)
Példák:
#include <stdio.h>
int main()
{
printf("Hello World in %d!\n", 2022);
printf("Hello %f part of World!\n", 0.001);
printf("%cello World!\n", 'H');
printf("Hello %s", "World!\n");
// Tetszőlege számú adható meg
printf("Hello %s in %d!%c", "World", 2022, '\n');
return 0;
}
scanf
- Függvény, amely adatot kér be a standard inputról (terminal) a felhasznlótól
- Visszatérési értéke a beolvasott karakterek számával egyezik meg (ha a beolvasás sikeres)
- A % jeles speciális karakterek itt is használhatóak, meghatározhatjuk vele a beolvasott adat típusát
- Meg kell adni a változót, ahova a beolvasott adatokat szeretnénk letárolni
- Valójában nem a változót adjuk át neki, hanem annak a memóriacímét egy pointerrel (pl.
&a) - Pointer (azaz mutató): memóriacímre mutat
- Valójában nem a változót adjuk át neki, hanem annak a memóriacímét egy pointerrel (pl.
Példa:
#include <stdio.h>
int main()
{
int a;
printf("Add meg az 'a' értékét: ");
scanf("%d", &a);
printf("a = %d\n", a);
return 0;
}
Típusok, adatok, utasítások
Definíció és deklaráció
- Deklaráció
- Amikor jelezzük a fordító felé, hogy ilyen változó, konstans vagy függvény létezik, használva lesz
- Definíció
- Amikor értéket adunk a változónak, konstansnak
- Amikor függvény esetében implementáljuk a függvény törzsét (kapcsos zárójelek közötti rész)
- Változók deklarálásakor érdemes egyben értéket is adni (azaz definiálni)
- Ha nem definiáljuk a változót, akkor a fordító 0-t ad neki értékként
- Konstansok értékét kötelezően a deklaráláskor tudjuk csak megadni
Típusok
- egész típusok
- char: 1 byte
- short: 2 byte
- int: 4 byte
- long: 8 byte
- long long: 8 byte
- lebegőpontos típusok
- float: 4 byte
- double: 8 byte
- void
- összetett típusok:
- array (tömb)
- struct
- union
Megjegyzések
- A típusok méretei architektúra függők, a megadott értékek csak általában igazak
- A
sizeof()függvénnyel le tudjuk kérni a foglalt memória méretét - A
sizeof()long-ot ad vissza, ezért%ld-vel jelöljük
- A
- A C nyelvben nincs logikai változó (bool)!
- A logikai értékeket int-ben (esetleg char-ban) tároljuk
- A void szigorú értelemben nem típus
- Akkor használjuk, amikor egy függvénynek nincs visszatérési értéke
- A pointereknél lesz még szerepe
Típusmódosítók
- unsigned
- Nem negatív értéket tárol
- Jelölése:
%u - Alapértelmezetten int-re használjuk, ezért nem kell kiíírni, hogy
unsigned int, elég csak az unsigned-ot kiírni
- const
- Konstans, azaz a változó értéke nem változtatható
- Egyszerre deklaráljuk és definiáljuk
- Redundáns többször használva egy deklarációban
- Ez a módosító balra hat (kivéve, ha első kulcsszó a deklarációban)
- pl.:
int const * pesetén a const az int-re hat - pl.:
const int==int const(ezek megegyeznek)
- pl.:
-
static
1) Ha a globális változó előtt használjuk, akkor az nem érhető el más .c fileból 2) Egy függvény lokális változója előtt használva az egyszer inicializálódik (ha a fv-t többször is meghívjuk akkor az egyszer lesz deklarálva és definiálva) - Kevésbé használatosak: - volatile - register
Overflow
Túlcsordulás (azaz overflow): Amikor túllépjük a megadott típusnak megfelelő maximális vagy minimális értéket. Ilyenkor a változó értéke a minimális vagy maximális értékre változik.
Példa: itt a b-nél overflow történik (mert nem tárolhat negatív számot)
#include <stdio.h>
int main()
{
int a = 0;
unsigned int b = 0;
printf("a = %d, b = %u\n", a, b);
a = a - 1;
b = b - 1;
printf("a = %d, b = %u\n", a, b);
return 0;
}
Összetett típusok
--> Tömbök
- Azonos típusú változók gyűjteménye
- A memórában egy összefüggő területet foglal el
- Inicializátorlista: a
{}közötti értékadást - A tömbök indexelése is 0-tól indul
- Az utolsó tömbindexünk a méret – 1 értékével egyezik meg
- Ha negatív vagy túl nagy tömbindexet adunk meg, akkor memóriaszemetet kapunk vissza
Példák:
#include <stdio.h>
int main()
{
// deklarálás, a fordító többnyire feltölti 0-val
// általánosan: típus változó[méret];
char a[5];
// a fordító kitalálja a tömb méretét
// általánosan: típus változó[] = { érték1, … értékN};
short b[] = {1, 2, 3, 4, 5};
// kevesebb érték esetén 0 a többi (ált.)
// általánosan: típus változó[méret] = { érték1, … értékN};
int c[5] = {1, 2, 3, 4, 5};
// egy adott elem elérése
// általánosan: tömb[index]
printf("c[0] = %d\n", c[0]);
return 0;
}
--> Többdimenziós tömbök (mátrixok)
- Mindenben megegyeznek az egydimenziós tömbökkel, csak az indexelésük különbözik
- Gyakorlatilag egy tömbben tömböt definiálunk
Példák:
#include <stdio.h>
int main()
{
// Deklarálás
// általánosan: típus tömb[méret1][méret2]…[méretD];
int a[2][3];
// Értékadás
// általánosan: tömb[méret1][méret2]…[méretD] = {{{}, {}, .. {}}, {}};
unsigned m[3][3] = {{11, 12, 13}, {21, 22, 23}, {31, 32, 33}};
// egy adott elem elérése
// általánosan: tömb[index1][index2]…[indexD]
printf("m[1][2] = %u\n", m[1][2]);
return 0;
}
--> Struktúrák
- Tetszőleges, már létező típus lehet benne
- Akár másik struct is lehet benne
Struct szintax általánosan:
Példa:
#include <stdio.h>
struct S
{
char karakter;
int szam;
};
int main()
{
// 1)
// az s1 az S egy struktúra változója
// általánosan: struct StructNév structVáltozó;
struct S s1;
// a . operátorral tudjuk elérni a már definiált változókat
// általánosan: structVáltozó.adattagX
s1.karakter = 'A';
s1.szam = 5;
// 2)
// az s2 is az S egy struktúra változója
// definíció / értékadás
// általánosan: structVáltozó = {érték1, érték2, … értékN};
struct S s2 = {'B', 4};
return 0;
}
--> Uniók
- Ritkán használt (nem fogjuk használni)
- Önhivatkozhat
- Mérete: a legnagyobb adattag mérete
- Az összes adattagjának egy memóriát foglal, aminek mérete megegyezik a legnagyobb méretű adattagéval
Általános szintax:
--> Typedef
- Ezzel a kulcsszóval egy már létező típusra tudunk hivatkozni új névvel : alias
Általános szintax:
Pl.:
Műveletek utasítások
Matematikai műveletek
- Szokásos aritmetikai műveletek:
+,-,*,/(sima osztás azaz div),%(az osztás maradékát adja vissza azaz mod) - Egyszerűsítések az egy változón történő műveletek esetében:
+=,-=,*=,/=
Példa:
- Inkrementálás:
++és dekrementálás:--- 1-gyel növelik, illetve csökkentik az egész szám értékét
- Prefix:
++a,--a- a változó értéke előbb inkementálódik, dekrementálódik, majd a beágyazó műveletek hajtódnak végre
- Possix:
a++,a--- a beágyazó műveletek hajtódnak végre, és azok után lesz csak a változó értéke módosítva
Példa:
#include <stdio.h>
int main()
{
int a = 42;
printf("a = %d\n", a);
// terminal: a = 42
int b = a++;
printf("a = %d, b = %d\n", a, b);
// terminal: a = 43, b = 42
int c = ++a;
printf("a = %d, b = %d, c = %d\n", a, b, c);
// terminal: a = 44, b = 42, c = 44
return 0;
}
Bitműveletek
- Valójában minden művelet bitműveletre van visszavezetve
- A változók értékei 0 és 1 formában vannak tárolva a memóriában
- Így ezek között a legtermészetesebb műveletek a bitműveletek
- Integer típusokon hívhatjuk meg őket
| Jele | Művelet |
|---|---|
& |
bitenkéni ÉS (AND) |
^ |
bitenkéni kizáró VAGY (XOR) |
\| |
bitenkéni VAGY (OR) |
~ |
bitenkénti negálás (NOT) |
>> és << |
bit léptetés (shift) |
Pl.: Az egész számok összeadása például egy bitenkénti ÉS művelet.
Példa:
#include <stdio.h>
int main()
{
// negálás
int a = 0;
a = ~a;
printf("a = %d\n", a);
// terminal: a = -1
// léptetés
int b = 2;
b = b << 1;
printf("b = %d\n", b);
// terminal: b = 4 (a 2 hatványait kapjuk vissza)
return 0;
}
Típuskonverzió
- A beépített típusok között átjárhatóság van
- Kasztolás (casting)