A C nyelvű program
C történelem¶
A UNIX fejlesztése 1969 körül zajlott az AT&T Bell Laboratóriumában. Az első változat Assembly nyelven DEC PDP-7 gépen készült. Mivel assembly nyelven nehézkes a fejlesztés, ezért a rendszer sok hibát tartalmazott. Éppen ezért kellett egy olyan új nyelv, ami egyszerre hardverközeli és magasszintű.
A Martin Richards által kifejlesztett BCPL nyelv lett az alapja annak a B nyelvnek, amit Ken Thomson fejlesztett ki 1970 körül a DEC PDP-7 számítógép UNIX rendszeréhez. Mind a BCPL, mind a B nyelv típus nélküliek, viszont a B-ből kifejlesztett C nyelv, amit 1973 körül fejleszti ki Ken Thompson és Dennis Ritchie, már típusokat tartalmaz (karaktereket, valamint különböző méretű egész és lebegőpontos számokat).
Mivel a UNIX rendszerhez egyre kevésbé volt alkalmas a B nyelv, így azt portolták C nyelvre. Mi sem bizonyítja jobban a UNIX és C összefonódását, hogy van olyan parancsértelmező is (csh), amely parancsértelmezőnek a szintaktikája a C-re hasonlít, illetve ezen rendszerek manuáljai is támogatják a C-hez közeli programok, fordítók alkalmazását, valamint a C függvény könyvtárakban található elemek megismerését.
A C nyelv is számos változáson ment keresztül az idők során, amelyet a különböző szabványokban rögzítenek is:
- 1978: Brian Kernighan és Dennis Ritchie – „K&R C” kvázi standard
- 1989-90: ANSI C, C89, ISO/IEC 9899:1990, C90
- American National Standards Institute elkészíti a C nyelv szabványát (ekkor már meg volt az ANSI C++ szabvány). Cél, hogy A K&R C-ből és az eddig nem hivatalos bővítésekből egy egységes rendszert alakítsanak ki. Az ANSI C-t szinte minden fordító támogatja ezután.
- International Organization for Standardization átveszi az ANSI-t kisebb módosításokkal.
- International Electrotechnical Commission
- 1999: ISO/IEC 9899:1999, \(C^{99}\) ( \(C^{99}\)) - A szabványosítás után a C relatív stabil maradt, a C++ viszont folyamatosan fejlődött, így az 1999-es szabványosítás célja, hogy behozzon pár olyan újítást a C nyelvbe is, ami használhatóbbá tette a C++-t.
- 2011: ISO/IEC 9899:2011, \(C^{11}\) ( \(C^{11}\) ) - Újabb lehetőségek hozzáadása a C-s könyvtárakhoz.
- 2018: ISO/IEC 9899:2018, \(C^{18}\) ( \(C^{18}\) ) - Nem vezetett be új elemeket, csak technikai pontosításokat tartalmaz az előző standardhoz képest.
A C nyelv népszerűségének oka, hogy magasszintű konstrukciói vannak, azaz olvasható és értelmezhető a kód a kezdők számára is, ugyanakkor olyan dolgokat is meg lehet benne valósítani, ami alacsony szintű ismereteket is igényel, ehhez persze már több ismeret szükséges. Ezekkel viszont igen hatékony kódokat lehet készíteni, nem is véletlen, hogy a számítógépek sok változatán van ezért C fordító.
A C nyelv tulajdonságai¶
A C egy nagyon kicsi nyelv. Szintaxisa a K&R-ben csupán néhány oldal. Strukturált nyelv, aminek lényege, hogy az elvégzendő feladatokat kisebb, egymáshoz meghatározott módon kapcsolódó részfeladatokra kell bontani. (Ld. vezérlési szerkezetek.)
Mivel szabványos nyelv, így a különböző plattformokon levő fordítói a szabvány miatt (a szabvány erejéig) egységesek, így lehetőség van a kód hordozhatóságára forráskód szinten.
Bár maga a nyelv kicsi, függvénykönyvtáraival sok minden megoldható. Ilyen a sztringek kezelése, input/output megvalósítása vagy a számtalan matematikai könyvtár jelenléte.
Utasításai között találunk proprocesszor utasításokat (ezek gyakorlatilag hasonlítanak az assembly nyelvek makróihoz), amik a fordítás első lépésében normál C utasításokká fordulnak le. Vannak olyan utasításai a nyelvnek, amik az adatok állapotát meghatározó deklaráló és aritmetikai utasítások, illetve vannak a végrehajtást befolyásoló vezérlő utasítások. A kód újrafelhasználhatóságában nagy szerepe van a függvényeknek.
C fejlesztő környezetek¶
Ahhoz, hogy megírjunk egy C programot, nem kell egyéb, mint egy szövegszerkesztő, amiben megírjuk a kódot. Ez lehet a vi, mcedit, gedit, kate, geany, ... A lényeg, hogy a kódot sima szöveges (text) formátumban mentse ki. Ahhoz, hogy ezt a programot a számítógép megértse szükséges, hogy lefordítsuk azt. Erre használhatjuk a gcc fordítót, ami a GNU Compiler Collection része (a továbbiakban a gcc lesz az általunk preferált fordító), de hasonlóan jó választás lehet erre a clang fordító C frontendje, ami az LLVM Compiler Infrastructure része.
A lefordított binárist futtathatjuk, ha nem megfelelően működik, akkor hibákat kereshetünk benne, vagy esetleg mérhetjük a teljesítményét futás közben.
A fejlesztést nagyban segíthetik, ha a fejlesztés során integrált fejlesztő környezetet (Integrated Developement Environment - IDE) használunk, amik egyben tartalmazzák a szövegszerkesztőt, a fordítót, és alkalmasak arra, hogy az elkészült binárist futtassuk bennük, vagy hibát keressünk benne. Az anjuta, eclipse, dev c++, Codeblocks, Codelite vagy a Microsoft Visual Studio alkalmas IDE-k C nyelvű programok fejlesztéséhez.
Minimális C program¶
A legegyszerűbb, futtatható C program csupán ennyi:
1 |
|
Ez a program nem csinál semmit. Vagyis de: amikor futtatjuk, vagyis az operációs rendszert felszólítjuk, hogy hajtsa végre ezt a programot, akkor az átadja a vezérlést ennek a programnak, illetve a program main függvénynek, ami utasítást nem tartalmaz, így rögtön vissza is tér, és visszaadja a vezérlést az operációs rendszer számára.
Lássunk egy olyan programot, aminek azért már szemmel látható hatása is lesz! A következő program a parancssorra kiírja a "Hello World!" szöveget:
1 2 3 4 5 6 7 |
|
Kimenet
Hello World!
Részletesebben nézzük meg, milyen elemei vannak ennek a programnak!
Ahogy az már az első példában is látszódott, egy futtatható program mindig kell, hogy tartalmazzon egy (és pontosan egy) main
metódust.
Ez lesz a program belépési pontja, ennek a függvénynek az utasításait kell végrehajtani.
Az első példához képest megfigyelhetjük, hogy itt a main
elé került egy int
tag, ez arra utal, hogy a függvény visszatérési értékének típusa int, azaz egész.
Bizonyos esetekben ezt nem kötelező kiírni (mármint az int
tagot, ezért maradhatott el az első esetben).
A függvény értéke annak a kifejezésnek az értéke, amely azon return
utasítást követ, amelyen keresztül az adott függvény végrehajtását befejeztük.
A main
függvény visszatérési értéke egy információ az operációs rendszer számára.
Amennyiben 0 a visszatérési érték (mint ebben a példában is), az azt jelenti, hogy minden a legnagyobb rendben történt, a program hiba nélkül lefutott.
Ha az érték 0-tól különböző, akkor az valami hibára utal, a visszatérési érték pedig utalhat a hiba típusára. (Érdekes kérdés, hogy vajon baj-e, ha elmarad a return
utasítás? Vajon az operációs rendszer hogy kapja meg ezt az értéket, illetve milyen értéket kap ilyen esetben, például az előző minimális C programnál?)
A program fő funkciója a szöveg kiíratása.
Ezt a printf
függvény végzi, ami kiírató utasítás NEM része az alap C nyelvnek, ez a kapcsolódó függvénykönyvtárak biztosítják.
A függvénykönyvtárak header állományai azok, amelyek deklarálják, hogy mely függvények érhetőek el bennük.
A printf
függvényt az stdio.h
headerben találjuk.
Éppen ezért, hogy aktuálisan ezt használhassuk is, az #include <stdio.h>
utasítással elérjük, hogy a fordító tudja, hogy valahol létezik egy ilyen nevű függvény.
Minden olyan sor, ami #
karakterrel kezdődik, a preprocesszornak szól, később részletesen látjuk, mi is történik ezekben a sorokban.
Programunk elején még van egy sor, amiről eddig nem tettünk említést. Általában igaz az, hogy a programjainkat érdemes kommenttel ellátni, ami arra szolgál, hogy a programkód böngészése nélkül is tudja egy idegen fejlesztő, hogy mit is csinál az adott program, függvény, esetleg csak egyszerűbb utasításrészlet. Ezek a komment sorok a C fordító számára láthatatlanok (szó szerint láthatatlanok, erről az előbb már említett preprocesszor gondoskodik).
C program fordítása linux alatt¶
Tegyük fel, hogy megírtuk a programunkat, és azt program.c néven elmentettük! C nyelvű programok esetében a programot definiáló részeket mindig ilyen c kiterjesztésű állományokba mentjük, azok a részek, amelyek csak deklarációkat tartalmaznak, h kiterjesztésű, úgynevezett header állományokba kerülnek majd.
Szóval adott a program.c, ezt a következő módon fordíthatjuk: gcc program.c
Mivel a programunk egy helyesen megírt C program, így a fordítás hatására elkészül a program binárisa, ami az a.out
nevet viseli.
Ezt a ./a.out
utasítással tudjuk futtatni.
Ha nem akarjuk, hogy minden programunk ezt a nevet viselje, és nem is akarunk azzal foglalkozni, hogy ezt az a.out
állományt átnevezgetjük utólag, használjuk a gcc program -o
kapcsolóját, amely után közvetlen megadhatjuk azt a nevet, amit szeretnénk, ha az elkészült állomány megkapna.
Azaz a gcc -o program program.c
hatására a lefordított programunk a program
nevet kapja, amit a ./program
paranccsal futtathatunk.
Ebben a példában nem volt arra szükség, hogy kérjük a fordítót a használt függvénykönyvtár programunkhoz való hozzászerkesztéséhez, mivel az stdio könyvtárat alapból hozzászerkeszti a programhoz.
Azonban elképzelhető, hogy olyan, pl. a matematikai könyvtárat akarjuk használni, aminél ezt jelezni kell a fordítónak. Ekkor egy plusz kapcsolót kell adnunk a fordításhoz: gcc -o program program.c -lm
Ha szeretnénk, hogy a fordító a kódunkban esetleg problémásabb helyeket kiemelje, figyelmeztessen, hogy adott ponton esetleg a kódunk hibát tartalmazhat, nem árt, ha a fordítást egy újabb kapcsolóval egészítjük ki: gcc -Wall -o program program.c
(A Wall az all warning kifejezésre utal)
Amikor lefordítjuk a programot, akkor látszólag annyi történik, hogy a forráskódból bináris állomány lesz. Azonban ez a lépés 4 elemi lépésből épül fel, amelyek bármelyikénél megszakadhat, megszakítható a folyamat, és bármelyik állapotból indítható a fordítás befejezése. Ezek a lépések a következők:
- preprocessing – előfeldolgozás
- compilation – fordítás (assembly nyelvre)
- assembly – fordítás (gépi kódra)
- linking – szerkesztés, a futtatható állomány előállítása (ehhez szükséges, hogy az összeszerkesztett object állományok egyikében legyen pontosan egy
main
függvény, ami a program belépési pontja lesz.)
A fordítás közben többféle fájllal dolgozunk. A szabványos végződések:
Fájl neve | Tartalma |
---|---|
file.c | C source file – C forrásfájl |
file.h | C header file – C fejléc fájl |
file.i | preprocessed C file – előfeldolgozott C fájl |
file.s | assembly source file – assembly nyelvű forrásfájl |
file.o | object file – gépi kódú fájl |
a.out | linked executable – szerkesztett futtatható fájl |
A fájl végződése utal a programozási nyelvre és arra, hogy mit kell vele csinálni.
Ahogy az ábra is mutatja, minden korábbi fázisból, illetve annak eredményéből el lehet érni egy adott pontot, egyszerűen ehhez a megfelelő kapcsolót kell használni.
A preprocesszált eredményhez a -E
, az assembly forráshoz a -S
, a gépi kódú fájlhoz a -c
(vigyázat, nem -o
) kapcsolót kell használni.
Mivel a preprocesszálás eredménye alap esetben a standard outputra kerül, így ha annak tartalmát meg szeretnénk őrizni, az eredményt át kell irányítsuk a megfelelő fájlba (ez történik a >
által).
Természetesen ha bármelyik fázisban hiba van, a fordítás leáll.
Érdekes C programok¶
Bár a kurzusnak nem célja, hogy ilyeneket írjunk, de érdekességképp jöjjön néhány program, amik bemutatják a C programok extrém eseteit. Az első példában már a kód külalakja felhívja a figyelmet arra, mit is csinál a program. Ha valaki nem látná bele, azok számára a kód alakja a Földre utal.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
(Copyright (c) 1992, Landon Curt Noll & Larry Bassel. All Rights Reserved. Permission for personal, educational or non-profit use is granted provided that this copyright and notice are included in its entirety and remains unaltered. All other uses must receive prior permission in writing from both Landon Curt Noll and Larry Bassel.)
Fordítsuk le a programot a gcc -o where where.c
paranccsal, majd futtassuk a ./where 46 20
paraméterezéssel! Fontos, hogy a terminál alap szélességén ne változtassunk ahhoz, hogy a megfelelő kimenetet kapjuk!
Hasonló program a következő.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
(Copyright (c) 2006, a1k0n.net, https://www.a1k0n.net/2006/09/15/obfuscated-c-donut.html)
Mivel itt matematikai függvényeket is használ a kód, a matekos könyvtárat is hozzá kell szerkeszteni a programhoz, ezért így fordítsuk: gcc -o donut donut.c -lm
, majd szimplán a ./donut
paranccsal futtathatjuk is a programot!
Aki azért egy kicsit közelebb szeretne kerülni ahhoz, hogyan is lehet hasonlóan összetett programot produkálni, annak gyakorlás gyanánt ezt a feladatot ajánljuk: https://www.spoj.com/problems/KAMIL/ Ebben a feladatban egy egyszerű kis algoritmust kell leimplementálni. Ami nehézzé teszi a feladatot az az, hogy ezt a lehető legkevesebb karakter felhasználásával kell elérni.
Szintaxis¶
Amikor meg akarunk határozni egy nyelvet (legyen az természetes nyelv, vagy programozási), akkor egy adott véges jelhalmaz (ábécé) segítségével meghatározott formai szabályokat kielégítő véges jelsorozatokat képzünk, amelyhez értelmet is rendelünk. Ezek a jelsorozatok alkotják meg a kommunikáció nyelvét.
Formai szabályok olyan rendszerét, amely meghatározza, hogy egy adott kommunikációs nyelvben melyek a szabályos jelsorozatok, a nyelv szintaxisának nevezzük. Egy nyelv szemantikája pedig azt határozza meg, hogy a szintaktikusan helyes jelsorozatok mit jelentenek. A nyelvet a szintaxis és a szemantika együttesen határozzák meg.
A szintaxis megadására számos módszer lehetséges. Mi a továbbiakban az úgynevezett szintaxis diagramokat fogjuk használni. Ebben minden szintaktikus egység egyedi elnevezést kap és a szintaktikus egységhez tartozó szabályos jelsorozatokat egy diagram (ábra) definiálja. Ezek az ábrák egységesen úgy néznek ki, hogy a szintaktikus egységek egy-egy dobozban (téglalapban) vannak, és a konkrét jelsorozatokat az irányított nyilakon való bejárás által kaphatjuk meg azáltal, hogy elkezdjük a diagramot bejárni a bemenettől annak kimenetéig. Valahányszor érintünk egy egységet, leírjuk az általa meghatározott jelsorozatok egy elemét.
Példaként nézzük meg, hogy az Azonosítót, mint szintaktikus egységet hogyan definiáljuk:
Azonosító |
---|
Ha elindulunk a bemeneti nyíltól, akkor választhatunk, hogy az azonosító első karaktere egy Betű vagy az "_" lehet, ezt az utat folytatva viszont tetszőleges Betű, vagy Számjegy, vagy "_" következhet az azonosítón belül (azaz számjegy nem lehet az azonosító kezdő karaktere). Az ábráról látszik, hogy mind a Betű, mind a Számjegy további szintaktikus egységek (hiszen téglalapban vannak), így ezeket definiálni kell még.
Betű |
---|
Számjegy |
---|
Talán nem nagy meglepetésünkre a számjegyek a tízes számrendszerben használt számjegyekből állnak, míg a betűk között az angol ábécé betűivel találkozhatunk (azaz egy C azonosító NEM tartalmazhat ékezetes betűket!!!).
A C nyelv elemi adattípusai¶
Ahhoz, hogy komolyabb C programokat is tudjunk írni szükséges, hogy legyenek adataink, amelyeken műveleteket tudunk végezni. Ezeknek az adatoknak pedig típussal kell rendelkezniük. Látni fogjuk majd, hogy az egyszerű adattípusokból hogyan tudunk összetett adattípusokat létrehozni, de hogy addig eljussunk, meg kell ismerjünk még pár dolgot. Magukkal az elemi adattípusokkal is részletesen fogunk foglalkozni még, de ahhoz, hogy addig is használni tudjuk őket, tartsunk egy kis összefoglalót róluk!
Adattípus megnevezése | C megnevezés | Leírása |
---|---|---|
karakter | char |
Egy karakter (betű, szám, írásjel, . . . ) ábrázolására való. |
egész | short int , int , long int |
Egész számok ábrázolására való típusok, értékkészletükben különböznek. |
valós | float , double |
Valós számok ábrázolására való típusok, értékkészletükben, pontosságukban különböznek. |
logikai | Bool |
C nyelvnek \(C^{99}\) előtt nem volt része a logikai _Bool adattípus, de logikai értéket adó műveletek már akkor is voltak. A \(C^{99}\) szabvány óta létező stdbool.h header fájl pedig tartalmazza a true és false értékekkel rendelkező bool típus definícióját is. |
Az elemi adattípusokon végezhető műveletek a matematikában megismert műveletekkel azonosak, így elsajátításuk egyszerű:
Adattípus | Műveletek |
---|---|
egész és karakter | aritmetikai műveletek: - előjelváltás, + összeadás, - kivonás, * szorzás, / egész osztás, % maradékos osztásrelációs műveletek == egyenlő, != nem egyenlő, < kisebb, > nagyobb, <= kisebb vagy egyenlő, >= nagyobb vagy egyelő |
valós | aritmetikai műveletek: - előjelváltás, + összeadás, - kivonás, * szorzás, / osztásrelációs műveletek == egyenlő, != nem egyenlő, < kisebb, > nagyobb, <= kisebb vagy egyenlő, >= nagyobb vagy egyelő |
logikai | logikai műveletek: ! tagadás, negáció, && logikai és, || logikai vagyrelációs műveletek: == azonosság, != kizáró vagy, < inverz implikáció tagadása, > implikáció tagadása, <= implikáció, >= inverz implikáció |
Az egész adattípus műveleteire teljesülnek az aritmetika ismert azonosságai, feltéve, hogy a művelet eredménye az adattípus értékhalmazába esik. Fontos, hogy a valós számok relációs műveleteinél az egyenlő, nem egyenlő műveletekkel óvatosan bánjunk! Látni fogjuk, hogy ez sokszor vihet minket hamis eredményhez a számábrázolás pontatlanságai miatt. Valós számok esetében ha egy-egy művelet eredménye az adott adattípus értékhalmazába is esik, az aritmetika ismert azonosságai csak adott pontossággal teljesülnek.
Numerikus adattípusok¶
Az int
, float
és double
adattípusokat összefoglalóan numerikus adattípusoknak nevezzük.
A numerikus adattípusok értékeinek leírására számleírást, röviden számokat használunk. A számokat a programjainkon belül szabvány szerint megadhatjuk nyolcas, tízes vagy tizenhatos számrendszerbeli leírással. (Bár a gcc ismeri a bináris számleírást is, mivel az nem szabványos, így használata nem javasolt.) A valós számok leírására tizedes törteket használhatunk, tízes exponenssel kiegészítve a számok normál alakkal való ábrázolásához.
Ahhoz, hogy a számleírás módja egyértelmű legyen, megadjuk azokat a szintaxis leírásokat, amelyek definiálják a nyelv szerint használatos numerikus számleírásokat.
Szám |
---|
Előjeltelen szám |
---|
Azaz külön definiálni kell az előjeltelen egész és valós számok szintaktikáját.
Előjeltelen egész |
---|
Előjeltelen valós |
---|
Mivel a Számjegy szintaktikai egységet már definiáltuk, így már csak a következőket kell definiálni:
Nem 0 számjegy |
---|
Oktális számjegy |
---|
Hexadecimális számjegy |
---|
Azaz látható, hogy egyértelmű lesz, mikor lesz egy szám decimális, vagy oktális, vagy hexadecimális alakban: ha a szám a 0 számjeggyel kezdődik, az oktálisan adott szám, ha a 0x karakterekkel, akkor hexadecimális, egyébként pedig decimális.
A leírásban talán kicsit furcsa lehet a előjeltelen egész számok után opcionálisan megadható l, ll, L, LL és u, U betűk. Ezek gyakorlatilag típus módosítók lesznek, amiből egy konstans számról is eldönthető, hogy az signed, vagy unsigned, esetleg long, vagy long long értelmezési tartománnyal bíró egész típusok. Hasonló jelentéssel bír az előjeltelen valós után levő l vagy f betű, amely meghatározza a valós szám értelmezési tartományát és pontosságát.
Kifejezések¶
Ha már vannak változóink, illetve különböző adataink, akkor ezek összetételével kifejezéseket hozhatunk létre. A kifejezésen olyan programkomponenst értünk, amely egy adattípus értékének olyan jelölése, amely műveleteket is tartalmazhat. A kifejezés által jelölt értéket a kifejezés kiértékelése határozza meg.
Egy kifejezés nagyon egyszerűen fogalmazva a következő lehet:
Kifejezés |
---|
Ezt tovább felbontva rekurzívan:
Numerikus kifejezés |
---|
Numerikus tag |
---|
Numerikus tényező |
---|
Logikai kifejezés |
---|
Logikai tag |
---|
Relációs kifejezés |
---|
(a még nem definiált elemekkel később találkozunk majd)
Egy kifejezés kiértékelését két előírás együttesen határozza meg:
- a kifejezés szerkezetén alapuló prioritási előírás, és
- a haladás irányára vonatkozó asszociativitási előírás
A műveletek között definiálva van egy erősségi sorrend, az úgynevezett prioritás. Talán az még mindenki számára ismerősen cseng korábbi tanulmányaiból, hogy a szorzás erősebb prioritású művelet, mint az összeadás, azaz előbb el kell végezni minden esetben a szorzásokat, és csak utána jönnek az összeadások. Ha megfigyeljük, a numerikus kifejezések szintaktikai definíciójában is előjön ez a szabály azzal, hogy a numerikus tag része a numerikus tényező. Általánosan is megfogalmazhatjuk ezt. Ha adott egy A⊕B⊗C alakú kifejezés, ahol a ⊕ művelet alacsonyabb prioritású, mint a ⊗ művelet, akkor az egész kifejezés a A⊕(B⊗C) zárójelezésnek megfelelően értékelődik ki.
Példa:
1 2 |
|
Azonos prioritású műveletek esetén a kiértékelést az asszociativitás iránya szerint kell elvégezni. Ez azt jelenti, hogy egy A⊕B⊗C alakú kifejezés esetén ha a ⊕ művelet ugyanolyan prioritású, mint a ⊗ művelet, akkor
- balról jobbra való asszociativitás esetén (A⊕B)⊗C ,
- jobbról balra való asszociativitás esetén A⊕(B⊗C) zárójelezés szerint értékelődik ki.
Példa:
1 2 |
|
Műveletek számokkal¶
Láthattuk, hogy bizonyos műveletek több típusnál is előfordulnak, azonban arra figyelni kell, hogy ezek különböző típusok esetében máshogy viselkedhetnek.
Ilyen az /
, azaz osztás művelete, ami egész számok esetében az osztás egész részét adja vissza, valós osztás esetében viszont az eredmény a valós osztás eredménye.
1 2 3 4 |
|
Azaz ha vagy az osztó, vagy az osztandó valós, akkor az osztás eredménye is valós lesz. Vigyázat! Hiába adjuk az egész osztás eredményét egy valós számnak, az osztás eredménye attól marad az egész osztás eredménye, mivel csak és kizárólag az osztó és osztandó típusa határozza meg az eredményt.
Amikor számokkal dolgozunk, sokszor előfordulhat, hogy valós és egész számokat szeretnénk alkalmazni egy műveleten belül.
Bár az egész adattípus nem része a valósnak, az egész számok használhatóak valósak mellett bizonyos műveletekben (+, -, *).
Ilyenkor az egész típusú érték átkonvertálódik valóssá egy implicit művelet által, amit a fordító generál.
Ahhoz, hogy spóroljunk a műveleteken, és hatékonyabb kódot írjunk, ha egy valós típusú érték mellett használunk egy egész értékű konstants, azt eleve jobb valós alakban megadni, hogy az esetleges konverziót elkerülhessük. Például ha az x
változó valós, akkor 2-vel való szorzáskor a legjobb, ha 2.0*x
-et írunk.
Ha egy numerikus kifejezésről el szeretnénk dönteni, hogy az egész, vagy valós típusú, akkor azt a kifejezés felépítése szerinti teljes indukcióval megtehetjük:
A tényező típusa:
- ha változó, akkor a deklarációban megadott típus;
- ha konstans, vagy literál, akkor a számleírás szerinti típus;
- ha
(K)
alakú kifejezés, akkor aK
kifejezés típusa; - ha
+/-T
alakú, akkor aT
tényező típusa; - ha függvényhívás, akkor a függvényművelet eredménytípusa.
A tag típusa:
- Ha
A%B
alakú, akkor egész (ekkorA
ésB
is csak egészek lehetnek); - ha
A*B
vagyA/B
alakú, ésA
ésB
is egész, akkor egész; - ha
A*B
vagyA/B
alakú ésA
vagyB
valós, akkor valós.
A kifejezés típusa:
- Ha
A
ésB
is egész, akkor egész azA+B
, vagyA-B
kifejezés típusa is; - ha
A
vagyB
valamelyike valós, akkor azA+B
, vagyA-B
kifejezés típusa is valós.
A C nyelv utasításai¶
A C nyelvben az utasításokat a ;
zárja le, ez az, ami utasítást csinál egy kifejezésből.
Ez jelzi a gép számára, hogy az addig írt utasítás befejeződött, és azt ki is kell értékelni.
Egy C függvény törzse a vezérlési szerkezetektől eltekintve nem más, mint kifejezések kiértékelésének sorozata.
Azaz a legegyszerűbb utasításaink azok, amit egy kifejezés ;
-vel való lezárásával kapunk.
Ha belegondolunk, a 0;
is egy teljesen jó utasítás (mivel a 0
konstans egy kifejezés), bár tény, hogy túl sok haszna nincsen. Ugyanakkor vannak olyan utasítások is, amiknek látszólag nincs értelme, de gyakorlatilag a mellékhatásaik végett használjuk őket.
Ilyen például a printf
függvény hívása, amelynek visszatérési értéke megadja a kiírt karakterek számát, de ennél nyilván sokkal fontosabb, hogy futása során valamit kiírt a képernyőre.
Az egyszerű utasítások összerakásával összetett utasításokat képezhetünk, amelyek a megfelelő kulcsszavakkal és szemantikával megadják a program vezérlését.
A C-ben a következő módon tudunk összetett utasításokat létrehozni a vezérlési módoknak megfelelően:
- Szekvenciális: véges sok adott művelet rögzített sorrendben egymás után történő végrehajtása, az utasítások
{
és}
zárójelek között való felsorolása által - Szelekciós: véges sok rögzített művelet közül adott feltétel alapján valamelyik végrehajtása az
if
vagyswitch
utasítás segítségével. - Ismétléses: adott művelet adott feltétel szerinti ismételt végrehajtása a
for
,while
, illetvedo...while
utasítással. - Eljárás: adott művelet alkalmazása adott argumentumokra, ami az argumentumok értékének pontosan meghatározott változását eredményezi, amelyet függvény vagy eljárás képzéssel tudunk megadni.
A következő fejezetben részletesen ismertetjük az egyes vezérlési szerkezetet leíró utasítások szintaktikáját és szemantikáját.