C (programozási nyelv)
C | |
Paradigma | imperatív (procedurális), strukturált |
Jellemző kiterjesztés | .h, .c |
Megjelent | 1972[1] |
Tervező | Dennis Ritchie |
Fejlesztő | Dennis Ritchie & Bell Labs |
Típusosság | statikus, gyenge |
Fordítóprogram | GCC, MSVC, Borland C, Watcom C |
Megvalósítások | Clang, GCC, Intel C, MSVC, Turbo C, Watcom C |
Hatással volt rá | B (BCPL,CPL), ALGOL 68, Assembly, Pascal |
Befolyásolt nyelvek | awk, csh, C++, C#, ObjC, BitC, D, Concurrent C, Java, Javascript, Rust |
Operációs rendszer | |
Weboldal |
A C egy általános célú programozási nyelv, melyet Dennis Ritchie fejlesztett ki Ken Thompson segítségével 1969 és 1973 között a UNIX rendszerekre az AT&T Bell Labs-nál.[2] Idővel jóformán minden operációs rendszerre készítettek C fordítóprogramot, és a legnépszerűbb programozási nyelvek egyikévé vált. Rendszerprogramozáshoz és felhasználói programok készítéséhez egyaránt jól használható. Az oktatásban és a számítógép-tudományban is jelentős szerepe van.
A C minden idők legszélesebb körben használt programozási nyelve,[3][4] és a C fordítók elérhetők a ma elérhető számítógép-architektúrák és operációs rendszerek többségére. Elterjedésében fontos szerepet játszott a RISC technológia. A sokféle processzorhoz operációs rendszerekre volt szükség, és az eleve C-ben írt Unix volt a legkönnyebben portolható.
Történet
[szerkesztés]Korai fejlesztések
[szerkesztés]A kezdeti fejlesztések az AT&T berkein belül történtek 1969 és 1973 között. A legkreatívabb időszak, Ritchie-nek köszönhetően 1972-ben volt. Azért lett „C” a nyelv neve, mert egy korábbi, „B” nevű programozási nyelv sok tulajdonságát „örökölte”. A leírások különböznek a „B” név forrását illetően: Ken Thompson írt egy programozási nyelvet, a BCPL-t, de írt egy Bon nevűt is, a feleségéről (Bonnie-ról) elnevezve.
Az 1973-as évben a C nyelv elég hatékonnyá vált, így a UNIX rendszermag legnagyobb részét, melyek PDP-11/20 assembly nyelven íródtak, újraírták C-ben. Ez volt az egyik első operációs rendszer rendszermag, mely nem assembly nyelven íródott, korábbiak, a Multics PL/I-ben íródott, a TRIPOS BCPL-ben.
K&R C
[szerkesztés]1978-ban megjelent a Dennis Ritchie és Brian Kernighan nevével fémjelzett A C programozási nyelv c. könyv első kiadása. Ez a könyv, melyet a C programozók csak K&R néven emlegettek, sokáig szolgált a nyelv formai leírásának forrásaként. A C nyelvnek az a verziója, melyet leírt, az a „K&R C” nyelv. (A könyv második kiadása az „ANSI C” szabványt írta le, lásd alább.)
A K&R a nyelv következő tulajdonságait vezette be:
struct
adattípuslong int
adattípusunsigned int
adattípus- A
=+
típusú értékadó operátorokat a+=
formára változtatták. (A 'var =- érték
' túlságosan hasonlított a 'var = -érték
'-hez, bár hatásuk egészen más.)
A K&R C a nyelv legalapvetőbb részének tekinthető, melyet egy C fordítónak mindenképpen ismernie kell. Sok éven keresztül, még az ANSI C bevezetése után is, a „legnagyobb közös osztó” volt a K&R, melyet a C programozók használtak, ha a legnagyobb mértékű (forrás szintű) kompatibilitásra volt szükség, hiszen nem minden C fordító támogatta a teljes ANSI C-t és a megfelelően megírt K&R C (forrás)kód megfelelt az ANSI C szabványnak is.
A K&R C megjelenése utáni években, sok „nem hivatalos” kiegészítés látott napvilágot, melyet az AT&T és néhány másik cég fordítói is támogattak.
Ilyen változtatások voltak többek közt:
void
típusú függvény ésvoid *
adattípus- függvények, melyek
struct
vagyunion
típusokat voltak képesek visszaadni (return) - különböző struktúráknak lehetnek azonos nevű mezői (korábban az összes struktúra összes mezője egy közös névtéren osztozott!)
- struktúra típusú változók értékadása (korábban ezt csak a
memcpy
függvénnyel lehetett megtenni) const
definíció, az érték írásvédettségéhez- szabvány programkönyvtár (library), mely a különböző cégek leggyakrabban támogatott függvényeit tartalmazta
- felsorolások (
enum
) - az un. „single-precision” (egyes pontosságú)
float
adattípus
ANSI C és ISO C
[szerkesztés]Az 1970-es évek vége felé, a C kezdte felváltani a BASIC nyelvet a személyi számítógépeken. Személyi számítógépekre is átültették az 1980-as években, így a C nyelv népszerűsége ugrásszerűen emelkedni kezdett. Ugyanebben az időben Bjarne Stroustrup és társai a Bell Labs-nél elkezdtek dolgozni objektumorientált nyelvi elemek hozzáadásán a C nyelvhez. A nyelv, amit készítettek a C++ nevet kapta, ez ma a legelterjedtebb programozási nyelv a Microsoft Windows operációs rendszereken, míg a C a UNIX világban megőrizte népszerűségét.
1983-ban az Amerikai Nemzeti Szabványügyi Hivatal (angolul: American National Standards Institute, röviden ANSI) megalakította az X3J11 bizottságot, hogy létrehozzanak egy egységes (szabvány) C definíciót. A hosszú és fáradságos folyamat végén 1989-ben elkészült a szabvány (egy évvel az első C++ ANSI szabvány után!) és jóváhagyták mint: ANSI X3.159–1989 „A C programozási nyelv”. A nyelvnek ezt a verzióját nevezik ANSI C-nek. 1990-ben az ANSI C szabványt (néhány apróbb módosítással) átvette a Nemzetközi Szabványügyi Szervezet (angolul: International Organization for Standardization, röviden ISO) mint ISO/EC 9899:1990.
Az ANSI C szabványosítás egyik célja az volt, hogy a K&R C-ből és a nem hivatalos bővítésekből egy egységeset alakítson ki. Belevettek azonban számos új megoldást is, mint például függvény prototípust (a C++ nyelvből) valamint egy jobban alkalmazható (fejlettebb) előfordítót (preprocesszor).
ANSI C-t szinte minden fordító támogat. A legtöbb C kód, mely manapság íródott, az ANSI C-n alapul. Bármilyen program, amely a szabvány C-ben íródott, helyesen működik bármely platformon, amelyen szabványos C létezik. Vannak azonban programok, melyek csak adott platformon vagy adott fordítóval fordíthatók le, a használt nem szabvány függvénygyűjtemények miatt (például grafikus függvények) és vannak olyan fordítók, melyek nem támogatják alapértelmezésben az ANSI C szabványt.
C99
[szerkesztés]Az ANSI szabványosítási folyamatot követően, a C nyelv viszonylag állandó maradt, míg a C++ fejlődött. Új C verzió, 1995-ben az első normatív kiegészítéssel jött létre, de ezt a változatot ritkán használják. A szabványt átdolgozták az 1990-es években és ebből lett az ISO 9899:1999 1999-ben. Ez a szabvány „C99” néven vált ismertté, majd 2000 márciusában bekerült az ANSI szabványok közé is.
C99 új tulajdonságai, többek közt:
- inline függvények
- változók definiálási helyére vonatkozó szabályai enyhítése (hasonlóképpen, mint C++-ban)
- új adattípusok, például:
long long int
, hogy a 32bitről a 64bitre való átállást megkönnyítsék, explicitbool
(stdbool.h
) és acomplex
(complex.h
) típus. - változó méretű tömbök
- hivatalosan is bevezették az egysoros kommentár jelölést
//
(a C++-ból) - több új függvény, mint például:
snprintf()
- több új „header” állomány, mint például az
inttypes.h
, amely rögzített méretű integer típusokat definiál:int8_t, int16_t, int32_t, int64_t
, illetve ezek előjel nélküli változatait.
Az érdeklődés a C99 új tulajdonságainak támogatásával kapcsolatban eléggé vegyes. Míg GCC (GNU Compiler Collection, korábban GNU C Compiler) és más fordítók támogatják a C99 újdonságait, addig a Microsoft és Borland által forgalmazottak nem, és ez a két cég nem is foglalkozik a C99 jövőbeli támogatásának lehetőségével jelenleg.
C11
[szerkesztés]2007-ben kezdődött a munka a C sztenderd egy másik revíziójával kapcsolatban, amit informálisan "C1X"-nek hívtak egészen addig, míg hivatalosan is nem publikálták 2011. december 8-án. A C sztenderdek tanácsa elfogadta az ajánlásokat az új lehetőségek limitált beépítésére, amelyeket még nem kezdtek el tesztelni létező implementáción.
A C11 sztenderd számos új lehetőséget adott hozzá a C és könyvtárakhoz, beleértve a típus generikus makrókat, anonim struktúrákat, javított Unicode támogatást, atomi operációkat, többszálúságot és határ ellenőrző függvényeket. Továbbá elkészítették a létező C99 könyvtár néhány portolását, és javították a kompatibilitást a C++-szal.
C18
[szerkesztés]A C18-at 2018 júniusában adták ki, ami a C programozási nyelv aktuális szabványa. Nem vezetett be új nyelvi elemeket, csak technikai korrekciókat, pontosításokat tartalmaz a C11-hez képest. Az __STDC_VERSION__ macro 201710L-nek van definiálva.
Beágyazott C
[szerkesztés]Rendszerint a beágyazott rendszerekhez nem szabványosított kiterjesztéseket használnak, hogy lehetővé tegyék az egzotikusabb funkciók használatát, mint pl. fix pontos aritmetikát, különböző memória bankok használatát és alap I/O műveleteket.
2008-ban a C szabványügyi bizottság publikált egy technikai beszámolót, hogy kiterjessze a C programozási nyelvet ezekkel a lehetőségekkel, az által, hogy közös szabványt biztosít. Ez rengeteg funkciót foglal magába ami nem része a normál C-nek, mint pl. fix pontos aritmetika, nevesített címtartományok és alapvető I/O hardver címzések.
A C nyelv jellemzői
[szerkesztés]- strukturált
- szabványos: minden platformon van fordítóprogramja, a kód a forrásprogram szintjén hordozható
- a C-program rendkívül hatékony gépi kódra fordul le.
A nyelv makrónyelv abban az értelemben, hogy a C-fordító assembly nyelvre fordít, a programozónak azonban egyetlen assembly sort sem kell leírnia (sőt, nem is kell tudnia erről).
A C strukturált programnyelv: bármelyik utasítás helyén állhat blokk, mely {
és }
jelek közé zárt tetszőleges típusú és számú utasításból állhat. A blokkok egymásba skatulyázhatók. A függvények utasításai blokkban helyezkednek el. A C-program belépési pontja a main
nevű függvény, mely az operációs rendszertől kapja a híváskor megadott paramétereket, és annak adja vissza az (egész típusú) visszatérési értékét.
Formai szabályok
[szerkesztés]A nyelv utasításai a preprocesszor-utasítások kivételével szabad formátumúak: ahol egy helyköz megengedett, ott akárhány helyköz, tabulátor, új sor lehet. A nyelv szavai (utasításnevek, változónevek, számok, műveleti jelek stb.) között lehet helyköz, de nem kötelező. Az utasítások pontosvesszővel végződnek. Az üres utasítás az előző utasítás vége után tett pontosvessző. A folytatósor – a sor végi \
– a szabad formátum miatt csak preprocesszor-utasításokban használatos.
A megjegyzéseket (kommenteket) /*
és */
közé kell zárni, és szabvány szerint nem ágyazhatók egymásba, bár sok fordítóprogram mégis megengedi. Az ANSI C-től kezdve használható a //
, mely a sor végéig tartó megjegyzést vezet be (a C++-hoz hasonlóan). Hosszabb megjegyzéseket a #if 0
...#endif
közé is lehet tenni; ezek – lévén preprocesszor-utasítások – egymásba ágyazhatók.
C-ben a nevek kis- és nagybetűkből, számjegyekből és aláhúzásból állhatnak, számjegy nem lehet az első karakter. A kis- és nagybetűk különbözőnek számítanak. A kialakult szokás szerint a változó- és függvénynevekben kisbetűket használunk, a preprocesszor-utasításokban rendszerint nagybetűket.
Utasítástípusok
[szerkesztés]A preprocesszor utasítások az assembly nyelvek makróihoz hasonlítanak: a fordítás első menetében „normál” C-utasításokká fordulnak le.
Az aritmetikai utasítások nagyon különböznek a többi programozási nyelvben megszokott értékadó utasításoktól. Ezt az aritmetikai utasítást vette át a C++ és a Java.
A nyelvnek nincs input/output utasítása, ezt szabványos könyvtári függvények végzik.
A végrehajtható utasítások (aritmetikai és vezérlő utasítások) függvényen belül, blokkban helyezkednek el. A C-program preprocesszor-utasításokból, deklarációkból és függvényekből áll.
Egy egyszerű példaprogram
[szerkesztés]#include <stdio.h> // preprocesszor utasítás
int main() // függvénydefiníció, egyúttal a program belépési pontja, ezúttal nincs paramétere
{ // blokk kezdete
int i; // deklaráció
for (i=1; i <= 3; i++) // vezérlő (ciklus-) utasítás. A ++ egyváltozós értékadó művelet: eggyel növeli i-t.
{ // újabb blokk-kezdet
printf("Haho\n"); // I/O műveletet végző könyvtári függvény. A konzolra ír.
// A stringkonstansot <code>"</code>-k közé kell zárni. A <code>\n</code> az új sor jele a stringben.
} // a belső blokk vége
return 0; // vezérlő utasítás: kilépés a függvényből. A <code>main</code> értékét az operációs rendszer kapja meg
// Windows-ban az <code>errorlevel</code>, Unixban a <code>$?</code> változóban.
} // main blokkjának vége
A program fordítása linuxban (ha a fenti kódot a haho.c
file-ba tettük):
gcc -o haho haho.c
Futtatás:
./haho
Kimenet:
Haho Haho Haho
A C-programozók a fenti ciklusutasítást for (i=0; i < 3; i++)
alakban szokták leírni, mert a tömbök indexelése 0-tól kezdődik a C-ben. A példában a kettő teljesen azonos.
Adattípusok
[szerkesztés]Egyszerű típusok
[szerkesztés]char | 8 |
short | 16 |
int | 16 |
long | 32 |
long long | 64 |
float | 32 |
double | 64 |
long double | 80 |
- char: egy karakter tárolására képes memóriaterület. Karakterkonstansok (pl. az A betű különböző alakokban):
'A'
,65
,\x41
,0101
(az utóbbi oktális, melyet a kezdő 0 jelez). A legfontosabb speciális karakterkonstansok:- '\n': új sor (LF)
- '\r': kocsi vissza (CR)
- '\t': tabulátor
- '\b': backspace
- '\a': alarm (sípolás)
- '\\': backslash
- short (vagy short int): rövid egész.
- int: az egész konstans formája azonos char-ral, csak az érték lehet nagyobb. Több karakter megadása aposztrófok között nem szabványos, bár néhány fordító megengedi.
- long (vagy long int) konstans pl.:
65L
. - long long (vagy long long int) konstans pl.:
65LL
. - float, double, long double konstans pl.:
3.14
,8.3e11
,8.3d-11
. Float típusú konstans3.14F
, long double3.14L
alakban adható meg. Ha nincs típusjelzés, a konstans double típusú. - void: speciális adattípus, mellyel semmilyen művelet nem végezhető, még értékadás és konverzió sem. Mutatók és függvények esetén használatos.
A char, short, int, long és long long fixpontos, a float, double és long double lebegőpontos típus. Fixpontos adattípuson nincs túlcsordulás-ellenőrzés: az hibás működést eredményez.
A C-ben nincsen string típus (bár string konstans van, a példaprogramban: "Haho\n"). A stringet karaktertömbben tartja, a string végét bináris nulla ('\0'
) jelzi.
A C-ben nincs logikai típus (igaz vagy hamis). A nem 0 értékű fixpontos kifejezés a logikai igaz, a 0 értékű a hamis.[5] A relációk (melyek szintén aritmetikai műveletek) igaz értékként 1-et adnak vissza.
A char
típusú változóval ugyanazok a műveletek elvégezhetők, mint az int
-tel. Ilyenkor a karakter egésszé konvertálódik.
A char, int, long és long long típus előtt használható a signed
ill. unsigned
típusmódosító. A nyelv nem definiálja, hogy a char
típus egész számként használva előjeles-e, ezért ha az érték 127-nél nagyobb, mindenképpen meg kell adni, hogy hordozható legyen a kód. Az int, long és long long előjeles, ha az unsigned
-et nem adjuk meg.
Az előjeltelen konstansot az utána írt U
jelzi, pl. 15U, 15UL, 15ULL. A hexadecimális alakú konstans (0xF) előjeltelen (az utána írt S
betűvel tehető előjelessé), a többi alak előjeles, ha nincs utána U.
A C nyelv az alábbi típusokkal tud műveletet végezni:
- int
- unsigned int
- signed long
- unsigned long
- signed long long
- unsigned long long
- double
- long double.
Minden más típus csak tárolásra való, aritmetikai műveletben azonnal átkonvertálódik a nála nagyobb, előjelben megfelelő típusra.
Deklarációk
[szerkesztés]A deklaráció a fordítóprogramnak szóló utasítás. Kódot nem generál, a fordítóprogram szimbólumtáblájában okoz változást.
A C-ben háromféle deklaráció van:
Használat előtt a változókat és típusokat deklarálni kell. A függvényeket nem kötelező, de nyomatékosan ajánlott.
Változó deklarálása
[szerkesztés]A deklaráció hatására foglalja le a fordítóprogram a memóriaterületet a változó számára, és megadja a memóriaterület nevét, amivel hivatkozni lehet a tartalmára.
Négy dolgot lehet/kell megadni a változó nevén felül:
- az adat láthatóságát a program különböző részeiből
- a tárolási osztályt
- az adat típusát
- a kezdőértéket.
A C-ben – meglehetősen szerencsétlen módon – az első kettőt nagyjából ugyanazokkal a kulcsszavakkal kell megadni.
Láthatóság
[szerkesztés]Az adat láthatósága C-ben háromféle lehet:
- globális (az egész programból látható)
- csak a forrásfájlból látható
- csak a blokkon belül látható.
A blokkon belül deklarált változók csak a blokkon belül láthatók (beleértve a blokk által tartalmazott blokkokat is). Ha a blokk egy külső blokkbeli vagy blokkon kívüli változónevet használ, akkor saját példányt definiál belőle, és (névvel) nem tudja elérni a feljebb levő azonos nevű változót.
C-ben függvényen belül nem lehet függvényt definiálni, ezért a függvényen (blokkon) kívüli adatok mindig statikusak, azaz a program indulásától kezdve ugyanazon a memóriaterületen vannak, így ezt a tényt nem kell külön megadni. A blokkon kívüli static
kulcsszó az adat vagy függvény láthatóságát a forrásfájlon belülre korlátozza. A blokkon kívül deklarált, static
nélküli változó és a static
nélküli függvény globális.
Globális változóra vagy függvényre a program többi forrásfájljából az extern
kulcsszóval hivatkozhatunk, melyben meg kell adni a változó nevét, típusát és a tárolási osztályt. Hogy ne kelljen mindezt többször leírni, általában saját header-fájlokat használunk, melyeket minden forrásfájl betölt a #include
preprocesszor-utasítással. extern
változónak nem lehet kezdőértéke. A program valamelyik forrásfájljában (általában a főprogramban) a változót extern
nélkül kell deklarálni, és itt kaphat kezdőértéket.
Tárolási osztály
[szerkesztés]Betöltéskor létrejövő adatok |
Verem | Változó memóriatartalom |
Kezdőértéket nem kapott adatok | ||
Programfájlban tárolt adatok |
Kezdőértéket kapott adatok | |
Konstansok | Konstans memóriatartalom | |
Programkód |
A tárolási osztály adja meg, hogy az adat a program melyik memóriaterületére kerül (lásd jobb oldali táblázat, középső oszlop).
A blokkon (függvényen) kívül deklarált adat mindig statikus, a blokkon belüli – ha mást nem adunk meg – dinamikus. Blokkon belüli adat a static
kulcsszóval tehető statikussá, és az extern
jelzi, hogy másik forrásprogramban van deklarálva. Az extern
kulcsszót blokkon kívül szokás használni, és mindig statikus adatra vonatkozik.
A statikus adatnak állandó helye (memóriacíme) van. A dinamikus adat a veremben tárolódik, a blokkba belépéskor foglalódik le a helye, kilépéskor felszabadul, kezdőértéke definiálatlan.
A register
kulcsszóval javasolhatjuk a fordítóprogramnak, hogy a dinamikus adatot ne veremben, hanem a CPU regiszterében tartsa. Az ilyen változóknak nincs memóriacímük, így a &
művelet nem használható rájuk. Kezdőértékük definiálatlan. Ha nincs elég regiszter, akkor a deklaráció ellenére verembe kerül az adat. A jelenlegi igen jól optimalizáló fordítók mellett a register
használata idejétmúlt.
A programban kezdőértéket nem kapott statikus adatok 0 értéket kapnak, amikor az operációs rendszer a memóriába tölti a programot.
Konstans változót a const
kulcsszóval lehet megadni, és kötelezően kezdőértéket kell kapjon, mely a program végrehajtása során nem változik, és a fordítóprogram ellenőrzi is, hogy ne szerepelhessen olyan utasításban, ahol értéket kaphatna. A konstans memóriaterületre kerülnek azok a konstansok is, melyeknek nincs nevük ("Haho\n"
a mintapéldában).
A változó típusa
[szerkesztés]Háromféle lehet:
Kezdőérték
[szerkesztés]Kezdőérték a változónév utáni =
jelet követő konstanssal adható meg. Kezdőérték adható dinamikus változónak is, de az érték beállításához a fordítóprogram kódot generál, és nem teszi a kezdőértékeket a konstansok memóriaterületére.[6]
Tömbök és összetett változók kezdőértékeit {
és }
közé kell tenni, a zárójelbeli értékeket vesszővel elválasztva. Nem hiba az utolsó érték után is kitenni a vesszőt.
Ha egy változónak nincs kezdőértéke, akkor az dinamikus változó esetén definiálatlan, statikus változó esetén 0 (lásd: tárolási osztály).
Példák változódeklarációra
[szerkesztés]int i;
int a, b=2;
static const unsigned short alfa = 88;
extern int globalis;
Struktúra
[szerkesztés]A struktúra különböző típusú adatokból álló adat. A struktúra szerkezetét és a változókat lehet együtt vagy külön-külön deklarálni. Az alábbi két példa egyenértékű:
struct datstr {
short ev;
short ho;
short nap;
};
struct datstr ma, holnap;
|
struct {
short ev;
short ho;
short nap;
} ma, holnap;
|
Az első példában az első utasítás az adatszerkezetet definiálja (melyet gyakran header-fájlba teszünk, ha több forrásfáljból is használni akarjuk), a második deklarálja a változókat.
A második esetben a struktúrának nem kell kell neve legyen, bár ilyenkor nem használhatjuk a definiált adatszerkezetet később, más változó deklarálásához.
Kezdőértékadás a deklarációban:
struct datstr ma = { 2015, 12, 4 };
Értékadás aritmetikai utasítással:
holnap = ma;
holnap.nap = 5;
A struktúrák egymásba ágyazása:
struct {
struct datstr dat;
short ora;
} pelda;
Az évre pelda.dat.ev
néven hivatkozhatunk, pelda.ev
néven nem.
Mutatóval adott struktúra tagjaira a ->
művelettel lehet hivatkozni.
Unió
[szerkesztés]Az unió (union
) formailag megegyezik a struktúrával, de a tagjai (melyek rendszerint struktúrák) azonos memóriaterületen helyezkednek el. Az unió mérete a legnagyobb tag mérete lesz. Arra szolgál, hogy ugyanazt a memóriaterületet a program különböző időpontokban különböző célokra használhassa. Rendszerprogramokban fordul elő, felhasználói programban ritka.
enum
[szerkesztés]Akkor használatos, ha egy egész változó csak néhány értéket vehet fel, és ezekre az értékekre (tipikusan kódokra) névvel akarunk hivatkozni a könnyebb megjegyezhetőség érdekében. Alakja a struktúrához hasonló, pl.:
enum httpkod { VAN=200, TILTOTT=403, NINCS=404 } htkod;
httpkod
a struktúranév megfelelője, htkod
a változó neve. A struktúrához hasonlóan külön is megadható a kettő. A kapcsos zárójelben nem kötelező értékeket megadni, ilyenkor a fordítóprogram 0-tól egyesével növekvő értékeket rendel a felsorolt nevekhez.
C-ben az enum – a C++-tól eltérően – nem definiál külön adattípust, egyszerűen hozzárendeli a felsorolt nevekhez az egész értékeket. A nevek ezután bármilyen aritmetikai kifejezésben szerepelhetnek, mintha egész típusú konstansok lennének, de a program hordozhatósága érdekében ezt a tulajdonságot nem ajánlatos kihasználni.
Tömbök
[szerkesztés]A programozásban tömbnek olyan változókat neveznek, melyek több azonos típusú adatból állnak. A deklaráció formája azonos a skalár (nem tömb) típusú változóval. Az elemek számát C-ben a változónév után szögletes zárójelben kell megadni (csak egész típusú érték lehet), a kezdőértékeket pedig a struktúráknál megismert módon. Pl:
int egesztomb[4];
const int allando[3] = { 1, 2, 3 };
static struct datstr { int ev; int ho; int nap; } dat[2] = { { 1954, 10, 19 }, { 2015, 12, 06 } };
A tömbök indexei 0-val kezdődnek. allando
elemeire allando[0], allando[1], allando[2]-vel lehet hivatkozni. C-ben nincs tömbindex-ellenőrzés: allando[3]-ra hivatkozás hibás működést fog eredményezni. A tömbindex-túlcsordulás az egyik leggyakoribb programozási hiba C-ben.
Bármilyen típusú változó lehet tömbben, beleértve a tömböt is: C-ben a kétdimenziós tömb az egydimenziós tömb tömbje:
double matrix[3][14];
A többdimenziós tömbök elemei sorfolytonosan tárolódnak. Nem hiba egydimenziós tömbként hivatkozni rájuk, pl. matrix[0][40]
.
A tömb elemszáma megadható fordítási időben kiértékelhető (konstans) kifejezéssel:
int tomb[3+8*4];
Ennek preprocesszor-változók használatakor van jelentősége.
Más forrásfájlban deklarált tömbnek nem kell méretet adni. Pl.:
extern int tomb[];
Akkor sem kell megadni a tömb méretét, ha az kezdőértéket kapott. Ilyenkor a méret a kezdőértékek száma lesz.
Mutatók
[szerkesztés]A mutató memóriacímet tároló változó. Leggyakrabban függvények paramétereiben és tömbelem-hivatkozásokban fordul elő.
A mutató típusa a memóriacímen tárolt adat típusát jelzi. A mutatót a változódeklarációban a név elé tett csillag jelzi:
int *mut;
Értéket a címképző operátorral adhatunk a mutatónak:
int egesz = 3, *mut;
mut = &egesz;
A mutatóval megadott adat értékére a *
operátorral hivatkozunk. A fenti két sor után egesz
és *mut
értéke egyaránt 3.
A tömb neve a tömb memóriacímét jelenti; ilyenkor nem kell kitenni az &
-et:
int tomb[8], *mut;
mut = tomb;
A mutatóhoz 1-et hozzáadva nem eggyel, hanem a fenti példában sizeof(int)
-tel nő a mutató értéke, vagyis a következő egészre (a tömb következő elemére) mutat. Ez azt jelenti, hogy *(mut+1)
és tomb[1]
a fenti példában ugyanazt az értéket adja, és *(mut+1)
is leírható mut[1]
alakban.
Különbség csak két azonos típusú mutató között értelmezett, és a két cím különbsége elosztódik a típus hosszával. A fenti példában mut-tomb
azt mondja meg, hogy mut
tomb
hányadik elemére mutat.
A mutató lehet void
típusú; ilyenkor a fordítóprogram nem ellenőrzi a mutató típusát. Pl. a malloc
nevű könyvtári függvény, mely memóriát kér a program számára, void
mutatót ad vissza, melyet a program a kívánt típusú mutatóba tesz. A void típus miatt az értéket eltároló utasítás nem jelez típuskonverziós hibát.
A mutató típusa a memóriacímen tárolt adat típusa. Ebbe a const
is beleértendő:
int egy;
const int *mut;
mut = &egy; // helyes
*mut = 5; // hibas: konstansnak nem adhato ertek
Struktúramutatók számára külön műveletet vezettek be. Pl.:
struct { int ev; int ho; int nap; } dat, *datmut;
datmut = &dat;
után dat.ev
-re (*datmut).ev
alakban kellene hivatkozni. (A zárójelre szükség van, mert a .
-nak nagyobb a prioritása, mint a *
-nak.) Ezt könnyíti meg a datmut->ev
alak. A kettő hatásában teljesen azonos.
A függvénymutatók használatát lásd a függvényeknél.
A mutatót visszaadó könyvtári függvények NULL
értéket adnak vissza sikertelenség esetén (pl. a memóriafoglalás nem sikerült). A NULL
az stdio.h
header-fájlban definiált konstans.
Mutató mutatóra is mutathat:
int mut=3,*mut1,**mut2,***mut3;
mut1 = &mut;
mut2 = &mut1;
mut3 = &mut2;
A fentiek után a mut
, *mut1
, **mut2
vagy ***mut3
kifejezések mindegyikének 3 az értéke.
Típusdeklaráció
[szerkesztés]A típusdeklaráció nevet ad egy adattípusnak. A típusnév a deklaráció után úgy használható, mint a beépített típusok, de – a C++-szal ellentétben – nem hoz létre új típust: a fordítóprogram úgy tekinti, mintha a típusnév helyett a típusdeklarációt írtuk volna le.
A típusdeklaráció alakja formailag azonos az adattípusokéval, de a tárolási osztályt megadó static
vagy extern
kulcsszó helyére a typedef
kerül. Az adat neve lesz a típusnév:
typedef unsigned int UINT24;
UINT24 valt;
Ezután valt
előjeltelen egész változóként használható.
Aritmetikai utasítások
[szerkesztés]Aritmetikai utasításnak egy pontosvesszővel lezárt aritmetikai kifejezést nevezünk. Az aritmetikai kifejezés változók vagy konstansok és műveletek kombinációja. (A C-ben csak aritmetikai művelet létezik, ezért a jelzőt el lehet hagyni.)
Az aritmetikai utasításban nem okvetlenül van értékadás: aritmetikai utasítás lehet egy függvényhívás, de szintaktikusan helyes a 1;
utasítás is, bár a fordítóprogram ilyenkor figyelmeztet, hogy az utasításnak nincs semmilyen hatása.
A C-ben az értékadás ugyanolyan aritmetikai művelet, mint pl. a szorzás. Pl. megengedett az
x = (a = b) * (c = d);
utasítás, melyet a többi programnyelvben
a = b;
c = d;
x = a * c;
alakban írnánk (természetesen C-ben is írható így). Az értékadás művelet (szükség esetén) automatikus konverziót végez, és ez a konvertált érték a művelet eredménye. „Mellékhatásként” az érték az =
bal oldalán levő változóba[7] is eltárolódik. (E mellékhatás miatt használjuk a műveletet, hiszen konverziós művelet is létezik.)
Miután az aritmetikai utasításoknak nincs külön nevük (mint pl. az if
vagy a többi programnyelv értékadó utasításának =
karaktere), ezért a fordító minden utasítást aritmetikai utasításnak tekint, ami nem fenntartott szóval kezdődik. Ebből az is következik, hogy C-ben nem szabad a fenntartott szavakat változónévnek használni.
A másik érdekesség, hogy a nyelvben nincs eljárás, csak függvény, de ez a tulajdonság is az aritmetikai utasítások jellegzetességéből adódik.[8]
Műveletek
[szerkesztés]Precedencia | Operátor | Leírás | Asszociativitás |
---|---|---|---|
1 legmagasabb |
::
|
Látókör meghatározás (csak C++) | Nincs |
2 | ++
|
Postfix növelés | Balról jobbra |
--
|
Postfix csökkentés | ||
()
|
Függvényhívás | ||
[]
|
Tömbindexelés | ||
.
|
Adattag kiválasztás referencia szerint | ||
->
|
Adattag kiválasztás pointeren keresztül | ||
typeid()
|
Futásidejű típusinformáció (csak C++) | ||
const_cast
|
Típuskonverzió (csak C++) | ||
dynamic_cast
|
Típuskonverzió (csak C++) | ||
reinterpret_cast
|
Típuskonverzió (csak C++) | ||
static_cast
|
Típuskonverzió (csak C++) | ||
3 | ++
|
Prefix növelés | Jobbról balra |
--
|
Prefix csökkentés | ||
+
|
Unáris plusz | ||
-
|
Unáris mínusz | ||
!
|
Logikai NEM | ||
~
|
Bitenkénti NEM | ||
(type)
|
Típuskonverzió | ||
*
|
Indirekció (dereferencia) | ||
&
|
Memóriacím lekérése | ||
sizeof
|
Méret lekérése | ||
_Alignof
|
Igazítási követelmény (C11 óta) | ||
new , new[]
|
Dinamikus memóriafoglalás (csak C++) | ||
delete , delete[]
|
Dinamikus memóriafelszabadítás (csak C++) | ||
4 | .*
|
Pointerből adattag (csak C++) | Balról jobbra |
->*
|
Pointerből adattag (csak C++) | ||
5 | *
|
Szorzás | Balról jobbra |
/
|
Osztás | ||
%
|
Maradék | ||
6 | +
|
Összeadás | Balról jobbra |
-
|
Kivonás | ||
7 | <<
|
Bitenkénti balra tolás | Balról jobbra |
>>
|
Bitenkénti jobbra tolás | ||
8 | <=>
|
Háromirányú összehasonlítás (nagyobb: 1, kisebb: -1, egyenlő: 0) (C++20 óta, csak C++) |
Balról jobbra |
9 | <
|
Kisebb mint | Balról jobbra |
<=
|
Kisebb-egyenlő | ||
>
|
Nagyobb mint | ||
>=
|
Nagyobb-egyenlő | ||
10 | ==
|
Egyenlő | Balról jobbra |
!=
|
Nem egyenlő | ||
11 | &
|
Bitenkénti ÉS | Balról jobbra |
12 | ^
|
Bitenkénti XOR (kizáró vagy) | Balról jobbra |
13 | |
|
Bitenkénti VAGY (megengedő vagy) | Balról jobbra |
14 | &&
|
Logikai ÉS | Balról jobbra |
15 | ||
|
Logikai VAGY | Balról jobbra |
16 | co_await
|
Párhuzamos folyamat feldolgozása (csak C++) | Jobbról balra |
co_yield
| |||
17 | ?:
|
Hármas feltételes elágazás (ha-akkor-különben) |
Jobbról balra |
=
|
Értékadás | ||
+=
|
Értékadás és összeg | ||
-=
|
Értékadás és különbség | ||
*=
|
Értékadás és szorzás | ||
/=
|
Értékadás és osztás | ||
%=
|
Értékadás és maradék | ||
<<=
|
Értékadás és bitenkénti balra tolás | ||
>>=
|
Értékadás és bitenkénti jobbra tolás | ||
&=
|
Értékadás és bitenkénti ÉS | ||
^=
|
Értékadás és bitenkénti XOR | ||
|=
|
Értékadás és bitenkénti VAGY | ||
throw
|
Dobás operátor (kivételdobás, csak C++) | ||
18 legalacsonyabb |
,
|
Vessző | Balról jobbra |
Egy több műveletet tartalmazó kifejezésben a műveletek prioritás szerinti sorrendben hajtódnak végre (lásd a jobb oldali táblázatot). Az azonos prioritású műveletek közötti sorrendet az asszociativitás dönti el. Miután az asszociativitásnak csak a prioritási szinten belül van szerepe, az egyes prioritási szintek asszociativitása eltérhet egymástól. A C nyelv – a „szokásos” programnyelvekkel ellentétben – használja ezt a lehetőséget.[9] A műveletek végrehajtási sorrendjét zárójelekkel téríthetjük el a prioritás szerintitől.
Ha a művelet két operandusa nem azonos típusú, akkor a „kisebb” típus automatikusan a „nagyobbra” konvertálódik a művelet előtt. Ez alól kivételek a nem szimmetrikus műveletek (pl. struktúra tagjára hivatkozás, tömbindexelés, léptetés).
- (): függvényhívás. A C-ben a függvény neve a függvény memóriacímét jelenti. Ezen cím meghívása a művelet. A zárójelben a függvény paraméterei adhatók meg. A zárójeleket akkor is ki kell írni, ha a függvénynek nincs paramétere.[10]
- []: tömb indexelés.
- . és ->: hozzáférés struktúra vagy unió tagjához.
.
esetén a struktúra változóval,->
esetén memóriacímmel (mutatóval) adott. - !: logikai nem.
!c
értéke 1, ha c == 0, egyébként 0. - ~: bitenkénti negálás.
- ++: egyváltozós értékadás, mely eggyel növeli a változó értékét. A művelet eredménye
++n
esetén n+1,n++
esetén n (vagyis utóbbi esetben a növelés előtti érték). - --: egyváltozós értékadás, mely eggyel csökkenti a változó értékét. A művelet eredménye
--n
esetén n-1,n--
esetén n (vagyis utóbbi esetben a csökkentés előtti érték). - változó előtti -: előjelváltás.
- változó előtti +: hatástalan, de az olvashatóság érdekében megengedett (pl.
x = +a;
) - (típus): explicit konverzió. Pl. a
(unsigned long)c
kifejezés a c változó értékét előjeltelen hosszú egésszé alakítja. - változó előtti *: a mutatóban tárolt érték
- változó előtti &: a változó memóriacíme
- sizeof(): a változó vagy típus mérete byte-ban.[11] Ha pl. tomb-ot így deklaráltuk:
int tomb[6];
, akkor asizeof(tomb)/sizeof(int)
kifejezés értéke tomb elemszáma (ez esetben 6) lesz. A változó vagy típus összetett is lehet (struktúra vagy unió).[12] - *: szorzás
- /: osztás
- %: maradékképzés. Fixpontos adatokon végezhető. Negatív osztási eredmény esetén a maradék előjele nem definiált.
- +: összeadás.
- -: kivonás.
- <<: bitenkénti balra léptetés; balról 0-k lépnek be. A második operandus a léptetésszám. Pl.
1<<5
értéke 32. - >>: bitenkénti jobbra léptetés. Negatív szám esetén a balról bejövő bitek értéke nem definiált.
- relációk: <, <=, >, >=, ==, !=. A kifejezés értéke 1, ha teljesül a reláció, 0, ha nem. Az egyenlőséget vizsgáló
==
nem tévesztendő össze az értékadó=
művelettel. - & (két operandussal): bitenkénti ÉS művelet
- ^: bitenkénti kizáró vagy művelet. (Hatványozás művelet nincs C-ben, de van rá könyvtári függvény: a
pow
.) - |: bitenkénti VAGY művelet.
- &&: logikai ÉS művelet. Ha a bal operandus értéke 0, a művelet eredménye 0, és a jobb operandus nem értékelődik ki. Így pl. az
a != 0 && 1000/a < 2
kifejezés sohasem vezet 0-val osztáshoz. - ||: logikai VAGY művelet. Ha a bal operandus értéke nem 0, a művelet eredménye 1, és a jobb operandus nem értékelődik ki.
- ? és: háromváltozós művelet, feltételes értékadáshoz használható. Az ? előtti kifejezés a feltétel, azt követi az igaz, majd a : után a hamishoz tartozó érték. Pl. az
5 < 3? 1 : 2
kifejezés értéke 2. A példabeli számok helyén tetszőleges kifejezés állhat. - kétváltozós értékadó műveletek: =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=. Az = az egyszerű értékadás.
a += 2;
a értékéhez 2-t ad, az eredményt a-ba teszi, egyúttal ez az érték a kifejezés értéke. A többi művelet teljesen hasonló. - ,: először kiértékelődik a vessző előtti, utána a vessző utáni kifejezés, és ez utóbbi lesz a vesszős kifejezés értéke. A műveletet szinte kizárólag a for utasításban használják.
A bitműveletek (~, <<, >>, &, ^, |, <<= és >>=) és a maradékképzés (%) csak fixpontos típusokon értelmezettek.
Vezérlő utasítások
[szerkesztés]Három típusuk van:
Az if utasítás
[szerkesztés]Alakja:
if(feltétel) utasítás; else utasítás2;
A többi programnyelvtől eltérően C-ben nincs then
kulcsszó (ezért kell a feltételt zárójelbe tenni). Az else
elmaradhat.
Az if
és else
után egy utasítás állhat, ami blokk is lehet (és majdnem mindig az is). Ha utasítás is if
, és a két if
-nek egy else
-e van, az a belsőhöz tartozik. Az ilyen helyzeteket a {
}
használatával célszerű elkerülni.
C-ben nincs logikai változó vagy kifejezés (a relációs műveletek is egész típusú értéket adnak vissza), ezért a feltétel egész[5] típusú aritmetikai kifejezés, melynek 0 értéke esetén a feltétel nem teljesül (else
ág), nem 0 érték esetén teljesül.
Példa: az alábbi kód:
if(a > 5)
{
x = 3;
y = 2;
}
else
{
x = 8;
y = 9;
}
így is írható (bár a könnyű téveszthetőség miatt nem javasolt):
if(a > 5)
x = 3, y = 2;
else x = 8, y = 9;
A switch utasítás
[szerkesztés]Többirányú elágazás egy egész típusú aritmetikai kifejezés lehetséges értékei szerint. A lehetséges értékek egész típusú konstansok. Alakja:
switch(kifejezés) { case érték1: ... break; case érték2: ... break; default: ... }
A case
utáni egész konstansok különbözőek kell legyenek. Arra a case
-ra kerül a vezérlés, melynek értékével egyezik a kifejezés értéke. Ha egyikkel sem, a default
ágra kerül a vezérlés. A default
elhagyható, ilyenkor a switch
semmit sem csinál, ha a kifejezés értéke mindegyik case
-tól különbözik.
A break
kilép a switch
utasításból. Nem kötelező minden ág végére kitenni: ilyenkor az ág „átcsurog” a következőbe. Miután ez rendszerint programhiba, ha szándékos, célszerű megjegyzésben jelezni.
Példa (HTTP-státuszokkal):
enum { VAN=200, NINCS=404, TILTOTT=403 } kod;
...
switch(kod) {
case VAN: puts("Van ilyen lap");
break;
case NINCS: puts("Nincs ilyen lap");
break;
case TILTOTT: puts("Nincs engedély a lap olvasására");
break;
default: printf("Ismeretlen kód: %d\n",kod);
break;
}
A while utasítás
[szerkesztés]Alakja:
while(feltétel) utasítás;
A feltétel az if utasításhoz hasonlóan egész kifejezés, az utasítás – a ciklusmag – szinte mindig blokk.
Először feltétel értékelődik ki. Ha ez nem 0, végrehajtódik az utasítás, majd újra feltétel kiértékelése következik mindaddig, amíg feltétel a 0 értéket nem veszi fel.
A break
utasítás kilép a ciklusból. A continue
kilép a ciklusmagból, és feltétel kiértékelésével folytatja.
Végtelen ciklus:
while(1);
A pontosvessző az üres utasítást jelenti.
A for utasítás
[szerkesztés]Alakja:
for(előkifejezés; feltétel; utókifejezés) utasítás;
Hatása ugyanaz, mintha ezt írtuk volna:
előkifejezés; while(feltétel) { utasítás; utókifejezés; }
Ha a feltétel elmarad, igaznak számít. A két kifejezés bármelyike elhagyható; a for(;feltétel;)
azonos a while(feltétel)
utasítással. utasítás is elmaradhat, de a pontosvessző nem (üres utasítás). Ez elég gyakori: a ciklus feladatát a ciklus fejléce látja el.
A break
utasítás kilép a ciklusból. A continue
kilép a ciklusmagból, és utókifejezés végrehajtásával folytatja.
Példa: megszámoljuk, hogy tomb
-ben hány 1-es érték van, és a szaml
változóba tesszük.
int tomb[1000],szaml,*mut;
...
for(mut=tomb,szaml=0; mut-tomb < sizeof(tomb)/sizeof(int); mut++)
{
if(*mut == 1)
{
szaml++;
}
}
sizeof(tomb)
a tomb
mérete byte-ban. sizeof(int)
az egész típus hossza byte-ban. A kettő hányadosa megadja tomb
elemszámát, így azt egyszer kellett csak leírni. mut-tomb
azt adja meg, hogy hányadik tömbelemnél jár a ciklus.
A ciklus lefutása után a mut
változó értéke tomb+1000
lesz. A C-ben nincs igazi ciklusváltozó; a for
utasításban szereplő változók értékét – a legtöbb nyelvtől eltérően – a ciklus lefutása vagy a break
-kel történő kilépés után is megkötés nélkül használhatjuk.
Végtelen ciklus:
for(;;);
A do/while utasítás
[szerkesztés]A ciklusutasítások harmadik formája. Abban különbözik while
-tól, hogy először hajtja végre a ciklusmagot, utána értékeli ki a feltételt (Fortran-típusú ciklus). C-ben ritkán használatos. Alakja:
do utasítás; while feltétel;
A goto utasítás
[szerkesztés]A strukturált programozást a goto utasítás elkerülésére, a program olvashatóbbá tételére találták ki, ezért a goto használata egyáltalán nem javasolt.
Alakja: goto címke;
A címke egy név, melyet kettőspont követ. A goto hatására a program a címkével megjelölt utasítással folytatódik.
Példa:
goto cimke;
...
cimke: ...
Függvények
[szerkesztés]A függvények a hívó programtól kapott paraméterek alapján kiszámítanak egy értéket, és visszaadják a hívónak. A paraméterek és a visszatérési értékek helyes értelmezéséről a függvény deklarációja gondoskodik. A deklaráció nyomatékosan ajánlott, de nem kötelező.
A C-ben nincs eljárás, de a hívó program – az aritmetikai utasítások szabályai miatt – nem köteles felhasználni a függvény visszatérési értékét, ezért a függvények használhatók eljárásként. A gyakorlatban így is zavart okozott a függvények eljárásként történő használata, ezért az ANSI C-ben bevezették a void típust, mellyel semmilyen művelet nem végezhető. Ez azt jelenti, hogy a void típust visszaadó függvény gyakorlatilag eljárásnak tekinthető.
Függvénydeklaráció
[szerkesztés]Két módja van:
- típusdeklaráció: csak a függvény visszatérési értékének típusát mondja meg, a paraméterekét nem.
- prototípus: a visszatérési érték típusán felül megadja a paraméterek számát és típusát is.
A függvényérték típusa bármilyen C-típus lehet, beleértve az egyszerű és összetett típusokat, a mutatót (a tömböt mutató formájában adja vissza a függvény) és a már említett void
típust.
Példa: a strcmp
könyvtári függvény két stringet hasonlít össze. Ha a kettő azonos, 0-t ad vissza, ha az első előbb van az abc-ben, -1-et, ha hátrébb, 1-et. A típusdeklaráció:
int strcmp();
A függvény prototípusa a string.h
header-fájlban van:
int strcmp(const char *string1, const char *string2);
Ha a deklaráció elmarad, a függvényt egész típusúnak tekinti a fordítóprogram. Ha a paraméterek deklarációja marad el, azok típusát az első hívás alapján állapítja meg.
Ha a függvénynek nincs paramétere, a prototípusban – a típusdeklarációtól való megkülönböztetés érdekében – a void
kulcsszót kell megadni: int fv(void);
.
Függvénydefiníció
[szerkesztés]A függvény utasításait adja meg. Két részből áll: a fej adja meg a paramétereket és az érték típusát, az azt követő blokk az utasításokat.
A fej kétféle formában írható a kétféle függvénydefinícióhoz hasonlóan.[13] Az eredeti, típusdefiníció-szerű alak:
int memcmp(string1,string2)
const char *string1,*string2;
{...}
A másik alak ugyanolyan, mint a prototípus, csak a ;
helyén áll a blokk. Ma már kizárólag ez az alak használatos:
int strcmp(const char *string1, const char *string2)
{...}
A függvény a return
utasítás utáni kifejezéssel adja meg a visszatérési értékét. A kifejezés automatikusan átkonvertálódik a függvény típusára. void
típusú függvény esetén a return
utáni kifejezés elmarad, és a return
-t sem kell kiírni, ha az a függvény utolsó utasítása a záró }
előtt.
Ha a függvény mutatót ad vissza, az nem mutathat dinamikus adatra (verembeli területre). A függvényből való visszatéréskor ui. a dinamikus változók memóriaterülete felszabadul, és a terület bármikor megváltozhat.
A függvény hívása
[szerkesztés]A paramétereket a függvény neve után kell írni zárójelbe, vesszővel elválasztva. A zárójelet akkor is ki kell írni, ha a függvénynek nincs paramétere,[10] ui. a zárójel a függvényhívó művelet.
A paraméterek átadása érték szerint történik, azaz a függvény az értékek másolatát kapja meg. Ebből az is következik, hogy nem tudja megváltoztatni a hívó változóinak értékét. Ezt úgy hidalják át, hogy a változó címét (mutatóját) adják át a függvénynek.[14]
Függvényhívásra példák a könyvtári függvények fejezetben találhatók.
Függvénymutató
[szerkesztés]A függvénymutató egy függvény memóriacímét tároló változó, melyen a függvényhívás művelete (()
) hajtható végre.
Függvénymutató is kétféleképpen deklarálható:
int (*comp)(); // típusdeklarációs forma
int (*comp)(const char *string1,const char *string2); // prototípus forma
Mindkettő azt jelenti, hogy a comp
nevű változó egy egész típusú függvény címét tartalmazza.[15] Érték így adható neki:
comp = strcmp;
Ezután pl. a comp("egyik",valt)
és strcmp("egyik",valt)
kifejezés teljesen azonos hatású.
Függvénymutatót kap paraméterként a rendezést végző qsort
és a szimbóltáblában kereső lsearch
, lfind
és bsearch
könyvtári függvény.
Preprocesszor utasítások
[szerkesztés]A preprocesszor-utasítások hatására a fordítás első menete a forrásprogramon hajt végre módosításokat, melynek eredménye a preprocesszor-utasítás nélküli C-program.
A preprocesszor-utasítások nem szabad formátumúak: a sor eleji #
-jellel kezdődnek, és a sor végével végződnek. Folytatósor a sor végi \
-sel írható.
#include
[szerkesztés]Alakja:
#include <fájlnév> #include "fájlnév"
Hatására a preprocesszor az include
utasítást a megadott nevű fájl (header-fájl) tartalmával helyettesíti (mintha beírtuk volna a programba). A két alak között az a különbség, hogy az első a fordítóprogram fájljai között keresi a megadott fájlt (linuxban pl. a /usr/include
könyvtárban), míg a második a C-programmal azonos könyvtárban (saját header-fájl).
A fájlnév tartalmazhat path-t, kiterjesztése a kialakult szokások szerint .h
Ugyancsak kialakult az a szokás, hogy a header-fájl végrehajtható utasítást nem, csak adat-, függvény- és típusdeklarációkat tartalmaz (a preprocesszor-utasításokon kívül).
A header-fájlok használata lehetővé teszi, hogy a deklarációkat egy helyen lehessen leírni. A forrásprogramoknak csak hivatkozniuk kell rá az #include
utasítással.
#define
[szerkesztés]Preprocesszor-változóhoz rendel értéket. A preprocesszor a változó helyére szövegszerűen behelyettesíti az értéket. Például ha egy tömb méretére több helyen hivatkozunk a programban:
#define TOMBMERET 100
int tomb[TOMBMERET],i;
for(i=0; i < TOMBMERET; i++)
...
Ezzel a tömb méretét egy helyen lehet változtatni a programban.
A #define
értéke bármilyen szöveg lehet. Ha C-kifejezésnek akarunk így nevet adni, nyomatékosan ajánlott a kifejezést zárójelbe tenni. A használatkor ui. szöveges másolás történik, nincs prioritásellenőrzés.
A legtöbb fordítóprogram lehetővé teszi, hogy a fordítóprogram hívásakor adhassunk meg preprocesszor-változókat. Pl. linuxban a fenti változó a
gcc -DTOMBMERET=100 ...
kapcsolóval adható meg. Több -D
kapcsoló írható. A #ifndef
utasítással meg lehet vizsgálni, hogy a változó létezik-e (és default érték is rendelhető hozzá, ha a fordításkor nem adtunk meg értéket).
#ifdef, #ifndef
[szerkesztés]Megvizsgálja, hogy létezik-e egy preprocesszor-változó. Alakja:
#ifdef preproc-változó ... #else ... #endif
A #else
ág elmaradhat. Több utasítás skatulyázható egymásba. A #ifdef
akkor teljesül, ha a változó létezik, #ifndef
akkor, ha nem.
Az utasítással forráskódot hagyhatunk ki a programból. A #else
-ig (ha elmarad, #endif
-ig) leírt szöveg (program) csak akkor kelül a programba, ha a feltétel teljesül. Ha nem teljesül, a #else
után leírt; ha nincs #else
, akkor semmi.
Példa. Gyakran okoz furcsa hibákat az, ha egy header-fájlt többször hívunk be a programba (esetleg nem is közvetlenül .c-ből, hanem másik headerfájlból). Ezért a headerfájlt így célszerű megírni (legyen a neve pelda.h
):
#ifndef PROBA_H
#define PROBA_H // létrehozzuk a változót, hogy a következő híváskor ne teljesüljön a feltétel
// Ide jön a header-fájl tartalma
#endif
#if
[szerkesztés]Abban különbözik #ifdef
-től, hogy a #if
után tetszőleges konstans (konstansokból és értéket kapott preprocesszor-változókból) álló fixpontos kifejezés írható. Ha a kifejezés értéke nem 0, a #if
utáni, ha 0, a #else
utáni kód kerül a programba.
Példa: szükségünk van egy legalább 24 bites előjeltelen egész típusra. A szabvány szerint az unsigned
típus legalább 16, az unsigned long
legalább 32 bites. A legnagyobb előjeltelen egész értékét a limits.h
header-fájl definiálja az UINT_MAX
nevű preprocesszor-változóban.
#include <limits.h>
#if 1L << 24 < UINT_MAX
typedef unsigned INT24;
#else
typedef unsigned long INT24;
#endif
Standard könyvtári függvények
[szerkesztés]I/O függvények
[szerkesztés]Adat | stdout | fájl |
---|---|---|
karakter | putchar | fputc |
string | puts | fputs |
formátumozott | printf | fprintf |
Szinte minden program használja az input-output függvények valamelyikét. Ezek fájlból olvasnak vagy fájlba írnak, és a stdio.h
header-fájlban vannak definiálva.
A fájlt meg kell nyitni. A fopen
függvény FILE
típusú mutatót ad vissza, és ugyancsak stdio.h
-ban van definiálva (fordítóprogramtól függően általában typedef struct ... FILE;
alakban). A többi fájlkezelő függvény erre az ún. fájlleíróra hivatkozik.
A program az induláskor az operációs rendszertől kap három nyitott fájlt (az alábbi globális nevek ugyancsak stdio.h
-ban vannak):
stdin
: standard bemenetstdout
: standard kimenetstderr
: standard hibakimenet
Adat | stdin | fájl |
---|---|---|
karakter | getchar | fgetc |
string | -[16] | fgets |
formátumozott | scanf | fscanf |
Ezeket nem kell megnyitni, de le lehet zárni, ha a program nem használja őket. Néhány I/O függvénynek nem kell fájleírót adni: ezek stdout
-ra írnak vagy stdin
-ről olvasnak.
A függvények pufferelnek: a kiírt adatok a memóriába kerülnek, és csak bizonyos mennyiség után, a fájl lezárásakor (fclose
) vagy a fflush
függvény meghívására íródnak ki.
A printf és scanf függvénycsaládnak a formátumot stringben kell megadni. A formátum %
-jellel kezdődik, és az adat típusára utaló betűvel végződik. A kettő között további információkat lehet megadni. A formátumstring utáni első paraméter az első %-hoz tartozó adat stb. A paraméterek száma tetszőleges, de a %-ok és a paraméterek párban kell legyenek.
Az sprintf
függvény fájl helyett karaktertömbbe írja a kimenetet. A sscanf
karaktertömbből veszi a bemenetet. A két családnak további függvényei is vannak.
Az alábbi programrészlet a meretek.txt
nevű fájlba írja, hány bájtos a gépen a short
, int
, long
és long long
típus.
FILE *fp;
static const char fnev[] = "meretek.txt";
...
if( (fp = fopen(fnev,"w")) == NULL)
{
fprintf(stderr,"Nem tudom írásra megnyitni a %s fájlt\n",fnev);
exit(2); // 2-es hibakóddal kilép a programból
}
fprintf(fp,"short = %d\nint = %d\nlong = %d\nlong long = %d\n",
sizeof(short),sizeof(int),sizeof(long),sizeof(long long));
if(ferror(fp) | fclose(fp)) // || nem jó, mert hiba esetén nem zárja be a fájlt
{
fprintf(stderr,"I/O hiba a %s fájlban\n",fnev);
exit(2); // 2-es hibakóddal kilép a programból
}
A meretek.txt
tartalma linuxban, x86_64 architektúrában:
short = 2 int = 4 long = 8 long long = 8
Néhány egyéb könyvtári függvény
[szerkesztés]headerfájl | feladat | példák |
---|---|---|
stdlib.h | kilépés a programból | exit, abort |
memóriakérés futás közben | malloc, calloc, realloc, free | |
rendezés, szimbóltábla kezelés | qsort, lsearch, lfind, bsearch | |
string konverziója C-típussá | atoi, atol, atof | |
string.h | stringkezelés | strlen, strcmp, strcat, strchr, strstr, strspn, strcspn |
ctype.h | karakterosztályozás | isalpha, isalnum, isupper, toupper, islower, tolower, isspace |
math.h | matematikai függvények | sin, asin, cos, acos, tan, atan, atan2, pow, sqrt |
limits.h | értékhatárok | preprocesszor-változók a különböző típusok minimális és maximális értékeire |
errno.h | hibakódok | az errno globális változó definíciója és lehetséges értékei az utolsó művelet sikerességéről
|
unistd.h | paranccsori paraméterek átvétele | getopt |
setjmp.h | fatális hiba kezelése | setjmp, longjmp |
Kapcsolódó szócikkek
[szerkesztés]- A magyar Wikikönyvekben további információk találhatók Programozás C nyelven témában.
Jegyzetek
[szerkesztés]- ↑ Ritchie, Dennis M.: The Development of the C Language, 1993. January. [2013. június 22-i dátummal az eredetiből archiválva]. (Hozzáférés: 2008. január 1.) „Thompson had made a brief attempt to produce a system coded in an early version of C—before structures—in 1972, but gave up the effort.”
- ↑ C/C++, The Internet encyclopedia 1. John Wiley and Sons (2004). ISBN 0-471-22201-1. Hozzáférés ideje: 2012. december 16. Archiválva 2013. december 13-i dátummal a Wayback Machine-ben Archivált másolat. [2013. december 13-i dátummal az eredetiből archiválva]. (Hozzáférés: 2013. december 7.)
- ↑ Programming Language Popularity, 2009. [2009. január 16-i dátummal az eredetiből archiválva]. (Hozzáférés: 2009. január 16.)
- ↑ TIOBE Programming Community Index, 2009. (Hozzáférés: 2009. május 6.)
- ↑ a b Kerülni kell két lebegőpontos szám egyenlőségének vizsgálatát, mert valamelyik érték kerekített lehet. Az egyenlőség helyett azt érdemes megnézni, hogy a különbségük abszolút értéke elég kicsi-e. Ezért előírás, hogy a logikainak használt érték fixpontos legyen.
- ↑ A kezdőértéket kapott összetett adat fordítóprogramtól függően kivétel lehet: ilyenkor a konstans adatterületről másolja a verembe a kezdőértéket a kód.
- ↑ Az
=
bal oldalán is lehet bizonyos korlátoknak eleget tevő kifejezés, ún. lvalue. Az lvalue lehet változónév (de nem lehet tömb- vagy függvénynév), vagy olyan aritmetikai kifejezés, melynek legutoljára végrehajtott művelete az indirekció (*
, ill. az ezzel azonos tömbindexelés). - ↑ Az értékadás nélküli függvényhívás szintaktikusan az
1;
utasításnak felel meg, de figyelmeztetést nem kapunk, mert a fordítóprogram tisztában van a mellékhatások lehetőségével. - ↑ Egyváltozós műveleteket vagy a többszörös értékadást nem is lehet máshogyan kiértékelni – függetlenül attól, hogy az adott nyelv kimondja-e a jobbról balra asszociativitást, vagy műveletnek tekinti-e az értékadást.
- ↑ a b A zárójel nélküli függvénynévből álló utasítás – a függvény memóriacíme – egy konstans érték, vagyis ugyanolyan utasítás, mint az
1;
: szintaktikusan helyes, de nem csinál semmit. - ↑ Pontosabban: a char típus hosszában.
sizeof(char)
értéke definíció szerint 1. - ↑ A sizeof művelet fordítási időben elvégezhető, ezért nem generál kódot. A C-fordító a konstans kifejezéseket fordítási időben értékeli ki, így az előbbi példában az osztást is. A kódban a 6 konstans kerül a helyére.
- ↑
float
típus esetén a két forma nem ugyanazt teszi. Az eredeti, típusdefiníciós forma afloat
típustdouble
-re konvertálta, és a fejlécbelifloat
deklarációt automatikusandouble
-nek vette. A prototípus formában már lehet konverzió nélkülifloat
típust átadni paraméternek – feltéve, hogy az első hívást megelőzi a prototípus alakú deklaráció. - ↑ C++-ban ezt a fordítóprogram is megteszi a referencia szerinti paraméterátadásnál.
- ↑ A deklarációban a zárójelre szükség van. A
int *comp();
utasítás ui. egy egész mutatót visszaadó függvény típusdeklarációja lenne. - ↑ A
gets
függvény tömbtúlcsordulást okozhat, ezért helyette afgets(tömb,méret,stdin)
-t kell használni.