Kihagyás

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
main() {}

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
/* Program, amely a parancssorra kiírja a "Hello World!" szöveget */
#include <stdio.h>

int main() {
  printf("Hello World!\n");
  return 0;
}

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.

kep

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
           main(l
      ,a,n,d)char**a;{
  for(d=atoi(a[1])/10*80-
 atoi(a[2])/5-596;n="@NKA\
CLCCGZAAQBEAADAFaISADJABBA^\
SNLGAQABDAXIMBAACTBATAHDBAN\
ZcEMMCCCCAAhEIJFAEAAABAfHJE\
TBdFLDAANEfDNBPHdBcBBBEA_AL\
 H E L L O,    W O R L D! "
   [l++-3];)for(;n-->64;)
      putchar(!d+++33^
           l&1);}

(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!

kep

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
             k;double sin()
         ,cos();main(){float A=
       0,B=0,i,j,z[1760];char b[
     1760];printf("\x1b[2J");for(;;
  ){memset(b,32,1760);memset(z,0,7040)
  ;for(j=0;6.28>j;j+=0.07)for(i=0;6.28
 >i;i+=0.02){float c=sin(i),d=cos(j),e=
 sin(A),f=sin(j),g=cos(A),h=d+2,D=1/(c*
 h*e+f*g+5),l=cos      (i),m=cos(B),n=s\
in(B),t=c*h*g-f*        e;int x=40+30*D*
(l*h*m-t*n),y=            12+15*D*(l*h*n
+t*m),o=x+80*y,          N=8*((f*e-c*d*g
 )*m-c*d*e-f*g-l        *d*n);if(22>y&&
 y>0&&x>0&&80>x&&D>z[o]){z[o]=D;;;b[o]=
 ".,-~:;=!*#$@"[N>0?N:0];}}/*#****!!-*/
  printf("\x1b[H");for(k=0;1761>k;k++)
   putchar(k%80?b[k]:10);A+=0.04;B+=
     0.02;}}/*****####*******!!=;:~
       ~::==!!!**********!!!==::-
         .,~~;;;========;;;:~-.
             ..,--------,*/

(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ó
kep

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ű
kep
Számjegy
kep

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ás
relá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ás
relá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 vagy
relá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
kep
Előjeltelen szám
kep

Azaz külön definiálni kell az előjeltelen egész és valós számok szintaktikáját.

Előjeltelen egész
kep
Előjeltelen valós
kep

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
kep
Oktális számjegy
kep
Hexadecimális számjegy
kep

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
kep

Ezt tovább felbontva rekurzívan:

Numerikus kifejezés
kep
Numerikus tag
kep
Numerikus tényező
kep
Logikai kifejezés
kep
Logikai tag
kep
Relációs kifejezés
kep

(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
a < b && a + - 5 * b < 7 || c != b
((a < b) && ((a + ((- 5) * b)) < 7)) || (c != b)

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
a + 5 - b - 7 + c
((( a + 5) - b) - 7) + c

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
15 / 6 == 2
15.0 / 6 == 2.5
15 / 6.0 == 2.5
15.0 / 6.0 == 2.5

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 a K kifejezés típusa;
  • ha +/-T alakú, akkor a T 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 (ekkor A és B is csak egészek lehetnek);
  • ha A*B vagy A/B alakú, és A és B is egész, akkor egész;
  • ha A*B vagy A/B alakú és A vagy B valós, akkor valós.

A kifejezés típusa:

  • Ha A és B is egész, akkor egész az A+B, vagy A-B kifejezés típusa is;
  • ha A vagy B valamelyike valós, akkor az A+B, vagy A-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 vagy switch 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, illetve do...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.


Utolsó frissítés: 2022-11-25 15:25:14