Informatika2-2015/Eloadas 8 C-1 C alapok for ciklus

A MathWikiből

Tartalomjegyzék

C alapok

Egy kis történelem

A C az egyik legrégebbi programozási nyelv ami létezik. Amikor kifejlesztették, akkor még így nézett ki egy modern számítógép. Amikor elkezdték fejleszteni, akkor még sok helyen lyukkártyákat használtak a programozáshoz. Mi azt a változatot fogjuk használni, ami 1989-ben lett sztenderdizálva, mert még most is az az alapértelmezett.

Mivel ilyen régi a nyelv, mások voltak a szempontok a kifejlesztésekor mint a mai programozási nyelveknél. Fontos volt a hatékonyság, mind a fordítóprogram, mind a kész, lefordított program részére. És hát sok mindent, ami a legtöbb mai programozási nyelvben egyértelmű, akkor még nem is találtak ki, mint ötlet. Ennek következtében sokszor sokminden kényelmetlenebb lesz mint a python-ban.

Ennek következtében manapság a legtöbb új programozási project-hez modernebb nyelveket használnak. Viszonylag nagy biztossággal tudom állítani, hogy ha matematikusok lesztek, nem fog kelleni C-ben programoznotok. Azért még mindig vannak dolgok, amihez a C van használva, főleg olyan dolgok, amikor fontos a hatékonyság, vagy a nyelv egyszerűsége:

  • A Linux operációs rendszer kernel-je, belsőségei.
  • Más programozási nyelvek implementációja. Pl. a python interpreter amit használtunk, C-ben van írva.
  • Úgynevezett "beágyazott rendszerek" programozása. Ilyen pl. egy kocsi belső digitális rendszere (Nem a GPS ami a pultba van építve, hanem pl. az ABS), router-ek, orvosi műszerek.

Azonban sok más programozási nyelv épül a C-re, sokat azzal a céllal fejlesztettek ki hogy a C különböző problémáit oldják meg, és ezeknek a nyelveknek szintaxisban és működésben is sok közös pontja van a C-vel. Ezért, ameddig ebből a tárgyból el fogunk jutni a C megtanulásában, az azokhoz a nyelvekhez is hasznos lesz, ezért hasznos számotokra ezt tanulni, hiába nem fogjátok magát a C nyelvet használni. Ezek közé a továbbfejlesztett nyelvek közé tartozik a C++, Java, C#, és a Go, sok más között.

Interpretált vs. Fordított nyelv

Vegyük a következő egyszerű python programot:

print "Hello World!"

Mentsük el ezt hello.py fájlnéven, és vegyük a következő, kb. ekvivalens, C programot:

#include <stdio.h>
 
void main() {
  printf("Hello World!\n");
}

És mentsük el hello.c fájlnéven.

Ekkor úgy tudom futtatni a python programot parancssorból, hogy beírom hogy

> python hello.py
Hello World!

(Windows-on, ha a .py kiterjesztés a python-hoz van rendelve, akkor elég duplaklikkelni rá, vagy csak azt írni parancssorba hogy "hello.py", de az akkor is ugyanúgy a python nevű programot fogja meghívni.)

Ilyenkor lefut a python nevű program, ami a python nyelv interpretere, és az beolvassa a python.py fájlt, és végrehajtja a benne levő utasításokat. Ahhoz hogy futtatni tudjam a python programomat, fel kellett hogy telepítsem a python-t a számítógépemre.

Ezzel ellentétben C-nél először ezt írom be a parancssorba:

> gcc hello.c -o hello.exe

Ekkor még nem fut le az általam írt program, csak létrejön egy új fájl, hello.exe néven. Ez egy alkalmazás, amit aztán külön tudok futtatni, pl. így

> hello
Hello World!

Ezt a hello.exe fájl át tudom küldeni más Windows-os gépekre, és azokon is tudom futtatni, attól függetlenül hogy a C azokra telepítve van-e. A gcc egy C fordító: lefordítja a .c fájlt egy alkalmazássá.

Ez két különböző megoldás a programozó nyelvek megvalósítására.

Összehasonlítva a kettőt: Forráskód:

  • Interpreternél oda kell adni magát a kódot a felhasználónak, így biztos bele tud nézni hogy hogy működik
  • Fordítónál a forráskódot titokban lehet tartani, így azt is hogy belül hogy működik a programunk (ami lehet hogy versenyelőny számunkra)

Sebesség:

  • Általában a Fordítós programnyelvek gyorsabbak/hatékonyabbak a program futásakor

Fejlesztés sebessége:

  • Interpreternél egyből el tud indulni a kód amit éppen módosítottunk. (Némelyik interpretált nyelvnél akár futás közben is változtatható a kód.)
  • Fordított nyelveknél van egy plusz lépés a kód megírása és a kipróbálás között, ami nagy programoknál néhány perc is lehet.

Ellenőrzés:

  • Interpretált nyelveknél csak a kódnak azt a részét ellenőrzi ami lefut, ezért könnyű félkész programnak csak a kész részeit tesztelni, de
  • Fordított nyelvnél az egész programot ellenőrzi (és, hát, feldolgozza) fordításkor, így könnyebb biztosnak lenni hogy nem maradt alapvető hiba a programban mielőtt az alkalmazást átadjuk.

Különböző fordítók

A python-hoz a python interpreter, amit az előbb említett módon a parancssorból a python paranccsal elindítok az a python nyelvnek a CPython nevű, C-ben megírt megvalósítása. Ez messze a legelterjedtebb megvalósítás, a szabvány szerint ahol nem egyértelmű a szabvány leírása, ott ennek a megvalósításnak kell megfelelni. Csak akkor használunk más változatot, amikor speciális körülmények vannak (pl. a CloudCoder-en belül), de a más változatoknak is az a célja, hogy a CPython-nak megfeleljenek.

Ezzel ellentétben a C-nél van egy szép ~520 oldalas pdf fájl, ami igyekszik minden részletre menően leírni hogy minden esetben mit kell csinálnia a programnak. (Megjegyzem hogy ez úgy 520 oldal, hogy a C nyelv jelentősen egyszerűbb mint a python. Ezeket a modern/bonyolultabb nyelveket már kb. lehetetlen lenne pontosan leírni hogy mikor mit kell csinálniuk, minden esetre kiterjedően.) Ez amit linkeltem, a C89 sztenderd, amit egy bizottság elfogadott nemzetközi ISO sztenderd C-nek, és a különböző fordítók ezt próbálják megvalósítani. Ellentétben a python-nal nincs egyértelmű győztes, több fordító is van elterjedt használatban.

  • Talán a legelterjedtebb a gcc (GNU Compiler Collection). Ezt fogjuk mi is használni laboron, és ennek egy változatát ajánlom otthoni használatra is. Ez egy nagyon régóta fejlesztett fordító, az első publikus változata 1987-ben lett közzétéve. Nyílt forráskódú, és a Linuxon ez az alapértelmezett, ezt használják kb. az összes linux változat fordítására is.
  • A gcc-hez hasonló alapelvekkel rendelkezik a clang (C language front end). Ez is nyílt forráskódú, de kicsit máshogy, és az Apple kezdte el jogi okok miatt erősen fejleszteni kb. 10 éve, de az elmúlt ~5 évben már eléggé feltört, és kezdi veszélyeztetni a gcc-t, van már egy-két linux változat is ami ezzel van fordítva. (Ez azért nagy szám, azért említem, mert a linux talán a legbonyolultabb létező C program, amelyik fordító azzal elbír az valószínűleg minden mással is.) Előnye hogy újabb, ezért a fordítóprogramok írásában azóta kifejlesztett technikákat tudták használni a megírásakor, ami néhány dologban jobbá teszi. Apple Mac rendszereken ez már egy ideje az alapértelmezett.
  • A Microsoft saját fordítója az MSVC (Microsoft Visual C). Ez ugyan nem nyílt forráskódú, de már sok éve mindenki számára ingyenesen elérhető. Windows-os fejlesztéshez elég népszerű, egybe van csomagolva a Visual Studio fejlesztő környezettel, aminek (most már) az ingyenes változata is jól versenybe száll a más ingyenes fejlesztőkörnyezetekkel. Azért nem ezt ajánlom, hogy ugyanazt használjátok otthon mint laboron, és mert a kezelőfelület ugyan tapasztalt programozók számára hatékony, kezdők számára ilyesztő lehet, sok felesleges dolgot tartalmaz. Windows-on ez az alapértelmezett.

Vannak még más fordítók is, de kevésbé népszerűek, úgyhogy most nem térek ki rájuk.

Alap szintaxis

Whitespace

Első dolog ami más mint a python-ban, hogy nem számít hogy hova tesszük az újsorokat, behúzásokat, szóközöket. Ha két nyelvi elem el van választva a kódban, akkor annyi szóközt, újsort, és tab-ot tehetsz közé amennyit akarsz, csak pl. egy változónéven vagy karakterláncon belülre ne tegyél. Ezeket a karaktereket, a szóközt/újsort/tabot együttesen whitespace-nek hívják.

Például nézzük az egyik lehetséges legegyszerűbb C programot:

#include <stdio.h>
 
void main()
{
  printf("Hello World!\n");
}

Ez ekvivalens ezzel:

#include <stdio.h>
 
     void
main
  () {printf(
"Hello World!\n")
      ;}

De azért persze az első változat olvashatóbb. Így tehát nem azt mondom ezzel, hogy felejtsétek el amit a python-nál tanultatok ebből a szempontból, hanem hogy továbbra is alkalmazzátok, annak ellenére hogy a nyelv maga már nem kötelez rá. (Ezért szeretik a tanárok előbb tanítani a python-t, ha nincsenek a szép kódra kötelezve az új tanulók, nagyon csúnya, és hát, a tanár számára nehezen olvasható, kód szokott születni.)

(Természetesen ennek egyik következménye, hogy a tapasztalt programozók versengenek egymással hogy ki tud olvashatatlanabb kódot írni, ha akar. Van egy verseny az interneten, az IOCCC, ahol minden évben ünneplik az abban az évben beküldött legcsúnyább kódokat, amik ennek ellenére valami érdekeset csinálnak.)

Mivel az új sor nem jelenti a programsor végét, ezért azt pontosvesszővel jelezzük, mivel a blokkot nem jelezhetjük azzal hogy több tab-ot teszünk a sor elejére, ezért azt kapcsos zárójellel jelezzük. További hasonló dolgokat meglátjuk amikor eljutunk az illetékes nyelvi elemig.

Típusosság

A python egy nem típusos nyelv. Ez azt jelenti, hogy egy változónak a típusa nem fix. Ezért működik a következő kód:

a = 5
print a
a = "Elemer"
print a
a = None
print a

Itt az a változónak sorról sorra változik a típusa, számról karakterláncra, majd NoneType-ra.

Ezzel ellentétben a C-nél minden változónak meg kell mondani a típusát mielőtt először használjuk, és aztán később nem változhat a típusa. Így ez a program nem helyes:

#include <stdio.h>
 
void main() {
  float a;
  a = 5.5;
  a = "Elemer";
}

Ha megpróbálom ezt lefordítani, akkor a gcc hibaüzenetet ad:

>gcc hello.c -o hello.exe
hello.c: In function 'main':
hello.c:6:5: error: incompatible types when assigning to type 'float' from type 'char *'
   a = "Cica";

Ez a kötelesség a típus megadására mindenre kiterjed, majd a függvényekre magukra, és a függvények paramétereire is.

Egyelőre ismernünk kell az int típust, ami egész számok tárolására használható, és a float típust, ami valós számokhoz.

if, while

Az if feltétel és a while ciklus alapvetően ugyanúgy néz ki mint python-ban, csak az eddig említett szintaxis különbségekkel: mindkét esetben a feltételt zárójelbe kell rakni, a futtatandó kódot pedig kapcsos zárójelbe. Példa if-re:

#include <stdio.h>
 
void main() {
  int a = 5;
  if (a > 0) {
    printf("Pozitiv!\n");
  } else {
    printf("Nem pozitiv!\n");
  }
}

Példa while-ra:

#include <stdio.h>
 
void main() {
  int a = 5;
  while (a > 0) {
    printf("Meg pozitiv!");
    a -= 1;
  }
}

Függvény definiálás

Függvényt definiálni a következő módon kell:

<függvény visszatérési típusa>   <függvény neve>  (  <paraméterek, típussal és névvel>  )  {
    <kód>
}

A kódon belül a return ugyanúgy return mint python-ban, és ebben az esetben még zárójelet se kell utána tenni feltétlenül. Például:

int osszead(int a, int b) {
  int osszeg;
  osszeg = a + b;
  return osszeg;
}

És akkor ezután használhatom az osszead függvényt, mint python-ban, bár a visszatérési értékét csak int típusú változóba tudom eltárolni.

Előfordul hogy a függvény nem tér vissza semmivel (mint amikor python-ban "None"-t adtunk vissza, vagy ha semmit), ilyenkor C-ben a függvény visszatérési értékének azt kell megadni hogy "void". Pl.:

void kiir(int a) {
    printf("%d\n", a);
}

És ekkor a visszatérési értéket nem lehet eltárolni (a "void" csak erre használható, "void" típusú változót nem lehet létrehozni). Tehát ilyesmit nem lehetne írni:

int i;
i = kiir(6);

A main nevű függvénynek speciális szerepe van a C-ben. Ez az a függvény, aminek az elejétől el kezd futni a program. Nem lehet csak úgy a fájlba parancsokat írni, main függvényen belülre kell tenni. A legegyszerűbb formája az amit itt láthattatok:

void main() {
  // kód...
}

Lehet kicsit másmilyen is a main, de most egyelőre számunkra jó ez.

for ciklus

A for ciklus kicsit bonyolultabb a C-ben, mint a python-ban. Nem tud magától valami listán/sorozaton végigmenni. Nézzünk egy példát:

#include <stdio.h>
 
void main()
{
    int i;
    for(i = 0; i < 5; i += 1) {
        printf("i = %d\n", i);
    }
}

A for parancs zárójelébe 3 utasítást lehet tenni, pontosvesszővel elválasztva. Lényegében úgy lehet leírni a működését, hogy ha ezt látjuk:

for(A; B; C) {
  <kód>
}

Akkor az ezzel a while-os kóddal ekvivalens:

A;
while (B) {
  <kód>
  C;
}

Csak a for ciklus kompaktabb, és jobban érthető. A zárójelben levő 3 elem:

  • A az inicializáció, az ciklusváltozót a kezdeti értékére állítja
  • B a feltétel
  • C a léptetés.

Mivel C-ben a legtöbb ciklust így szokták megírni, és ilyenkor mindig egyesével kell növelni ciklusváltozó értékét, ezért van rá eg speciális szintaxis, ahelyett hogy "i += 1", írhatom csak azt hogy "++i", és az a "pluszplusz" azt jelenti, hogy növeljük a változó értékét egyel. Így a for ciklus legsztenderdebb kinézete, C-ben és sok más nyelven, ilyesmi:

for(i = 0; i < n; ++i) {

(Ez ugyan sok kódnak nézhet ki egy egyszerű ciklushoz, de sok év kódolás után én már kb. feltételes reflexként, 10 másodperc alatt be tudom írni.)

printf

A parancssorra kiírás az a python-al ellentétben itt nem beépített parancs, hanem csak egy beépített könvtárban levő parancs. (Ez nem véletlen, mint mondtam, a C-t olyan környezetben is használják, ahol nincs értelme parancssornak, mint a kocsi beépített rendszere, így ott ezt a könyvtárat nem fogják kérni a programozók.)

Ez a parancs a printf(). Ez az "stdio.h" beépített könyvtárban van, ezért láthattátok már az eddigi példakódok elején hogy

#include <stdio.h>

Ez körülbelül megfelel a python "import" parancsának. (Megjegyzés, az "#include"-ra, és még majd néhány ilyen speciális, kettőskereszttel kezdődő parancsra nem érvényes hogy nem törődnek a whitespace-szel. Az ilyen spec parancsok mindig a sor végéig tartanak.)

Miután be include-oltuk az "stdio.h"-t, elérhető a printf() függvény. Ennek első paramétere egy karakterlánc, és a legegyszerűbb esetben, ha csak ez az egy paramétere van, akkor egyszerűen kiírja a parancssorra. Magától nem tesz újsort, itt mindenképpen kézzel kell "\n"-eket tenni a szövegbe.

Ha számokat vagy más dolgokat akarunk kiírni, akkor a python-os format-hoz kell hasonlóan használni. Ahova egy egész számot akarunk rakni, oda írjuk a karakterláncba hogy "%d", és aztán plusz paraméterekként megadjuk a beírandó számokat.

Pl ez hogy

#include <stdio.h>
 
void main()
{
    int i;
    i = 10;
    printf("i = %d\ni+1 = %d\n", i, i+1);
}

Azt írja ki hogy

i = 10
i+1 = 11
Személyes eszközök