Számábrázolás. Alprogramok
Tartalom
- Emlékeztető: tömbök, tételek
- Angry Birds szorgalmi feladat
- Számítógép, számábrázolás, számrendszerek
- Számítógép: elvi felépítés
- Adatok és programok
- Érdekesség: kapcsolók generációi
- Számábrázolás
- Számrendszerek (numeral system)
- Átalakítás kettes számrendszerbe
- Binary digit = bit
- Egész típusok a C-ben
- Túlcsordulás és kettes komplemens
- A túlcsordulás élőben
- Valós számok ábrázolása
- Furcsaságok: ½+⅓+⅙, 9,95 és 0-tól 1-ig
- Lebegőpontos típusok C-ben
- Számok a C nyelvben
- Bitműveletek
- Boole-féle algebra
- Bitműveletek: léptetés (shift)
- A bitenkénti VAGY művelet:
|
- A bitenkénti kizáró vagy:
^
- A bitenkénti ÉS művelet:
&
- A bitenkénti tagadás:
~
- Függvények
- Prímszámok 2-től 1000-ig
- Alprogramok: függvények
- Függvény példa: prímek 2-től 1000-ig
- A függvényhívás menete
- Függvények paraméterei
- Paraméterek – a klasszikus hiba
- Nagyobb program: deklarációk, definíciók
- A
void
típus: „semmilyen” - Fő- és mellékhatás I.
- Fő- és mellékhatás II.
- Funkcionális dekompozíció
- Dekompozíció példa: Cesàro és a π
- Dekompozíció: a π meghatározása I.
- Dekompozíció: a π meghatározása II.
- Procedurális/hierarchikus programozás
1 Emlékeztető: tömbök, tételek
Tömbök
/* 10 elem, tomb[0…9] */ int tomb[10]; for (i=0; i<10; i+=1) printf("%d\n", tomb[i]);
- Sorszámozott tároló
- Egyforma típusú elemek
- Adatok ↔ programok: sok elem → tömb, sok művelet → ciklus
Tételek
/* 'E' betűk számlálása */ db=0; scanf("%c", &betu); while (betu!='\n') { if (betu=='e' || betu=='E') db+=1; scanf("%c", &betu); }
- Számlálás, összegzés, keresés…
- Megoldási sémák gyakori, alapvető problémákhoz
- Átalakíthatók egy adott feladathoz
4 Számítógép: elvi felépítés

- Processzor
- CPU (central processing unit)
- Memória (memory)
- Ez tárolja az adatokat és a programokat
- Perifériák (peripheral device)
- HDD, DVD, megjelenítő, billentyűzet, egér, hangkártya, óra, …
- Busz v. sín (bus)
- Vezetékek, amelyeken a kommunikáció történik.
5 Adatok és programok
CPU működése
- Elemi, egyszerű lépések: gépi utasítások
- A fordítóprogram állítja elő a C forráskódból
x=x+2;
mov eax, 1055h x a memóriából a processzorba add eax, 2 a processzor hozzáad 2-t mov 1055h, eax eredmény a memóriába

Memória működése: számok tárolása
- Karakterek → számok
- Képek → fényesség értékek → számok
- Hang → levegőnyomás értékek → számok
- Gépi utasítások (mov, add) → számok
Érdekesség: A végrehajtott program, és az adatok, amelyeken a program dolgozik, lehetnek külön memóriában is. Az első automatikusan működő számítógépek a programot nem a belső memóriájukban tárolták, hanem papírszalagról vagy lyukkártyáról olvasták be azt futás közben. Ennek egyszerűen az volt az oka, hogy nagyon költséges és bonyolult volt relékből (lásd lent) memóriát csinálni. A tervezők pedig ott spóroltak, ahol tudtak. Ahogyan a technika fejlődött, úgy vált lehetővé, hogy a programot is a központi memóriában tárolják. Ezt az elvet Neumann János (John von Neumann) javasolta kollégáival, és ma Neumann-féle architektúrának nevezzük. Az első ilyen elven működő számítógép az EDVAC nevet viselte. Ez egyben az első kettes számrendszert használó, már nem elektromechanikus, hanem teljesen elektronikus számítógép volt. A külön programmemória elve a fentiek ellenére nem halt ki: ezt ma Harvard architektúrának nevezzük, az EDVAC-nál régebbi régebbi Mark I számítógép nyomán.
6 Érdekesség: kapcsolók generációi
A mai számítógépek digitális elven működnek. Csak egész számokkal tudnak dolgozni, amelyeket kettes számrendszerben tárolnak. A kettes számrendszer előnye az, hogy csak két állapot van benne: 0 és 1. Ez elektronikusan könnyen kezelhető (nincs áram, van áram), ezért a működést kapcsolók adják. Bármi, ami kapcsolóként tud működni, az használható számítógép építésére is.
Az alábbi fényképek a számítógépek generációit mutatják. Ezek elvben nem különböznek egymástól, csak a gyakorlatban, méghozzá abból az egyetlen szempontból, hogy milyen elektronikus, vagy esetleg még elektromechanikus eszközt használtak kapcsolónak. Az első három képen lévő eszköz egyetlen kapcsolónak felel meg, míg a jobb alsó képen látható integrált áramkörön már sok millió kapcsoló van. Összehasonlításképp: 3-4000 kapcsoló használatával már egészen jól használható processzor tervezhető, egy Core i7 processzorban viszont már 730 millió darab van.

Relék (relay): az áram hatására a bennük lévő tekercsben (jobb oldalt) mágneses tér keletkezik, és így lehet vezérelni a kapcsolót (bal oldalt). Egy ilyen eszköz kb. 3-4 cm nagy. Manapság is használnak ilyet nagyobb áramok kapcsolására, pl. autókban is.

Elektroncső (tube): a bennük lévő vákuumban repülő elektronok mozgása vezérelhető az elektromos tér változtatásával. Ezek is viszonylag nagyok: 3-4 cm, ráadásul fűteni kell a belsejüket, hogy az elektronok kilépjenek a fémből.

Tranzisztor (transistor): a félvezető anyagok vezetőképessége (ellenállása) elektromos úton szabályozható, így kapcsolónak is használhatóak. A képen látható tranzisztorban a félvezető szilícium darabka 1 mm-nél is kisebb. A védő fém vagy műanyag tokozás nagyobb, 3-4 mm-es.

Integrált áramkör (integrated circuit): ebben is tranzisztorok vannak, azonban az előzőnél jóval kisebbek. Egy 1 cm2 méretű szilícium lapra akár több tízmillió transzisztor integrálható, amelyek egyesével alig néhány tíz nanométeresek (vagyis méretük egy ember hajszál vastagságának ezrede). A fenti processzor mikroszkóp alatt forgatva egy videón is látható.
7 Számábrázolás
Római: I, II, III, IV, V, … X, C, D, M
- Egyszerű összeadás: III + VIII = VIIIIII = VVI = XI
1 123 +639 ──── 762
Hindu-arab: 345, 1203, 2615
- Megjelenik a nulla!
- Számjegyek, helyiértékek – egyszerű a szorzás, osztás is! Minden helyiérték ugyanúgy működik.
- 2615 = 2·103 + 6·102 + 1·101 + 5·100
sorszám | 3 | 2 | 1 | 0 |
---|---|---|---|---|
számjegy | 2 | 6 | 1 | 5 |
helyiérték | 103 | 102 | 101 | 100 |
valódi érték | 2000 | 600 | 10 | 5 |
8 Számrendszerek (numeral system)


alap | példa |
---|---|
10 decimális | 1203tíz = 1·103 + 2·102 + 0·101 + 3·100 |
8 oktális | 377nyolc = 3·82 + 7·81 + 7·80 = 255tíz |
2 bináris | 1101kettő = 1·23 + 1·22 + 0·21 + 1·20 = 13tíz |
A mindennapi életben a tízes számrendszert használjuk. Ennek oka nagyon egyszerű: azért alakult így ki, mert tíz ujjunk van. Más számrendszerek is használhatóak, és a hindu-arab számírás logikus felépítése miatt ezekben a szabályok pontosan ugyanazok, mint a tízes számrendszerben.
A létező legegyszerűbb számrendszer a kettes alapú. Ebben csak kétféle számjegy van, a 0 és az 1. Hogy miért ez a legegyszerűbb? Mert ebben az összeadó és a szorzótábla nem tízszer tízes, hanem mindössze kétszer kettes.
+ | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 10 |

× | 0 | 1 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
9 Átalakítás kettes számrendszerbe
maradék | |||||||
---|---|---|---|---|---|---|---|
helyiérték | / 64 | / 32 | / 16 | / 8 | / 4 | / 2 | / 1 |
számjegy |
Szám: tíz kettő
Az átalakítás lépései: a számot leosztjuk kettő első olyan hatványával, amely nagyobb nála. Az eredmény egy számjegy, a hányadost pedig felírjuk a következő oszlopba. Így folytatjuk az egyre kisebb hatványokkal, amíg el nem érünk 0-ig. (A legutolsó esetben eggyel osztunk, aminek a maradéka biztosan nulla lesz már.) Az osztások során sehol nem kaphatunk 1-nél nagyobb értéket; ha ilyen történne, akkor kettő egy nagyobb hatványától kell indulnunk.
számjegy | |||||||
---|---|---|---|---|---|---|---|
helyiérték | ×64 | ×32 | ×16 | ×8 | ×4 | ×2 | ×1 |
valódi érték |
Szám: kettő tíz
Az átalakítás lépései: a szám számjegyeit (alaki értékek) összeszorozzuk kettő megfelelő hatványaival (helyi értékek). Az így kapott számok (valódi értékek) összege adja az eredményt.
A más számrendszerekbe átalakítás ugyanígy működik, csak az ottani alap hatványait kell használni.
10 Binary digit = bit
Érdekesség: A kettes számrendszernek minden más számrendszernél nagyobb jelentősége van. A 0 és 1 számjegyeket nagyon könnyű automatikusan, elektronikusan kezelni: van áram – nincs áram, világít a lámpa – sötét a lámpa, be van kapcsolva – ki van kapcsolva a kapcsoló. A „binary digit”, azaz bináris számjegy szókapcsolatot eredetileg „bigit” vagy „binit” néven rövidítették. Később a „bit” szót John Tukey, amerikai matematikus javasolta. (Ő találta ki a „software” szót is.)
A szó tudományos írásban először Claude Shannon M.Sc. diplomamunkájában szerepelt, amelynek címe A Symbolic Analysis of Relay and Switching Circuits. Ebben megmutatta, hogy az addig telefonközpontokban használt relék segítségével logikai problémák is megoldhatóak. Pár évvel később megépült az első relékből felépített, kettes számrendszert használó számítógép, a Harvard Mark I. Azóta gyakorlatilag nem készül olyan számítógép, amely nem bináris elven működne.

Bitek és bitcsoportok
- bit
- Az információ alapegysége: 0 vagy 1.
- bájt (byte): bitek csoportja
- A memória legkisebb egysége: általában 8 bit.
- szó (word): több bájtos adategység
- Általában 4 bájt (32 bit), vagy 8 bájt (64 bit).

Előtagok (prefix)
- kilobájt (kB) és kibibájt (KiB)
- 103=1000 ≈ 210=1024 bájt.
- megabájt (MB) és mibibájt (MiB)
- 106=1000000 ≈ 220=1048576 bájt.
- gigabájt (GB, GiB), terabájt (TB, TiB)
- 109≈230 és 1012≈240 bájt.
A kettes számrendszerbeli működés miatt a szokásos mértékegységeknek megvan a bináris párja. Bár a kilo- előtag általában ezret jelent, a számítástechnikában inkább 1024-et, azaz 210-t. Ezt azért választották meg így, mert a kettő között nagyon kicsi a különbség. Sajnos gyakran keverik is a kettőt. A merevlemezgyártók például előszeretettel használják a kilo=1000 jelölést, mert így nagyobb kapacitást írhatnak rá az eladott merevlemezekre. Hogy ne kelljen mindig hozzátenni, ha pontosak akarunk lenni, hogy a bináris vagy a decimális prefixumról beszélünk, bevezették a kibibájt, mebibájt stb. jelöléseket. Egy DVD kapacitása így 4,7 gigabájt, azaz 4,3 gibibájt.
11 Egész típusok a C-ben
név | tipikus méret | tipikus tartomány | printf, scanf | megjegyzés |
---|---|---|---|---|
signed char unsigned char | 8 bit | -128…127 0…255 | legkisebb, mindig egy bájt | |
signed short int unsigned short int | 16 bit | -32768…32767 0…65535 | %hd %hu | |
signed int unsigned int | 32 bit | -231…231-1 (±2·109) 0…232-1 (4·109) | %d %u | tipikus szóhossz a gépen |
signed long int unsigned long int | 32 bit | -231…231-1 (±2·109) 0…232-1 (4·109) | %ld %lu | néha akár 64 bit |
signed long long int unsigned long long int | 64 bit | kb. ±9·1018 kb. 0…1,8·1019 | %lld %llu |
Mivel a számítógép nem végtelen nagy, a tárolt adatok sem lehetnek azok. Ha szeretnénk egy számot eltárolni, akkor arra egy véges, fix méretű memóriaterületet kell kijelölnünk. Ezáltal keletkezik „legkisebb” és „legnagyobb” szám, bár matematikailag ez értelmetlenül hangzik. A számítógépen használt számok emiatt a matematikai számfogalomnak csak tökéletlen modelljei. A matematika számai lehetnek egészek, racionálisak, irracionálisak – a programjainkban ezzel szemben rögzítenünk kell, hogy egy változó típusa egész-e vagy nem, sőt még a tartományban és a pontosságban is be vagyunk korlátozva.
short b;
unsigned c;
Egy egész szám tárolására egy vagy
több bájtot foglalunk le. A számítógép ezeket általában „hardverből”, áramkörileg tudja kezelni.
Az egész típus neve a C-ben int
, amelyhez társulhatnak
ún. módosító jelzők (specifier). Pl. az unsigned
jelző az előjel
nélkülit jelenti, a signed
pedig az előjelest.
Vannak bizonyos alapértelmezések:
- Az
int
típus (méret megadása nélkül) mindig azt a méretet jelenti, amely az adott számítógépen az optimális, vagyis amellyel a processzor leggyorsabban tud dolgozni. Ez manapság a 32 bitest szokta jelenteni, de lehetnek eltérések. - Ha nincs
signed
vagyunsigned
megadva, akkorsigned
, azaz előjeles lesz a változó. Kivétel achar
: ennél nincs alapértelmezés. Ajánlatos így használni:char
: betűsigned char
: 1 bájtos, előjeles egész számunsigned char
: 1 bájtos, előjel nélküli egész szám
- Ha van módosító megadva, akkor az
int
szó elhagyható. Pl.long x;
ugyanaz, mintlong int x;
éssigned x;
ugyanolyan típusú változót hoz létre, mint asigned int x;
utasítás.
A szabvány nem köti meg a típusok pontos méretét, hogy minél többféle számítógépen működhessenek a C programok.
A fenti táblázat a tipikus értékeket mutatja. Ritka, de van, ahol a char
9 bites vagy az int
csak 16!
Erre figyelni kell, amikor hordozható (portable) programokat szeretnénk írni, amelyek különféle gépeken működnek. Főleg, ha
Interneten kommunikálnak egymással vagy egymás fájljait kell olvassák.
12 Túlcsordulás és kettes komplemens
Túlcsordulás: ha nem fér el az eredmény
11111111 255
+00000001 + 1
───────── ───
100000000 256 → 0
0-1 = 255
Ha egy eredmény nem fér el az adott bitszámban, túlcsordulást kapunk. Emiatt 8 biten: 255+1 = 0, és 0-1 = 255.
A biteket az alsó helyiértékektől számozzuk, aszerint, hogy 2 hányadik hatványának felel meg. 20 → 0. bit, 21 → 1. bit stb. A helyiértékek matematikából megszokott neve alapján a legkisebb helyiértékű bitet (least significant bit, LSB) legalsónak, a legnagyobb helyiértékűt (most significant bit, MSB) legfelsőnek nevezzük.
bitek | érték |
---|---|
11111110 | -2 |
11111111 | -1 |
00000000 | 0 |
00000001 | 1 |
00000010 | 2 |
Negatív számok tárolása: a kettes komplemens
- Használjuk ki, hogy 11111111kettő+1 = 0
- Az előjelet a legfelső bit dönti el
- Legfelső bit 0 → nemnegatív szám.
00000011
→ értéke 3tíz - Legfelső bit 1 → negatív szám.
Értéke a többi bit negáltja + 1:
11111110
→0000001
kettő + 1 = 2tíz → a szám −2
A kettes komplemens ábrázolás előnye, hogy a
túlcsordulások miatt automatikusan előállnak a helyes pozitív/negatív
értékek – nem kell megkülönböztetnie a hardvernek (az áramköröknek)
a pozitív és negatív számokkal végzett összeadásokat, kivonásokat. (Egy
négy bites példát tekintve 1110+0011=0001
, azaz
−2+3=1
, ami éppen a helyes eredmény.) Ezért mindenhol
ezt szokás használni.
Egyébként ugyanez működik tízes alapú számrendszerben is. Pl. 3 számjegyen 17+999=1016, amiből ha eldobjuk a túlcsordulás miatti ezrest, akkor 16-ot kapunk, ami pont 17-1. A témakör a digitális technika tárgyból még fog szerepelni részletesen.
Miért fontos ezt ismerni? Mert így tudjátok, miért adhat a számítógép negatív számot két pozitív összegeként. Mert túlcsordulás történt!
13 A túlcsordulás élőben
#include <stdio.h> int main() { unsigned char kicsi; int i; kicsi=1; for (i=0; i<20; i+=1) { printf("%u\n", kicsi); kicsi*=2; } return 0; }
Típusok, amelyekkel érdemes kipróbálni: unsigned char
, signed char
, short
.
Bármekkorával is próbáljuk, előbb-utóbb előjön a probléma. Ha kettővel szorozgatjuk a számot, akkor előbb-utóbb
nulla lesz az eredmény, így könnyű tetten érni a hibát. Ha hárommal szorzunk a ciklusban, akkor már kevésbé!
14 Valós számok ábrázolása
Lebegőpontos ábrázolás (floating point)
± mantissza · 2karakterisztika
- Pl. 4 = 1·22 = 100kettő, ¼ = 1·2-2 = 0.01kettő
- Véges a méret: adott bitszám a mantisszának és a kitevőnek
- Korlátos az ábrázolható számtartomány és a pontosság is:
- A számítások eredménye sokszor nem pontos!
- Az
==
és!=
operátorok használata általában értelmetlen, de a többi is adhat váratlan eredményt
A korlátos pontosság miatt még az is előfordulhat, hogy a+b=a
, ahol
a
egy nagy, b
pedig egy kicsi szám.
Ez történik (pirossal az értékes jegyek):
a 10000000000000.0000000 b + 0.0000001 ─────────────────────── a+b 10000000000000.0000001 → 10000000000000 lesz!
15 Furcsaságok: ½+⅓+⅙, 9,95 és 0-tól 1-ig
#include <stdio.h> int main() { double szam; printf("%e\n", 1.0/2.0 + 1.0/3.0 + 1.0/6.0 - 1); printf("%.16f\n", 9.95); for (szam=0; szam<1; szam+=0.1) printf("%f\n", szam); return 0; }
Egyes géptípusokon az ½+⅓+⅙-1 műveletsor eredménye nem adja ki a 0-t.
Ez azért van, mert a törtek tizedes törtként (kettedes törtként)
nem ábrázolhatóak pontosan. Az ⅓ tört értéke például 1.333333…, amelynél
minél több hármast írunk, annál pontosabban adjuk meg a számot, de teljesen
pontosak sosem lehetünk. (A %e
ugyanolyan, mint a %f
,
csak a normálalakú kiírást jelenti.)
Hasonlóan a fentihez, a 9,95 szám, bár tízes számrendszerben kerek, kettes számrendszerben nem az. Ezért nem ábrázolható pontosan. Banki szoftverekben nem szoktak lebegőpontos számokat használni, hiszen még egy ilyen egyszerű pénzösszeg, mint a 9,95 € (9 euró 95 cent), sem adható meg pontosan.
A ciklus pedig adhat olyan kimenetet, amelyben 11-szer történik meg a kiírás.
A dolog külön érdekessége, hogy a ciklus, bár feltételében szam<1 van, gyakorlatilag
1-nél is lefut! Nem 10-szer (0; 0,1; … 0,9), hanem 11-szer!
És ez teljesen esetleges; szam=1, szam<2 esetén csak 10-szer fut le, mivel
ott úgy alakulnak a kerekítési hibák. Az eredmény géptípusonként változhat,
a pontosság függvényében. Ha a printf()
formátumsztringjébe
%.16f
-et írunk, akkor egyből látszik, mi okozza a hibát.
A jelenség azért érhető ilyen könnyen tetten, mert a tízes számrendszerben
„egész” számok, mint a 0,1, kettes számrendszerben nem azok.
Kettes számrendszerben „kerek” törtek esetén (0,5; 0,25; 0,125; 0,0625…) nincs
ilyen probléma. Érdemes kipróbálni!
16 Lebegőpontos típusok C-ben
név | tipikus tartomány | pontosság (tizedesjegy) | printf | scanf |
---|---|---|---|---|
float | ±10±38 | kb. 7 | %f | |
double | ±10±308 | kb. 16 | %f | %lf |
long double | ±10±4932 | kb. 36 | %Lf |
A támogatott típusok itt is eltérnek számítógépenként. A long double
gyakran nem
létezik, hanem a double
szinonímája csak.
Ha valós szám kell, legtöbbször double
-t használunk.
Figyeljünk arra, hogy a
printf()
-nek és a scanf()
-nek adott
formátumkód a double
esetében nem ugyanaz. A kiíráshoz
ennél %f
kell, a beolvasáshoz %lf
.
Ennek mélyebb, történelmi okai vannak, amelyek itt nem lényegesek.
17 Számok a C nyelvben
dec | hex |
---|---|
10 | a |
11 | b |
12 | c |
13 | d |
14 | e |
15 | f |
A tizenhatos számrendszer
- Kettes számrendszer: túl sok számjegy
- Tizenhatos (hexadecimal) számrendszer kényelmesebb
- 4-es csoportokban (nibble) átalakíthatóak
- Pl. 10011111kettő = 9ftizenhat
- A 10…15 alaki értékek jelölésére az a…f vagy A…F betűket használjuk
Használhatnánk a nyolcas számrendszert is azzal a céllal, hogy spóroljunk a számjegyekkel. Azzal azonban van egy kis probléma. Manapság szinte mindegyik számítógépen nyolc bites a bájt. Ha egy ilyet nyolcas számrendszerben írunk le, akkor 2-3-3 bites csoportok adódnak: 10'101'111kettő=257nyolc. Ezzel önmagában nem is lenne probléma, azonban ha egy két bájtos, azaz 16 bites számot szeretnénk átírni, akkor az egymás mellé tett bájtok nyolcas átírása eltér attól, mint a két bájtté külön. Ha az előző bitsorozatot kétszer egymás mellé írjuk, annak átírása: 1'010'111'110'101'111kettő=127657nyolc, nem pedig 257257nyolc, ahogyan a két nyolcas számrendszerbeli egymás után írása miatt gondolnánk. A tizenhatos számrendszerrel nincs ilyen probléma, mert ott nem három, hanem négy bit van egy csoportban, és egy bájt nyolc bitje pontosan két csoportot ad. A négybites csoportok angol neve a nibble (esetleg nybble).
Számok megadása C-ben (numerikus literálisok)
23
: egész szám- Kettes számrendszerben megadott konstans nincsen!
012
= 12nyolc (0
-val kezdődik),0xFCE2
= FCE2tizenhat (0x
-szel kezdődik)- Ritkán:
12U
,12u
– unsigned,12L
,12l
– long
3.14
: valós (van benne.
vagye
).45
= 0,45 (a legelső 0 elhagyható)6.022e23
= 6,022·1023- Ritkán:
12.3F
– float,12.3L
– long double
19 Boole-féle algebra
A Boole-féle algebrában a változóknak két értékük lehet: HAMIS és IGAZ, vagy bitekben gondolkodva 0 és 1. A sokféle művelet közül a fontosabbak a következők.
0 | 1 |
---|---|
1 | 0 |
0 | 1 | |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | |
---|---|---|
0 | 0 | 1 |
1 | 1 | 1 |
0 | 1 | |
---|---|---|
0 | 0 | 1 |
1 | 1 | 0 |
Digit I.
- NOT: mindig az ellenkezője
- AND: akkor 1, ha mindkettő 1 (0, ha a bármelyik 0)
- OR: akkor 1, ha bármelyik 1 (0, ha mindkettő 0)
- XOR: akkor 1, ha nem egyformák (0, ha egyformák)
A fenti mondatok a logikai műveletek szokásos értelmezését adják. Érdemes azonban más szempontból is vizsgálni őket: az egyik bemenetet változatlannak gondolva azt figyelni, hogyan reagál a kimenet a másik bemenet megváltozására.
- Az ÉS művelet ugyanezt csinálja nullákkal. Ha az egyik bemenet 0 (vízszintes), akkor a másik bemenet (függőleges) értékétől függetlenül 0-t ad a kimeneten. Ha az előbbi bemenet 1-es, akkor az utóbbit lemásolja.
- A VAGY műveletnél: ha függőlegesen a 0-t választjuk, akkor a kimeneten a vízszintesen kiválasztott bit jelenik meg. Ha az 1-et, akkor pedig fixen 1. Szóval a VAGY művelet lemásolja az egyik bemenetét, ha a másik 0, és fixen 1-et ad a tőle függetlenül, ha a másik 1.
- Az XOR (exclusive or), ha az egyik bemenet 0, akkor a másikat lemásolja. Ha az előbbi 1-es, akkor pedig az utóbbit másolja – de negálva.
A következő bitműveletes feladatoknál egyértelmű lesz, hogy miért hasznosak ezek a megfigyelések. Ugyanis pl. a VAGY művelet ezen tulajdonsága miatt használható arra, hogy egy szám egy adott bitjét 1-be állítsuk, miközben a többi változatlanul marad.
Bitműveletek használata
- Minden bit kihasználása: egy 8 bites
unsigned char
-ban 8 igaz/hamis értéket tárolhatunk - Hardverközeli programozás: hardvereszközben (pl. grafikus kártya) adott bit 1-esbe állításával bekapcsolunk valamit
- Hálózatprogramozás: egy internet adatcsomag adott bitjei jelzik, hogy kapcsolat létrehozása, lebontása stb. történik-e
- Kriptográfia: titkosítási és ellenőrző összegeket előállító algoritmusok
- Véletlenszámok: pszeudo-véletlenszámokat előállító algoritmusok
Az utóbbi két témakörről szólnak az SHA-1 és a Mersenne twister Wikipedia szócikkek. Ezekben a bitműveleteket arra használják, hogy minél jobban összekeverjék az adatok bitjeit. A Mersenne twister a legelterjedtebben használt algoritmus, amely pszeudo-véletlenszámokat állít elő: vagyis egy bonyolult matematikai műveletsort végez, amelynek a kimenete látszólag véletlenszerű értékekből áll.
Még egy nagyon fontos megjegyzés: a bitenkénti műveletek nem keverendőek a logikai műveletekkel! Míg a bitenkénti
~
művelet egy egész szám összes bitjét ellentettjére állítja, a logikai!
művelet nem nulla számból nullát csinál, és nullából nem nullát (egyet). Ugyanígy, a bitenkénti|
nem ugyanaz, mint a logikai||
, és a bitenkénti&
mást csinál, mint a logikai&&
művelet. (A^
kizáró vagy műveletnek nincsen logikai párja.)
20 Bitműveletek: léptetés (shift)
Balra léptetés: << operátor
10000110 előtte 00110000 balra 3-mal
x = 134 << 3;
- A felső három elveszik, alulról három nulla jön be.
- Mintha szoroznánk 23-nal
Jobbra léptetés: >> operátor
01010110 előtte 00010101 jobbra 2-vel
x = 86 >> 2;
- Az alsó kettő elveszik, felülről két nulla jön be.
- Mintha osztanánk 22-nal
A bitenkénti léptetés operátorok ugyanolyanok, mint az összeadás
vagy a szorzás: két operandusuk van, és létrehoznak egy harmadik számot, az
eredményt. Eközben a két operandusukat nem változtatják meg!
A b=a<<3
kifejezés így az a
változó értékét
változatlanul hagyja, és csak b
változik! A többivel párhuzamosan
viszont ezeknek is megvan a rövidített, értékadó párjuk:
x<<=3
ugyanaz, mintx = x<<3
ésx>>=2
ugyanaz, mintx = x>>3
.
A
<<=
és>>=
operátorok azok, amelyek léptetnek is és értéket is adnak, a<<
és a>>
operátorok nem! Mindez igaz a következő diákon bemutatott operátorokra is.
21 A bitenkénti VAGY művelet: |
Ez a művelet két szám bitjeit hozza VAGY kapcsolatba páronként, vagyis az egyik szám 0. bitjét a másik szám 0. bitjével, 1. bitet az 1. bittel stb. A VAGY műveletnél „az eredmény 1, ha bármelyik 1”. Ezt adott bitek 1-be állítására szokás használni.
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|
A | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
B | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
A|B | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
„pipe”,
álló vonal
Feladat: állítsuk egy szám 5. bitjét 1-be!
- Olyan maszk (mask) kell, amiben az 5. bit 1-es, többi 0
- Ez a szám az
1<<5
:
00000001 1
00100000 1<<5
x = x | 1<<5;
vagy egyszerűbben:x |= 1<<5;
22 A bitenkénti kizáró vagy: ^
A két operandusának ugyanolyan sorszámú bitjeit hozza KIZÁRÓ VAGY kapcsolatba. Mivel a kizáró VAGY műveletnél az eredmény akkor 0, ha egyformák a bemeneti bitek, és akkor 1, ha nem egyformák, ezt adott bitek negálására szokás használni.
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|
A | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
B | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 |
A^B | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
„caret”,
kalap
Feladat: negáljuk egy szám 3. és 4. bitjét!
- A maszk:
1<<3 | 1<<4
- Így:
x = x ^ (1<<3 | 1<<4);
- Vagy másképpen:
x = x ^ 1<<3 ^ 1<<4;
Az x=x^y
kifejezés rövidítve is írható: x^=y
,
vagyis a fenti példa rövid változata: x ^= 1<<3 | 1<<4;
23 A bitenkénti ÉS művelet: &
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|
A | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
B | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
A&B | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
A két operandusának ugyanolyan sorszámú bitjeit hozza ÉS kapcsolatba.
„et” vagy
„és” jel
Feladat: ellenőrizzük, egyes-e a szám 3. bitje!
- Vágjuk ki csak azt a bitet, minden másikat nullázva
- A maszk:
1<<3
if ((x & 1<<3) != 0) …
- A zárójelezés a műveletek erőssége (precedenciája) miatt szükséges. Különben 1<<(3!=0)-t jelentené a kifejezés jobb oldala.
24 A bitenkénti tagadás: ~
A ~
operátor a bitenkénti negálás jele C-ben.
Ezt egy szám vagy változó neve elé írva olyan számot kapunk, amelyben
minden bit ellenkezőjére (negáltjára) van váltva.
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|
A | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 |
~A | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
„tilde”,
hullámvonal
Feladat: állítsuk egy szám 3. bitjét 0-ba!
- ÉS: „az eredmény 0, ha bármelyik 0”
- Maszk, amelyben csak a 3. bit 0:
~(1<<3)
x = x & ~(1<<3);
- Vagy rövidebben:
x &= ~(1<<3);
26 Prímszámok 2-től 1000-ig
Számoljuk meg, hány prímszám van 2 és 1000 között!
int sz, db; db=0; for (sz=2; sz<=1000; sz+=1) if (… sz egy prím …) ! db+=1;Lineáris keresés tétele
int oszt, van; van=0; oszt=2; while (oszt<sz && !van) { if (sz%oszt==0) van=1; oszt+=1; }
int sz, db, oszt, van; db=0; for (sz=2; sz<=1000; sz+=1) { van=0; oszt=2; while (oszt<sz && !van) { if (sz%oszt==0) van=1; oszt+=1; } if (!van) db+=1; } printf("%d prím.\n", db);
Az előző előadáson azzal a megállapítással zártuk ezt a feladatot, hogy a két egymástól független programrész összedolgozása által egy nehezen értelmezhető program keletkezett. Nagyban rontotta az áttekinthetőségét az, hogy az egyik algoritmust bele kellett építenünk a másikba, kettévágva azt. Megírni is nehezebb egy ilyet, hiszen egyszerre több dolgon kell gondolkozni.
27 Alprogramok: függvények
Tegyük fel, hogy van egy programrész, amely
megmondja egy adott számról, hogy prím-e, vagy nem. Ez a programrész
egy ún. függvény. Ahogyan a sin()
is, amely
kap egy szöget, és megadja annak szinuszát, ez kap egy egész számot,
és meghatározza annak prím vagy nem prím voltát. Ha van egy ilyen,
akkor a prímek számlálása feladat nagyon egyszerű: a kékkel jelölt
„… sz egy prím …” programrész helyére csak annyit kell írnunk, hogy
prim_e(sz)
. Mint az lent is látható, ilyet a C-ben
lehet csinálni.
Alprogram (subprogram), szubrutin (subroutine)
Az egész programunk együttműködő alprorgamokból épülhet fel!
if (… sz egy prím …)
db+=1;
if (prim_e(sz)) db+=1;
Ezekhez is tartozik specifikáció: bemenet → kimenet összefüggése.
C-ben a nevük: függvény (function)
y=x²
double negyzet(double x) // x: bemenet { return x*x; // x²: kimenet }
28 Függvény példa: prímek 2-től 1000-ig
A részfeladatokat külön függvényben
írhatjuk meg. Így egy nagyobb program áttekinthetőbb,
kezelhetőbb lehet. Sőt a gyakran
ismétlődő programrészeket is így csak egyszer kell majd
megírnunk. A programok így kisebbek, hatékonyabbak lehetnek.
Egy feladat elvégzéséhez szükséges programrész kódja csak
egyszer fog szerepelni a memóriában, mindenhol máshol csak
hivatkozunk rá! A printf()
függvényt is egyszer megírta
valaki, és mindenhol használhatjuk.
Főprogram
int main() { int db, sz; db=0; for (sz=2; sz<=1000; sz+=1) if (prim_e(sz)) // prím? db+=1; printf("%d db.\n", db); return 0; }
Alprogram
int prim_e(int szam) // prím? { int oszt, van; van=0; oszt=2; while (oszt<szam && !van) { if (szam%oszt==0) van=1; oszt+=1; } return !van; }
Elnevezések: fejléc, függvénytörzs, paraméter, visszatérési érték, hívás, visszatérés, lokális változó, láthatóság, élettartam.
Dokumentálandó: bemenet, kimenet, feladat, hibalehetőségek.
Függvények definiálása
visszatérési_típus függvénynév(paraméterlista) { … függvénytörzs … return visszatérési_érték; }
A fejléc (header) meghatározza a függvény nevét, a paraméterei és a visszatérési értéke (return value) típusát. A függvénytörzs (function body) tartalmazza azt a programrészt, amely a függvény feladatát elvégzi.
Visszatérés a függvényből
A függvény törzsében elhelyezett return
utasítással
visszatérhetünk (return) a függvény hívásának (call) helyére.
Ezzel egyben megadjuk a visszatérési értéket is, amelyet
egyébként a függvény értékének (function value) is nevezünk. Fontos, hogy a return
utasítás ezt a két szerepet elválaszthatatlanul összeköti! Ami a return
után van, az már nem hajtódik végre. (Viszont egy függvényben lehet több helyen
is return
utasítás.)
Lokális változók
- A függvényen belül vannak definiálva
- Függvénybe belépéskor jönnek létre
- Végén megszűnnek → értékük elveszik
- Minden függvény csak a sajátjait látja! (láthatóság, scope)
Mi az előnyük? Egyrészt az, hogy minden függvénynek
lehetnek saját lokális változói, amelyekben olyan értékeket tárolnak, amelyekre
csak a futásuk idején van szükség. A fenti példában az osztó
változóra nincsen már szükség, amint meghatároztuk, hogy a szám prím-e.
(A van
változó is lokális, és az is megszűnik, azonban az
értéke lemásolódik, és átadódik a hívónak.) Ezek a változók csak a függvényen
belül látszanak (a láthatóságuk (scope) csak a függvényen belülre terjed ki),
és így a nevük csak azon belül értelmezett. Másik függvényeknek
lehetnek ugyanolyan nevű lokális változóik, mint ennek, a nevek mégsem
fognak ütközni. A másik előny, hogy a változó nem foglal memóriát, csak akkor,
ha az azt definiáló függvény belsejében vagyunk. Vagyis a változó élettartama
(extent, vagy más néven lifetime) is csak arra az időre terjed ki, amíg a függvény végrehajtása tart.
A main()
függvény
Most már tudjuk, hogy a main()
is
egy függvény. Az egy egész számmal kell visszatérjen, amelynek
hibajelző szerepe van. Egyelőre mindig 0-ra állítjuk, ami annál azt
jelenti, hogy nincs hiba. Hogy a paraméterei mik, azt a kérdést
egyelőre hagyjuk nyitva!
Függvények dokumentációja
A függvények olyan kis programrészek, amelyek egy jól elhatárolt részfeladatot hajtanak végre. Ezért egy függvény dokumentálásakor pontosan meg kell határozni, hogy mire való, milyen feladatot hajt végre. A programokhoz hasonlóan rögzíteni kell azt is, hogy milyen bemenetet vár és milyen kimenetet állít elő a futása során. A bemenet dokumentálásához hozzá tartozik a bemeneti tartomány leírása is (pl. a négyzetgyököt vonó függvény nem kaphat negatív számot paraméterként). A működés leírásához pedig a hibalehetőségek rögzítése. Mindezeket a függvények előtt, a forráskódban, megjegyzés (comment) formájában is meg kell tenni, hogy ezáltal a kód kezelhetővé, karbantarthatóvá váljon. Ahol van hely, ezt meg fogjuk tenni (sajnos az előadás diákra nem mindenhol fér oda ez).
29 A függvényhívás menete
double faktorialis(int mie) { double szorzat; int i; szorzat=1; for (i=2; i<=mie; i+=1) szorzat*=i; return szorzat; } int main() { int sz; double eredm; printf("sz="); scanf("%d", &sz); eredm=faktorialis(sz); printf("%d!=%.0f\n", sz, eredm); return 0; }
main() sz: eredm:
faktorialis() mie: szorzat: i: (vissza):
Az animáció azt mutatja, hogy mely függvényekhez tartozó lokális változók mely időpillanatban léteznek.
A faktoriális függvény olyan, mint a teljes programunk: van kimenete, van bemenete, csak ezek nem a képernyő és a billentyűzet, hanem a főprogrammal történő kommunikáció által valósulnak meg:
(Megjegyzés: a printf()
és a scanf()
függvények is
alprogramok, amelyek azonban az ábrán nem szerepelnek.)
30 Függvények paraméterei
double teglalap_kerulet(double a, double b) { return 2*(a+b); }
printf("%f", teglalap_kerulet(2, 3.4)); // a=2, b=3.4
Formális paraméter (formal p.): a neveik a függvény fejlécében.
- Szimbolikus paraméternek is nevezik (symbolic parameter)
- A függvényen belüli szerep szerint kell elnevezni
Aktuális paraméter (actual p.): a hívás helyén adott érték.
- Híváskor a megadás sorrendje számít
- Nem csak változó lehet, hanem konstans is
- Automatikusan inicializált lokális változók. Ugyanúgy megszűnnek!
Fontos, hogy a paraméterlista megadásánál minden egyes paraméter
elé oda kell írni annak típusát. Akkor is, ha azok egyformák.
Ezért van a fenti függvény fejlécében double a, double b
, és
nem pedig double a,b
, ami helytelen szintaktikailag.
31 Paraméterek – a klasszikus hiba
#include <stdio.h> void szamol(int a, int b, int osszeg) { osszeg=a+b; /* el fog veszni! */ } int main() { int szum; szum=0; printf("előtte: %d\n", szum); szamol(5, 6, szum); printf("utána: %d\n", szum); return 0; }
Mivel a paraméter is lokális változó, a
függvényből visszatérve megszűnik létezni! Emiatt a paraméteren
keresztül közvetlenül nem lehet visszaadni értéket! A fenti
programban is a függvény nem a szum
változót, hanem a
szum
változó tartalmának másolatát kapja csak
meg, vagyis 0-t. Az osszeg
nevű lokális váltózba tényleg bekerül az összeg,
de megszűnik a return
utasítás után!
A problémára megoldására egy későbbi előadáson fogunk visszatérni.
32 Nagyobb program: deklarációk, definíciók
double kerulet(double a, double b); // deklaráció/prototípus int main() { printf("%f", kerulet(2, 3.4)); return 0; } /* kiszámolja egy a,b oldalú téglalap kerületét */ double kerulet(double a, double b) // definíció { return 2*(a+b); }
- A fordítónak a
main()
-nél tudnia kell, mit jelent akerulet
- Nevét, paraméterek számát és típusát, visszatérés típusát
- Vagyis deklarálni kell; definiálni ráér később is
- A deklaráció kihagyása hiba!
A fenti példa azt is kiemeli, miért olyan fontos ez. A fordító
minden függvényhívás helyén elvégzi a paraméterek ellenőrzését: a függvénynek
csak olyan típusú paraméterek adhatóak, amelyeket a fejléce alapján vár.
A main()
-ben itt a hívás helyén egy érdekes dolog történik. A
2
konstans egy egész, ezért a típusa int
– viszont
a fordító tudja, hogy a kerulet()
valós paramétert vár, ezért
előbb elvégzi a 2→2.0 átalakítást. Fordított esetben, pl. egészet
váró függvénynek 3.14
-et adva először egy lefelé kerekítés
történne, és a függvény végülis a 3-as számot kapná meg. Az ilyesmit általában
egyébként egy figyelmeztető üzenettel jeleznek is a fordítók, ugyanis a törtrészt ilyenkor
elveszítjük, amit lehet, hogy nem szeretnénk.
A függvényt egyébként nem szükséges a main()
függvény
után definiálni, előtte pedig csak deklarálni. Az egész definíció áthelyezhető
a main()
függvény elé is. Ilyenkor külön deklarációra nincsen
szükség, mert a definíció is tartalmazza a nevet és a típusokat. A lényeg az,
hogy a függvényhívás helyén a függvény típusának már ismertnek kell lennie:
/* kiszámolja egy a,b oldalú téglalap kerületét */ double kerulet(double a, double b) // definíció { return 2*(a+b); } int main() { printf("%f", kerulet(2, 3.4)); return 0; }
33 A void
típus: „semmilyen”
int beolvas_szam(void) { int sz; scanf("%d", &sz); return sz; }
void kiir_szam(int szam) { printf("%d", szam); return; }
- Ha a függvénynek nincs paramétere, azt a
void
szóval jelöljük - Ha nincs visszatérési értéke, azt is
- Ilyenkor a
return
önmagában áll:return;
- Vagy el is hagyható
- Ilyenkor a
x=beolvas_szam(); // A híváshoz ilyenkor is kell a ( ) kiir_szam(x);
34 Fő- és mellékhatás I.
int negyzet(int szam) { return szam*szam; }
Főhatás: a függvény értéke. A függvény kiértékelése: a függvény lefut, és a hívás helyén lévő kifejezésbe a visszatérési értéke behelyettesíthető.
void szam_kiir(int szam) { printf("%d", szam); }
Mellékhatás: a függvény valahol változást okoz. (Ezt mellékhatásnak nevezzük még akkor is, ha kifejezetten ez a célja a függvénynek!)
Általában igyekszünk olyan függvényeket írni, amelyeknek csak főhatása vagy csak mellékhatása van. Ennek az elvnek neve is van: command-query separation. Eszerint kétféle függvény van. Az egyik fajta a parancsfüggvény (command), amelyet azért használunk, hogy hatása legyen. A másik fajtának kérdéseket teszünk fel (query), amely kiszámol valamit, de mellékhatása nincs. Ha ez a kettő keveredik, az könnyen összevisszasághoz, átláthatatlan programfelépítéshez és nehezen megtalálható hibákhoz vezet.

Fontos: ha a specifikáció nem kéri a kiírást, akkor kifejezetten hibának számít, ha a függvény mégis ilyet tesz! Például kiírja a képernyőre az eredményt ahelyett, hogy visszatérne vele. Hadd döntse el a hívó, mit szeretne csinálni vele!
35 Fő- és mellékhatás II.
Szám faktoriálisa
int fakt(int mie);
printf("%d\n", fakt(6)); printf("%d\n", fakt(6)); printf("%d\n", fakt(6));
720 720 720
Véletlenszám
#include <stdlib.h>
printf("%d\n", rand()); printf("%d\n", rand()); printf("%d\n", rand());
8311623 2141262 16641798
- Ha a függvény értéke csak a paramétereitől függ, mindig ugyanaz kell legyen az eredmény.
- Ha van mellékhatása, ez nem biztos! Valahol valaminek történnie kell,
hogy a
rand()
mindig mást ad…
Vannak esetek, amikor a command-query separation elvet nem
tartjuk be.
A véletlenszám függvény például kell rendelkezzen valamiféle belső állapottal.
Láthatóan a kimenete nem a bemenő paraméter függvénye, hiszen nincs is neki!
Általában sem lenne sok értelme a void
paraméterű vagy visszatérési értékű függvényeknek, ha nem lenne mellékhatásuk.
Az ilyenek matematikai értelemben véve nem függvények már, de ennek ellenére C-ben
így hívjuk őket.
37 Dekompozíció példa: Cesàro és a π
Ernesto Cesàro (olasz matematikus): válasszunk ki véletlenszerűen két egész számot. Annak a valószínűsége, hogy ezek relatív prímek, 6/π².
Feladat: írjunk programot, amely megbecsüli a π értékét!
A következőképpen nézhet ki. Először is, rendezzük az egyenletet:
6 6 P=─ → π²=─ π² P
int main() { double pi = sqrt(6/valoszinuseg()); printf("%f", pi); return 0; }
A keresett valószínűséget majd a függvény megmondja.
38 Dekompozíció: a π meghatározása I.
Hogyan számoljuk ki a valószínűséget? Adott egy kísérletünk,
legyen ez kiserlet()
. Térjen ez a függvény vissza igazzal,
ha a kísérlet sikerült! Végezzük el ezerszer! A
sikeres kísérletek számát 1000-rel osztva megkapjuk a becsült
valószínűséget.
módszer
/* P meghatározása kísérletezéssel */ double valoszinuseg() { int i, db; db=0; for (i=1; i<=1000; i+=1) if (kiserlet()) // elvégzi a kísérletet db+=1; return db/1000.0; /* egész osztás elkerülése! */ }
Mi a kísérlet? Az, hogy két véletlenszám relatív prím. Gyártsunk ehhez 1 és 1000 között véletlenszámokat, és hasonlítsuk a legnagyobb közös osztójukat 1-hez:
/* A kísérlet: a legnagyobb közös osztójuk 1? */ int kiserlet() { return lnko(rand()%1000+1, rand()%1000+1)==1; }
39 Dekompozíció: a π meghatározása II.
És hogyan számolunk legnagyobb közös osztót? Euklidész módszerét megnézhetjük a Wikipédián is. A pszeudokódot csak át kell írni C-be:
– Donald Knuth
/* visszatér a két szám legnagyobb közös osztójával. */ int lnko(int a, int b) { while (b!=0) { int t=b; b=a%b; a=t; } return a; }
Érdekesség: Donald Knuth amerikai programozó. Leghíresebb műve a „Számítógépprogramozás művészete” (The Art of Computer Programming) című többkötetes könyv. Az ő nevéhez fűződik a TeX nevű szövegszedő program kifejlesztése is, amelynek különféle változatait most is használják könyvek, folyóiratok szerkesztéséhez, szedéséhez. Az elterjedt irodai programcsomagokkal készített dokumentumok külleme meg sem közelíti azt, ami a TeX segítségével elérhető!
Az eredmény
3.131121
40 Procedurális/hierarchikus programozás
A funkcionális dekompozíció (functional decomposition) egy tervezési elv. A másik neve a felülről lefelé (top-down) elv. Lényege, hogy a problémát részfeladatokra bontjuk. Az egész rendszert, programot úgy tervezzük meg, hogy közben a részfeladatokat megoldottnak tekintjük. Az egyes részfeladatok megoldása közben így nem kell a többi részleteivel bajlódni. A részfeladatok ugyanúgy specifikálhatóak, mintha egy teljes programról lenne szó.
– Gerald J. Sussman
A funkcionális dekompozíció céljai:
- Felülről lefelé tervezés (top-down design)
- Tervezés egyszerűsítése: „oszd meg és uralkodj”
- A programrészek közötti csatolások csökkentése
Érdekesség: Gerald Jay Sussman amerikai programozó, matematikus, aki legtöbbet a mesterséges intelligenciával foglalkozik. Az ő nevéhez is fűződik a Structure and Interpretation of Computer Programs című könyv megírása is, amelyhez egy programozás alapjait bemutató tárgy is tartozott az MIT egyetemen. A fenti idézet az egyik előadásáról származik. A tárgyat egyébként a saját maguk által kifejlesztett programozási nyelvvel tanították, amelynek a neve Scheme.
A top-down tervezést „wishful thinking” néven mutatta be (kb. ábrándozó gondolkodás). Hiszen éppen ez a lényege: „Bárcsak lenne egy olyan függvény, amelyik megmondja egy számról, hogy prímszám-e… Mert ha igen, akkor milyen egyszerű lenne a feladatunk!” Sokszor ezzel a gondolkodásmóddal tudjuk szétválasztani a részfeladatokat.
A fenti feladat a felülről lefelé (top-down) tervezést szemlélteti. Azt kell látni, hogy minden lépésben csak egy kicsit lépünk a megoldás felé; a következő lépést pedig mindig megoldottnak tekintjük.