Rozdiely medzi jazykmi C a Python

Cieľom stránky je upozorniť vás na odlišnosti medzi jazykmi po absolvovaní Základy programování, ktoré by vám mohli prechod na C sťažiť. Ak máte námety na úpravu/doplnenie stránky, napíšte do diskusného fóra.

Vlastnosti jazyka

Interpretácia a kompilácia

Python je jazyk interpretovaný, C je kompilovaný. Jednoducho povedané, zatiaľ čo v Pythone ste napísaný kód "priamo spustili", v C je nutné váš kód pomocou kompilátora previesť do strojového kódu. Výsledkom je spustiteľný (napr. .exe na Windows) súbor, ktorý je platformovo závislý –- čo preložíte pod Windows, nespustíte pod Unixom a naopak. Na vlastnosti platformy, na ktorej bude program bežať, je nutné brať ohľad už pri písaní kódu — rovnaký kód na rôznych platformách môže produkovať rôzne výsledky.

Výhodou kompilácie je, že výsledný program je zvyčajne podstatne rýchlejší ako interpretovaný. Kompilátor vám tiež umožňuje odhaliť niektoré chyby už pred spustením programu — ak sa program nedá skompilovať, kompilátor vypíše popis chyby a miesto, kde k nej došlo. Pri kóde, ktorý síce je možné skompilovať, ale s veľkou pravdepodobnosťou obsahuje chybu, vám kompilátor môže vypísať varovanie (pre dodatočné varovania je vhodné používať prepínače -Wall a -Wextra).

Nevýhodou kompilácie (okrem spomínanej väčšej platformovej závislosti) je menšia miera voľnosti pri používaní programu — nie je možné tak jednoducho "interaktívne riadiť" beh programu.

Typový systém

Podobne ako Python, aj C používa rôzne typy premenných (napr. int pre celé čísla, double pre reálne, atď). Kým Python využíva implicitné dynamické typovanie (tzn. typ premennej je určený automaticky podľa priradenej hodnoty a môže sa počas výpočtu meniť), v C je nutné pri deklarácii premennej jej typ priamo uviesť a počas výpočtu ho nie je možné zmeniť.

Príklad: Ak napr. chceme vytvoriť celočíselnú premennú x, musíme uviesť príkaz int x;. Ak chceme premennej rovno priradiť nejakú hodnotu, môžeme použiť zápis v tvare int x=0;. Naraz je možné deklarovať viacero premenných rovnakého typu: int a,b;. Pri ďalších výpočtoch už typ premennej neuvádzame:

int a = 5, b = 2, c;
c = a + b;
  • Jazyk C sám od seba premenné nijakým spôsobom neinicializuje. Ak použijeme zápis int x;, môže daná premenná obsahovať ľubovoľnú hodnotu. Preto sa odporúča premennú inicializovať na vhodnú hodnotu zápisom int x=0;

  • Jazyk C nemá špeciálny dátový typ na reprezentáciu pravdivostných hodnôt (True/False). Namiesto toho sa ako pravdivostné hodnoty používajú celé čísla. 0 znamená false, nenulové číslo znamená true.

  • Všetky celočíselné typy v C majú obmedzený rozsah hodnôt, na rozdiel od Pythonu nie je možné pracovať s ľubovoľne veľkými číslami. Pri prekročení daného rozsahu dochádza k tzv. pretečeniu. Rozsah jednotlivých dátových typov sa môže líšiť v závislosti na platforme.

Štruktúra programu

Základom spustiteľného programu v jazyku C je funkcia int main(), ktorá sa automaticky zavolá pri spustení programu. Funkcia musí na konci svojho behu (tzn. na konci behu celého programu) vrátiť celé číslo. Podľa konvencie sa používa 0 ak program prebehol bez chyby a číslo väčšie ako 0, ak pri behu programu nastala chyba (napr. chybný vstup, nepodarilo sa otvoriť súbor, …​).

Kód je samozrejme možné a vhodné deliť na pomocné funkcie. Na rozdiel od Pythonu, v C záleží na poradí, v ktorom funkcie definujeme a to nasledovne: ak funkcia f niekde vo svojej definícii volá funkciu g, potom funkcia g musí byť definovaná (alebo aspoň deklarovaná) pred funkciou f. Deklarácia funkcie znamená, že uvedieme len hlavičku (návratový typ, názov funkcie a typ parametrov) bez samotnej definície funkcie.

Kód je možné rozdeliť aj do viacerých súborov (bude preberané neskôr počas semestra).

Syntax

Oddeľovanie príkazov / spájanie príkazov do blokov

V Pythone sa pre oddelenie príkazov zvyčajne používa odriadkovanie, v C je nutné príkazy ukončovať znakom ; (bodkočiarka) bez ohľadu na to, či sa na danom riadku nachádza ďalší príkaz.

V Pythone sa pre spojenie viacerých príkazov do logického bloku (príkazy vo funkcii/podmienke/cykle) používa odsadenie od okraja. V C namiesto toho uzatvárame blok príkazov do zložených zátvoriek {}. Samotné odsadenie v C nemá žiaden význam z hľadiska funkcionality programu, napriek tomu ho však používame z dôvodu prehľadnosti.

Základné operátory

Syntax priradenia hodnoty (=) sa nemení. Tiež sa nemení syntax základných aritmetických operácií (+,-,*,/,%). Je možné použiť skrátené zápisy — napr. x+=5; namiesto x=x+5;. Namiesto x+=1; je možné použiť ++x; alebo x++;, analogicky namiesto x-=1; môžeme napísať x--; či --x;.

C nemá zabudovaný operátor pre umocňovanie (v Pythone **). Pri použití a^b ide o bitový xor, nie umocňovanie.
Pri aplikovaní / na dve celé čísla sa rovnako ako v Pythone 2.7 vypočíta celočíselný podiel. Ak je aspoň jeden z operandov reálne číslo, použije sa reálne delenie. Na pretypovanie hodnoty premennej x použijeme zápis (float) x.

Pre porovnanie dvoch hodnôt (napr. v podmienke) sa aj v C používa ==. Rozdiel nastáva u logických spojok — namiesto not, and a or sa používa !, && a ||.

Je nutné dať si pozor na zdvojenie znakov pri &&, || a ==. Ak v prvých dvoch prípadoch znak nezdvojíme, namiesto logickej spojky sa použije bitová operácia, ak nezdvojíme znak =, namiesto porovnania sa vykoná priradenie. Kód bez zdvojených znakov sa skompiluje, ale s veľkou pravdepodobnosťou nebude počítať správne.

Podmienený príkaz

V C je syntax podmieneného príkazu nasledovná:

if (podmienka)
	prikaz1;
else
	prikaz2;

Na rozdiel od Pythonu podmienka vždy musí byť v zátvorke. Časť else nie je povinná, ak chceme namiesto prikaz1 alebo prikaz2 vykonať viacero príkazov, uzavrieme ich do zložených zátvoriek {}. Napr.

if (a>b) {
	a=0;
	b++;
} else {
	b=0;
	a++;
}
V podmienenom príkaze sa odporúča použiť zátvorky {} aj v prípade, že chceme vykonať len jeden príkaz — tým sa vyhneme zbytočným chybám pri úprave kódu. Ak napríklad máme if (a>b) a=0; a rozhodneme sa pridať do podmienky príkaz b++;, mohlo by sa stať, že napíšeme if (a>b) a=0; b++;. V tomto prípade sa ale príkaz b++; vykoná vždy, nie len ak je splnená podmienka.

While cyklus

Význam while cyklu je rovnaký ako v Pythone, podobná je aj syntax: while (podmienka) prikaz;. Ak chceme opakovať viacero príkazov, uzavrieme ich do zložených zátvoriek {}. Aj v tomto prípade musí byť podmienka vždy v zátvorke. Je tiež možné použiť while cyklus s podmienkou na konci. Jeho syntax je do prikaz; while (podmienka);. Význam tohoto zápisu je rovnaký, ako keby sme použili prikaz; while (podmienka) prikaz; (tzn. prikaz; sa vždy vykoná aspoň raz). Vo while cykle je možné používať príkazy break; a continue; s rovnakým významom ako v Pythone.

For cyklus

For cyklus v C sa od for cyklu v Pythone líši svojou syntaxou aj významom. Zatiaľ čo v Pythone používame zápis for premenna in zoznam: prikaz pre cyklus, kde premenna postupne nadobúda hodnoty daného zoznamu, v C sa používa zápis v tvare:

for (p1; p2; p3)
	prikaz;

Význam zápisu je nasledovný:

  • p1 je príkaz, ktorý sa vykoná len pred prvou iteráciou cyklu (používa sa zvyčajne pre inicializáciu hodnoty riadiacej premennej)

  • p2 je podmienka cyklu, ktorá sa vyhodnotí pred každou iteráciou. Ak nie je splnená, cyklus končí.

  • p3 je príkaz, ktorý sa vykoná na konci každej iterácie (obvykle sa používa na zmenu hodnoty riadiacej premennej)

  • prikaz je príkaz, ktorý chceme opakovať. Podobne ako u while cyklu môžeme použiť aj blok príkazov v zložených zátvorkách.

Ak teda napríklad chceme prepísať for i in range(n): print i do C, napíšeme

for (int i=0; i<n; i++)
	printf("%d ",i);

Je možné používať príkazy break; a continue; s rovnakým významom ako v Pythone.

Definícia funkcie

Pri definovaní funkcie musíme v C vždy uviesť jej návratový typ (typ hodnoty, ktorú funkcia vráti). Ak funkcia nevracia žiadnu hodnotu, použijeme namiesto návratového typu označenie void. Tiež je nutné špecifikovať aj typy jednotlivých parametrov. Na rozdiel od Pythonu, nie je možné použiť predvolené hodnoty parametrov. Definícia funkcie má nasledujúcu štruktúru:

typ nazov(typ_1 p_1, typ_2 p_2, ... , typ_n p_n) {
	prikaz_1; prikaz_2; ... prikaz_m
}

Napríklad funkcia pre súčet dvoch celočíselných premenných sa zapíše nasledovne:

int sucet(int a, int b) {
	return a+b;
}

Používanie funkcií z iných súborov (napr. rôznych knižníc)

V Pythone sa pre zahrnutie funkcií z iných súborov používa príkaz import, ktorého analógiou (s drobnými rozdielmi) je v C direktíva include. Zatiaľ čo pre import existujú rôzne variácie (import x,import x as y, from x import a, from x import *) u include existujú len dva druhy zápisu: #include <subor.h> pre súbory, ktoré sa nachádzajú vo vopred definovaných adresároch, ktoré kompilátor automaticky prehľadáva (napríklad knižnice, ktoré sú súčasťou jazyka) a #include "subor.h" pre ostatné súbory. Oba zápisy sú v podstate ekvivalentné from subor import *, tzn. v programe je možné používať všetky funkcie definované v danom súbore priamo pod ich menami.

Dátové štruktúry

Pole

C neobsahuje zoznamy ani n-tice ako ich poznáme z Pythonu. Ako čiastočnú náhradu je možné využiť pole. Prístup k položke v poli je podobný ako u zoznamu v Pythone, tzn. a[i] označuje prvok na i-tej pozícii poľa a, indexuje sa od nuly. Nie je možné použiť zápisy pre podzoznamy ako napríklad L[1:47:2] ani zápis a[-1] pre prístup k poslednému prvku. Na polia nie je možné priamo používať príkaz priradenia, meniť ich veľkosť, či priamo porovnať, či obsahujú rovnaké prvky. Na rozdiel od Pythonu musia mať všetky prvky v poli rovnaký typ. Ak napríklad chceme vytvoriť pole pre 10 celých čísel, použijeme zápis int a[10];. (Polia je možné vytvárať aj viacrozmerné, tiež je možné ich dynamicky alokovať. Detailnejšie sa práca s poľom bude preberať v priebehu semestra.)

C nekontroluje prístup mimo poľa -– ak napr. máme 5 prvkové pole a niekde v programe pristupujeme na 7. pozíciu, program sa korektne preloží, jeho chovanie však bude nedefinované (program môže padnúť na Segmentation Fault, ale môže aj prebehnúť zdanlivo bez chyby. Môže sa dokonca stať, že si prepíšete hodnotu premennej v inej časti programu).

Reťazce

C na rozdiel od Pythonu neobsahuje špeciálnu dátovú štruktúru pre prácu s reťazcami. Namiesto toho sa používa pole znakov, v ktorom koniec reťazca označuje špeciálny znak \0. Na prácu s takto definovanými reťazcami nie je možné použiť základné operátory, ako ich náhradu je možné použiť niektoré funkcie zo string.h — napríklad strcmp pre porovnanie reťazcov, strcat pre zreťazenie, strcpy pre kopírovanie atď.

Reťazce budú detailne preberané počas semestra.

Pokročilejšie dátové štruktúry

C samo o sebe neobsahuje dátové štruktúry zásobník, fronta, množina, slovník atď. V praxi je možné použiť existujúce implementácie, alebo si uvedené štruktúry samostatne naprogramovať (to si vyskúšate v priebehu semestra :-) ).

Vstup a výstup

Základné funkcie pre prácu so štandardným vstupom a výstupom sú definované v hlavičkovom súbore stdio.h.

Štandardný výstup

Ak chceme na výstup vypísať jednoduchý text a odriadkovať, môžeme použiť funkciu puts (napr. puts("Hello World"); je ekvivalent Pythonovského print "Hello World"). Ak chceme vypísať text bez odriadkovania, môžeme použiť funkciu printf. Túto funkciu použijeme aj prípade, že chceme vypísať hodnoty nejakých premenných. Funkcia pracuje s premenlivým počtom parametrov. Prvým je tzv. formátovací reťazec, ktorý obsahuje tzv. špecifikátory — špeciálne reťazce začínajúce znakom %. Tie sa vo výpise nahradia hodnotami, ktoré sú uvedené ako ďalšie parametre funkcie. Presná podoba špecifikátorov závisí od typu hodnoty, ktorú chceme vypisovať — napr. %d pre celé čísla, %f pre typ float, atď. Presný prehľad je dostupný v dokumentácii. Volanie funkcie môže vyzerať napríklad takto:

printf("Hodnota premennej x typu float je %.3f (tri desatinne miesta)", x);

Formátovací reťazec tiež môže obsahovať sekvencie začínajúce znakom \ — napr. \n pre odriadkovanie, \t pre tabulátor, \" pre úvodzovky, pass:c[\\] pre výpis znaku \, …​

Štandardný vstup

Na načítanie údajov od užívateľa sa najčastejšie používa funkcia scanf, ktorej syntax je podobná ako u funkcie printf. Prvým parametrom je formátovací reťazec, ktorý určuje, aké hodnoty chceme načítať (ich typ a počet) a prípadne detailnejšie špecifikuje formát vstupu. Nasledujú adresy premenných, kam chceme jednotlivé hodnoty načítať. (Adresu premennej x zapíšeme ako &x, dôvody a význam tohto zápisu budú vysvetlené v priebehu semestra). Ak napríklad chceme načítať dve celé čísla do premenných x,y, ktoré môžu byť na vstupe oddelené ľubovoľnými bielymi znakmi (medzera, tabulátor, nový riadok), použijeme scanf("%d%d",&x,&y);. Podrobnosti sú opäť dostupné v dokumentácii.

Formátovací reťazec u scanf neslúži pre zobrazenie výzvy na zadanie hodnoty. Tzn. zápis scanf("Zadaj x: %d",&x); nie je správnou náhradou Pythonovského input("Zadaj x: "). Pre zobrazenie výzvy je potrebné použiť printf (resp. puts). Správnou náhradou input("Zadaj x: ") teda je printf("Zadaj x: "); scanf("%d",&x);