Ugrás a tartalomhoz

C (programozási nyelv)

Ellenőrzött
A Wikipédiából, a szabad enciklopédiából
C

Paradigmaimperatív (procedurális), strukturált
Jellemző kiterjesztés.h, .c
Megjelent1972[1]
TervezőDennis Ritchie
FejlesztőDennis Ritchie & Bell Labs
Típusosságstatikus, gyenge
FordítóprogramGCC, MSVC, Borland C, Watcom C
MegvalósításokClang, GCC, Intel C, MSVC, Turbo C, Watcom C
Hatással volt ráB (BCPL,CPL), ALGOL 68, Assembly, Pascal
Befolyásolt nyelvekawk, 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.

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ípus
  • long int adattípus
  • unsigned 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 és void * adattípus
  • függvények, melyek struct vagy union 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.

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, explicit bool (stdbool.h) és a complex (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.

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.

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
&nbsp;
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]
Változóméretek (legalább; bitben)
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ú konstans 3.14F, long double 3.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:

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]
A C-program memóriaterületei
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.

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 a sizeof(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

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]
Standard kiíró függvények
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 bemenet
  • stdout: standard kimenet
  • stderr: standard hibakimenet
Standard beolvasó függvények
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]

Jegyzetek

[szerkesztés]
  1. 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.”
  2. 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.)
  3. Programming Language Popularity, 2009. [2009. január 16-i dátummal az eredetiből archiválva]. (Hozzáférés: 2009. január 16.)
  4. TIOBE Programming Community Index, 2009. (Hozzáférés: 2009. május 6.)
  5. 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.
  6. 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.
  7. 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).
  8. 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.
  9. 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.
  10. 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.
  11. Pontosabban: a char típus hosszában. sizeof(char) értéke definíció szerint 1.
  12. 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.
  13. float típus esetén a két forma nem ugyanazt teszi. Az eredeti, típusdefiníciós forma a float típust double-re konvertálta, és a fejlécbeli float deklarációt automatikusan double-nek vette. A prototípus formában már lehet konverzió nélküli float típust átadni paraméternek – feltéve, hogy az első hívást megelőzi a prototípus alakú deklaráció.
  14. C++-ban ezt a fordítóprogram is megteszi a referencia szerinti paraméterátadásnál.
  15. 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.
  16. A gets függvény tömbtúlcsordulást okozhat, ezért helyette a fgets(tömb,méret,stdin)-t kell használni.