KURZUS: Programozás alapjai

MODUL: II. modul

4. lecke: Típusok és konstansok

Ebben a leckében pontosítjuk a típusokkal és konstansokkal kapcsolatosan eddig megszerzett ismereteket. Részletesen megnézzük, formálisan is definiáljuk a már ismert típusok, és az ilyen típusú konstansok szabályait, valamint eddig nem ismert típusokkal is foglalkozunk, mint pl. felsorolt típusok. Nagyon röviden szó esik végül a typedef kulcsszó használatáról is.

A lecke végére a hallgatónak tisztában kell lennie

  • a forráskód szimbólumokra bontásának módjával,
  • az azonosítók képzésének pontos szabályaival,
  • az összes használható egész típus tulajdonságaival,
  • a felsorolás típussal,
  • az összes használható valós típus tulajdonságaival,
  • karakterek és karakterláncok használatának módjával, egyszerűbb karakterlánc-kezelő függvényeket képesnek kell lennie önállóan elkészíteni,
  • a typedef működésének alapelveivel.

A lecke kulcsfogalmai: szimbólum, elválasztó jel, azonosító, függvény, típusmódosító, minősítő, alap- és származtatott típusok, felsorolás típus, enum specifikátor, enumerátor, névterület, egész típus, aritmetikai típus, definíciós deklaráció, referencia deklaráció, deklarációs pont.

Szimbólumok

A fordító a forráskódot szintaktikai egységekre, vagy más elnevezéssel szimbólumokra (token), és fehér karakterekre tördeli. A több egymást követő fehér karakterből csak egyet tart meg. Ebből következőleg:

  • Egyetlen C utasítás akár szimbólumonként külön-külön sorba írható.
  • Egy sorban több C utasítás is megadható.

Pontosan hat szimbólum (int i ; float f ;) lesz a következőkből:

int
i;
float f ;

vagy

int i; float f;

A karakter, vagy karakterlánc konstansokban előforduló, akárhány fehér karaktert változatlanul hagyja azonban a fordító.

Említettük már, hogy a programnyelv szimbólumokból áll. Most ismertetjük a szimbólum definícióját módosított Backus-Naur, metanyelvi leírással:

szimbólum:
  operátor
  kulcsszó
  elválasztójel
  azonosító
  konstans
  karakterlánc

Az értelmezés nagyon egyszerű: a felsorolt hat fogalom mindegyike szimbólum. Az operátorokról már esett szó, de a részletekre később még visszatérünk.

A kulcsszavak:

autodoubleintstruct
breakelselongswitch
caseenumregistertypedef
charexternreturnunion
constfloatshortunsigned
continueforsignedvoid
defaultgotosizeofvolatile
doifstaticwhile

Vannak ezeken kívül még nem szabványos kulcsszavak és más védett azonosítók, de ezek mindig megtudhatók a programfejlesztő rendszer segítségéből! Ilyenekre kell gondolni, mint a cdecl, a pascal, az stdcall, vagy egy-két aláhúzás karakterrel kezdődőkre, mint például az __STDC__ stb.

Elválasztójel

A szintaktikai egységeket (a szimbólumokat) egymástól legalább egy fehér karakterrel el kell választani. Nincs szükség azonban az elválasztó fehér karakterre, ha a két nyelvi egység közé a szintaktikai szabályok szerint egyébként is valamilyen elválasztójelet kell írni. Az operátorok is elválasztójelnek minősülnek kifejezésekben.

elválasztójel: (a következők egyike!)

  [ ] ( ) { } * , : = ; ... #

Nézzük meg néhány elválasztójel funkcióját!

Az utasítást záró pontosvessző minden példában benne van.

A kerek zárójeleknek csoportosító funkciója van kifejezésekben. Van, amikor a szintaktika része. Függvényhívásnál az aktuális paramétereket, függvénydeklarációban és definícióban a formális paramétereket ebbe kell tenni.

d = c*(a+b);
if(d==z) ++x;
fv(akt, par);
void fv2(int n);

A szögletes zárójelek tömbök deklarációjában és indexelő operátorként használatosak.

char kar, lanc[] = "Sztan és Pan.";
kar = lanc[3];

A kapcsos zárójelekbe tett több utasítás szintaktikailag egyetlen utasításnak minősül. A dolgot összetett utasításnak, blokknak nevezzük. Az összetett utasítás (blokk) záró kapcsos zárójele után tilos pontosvesszőt tenni! (Pontosabban, a pontosvessző egy további, hatástalan üres utasítást generál, de a program mérete ettől még növekedhet.)

if(a<b){ /* Illegális pontosvessző használat. */
  a=2; z=b+6; };

A csillag elválasztó-jelnek többféle szerepe van a nyelvben. Eddig csak a szorzás operátor funkcióját ismerjük.

a = 3.14*b;

Az egyenlőség jel hozzárendelés operátor, és deklarációs utasításban elválasztja a változót az inicializátortól, vagy inicializátorlistától.

char tomb[5] = {0, 1, 2, 3, 4};
int x=5, b, c=4;
b = x+c;

A kettős kereszt előfeldolgozó (preprocessor) direktíva kezdete. A sorbeli első nem fehér karakternek kell annak lennie.

#include <stdio.h>
#define TRUE 1
#define FALSE 0
Azonosító

azonosító:
  nem-számjegy
  azonosító nem-számjegy
  azonosító számjegy

nem-számjegy: (a következők egyike!)
  a ... z A ... Z _

számjegy: (a következők egyike!)
  0 1 2 3 4 5 6 7 8 9

Az azonosító változóknak, függvényeknek, felhasználó definiálta adattípusoknak stb. adott, a következő pontokban pontosított név:

  • Kisbetűvel, nagybetűvel vagy aláhúzás (_) karakterrel köteles kezdődni.
  • A további karakterek lehetnek számjegyek is.
  • Az azonosító nem lehet kulcsszó vagy valamilyen előredefiniált, védett azonosító.
  • Az azonosítók kis- és nagybetű érzékenyek, azaz az Osszeg, az osszeg vagy az osszeG három különböző azonosító.
  • Kerülni kell a két és az egy aláhúzás karakterrel kezdődő nevek használatát is!
  • Az azonosítók első, mondjuk, 31 karaktere (programfejlesztő környezetben szabályozható) szignifikáns. Ez azt jelenti, hogy hosszabb nevek is használhatók, de az első 31 karakterükben nem különböző azonosítók ugyanannak minősülnek.
Típusok és konstansok a nyelvben

A nyelvben összesen négy típuskategória van:

  • A függvény a nyelv kódgeneráló egysége. Az összes többi kategória csak memóriát foglal az adatoknak.
  • A void típus többnyire valaminek a meg nem létét jelzi. Például nincs paramétere és visszaadott értéke a void main(void) függvénynek.
  • Skalár az aritmetikai típus, mely tovább bontható fixpontos egész és lebegőpontos valós ábrázolású típusokra. Ilyen a felsorolás (enum) típus és a mutató is. (Ezekről hamarosan lesz még szó.)
  • Aggregátum (összetett típus) a tömb, a struktúra és az unió.

A mutatókkal, a struktúrákkal és az uniókkal később foglalkozunk. A típusokat úgy is csoportosíthatnánk, hogy vannak

  • alaptípusok és
  • származtatott típusok.

Az alaptípusok a void, a char, az int, a float és a double. A származtatás pedig a short, a long, a signed és az unsigned ún. típusmódosítókkal történhet. A short és a long, valamint a signed és az unsigned egymást kizáró módosító párok, de a két pár egyazon alaptípusra egyszerre is alkalmazható, azaz létezik

  • unsigned long int vagy
  • signed short int stb.

A signed és az unsigned módosító azonban csak egész típusokra (char és int) alkalmazható, lebegőpontos valós és a void alaptípusra nem.

A származtatott típusokba mindig beleértendők az ilyen típusú értékkel visszatérő függvények, az ilyen típust paraméterként fogadó függvények, a tömbök stb. Ha programunkban valamilyen azonosítót használni kívánunk, akkor előbb deklarálni (definiálni) kell. A deklaráció teremti meg a kapcsolatot az azonosító és az objektum között, és rögzíti legalább az objektum adattípusát. A származtatott típust is szokás egyszerűen típusnak nevezni.

"Készpénzre váltva" az előző bekezdésben mondottakat: ha a típus valamilyen nem void adattípus, akkor a deklarációk következőképp szemléltethetők:

típus t, t1, t2; /* Három típus típusú objektum. */
típus f(void); /* Típus típusú értéket visszaadó, paraméter nélküli függvény. */
void fv(típus i); /*Típus típusú paramétert fogadó eljárás. */
típus tt[10]; /* 10 elemű, típus típusú tömb. Az elemek rendre: tt[0],
..., tt[9]. */

Feltétlenül említést kell tennünk még két, definícióban használható módosítóról, melyek alaposan megváltoztatják a deklarált objektum tulajdonságait. E kulcsszavakat minősítők megnevezéssel is illetik.

A const nem módosítható objektumot definiál, azaz meggátolja a hozzárendelést az objektumhoz, és az olyan mellékhatásokat, mint az inkrementálás, dekrementálás stb. Magyarán: nem engedi meg az azonosító elérését balértékként. A const a deklaráció elején bármilyen alaptípussal, aggregátum típussal stb. állhat, de tilos többtételes deklaráció első vesszője után kiírni:

float f = 4.5, const cf = 5.6; /* HIBÁS! */

A const típusú objektum értékkel való ellátásának egyetlen módja az inicializálás. Tehát az ilyen objektum definíciójában kötelező neki kezdőértéket adni, mert ez később már nem tehető meg. Például:

const float pi = 3.1415926;
const max2int = 32767;

Az elő nem írt alaptípus miatt a deklaráció specifikátorként egyedül álló const tulajdonképpen const int, s ezért a max2int is az. E deklarációk után "botor dolgok" a következő utasítások:

pi = 3.; /* Szintaktikai hiba. */
int i = max2int++; /* Szintaktikai hiba. */

A volatile szó jelentése elpárolgó, illékony, állhatatlan. Módosítóként azt jelzi, hogy az illető változó értéke program végrehajtáson kívüli okból (megszakítás, B/K port, konkurensen futó végrehajtási szál stb.) is megváltozhat.

A volatile kulcsszó deklaráción belüli elhelyezésének szabályai egyeznek a const-éival. A fordító az ilyen változót nem helyezheti el regiszterben, ill. az ilyen objektumot is tartalmazó kifejezés kiértékelése során nem indulhat ki abból, hogy az érték közben nem változik meg. Szóval az ilyen változó minden hivatkozásához elérési kódot kell generálnia akkor is, ha az látszólag hatástalannak tűnik. Például:

volatile ticks; /* volatile int a típus. */
void interrupt timer(void) {++ticks;}
void varj(int ennyit){
  ticks = 0;
  while( ticks < ennyit ); } /* Ne tegyen semmit. */

A while-beli feltétel kiértékelésekor a ciklus minden ütemében tölteni kell ticks értékét.

Egy objektum egyszerre lehet const és volatile, amikor is az őt birtokló program nem módosíthatja, de megváltoztathatja az értékét bármely aszinkron program vagy végrehajtási szál.

A konstansok definíciója a következő:

konstans:
  egész-konstans
  enum-konstans
  lebegőpontos-konstans
  karakter-konstans

Tudjuk, hogy az objektum is lehet konstans, de itt most nem ilyen állandóról van szó. Az "igazi" konstans nem azonosítható memória területet takar, mely fix, a program futása alatt meg nem változtatható értéket tartalmaz. A konstansnak nincs szoftveresen is használható címe, de van adattípusa, mely meghatározza az állandónak

  • lefoglalandó memória mennyiségét és
  • a benne tárolt érték belsőábrázolási formáját.
Egész típusok és konstansok

Már említettük, hogy az egész típusok a char és az int alaptípusból a signed - unsigned, valamint a short - long módosító párok alkalmazásával állíthatók elő, azaz a deklaráció írásszabálya eltekintve a tárolási osztálytól és az inicializálástól:

<típusmódosítók> <alaptípus> azonosítólista;

A típusmódosítók alaptípus párost azonban a továbbiakban is típusnak fogjuk nevezni. A szabályok a következők:

  • Mind az alaptípus, mind a típusmódosítók elhagyható. A kettő együtt azonban nem.
  • Ha elhagyjuk az alaptípust, alapértelmezés az int.
  • A short - long módosítók elhagyásakor nincs rájuk vonatkozó alapértelmezés.
  • Ha elhagyjuk a signed - unsigned módosítót, alapértelmezés a signed.
TípusMéret bájtbanMinimális értékMaximális érték
char, signed char1-128127
unsigned char10255
short, short int, signed short int2-3276832767
unsigned short, unsigned short int2065535
int, signed int2 vagy 4short vagy longshort vagy long
unsigned, unsigned int2 vagy 4ugyanígy, de unsignedugyanígy, de unsigned
long, long int, signed long int4-21474836482147483647
unsigned long, unsigned long int404294967295
  • A char alaptípussal kapcsolatban kiegészítésre szorul a signed char alapértelmezés! A programfejlesztő rendszerben ugyanis unsigned char is beállítható a char típus alapértelmezéseként. A lehetséges karaktertípusok ilyenkor így változnak:
TípusMéret bájtbanMinimális értékMaximális érték
char, unsigned char10255
signed char1-128127
  • Az ANSI szabvány nem ír elő pontos méretet az int típusra, csupán csak annyit, hogy:
    short <= int <= long.
  • A belsőábrázolás fixpontos, bináris egész, ezért signed típusokra a negatív számokat kettes komplemensük alakjában tárolja a fordító.
  • A szabványos limits.h fejfájlban találunk szimbolikus állandókat (CHAR_MIN, UCHAR_MAX, SHRT_MIN stb.) az ábrázolási korlátokra!

A nyelv szerint nincs sem egész túlcsordulás, sem alulcsordulás. Az egész konstansnál láthatunk erre is egy rövid példát!

egész-konstans:
  decimális-konstans <egész-utótag>
  oktális-konstans <egész-utótag>
  hexadecimális-konstans <egész-utótag>

A metanyelvben a < ... > az elhagyhatóságot jelöli!

egész-konstans:
  decimális-konstans<egész-utótag>
  oktális-konstans<egész-utótag>
  hexadecimális-konstans<egész-utótag>

decimális-konstans:
  nemzérus-számjegy
  decimális-konstans számjegy

oktális-konstans:
  0
  oktális-konstans oktális-számjegy

hexadecimális-konstans:
  0xhexadecimális-számjegy
  0Xhexadecimális-számjegy
  hexadecimális-konstans hexadecimális-számjegy

nemzérus-számjegy: (a következők egyike!)
  1 2 3 4 5 6 7 8 9

oktális-számjegy: (a következők egyike!)
  0 1 2 3 4 5 6 7

hexadecimális-számjegy: (a következők egyike!)
  0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F

egész-utótag:
  unsigned-utótag<long-utótag>
  long-utótag<unsigned-utótag>

unsigned-utótag: (a következők egyike!)
  u U

long-utótag: (a következők egyike!)
  l L

A definíció szerint:

  • Az egész konstans lehet decimális, oktális és hexadecimális.
  • Az egész konstansnak nincs előjele (pozitív), azaz a negatív egész konstans előtt egy egyoperandusos mínusz operátor áll.
  • A decimális egész konstans int, long int, vagy unsigned long int belsőábrázolású a konstans értékétől függően alapértelmezés szerint. Az oktális és hexadecimális egész konstans az értékétől függően int, unsigned int, long int, vagy unsigned long int belsőábrázolású.
DecimálisOktálisHexadecimálisTípus
0-327670-0777770X0-0X7FFFint
0100000-017777770X8000-0XFFFFunsigned
32767-214748364702000000-0177777777770X10000-0X7FFFFFFFlong
2147483648-4294967295020000000000-0377777777770X80000000-0XFFFFFFFFunsigned long

Lássunk néhány példát a decimális, az oktális és a hexadecimális egész konstansokra!

int i = 10;
int j = 010;  /* j kezdetben decimálisan 8. */
int k = 0;    /* k decimálisan és oktálisan is 0. */
int l = 0XFF; /* k decimálisan 255. */

Explicit egész utótagot a konstans utána írva megváltoztathatjuk az alapértelmezett belsőábrázolást. Az inicializátor konstansok típusegyeztetésével elkerülhető a szükségtelen közbenső konverzió. Például:

unsigned ui=2u;
long li=16l;
unsigned long uli=17lu;

Az egész konstans 0 és 4294967295 értékhatárok közt megengedett. Ez azonban a programozó felelőssége, mert a 4294967295-nél nagyobb érték egyszerűen csonkul. Legfeljebb egy "halk" figyelmeztető üzenet jöhet a fordítótól. Például a

#include <stdio.h>
void main(void){
  unsigned long pipipp;
  pipipp = 4300000000;
  printf("Pipipp = %ld\n", pipipp); }

hatására 5032704 jelenik meg, ami egy híján 4300000000 - 4294967295.

Felsorolás (enum) típus és konstans

Az enum (enumerate) adattípus mnemonikussá tesz egy sorozat egész értékre való hivatkozást. Például az

enum napok{ vasar, hetfo, kedd, szerda, csut, pentek, szomb } nap;

deklaráció létrehoz egy egyedi enum napok egész típust. Helyet foglal egy ilyen típusú, nap azonosítójú változónak, és definiál hét, konstans egész értéket reprezentáló enumerátort: az enumerátor készletet.

Felsorolás típusú változóban a típushoz definiált enumerátor készlet egyik értékét tarthatjuk. Az enumerátort enum konstansnak is nevezik. Felsorolás típusú változókat használhatunk index kifejezésben, minden aritmetikai és relációs operátor operandusaként stb. Tulajdonképpen az enum a #define direktíva alternatívájának is tekinthető.

ANSI C-ben az enumerátor értékét definiáló kifejezés csak egészértékű konstans kifejezés lehet, és típusa mindig int. Az enum változó tárolásához használt memória is annyi, mint az int típushoz használt. enum típusú konstans vagy érték a C-ben ott használható, ahol egész kifejezés is. Lássuk a szintaktikát!

enum-specifikátor:
  enum <azonosító> {enumerátorlista}
  enum azonosító

enumerátorlista:
  enumerátor
  enumerátorlista, enumerátor

enumerátor:
  enum-konstans
  enum-konstans = konstans-kifejezés

enum-konstans:
  azonosító

Az enum-specifikátor definíciójában az enumerátorlistával definiált enum típus opcionális azonosítóját enum címkének (tag) nevezzük. Ha ezt elhagyjuk a definícióból, névtelen enum típust kapunk. Ennek az az ódiuma, hogy később nincs lehetőségünk ilyen felsorolás típusú változók definíciójára, hisz nem tudunk a névtelen enum-ra hivatkozni. Névtelen enum típusú változók tehát csak a névtelen enum deklarációjában definiálhatók, máshol nem. Az

enum {jan, feb, marc, apr, maj, jun, jul, aug, szep, okt, nov, dec};

így teljesen használhatatlan, hisz képtelenség ilyen típusú változót deklarálni. A probléma az azonosítólista megadásával elkerülhető:

enum {jan, feb, marc, apr, maj, jun, jul, aug, szep, okt, nov, dec} ho=jan, honap;

Foglaljuk össze az enum deklarációval kapcsolatos tapasztalatainkat:

enum <enum címke> <{enumerátorlista}> <azonosítólista>;

ahol a < > most is az elhagyhatóságot jelöli. Az enum címke megadása tehát azt biztosítja, hogy az "enum enum címke" után azonosítólistát írva később ilyen felsorolás típusú változókat definiálhatunk. A fejezet elején említett deklarációs példában a napok azonosító az opcionálisan megadható enum címke, mely később enum napok típusú változók definícióiban használható fel. Például:

enum napok fizetes_nap, unnepnap;

Térjünk egy kicsit vissza az enumerátorokhoz! Láttuk, hogy az enum konstans a felsorolás típus deklarációjában definiált azonosító. Miután az enumerátor egész adattípusú, kifejezésekben ott használható, ahol egyébként egész konstans is megadható. Az enumerátor azonosítójának egyedinek kell lennie az enum deklaráció hatáskörében beleértve a normál változók neveit és más enumerátorlisták azonosítóit. Az enum konstansok az egyszerű változók névterületén helyezkednek el tehát. Az enum címkéknek viszont az enum, a struktúra és az uniócímkék névterületén kell egyedinek lenniük.

A névterület olyan azonosító csoport, melyen belül az azonosítóknak egyedieknek kell lenniük. A C-ben több névterület van, amiből itt kettőt meg is említettünk. Két különböző névterületen létező, ugyanazon azonosítónak semmi köze nincs egymáshoz. A névterületeket majd egy későbbi fejezetben tárgyaljuk!

Lássunk példákat ezen közbevetés után!

int hetfo = 11; /* A hetfo egyszerű int típusú változó.*/
{ enum napok{
  vasar, hetfo, kedd, szerda, csut, pentek, szomb};
  /* A hetfo enumerátor ebben a blokkban elrejti a hetfo
    int típusú változót. */
  double csut; /* HIBÁS, mert ezen a névterületen van
                  már egy "csut" enumerátor.
  /* . . . */ }
hetfo+=2; /* OK, mert itt már csak a hetfo int
            változó létezik. */

Az enum napok deklarációban szereplő enum konstansok implicit módon kaptak értéket zérustól indulva és mindig eggyel növekedve. A vasar így 0, a hetfo 1, ..., és a pentek 6. Az enum konstansok azonban expliciten is inicializálhatók. Akár negatívak is lehetnek, ill. több enumerátor lehet azonos értékű is. Például:

enum ermek{ egyes=1, kettes, otos=5, tizes=2*otos,
            huszas=kettes*tizes, szazas=otos*huszas};

Vegyük észre, hogy az enumerátor definiált már az enumerátorlista következő tagjához érve!

Mondottuk, hogy a felsorolás típusok mindenütt feltűnhetnek, ahol egyébként az egész típusok megengedettek. Például:

enum napok{
  vasar, hetfo, kedd, szerda, csut, pentek, szomb};
enum napok fizetes_nap = szerda, nap;
int i = kedd; /* OK */
nap = hetfo;  /* OK */
hetfo = kedd; /* HIBÁS, mert a hetfo konstans. */

enum típusú változóhoz egész érték hozzárendelése megengedett, de explicit típusmódosító szerkezet használata javallott. Tehát a

fizetes_nap = 5;

helyett

fizetes_nap = (enum napok) 5;

írandó.

A fordítóban nincs mechanizmus a konvertálandó egész érték érvényességének ellenőrzésére! Pontosabban a következő lehetetlenség elkerülése a programozó felelőssége:

n = (napok) 958;

Az enum típus egész (integral) típus. Implicit módon konvertál így egésszé bármely enumerátort a fordító, de a másik irányban az explicit típusmódosítás javasolt:

int i;
enum napok n = szomb; /* OK */
i = n; /* OK */
n = 5; /* Nem javasolt! */
n = (napok) 5; /* OK */

Egész típusnak az egész értékek tárolására alkalmas, aritmetikai adattípusokat (char, short, int, long és enum) nevezzük.

Valós típusok és konstans

Az IEEE lebegőpontos belsőábrázolású valós alaptípusok a float és a double. Egyedül a long módosító használata megengedett, s az is csak a double előtt, azaz van még long double származtatott típus. A float típusú, egyszeres pontosságú lebegőpontos ábrázolás 4 bájtot foglal, melyből 8 bit a 128 többletes, bináris exponens, 1 bit a mantissza előjele (1 a negatív!) és 23 bit a mantissza. A mantissza 1.0 és 2.0 közötti szám. Miután a mantissza legnagyobb helyiértékű bitje mindig egy, az ábrázolás ezt nem tárolja.

előjeleltérített karakterisztikamantissza
31. bit30 - 23 bitek22 - 0 bitek

A double exponens 11-, s a mantissza 52 bites.

előjeleltérített karakterisztikamantissza
63. bit62 - 52 bitek51 - 0 bitek

A lebegőpontos belsőábrázolás méretei, megközelítő határai és decimális jegyben mért pontossága a következő táblázatban látható:

TípusMéret bájtbanHatárok:Pontosság:
float4 ± 3.4*10-38 - ± 3.4*10+386-7 decimális jegy
double8 ± 1.7*10-308 - ± 1.7*10+30815-16 decimális jegy
long double10 ± 1.2*10-4932 - ± 1.2*10+493219 decimális jegy

Ehhez már csak annyit kell hozzáfűzni, hogy lebegőpontos zérus az, amikor a belsőábrázolás minden bitje zérus.

A leírás az IEEE szabványos lebegőpontos ábrázolásról szól, de elképzelhető, hogy a konkrét fordító más formával dolgozik. A szabványos float.h fejfájlban azonban mindig megtalálhatók szimbolikus állandók (FLT_MIN, DBL_MAX stb.) alakjában az ábrázolási határok és más konkrétumok az aktuális belsőábrázolásról.

lebegőpontos-konstans:
  tört-konstans <exponens-rész> <float-utótag>
  számjegy-sor exponens-rész <float-utótag>

tört-konstans:
  <számjegy-sor> . számjegy-sor
  számjegy-sor .

exponens-rész:
  e <előjel> számjegy-sor
  E <előjel> számjegy-sor

előjel: (a következők egyike!)
  + -

számjegy-sor:
  számjegy
  számjegy-sor számjegy

float-utótag: (a következők egyike!)
  f l F L

  • A mantisszából elhagyható a decimális egész rész vagy a tört rész, de a kettő együtt nem.
  • Elhagyható a tizedes pont vagy az exponens rész, de mindkettő nem.
  • A lebegőpontos konstans előjeltelen (pozitív). A negatív lebegőpontos konstans előtt egy egyoperandusos mínusz operátor áll.
  • Float utótag nélkül a lebegőpontos konstans double belsőábrázolású. Az utótag megadásával kikényszeríthetjük a float (f vagy F), ill. a long double (l vagy L) belsőábrázolást.

double belsőábrázolású lebegőpontos konstansok:

-.5e35, 5., 5E-4, 3.4

Ugyanezek float és long double belsőábrázolásban:

-.5e35f, 5.L, 5E-4F, 3.4l

Az egyszeri programozó beírta a forrásszövegébe a

float v=143736120;
/* . . . */
printf("%9.0f\n", v);

sorokat, és meglepődött a 143736128-as eredményen. Ha v értékét eggyel csökkentette, akkor meg 143736112-őt kapott. Mi lehet a probléma?

A 143736120 (0X8913D38) binárisan

1000 1001 0001 0011 1101 0011 | 1000

de csak 24 bit fér el a belsőábrázolás mantisszájában. A fordító a legmagasabb helyi értékű, lecsorduló bit értékével megnöveli a mantisszát. Most, a karakterisztikával nem foglalkozva, az új érték:

1000 1001 0001 0011 1101 0100 | 0000

ami éppen 143736128 (0X8913D40).

A 143736119 (0X8913D37) binárisan

1000 1001 0001 0011 1101 0011 | 0111

esetében az eredmény

1000 1001 0001 0011 1101 0011 | 0000

lesz, ami143736112 (0X8913D30).

Komolyan kell tehát venni a lebegőpontos ábrázolások decimális jegyben mért pontosságát, és az ábrázolási határokat. Még két dologra feltétlenül oda kell figyelni:

  • A decimális véges tizedes tört többnyire nem véges bináris tört, azaz az átalakítás oda-vissza nem teljesen pontos.
  • Matematikai iterációt, ami addig tart, míg két, számolt, valós érték különbsége zérus nem lesz, nem szabad egy az egyben megvalósítani, mert az esetek többségében végtelen ciklushoz vezet.
Karakter típus és konstans

A karakter típusról már szó esett az egész típusok tárgyalásánál. Tudjuk, hogy három karakter típus van: char, signed char és unsigned char.

A karakter típusú objektum helyfoglalása 1 bájt mindenképp. A karakter konstans típusára és helyfoglalására rögtön kitérünk definíciója után!

karakter-konstans:
  'c-karakter-sor'

c-karakter-sor:
  c-karakter
  c-karakter-sor c-karakter

c-karakter:
  bármilyen karakter aposztróf ('), fordított per jel (\) és soremelés (\n) kivételével
  escape-szekvencia

SzekvenciaÉrtékKarakterFunkció
\00X00NULkarakterlánc vége
\a0X07BELfütty
\b0X08BSvisszatörlés
\t0X09HTvízszintes tab
\n0X0ALFsoremelés
\v0X0BVTfüggőleges tab
\f0X0CFFlapdobás
\r0X0DCRkocsi vissza
\"0X22"macskaköröm
\'0X27'aposztróf
\?0X3F?kérdőjel
\\0X5C\fordított per jel
\ooobármibármimax. 3 oktális számjegy
\xhhbármibármimax. 3 hexadec. Jegy
\Xhhbármibármimax. 3 hexadec. jegy

A definícióból következőleg a karakter konstans aposztrófok közt álló egy vagy több karakter, ill. escape szekvencia. Ezen az elven beszélhetünk egykarakteres és többkarakteres karakter konstansról.

  • A karakter konstans adattípusa mindenképpen int. Tudjuk, hogy az int helyfoglalása 2 vagy 4 bájt, így maximum négy karakteres karakter konstans létezhet. Például:
'An', '\n\r', 'Alfi', 'tag', '\007\007\007'

Tudjuk, hogy az int belsőábrázolású karakter konstans esetében is érvényben van a signed char alapértelmezés. Ennek következtében az int méreténél kevesebb karakterből álló karakter konstansok előjel kiterjesztéssel alakulnak int belsőábrázolásúvá. (Azaz az újonnan megjelenő, magas helyi értékeken álló biteket az eddigi legmagasabb helyi értéken álló, előjelet jelző bit értékével tölti fel a fordító.) Konkrét példaként tekintsük a 2 bájtos int-et, és a 852-es kódlapot! Az é betű kódja 130 (0X82) és az a betűé 97 (0X61). Az 'é'-ből így 0XFF82 és az 'a'-ból 0X0061 lesz a memóriában, s így igaz lesz a következő reláció:

'é' < 'a'

Ha unsigned char az alapértelmezés (ami viszont nem túl gyakori eset), akkor a felső bájt(ok)at bizonyosan 0X00-val tölti fel a fordító (az 'é'-ből 0X0082 lesz), és nem jön elő az előbb taglalt probléma.

A következő kis példa szemlélteti a tárgyalt karakter típus és karakter konstans méreteket!

#include <stdio.h>
#include <stdlib.h>
#define CH 'x' /* Egykarakteres karakter konstans. */
void main(void) {
  char ch = CH; /* Karakter típusú változó. */
  printf("Az int mérete :\t\t\t%d\n", sizeof(int));
  printf("A char mérete :\t\t\t%d\n", sizeof(char));
  printf("A karakter konstans mérete:\t %d\n", sizeof(CH));
  printf("A karakter változó mérete:\t%d\n", sizeof(ch)); }

Szólnunk kell még néhány szót a pontosítás kedvéért az escape szekvenciáról! A \ jelet követheti egy a táblázatban ismertetett karakter, vagy egy legfeljebb háromjegyű oktális szám, vagy x után hexadecimális számjegyek. Például a '\03' a Ctrl+C, a '\x3F' a ? karakter stb. Szóval az escape szekvencia leírhat vezérlő és "igazi" karaktereket egyaránt. Nézzük még a szabályokat!

  • Ha az escape szekvencia fordított per jelét nem legális karakter követi, akkor legfeljebb figyelmeztető üzenetet kapunk, és a fordító elhagyja a \ jelet, de megtartja az illegális karaktert. Például a \z-ből a z marad meg.
  • Az oktálisan vagy hexadecimálisan adott escape szekvencia végét az első nem oktális, ill. hexadecimális karakter is jelzi. Például a '\518'-ból két karakteres karakter konstans lesz, ahol a karakterek rendre a '\51' és a '8', azaz ')8'.
  • Vigyázzunk a nem egyértelmű megadásra is! Például a "\712 kőműves." karakterláncból látszik a programozó törekvése, azaz hogy a BEL karakter után a 12 kőműves szöveg következne. Ez azonban így szintaktikai hiba. A helyes eredményhez például "\7" "12 kőműves." módon juthatunk.

Az eddig tárgyalt adattípusokat összefoglaló névvel aritmetikai adattípusoknak is nevezhetjük. Az aritmetikai adattípusok "zavarba ejtő" bősége a nyelvben azonban nem arra szolgál, hogy a programozó

  • csak úgy "ukk-mukk-fukk" kiválasszon valamilyen adattípust adatai és (rész)eredményei tárolására, hanem arra, hogy
  • a számábrázolási korlátokat tekintetbe véve alaposan végiggondolja, hogy érték és pontosság vesztés nélkül milyen adattípust kell használnia az egyes adataihoz, (rész)eredményeihez. Esetleg milyen explicit konverziókat kell előírnia a részletszámítások végzése során a pontosság vesztés elkerüléséhez.
  • "Ökölszabályként" annyit mondhatunk, hogy egész értékek tárolásához leggyakrabban az int típust szokták választani, még akkor is, ha szükségtelenül sok tárhelyet igényel, mert a korszerű processzorok jellemzően ezzel végzik el leggyorsabban a műveleteket. Kivételt képez persze, ha a futási sebesség enyhe növekedése aránytalan memóriapazarlással járna, mint pl. egy nagy elemszámú tömb esetében. Valós számoknál többnyire a double szokta a jó megoldást jelenteni, de a float típussal a legtöbb hardver sokkal gyorsabban tud dolgozni, és csak feleannyi memóriát igényel, ezért teljesítményigényes, pontosságra kevésbé érzékeny alkalmazások (pl. játékprogramok) esetén gyakran választják.

Készítsen szorzat piramist! A piramis alsó sora 16 darab 100-tól 115-ig terjedő egész szám! A következő sorokat úgy kapjuk, hogy az alattuk levő sor elemeit páronként összeszorozzuk. A második sor így néz ki tehát: 100*101 = 10100, 102*103 = 10506, ..., 114*115 = 13110. A harmadik sorban a következők állnak: 10100*10506 = 106110600, 10920*11342 = 126854640, ..., 12656*13110 = 165920160, és így tovább. Figyeljünk arra, hogy az eredményül kapott számokat mindig a lehető legkisebb helyen tároljuk, de az értékeknek nem szabad csonkulniuk! (A megoldásnál feltételeztük, hogy az adott fordító 2 bájtos short és int, valamint 4 bájtos long típussal dolgozik; ettől eltérő esetben más eredményt adhat, és más típusokat is kellene alkalmazni.)

/* PELDA12.C: Szorzat piramis */
#include <stdio.h>
#define N 16
void main(void){
  int i;        /* Az also sor: 100-s nagysagrend. */
  char n[N]={100, 101, 102, 103, 104, 105, 106, 107,
            108, 109, 110, 111, 112, 113, 114, 115};
  short n1[N/2]; /* 2. sor: 10000-s nagysagrend. */
  long n2[N/4];  /* 3. sor: 100000000 kb. */
  long double n3[N/8]; /* 4. sor: 10 a 16-n kb. */
  printf("Szorzat piramis: elso szint\n");
  for(i=0; i<N; i=i+2){
    n1[i/2]=(short)n[i]*(short)n[i+1];
    printf("%3d*%3d = %5hd\n", n[i], n[i+1], n1[i/2]); }
  printf("Szorzat piramis: masodik szint\n");
  for(i=0; i<N/2; i=i+2){
    n2[i/2]=(long)n1[i]*(long)n1[i+1];
    printf("%5hd*%5hd = %9ld\n", n1[i], n1[i+1], n2[i/2]); }
  printf("Szorzat piramis: harmadik szint\n");
  for(i=0; i<N/4; i=i+2){
    n3[i/2]=(long double)n2[i]*(long double)n2[i+1];
    printf("%9ld*%9ld = %17.0Lf\n",
          n2[i], n2[i+1], n3[i/2]); }
  printf("Szorzat piramis: csucs\n");
  printf("%17.0Lf*%17.0Lf ~ %33.0Lf\n",
        n3[0], n3[1], n3[0]*n3[1]);}

Már a pelda7.c példában találkoztunk az egészekre vonatkozó h és l hosszmódosítókkal a formátumspecifikációkban. Most a long double típusú paraméter jelzésére való L hosszmódosítót ismerhettük meg.

Ha a szorzat piramis második szintjének kiszámítását végző

n2[i/2]=(long)n1[i]*(long)n1[i+1];

kifejezésből elhagyjuk a két, explicit (long) típusmódosítót, akkor a listarész a következőképp módosul:

Szorzat piramis: második szint
10100*10506 = 7816
10920*11342 = -8400
. . .

Ez vitathatatlanul rossz, de miért?

  • Az n1 tömb short típusú.
  • A kétoperandusos szorzási művelet operandusai azonos típusúak, tehát semmiféle implicit konverzió nem történik, és az eredmény típusa is short.
  • A 32 bites regiszterben a 10100*10506-os szorzás végrehajtása után ugyan ott van a helyes eredmény, a 106110600 (0X6531E88), de a 16 bites short típus miatt csak az alsó két bájt képezi az eredményt. Az meg 0X1E88, vagyis 7816.
  • Megemlítjük még, hogy a "csúcs" értékének (n3[0]*n3[1]) pontosság vesztés nélküli tárolásához még a long double típus is kevés!
Karakterlánc (string literal)

A karakterláncokról már esett szó korábban, de most megadjuk a formális definíciójukat is:

karakterlánc:
  "<s-karakter-sor>"

s-karakter-sor:
  s-karakter
  s-karakter-sor s-karakter

s-karakter:
  bármilyen karakter idézőjel ("), fordított per jel (\) és a soremelés (\n) kivételével
  escape-szekvencia

A karakterlánc adattípusa char tömb. A tömb mérete mindig eggyel hosszabb, mint ahány karakterből a karakterlánc áll, mert a nyelv a lánc végét '\0' karakterrel jelzi. A karakterlánc konstansokat mindig a statikus adatterületen helyezi el a fordító.

Például a "Jani" karakterlánc egymást követő, növekvő című memória bájtokon így helyezkedik el.

A karakterlánc karaktereiként használható az escape szekvencia is. Például a

"\t\t\"Név\"\\\tCím\n\n

karakterláncot printf függvény paramétereként megadva

        "Név"\ Cím

jelenik meg utána két soremeléssel.

A csak fehér karakterekkel elválasztott karakterlánc konstansokat a fordító elemzési fázisa alatt egyetlen karakterlánccá egyesíti:

"Egy " "kettő, " "három... " -> "Egy kettő, három..."

Tudjuk, hogy a karakterlánc konstans a folytatássor (\) jelet használva több sorba is írható:

printf("Ez igazából egyetlen \
karakterlánc lesz.\n");

Írjunk meg néhány karakterláncot kezelő függvényt! Legyen az első az unsigned strlen(char s[]), ami a paraméter karakterlánc hosszát szolgáltatja! Indítsunk egy változót zérustól, és indexeljünk előre vele a karakterláncon, míg a lánczáró nullát el nem érjük! Ez az index a karakterlánc hossza is egyben.

unsigned strlen(char s[]){
  unsigned i=0u;
  while(s[i]!='\0') ++i;
  return i; }

Valósítson meg a void strcopy(char cél[], char forrás[]) hozzárendelést a karakterláncok körében azzal, hogy a forrás karakterláncot átmásolja a cél karaktertömbbe! Indexeljünk most is végig egy változóval a forrás karakterláncon a lezáró zérusig! Közben minden indexre rendeljük hozzá a forrás tömbelemet a cél tömbelemhez! Vigyázzunk, hogy a lánczáró nulla is átkerüljön!

void strcopy(char cél[], char forrás[]){
  int i=0;
  while((cél[i]=forrás[i])!=0) ++i; }

Javítsunk kicsit ezen a megoldáson! A while-beli reláció elhagyható, hisz a hozzárendelés is mindig igaz (nem zérus), míg a forrás[i] nem lánczáró zérus. Belátható, hogy a '\0' karakter is átkerül, mert a while csak a hozzárendelés végrehajtása után veszi csak észre, hogy kifejezése hamissá (zérussá) vált. Tehát:

while(cél[i]=forrás[i]) ++i;

Lássuk be, hogy a

while(cél[i++]=forrás[i]);

is tökéletesen funkcionál, hisz

  • Előállítja a forrás tömb i-ik elemét.
  • Ezt hozzárendeli cél tömb i-ik eleméhez.
  • Az utótag ++ miatt mellékhatásként közben i értéke is nő egyet.

A legjobb megoldásunk tehát:

void strcopy(char cél[], char forrás[]){
  int i=0;
  while(cél[i++]=forrás[i]); }

Készítsünk void strct(char s1[], char s2[]) függvényt, ami egyesíti a két paraméter karakterláncot s1-be! Indexeljünk előre az s1-en a lánczáró nulláig, s ezután ide kell másolni az s2 karakterláncot! Belátható, hogy a helyes megoldáshoz két indexváltozó szükséges, mert a másolásnál az egyiknek s1 lánczáró nullájának indexétől kell indulnia, míg a másiknak s2-n zérustól.

void strct(char s1[], char s2[]){
  int i=0, j=0;
  while(s1[i]) ++i;
  while(s1[i++]=s2[j++]); }

Készítsünk void strup(char s[]) függvényt, ami nagybetűssé alakítja saját helyén az s karakterláncot! Indexeljünk végig az s karaktertömbön a lánczáró zérusig! Egy-egy pozíción meg kell vizsgálni a tömbelem értékét. Ha nem kisbetű, akkor nem kell tenni semmit. Ha kisbetű, át kell írni nagybetűvé. Ha csak az angol ábécé betűivel foglalkozunk, akkor meg kell határozni a kisbetű tömbelem eltolását 'a'-hoz képest, s ezt az eltolást hozzá kell adni 'A'-hoz.

void strup(char s[]){
  int i;
  for(i=0; s[i]; ++i)
    if(s[i]>='a'&&s[i]<='z')s[i]=s[i]-'a'+'A'; }

Feltétlenül meg kell említeni, hogy a most megírt karakterlánc kezelő függvények kicsit más névvel, de azonos funkcióval, és paraméterezéssel léteznek a szabvány könyvtárban. (Ez egyes fordítóprogramoknak gondot is okozhat. Ebben az esetben próbálkozzunk a függvények átnevezésével!) Használatukhoz azonban a string.h fejfájlt be kell kapcsolni. A névlista:

strlen -> strlen

strcopy -> strcpy

strct -> strcat

strup -> strupr

Próbáljuk ki a megírt függvényeinket egy rövid programmal! Kérjünk be egy sort a szabvány bemenetről! Alakítsuk át nagybetűssé! Tegyük elé az ELEJE:, és mögé a :VÉGE szöveget! Jelentessük meg az eredményt! Írjuk még ki az eredeti és az egyesített karakterlánc hosszát!

Az ismétlés elkerülése miatt most a függvények teste helyett csak /* ... */-eket közlünk. Működőképes programhoz persze oda kell másolni a függvénydefiníció forrásszövegét is.

/* PELDA13.C: Karakterlanc kezelese */
#include <stdio.h>
#define SOR 80  /* Bemeneti sor max. hossza. */
int getline(char s[],int n){/*...*/}
unsigned strlen(char s[]){/*...*/}
void strcopy(char cel[], char forras[]){/*...*/}
void strct(char s1[], char s2[]){/*...*/}
void strup(char s[]){/*...*/}
void main(void){
  int n;          /* A bemeneti sor hossza. */
  char sor[SOR+1], /* A bemeneti sor. */
      egy[SOR*2]; /* Az egyesitett karakterlanc. */
  printf("Adjon meg egy sort!\n"
        "Nagybetusse alakitva megjelentetjuk\n"
        "ELEJE: :VEGE szovegekbe zarva.\n"
        "Kozoljuk az eredeti és a vegso hosszt is.\n");
  n=getline(sor,SOR);
  strcopy(egy, "ELEJE:");
  strct(egy, sor);
  strup(egy);
  strct(egy, ":VEGE");
  printf("%s\nEredeti hossz:%3d\nMostani hossz:%3d\n",
        egy, n, strlen(egy)); }
Deklaráció

A deklaráció megteremti a kapcsolatot az azonosító és az objektum között, ill. rögzíti például többek között az objektum adattípusát. Kétféle deklarációról beszélhetünk:

  • A definíciós deklarációval (definíció) helyet foglaltatunk az objektumnak a memóriában, és esetleg kezdőértékkel is inicializáljuk. Ilyen deklaráció egy azonosítóra csak egyetlen egy létezhet az egész forrásprogramban.
  • A referencia deklaráció (deklaráció) nem foglal memóriát az objektumnak, de tudatja a fordítóval, hogy van egy ilyen azonosítójú, adattípusú stb. objektum a programban. Ebből a deklarációból - egymásnak ellent nem mondóan - több is létezhet ugyanarra az objektumra.
  • Szólnunk kell még deklarációs pontról! Ez az azonosító deklarációjának helye a forrásprogramban. Az azonosító deklarációs pontja előtt legálisan nem érhető el a forráskódban.

Tudjuk azt is, hogy a deklaráció elhelyezése és maga a deklaráció meghatározza az objektum attribútumait:

  • típus,
  • tárolási osztály,
  • hatáskör,
  • élettartam stb.
int x;
int x; /* OK legális, hisz nem mondtunk újat. */
int y;
int y = 4; /* OK. Most specifikáltuk, hogy y-t 4-re kell
              inicializálni. */
int z = 4;
int z = 6; /* HIBÁS, mert mindkettő inicializálna. */

A deklarálható objektumok a következők:

  • változók,
  • függvények,
  • típusok,
  • típusok tömbjei,
  • enum konstansok és címkék,
  • valamint néhány további objektum, melyekről későbbi leckékben esik majd szó (struktúra, unió és utasítás címkék, struktúra és uniótagok, valamint előfeldolgozó makrók).

A deklarátor szintaktika rekurzivitása miatt egészen komplex deklarátorok is megengedettek. Ezt el szokás kerülni típusdefinícióval (typedef). A deklaráció metanyelvi leírása a következő:

deklaráció:
  deklaráció-specifikátorok<init-deklarátorlista>

deklaráció-specifikátorok:
  tárolási-osztály-specifikátor<deklaráció-specifikátorok>
  típusspecifikátor<deklaráció-specifikátorok>

init-deklarátorlista:
  init-deklarátor
  init-deklarátorlista, init-deklarátor

init-deklarátor:
  deklarátor
  deklarátor=inicializátor

inicializátor:
  hozzárendelés-kifejezés
  {inicializátorlista}
  {inicializátorlista ,}

inicializátorlista:
  inicializátor
  inicializátorlista, inicializátor

tárolási-osztály-specifikátor:
  auto
  register
  extern
  static
  typedef

típusspecifikátor:
  void
  char
  short
  int
  long
  float
  double
  signed
  unsigned
  const
  volatile
  struktúra-vagy-unió-specifikátor
  enum-specifikátor
  typedef-név

typedef-név:
  azonosító

deklarátor:
  <mutató>direkt-deklarátor

direkt-deklarátor:
  azonosító
  (deklarátor)
  direkt-deklarátor [<konstans-kifejezés>]
  direkt-deklarátor (paraméter-típus-lista)
  direkt-deklarátor (<azonosítólista>)

konstans-kifejezés:
  feltételes-kifejezés

azonosítólista:
  azonosító
  azonosítólista, azonosító

A mutatókkal, a struktúrával, az unióval, a függvényekkel, az utasítás címkékkel és a makrókkal később foglalkozunk, így a vonatkozó metanyelvi leírások is ott lesznek találhatók.

A tárolási-osztály-specifikátorokat teljes részletességgel majd később taglaljuk, de közülük kettőről (auto és extern) már esett szó korábban. A következő pontban némi előzetes képet kapunk a typedef-ről is.

A típusspecifikátorokat alaptípusokra és típusmódosítókra bontottuk korábban. A típusmódosítók és alaptípusok megengedett, együttes használatát korábban tisztáztuk. A const és a volatile viszont bármely alaptípussal és típusmódosítóval együtt szinte korlátozás nélkül használható.

A deklarátor egyedi azonosítót határoz meg. Mikor az azonosító megjelenik egy vele egyező típusú kifejezésben, akkor a vele elnevezett objektum értékét eredményezi.

A konstans-kifejezés mindig fordítási időben is meghatározható konstans értékké értékelhető ki, azaz értéke bele kell, hogy férjen a típus ábrázolási határaiba. A konstans kifejezés bárhol alkalmazható, ahol konstans egyébként használható. A konstans kifejezés operandusai lehetnek egész konstansok, karakter konstansok, lebegőpontos konstansok, enumerátorok, típusmódosító szerkezetek, sizeof kifejezések stb. A konstans kifejezés azonban a sizeof operátor operandusától eltekintve nem tartalmazhatja a következő operátorok egyikét sem:

  • hozzárendelés,
  • vessző,
  • dekrementálás,
  • inkrementálás és
  • függvényhívás.
Elemi típusdefiníció (typedef)

Nem tartozik igazán ide a típusdefiníció tárgyalása, de miután a typedef kulcsszót a tárolási osztály specifikátor helyére kell írni, itt adunk róla egy rövid ismertetést.

A typedef kulcsszóval nem új adattípust, hanem új adattípus specifikátort definiálunk. Legyen szó például az

auto long int brigi;

definícióról, mely szerint a brigi egy 32 bites, signed long int típusú, lokális élettartamú objektum azonosítója. Használjuk most az auto kulcsszó helyett a typedef-et, és az azonosítót írjuk át nagybetűsre!

typedef long int BRIGI;

ahol a BRIGI azonosító nem képez futásidejű objektumot, hanem egy új típusspecifikátor csak. A programban ezután a BRIGI típusspecifikátorként alkalmazható deklarációkban. Például az

extern BRIGI fizetni;

ugyanolyan hatású, mint az

extern long int fizetni;

Ez az egyszerű példa megoldható lenne a

#define BRIGI long

módon is, a typedef-fel azonban az egyszerű szöveghelyettesítésnél komplexebb alkalmazások is megvalósíthatóak. A typedef nem hoz létre új típust tulajdonképpen, csak létező típusokra kreálható vele új kulcsszó. Komplexebb deklarációk egyszerűsítésére való.

A típusdefiníció "megbonyolítására" a későbbiekben még visszatérünk! Meg kell jegyeznünk azonban annyit, hogy a typedef-fel létrehozott típusspecifikátor nem használható a deklarációban más típusspecifikátorokkal együtt! Legfeljebb a const és a volatile módosítók alkalmazhatók rá! Például

unsigned BRIGI keresni; /* HIBÁS. */
const BRIGI kaba = 2; /* OK */
Feladatok
1. Jelölje meg, hogy mely azonosítók helyesek, és melyek nem!
6os_villamos
Nagy_Jani
Moszer Aranka
Nagy_János
puffer

2. Olyan függvényt szerettünk volna írni, ami igaz értéket szolgáltat, ha a paraméter karakterlánc SS:SS:SS formátumú, ahol S egy tetszőleges számjegy, egyébként hamisat. Sajnos nem sikerült.

/*1*/int idoe(char s[]) {
/*2*/  int i;
/*3*/  for(i=0; i<9; i++) {
/*4*/    if(i==2 || i==5) { if(s[i]!=':') return 0; }
/*5*/    else if(i==8 && s[i]!='\0') return 0;
/*6*/    else if(s[i]<'0' || s[i]>'9') return 0; }
/*7*/  return 1; }
Jelölje meg azokat a sorokat, amikkel a függvény helyes működésre bírható!
/*3*/  for(i=0; i<8; i++) {
/*5*/    else if(i==8) { if(s[i]!='\0') return 0; }
/*6*/    else if(s[i]<0 || s[i]>9) return 0; }
/*7*/    else return 1; }

3. Olyan függvényt szerettünk volna írni, ami visszaadja a SS:SS:SS formátumú karakterlánc adott indexű helyén kezdődő, két számjegyű szám értékét, de sajnos nem sikerült.

/*1*/int ertek(char s[], int i) {
/*2*/  int magas, alacsony;
/*3*/  magas = s[++i] - '0';
/*4*/  alacsony = s[i] - '0';
/*5*/  return magas*10 + alacsony; }
Jelölje meg azokat a sorokat, amikkel a függvény helyes működésre bírható!
/*2*/  int magas=0, alacsony=0;
/*3*/  magas = s[i++] - '0';
/*5*/  return magas + alacsony*10; }

4. El szerettük volna készíteni azt a függvényt, ami kisbetűssé alakítja saját helyén az s karakterláncot, de sajnos nem sikerült.

/*1*/void strlw(char s[]) {
/*2*/  int i;
/*3*/  while(s[i++])
/*4*/    if(s[i]>='A' && s[i]<='Z') s[i] = s[i]-'a'+'A'; }
Jelölje meg azokat a sorokat, amikkel a függvény helyes működésre bírható!
/*2*/  int i=0;
/*3*/  while(s[++i])
/*3*/  while(s[i++]);
/*4*/    if(s[i]>='A' || s[i]<='Z') s[i] = s[i]-'A'+'a'; }
/*4*/    if(s[i]>='A' && s[i]<='Z') s[i] = s[i]-'A'+'a'; }
/*4*/    if(s[i]>='a' && s[i]<='z') s[i] = s[i]-'A'+'a'; }

5. El szerettük volna készíteni azt a függvényt, ami kitörli az s karakterláncból a nem kívánatos c betűket, de sajnos nem sikerült.

/*1*/void chdel(char s[], int c){
/*2*/  int i,j;
/*3*/  for(i=j=0; s[i]!='\0'; i++)
/*4*/    if(s[i]=c) s[j++]=s[i];
/*5*/  s[j]='\0'; }
Jelölje meg azokat a sorokat, amikkel a függvény helyes működésre bírható!
/*3*/  for(i=j=0; s[i++]!='\0'; )
/*3*/  for(i=j=0; s[i]!='\0'; j++)
/*3*/  for(i=0; s[i]!='\0'; i++)
/*4*/    if(s[i]!=c) s[j++]=s[i];
/*4*/    if(s[i]==c) s[j++]=s[i];
/*4*/    if(s[i]!=c) s[++j]=s[i];

6. Készítsen olyan függvényt, ami képes a paraméterként adott karakterlánc betűinek sorrendjét a saját helyén megfordítani (pl. mozi -> izom)! Készítsen tesztprogramot is a függvény kipróbálásához!

#define MAX 128
#include <stdio.h>
#include <string.h>
void reverse(char s[]){
  int i,j;
  char c;
  for(i=0, j=strlen(s)-1; i<j; ++i, --j){
    c=s[i]; s[i]=s[j]; s[j]=c; } }
int getline(char s[], int lim){
  int c,i;
  for(i=0; i<lim && (c=getchar())!=EOF && c!='\n'; ++i) s[i]=c;
  s[i]='\0';
  while(c!=EOF && c!='\n') c=getchar();
  return(i); }
void main(void){
  char szo[MAX+1];
  printf("Adja meg a megforditando szot!\n");
  while(!getline(szo,MAX));
  printf("Eredeti:\n%s\n\n", szo);
  reverse(szo);
  printf("Megforditott:\n%s\n", szo); }

7. Olyan programot szerettünk volna írni, ami képes egy öt elemű, egészekből álló tömb elemeit egymástól vesszővel elválasztva kiírni, az utolsó elem után pedig pontot tenni. Sajnos ez nem sikerült.

/*01*/#include <stdio.h>
/*02*/void nyomtat(int tomb[], int n) {
/*03*/  int i;
/*04*/  for(i=0; i<n; i++) {
/*05*/    printf("%d", tomb[i]);
/*06*/    if(i==n-1) printf(".\n");
/*07*/    else printf(','); } }
/*08*/void main(void){
/*09*/  int szamok[] = {1, 23, 4, 9, -16};
/*10*/  nyomtat(szamok, sizeof(szamok)); }
Jelölje meg azokat a sorokat, amelyek felhasználásával a program működőképessé tehető!
/*02*/void nyomtat(int szamok[], int n) {
/*06*/    if(i==n) printf(".\n");
/*06*/    if(i<n-1) printf(".\n");
/*07*/    else printf(","); } }
/*09*/  int szamok[5] = {1, 23, 4, 9, -16};
/*10*/  nyomtat(szamok, sizeof(szamok)/sizeof(int)); }

8. Olyan programot szerettünk volna írni, ami képes egy öt elemű, egészekből álló tömb elemeit egymástól vesszővel elválasztva fordított sorrendben kiírni, az utolsó elem után pedig pontot tenni. Sajnos ez nem sikerült.

/*01*/#include <stdio.h>
/*02*/void nyomtat(int tomb[], int n) {
/*03*/  int i;
/*04*/  for(i=n; i>=0; i--) {
/*05*/    printf("%d", tomb[i]);
/*06*/    if(!i) printf(".\n");
/*07*/    else printf(","); } }
/*08*/void main(void){
/*09*/  int szamok[] = {1, 23, 4, 9, -16};
/*10*/  nyomtat(sizeof(szamok)/sizeof(int), szamok); }
Jelölje meg azokat a sorokat, amelyek felhasználásával a program működőképessé tehető!
/*04*/  for(i=--n; i>=0; i--) {
/*04*/  for(i=n-1; i>=0; i--) {
/*04*/  for(i=n-1; i>0; i--) {
/*06*/    if(i) printf(".\n");
/*10*/  nyomtat(szamok, sizeof(szamok)/sizeof(int)); }

9. Olyan programot szerettünk volna írni, ami képes egy öt elemű, egészekből álló tömb elemeit összeadni, majd a szabvány kimeneten megjeleníteni. Sajnos ez nem sikerült.

/*1*/#include <stdio.h>
/*2*/void main(void){
/*3*/  unsigned int szamok[] = {1, 23, 4, 9, -16}, i;
/*4*/  signed long osszeg = 0l;
/*5*/  for(i=0; i<5; i++)
/*6*/    osszeg = osszeg + szamok[i];
/*7*/  printf("%ld\n", osszeg); }
Jelölje meg azokat a sorokat, amelyek felhasználásával a program működőképessé tehető!
/*3*/  signed int szamok[] = {1, 23, 4, 9, -16}, i;
/*3*/  int szamok[] = {1, 23, 4, 9, -16}, i;
/*3*/  char szamok[] = {1, 23, 4, 9, -16}, i;
/*4*/  long osszeg = 0;
/*7*/  printf("%hd\n", osszeg); }
/*7*/  printf("%d\n", osszeg); }