+ All Categories
Home > Documents > Programovací jazyk C

Programovací jazyk C

Date post: 13-Jan-2016
Category:
Upload: toby
View: 46 times
Download: 3 times
Share this document with a friend
Description:
Ken Thompson 1943 - Dennis MacAlistair Ritchie 1941 -. Počítač PDP-7. Programovací jazyk C. http://cs.wikipedia.org/wiki/C_(programovací_jazyk). - PowerPoint PPT Presentation
82
Programovací jazyk C Ken Thompson 1943 - Dennis MacAlistair Ritchie 1941 - Počítač PDP-7 Operační systém UNIX byl vyvinut v sedmdesátých letech pro společnost Bell Telephone Laboratories (divize AT&T) na počítači PDP-7. V souvislosti s UNIXem byl také vyvinut programovací jazyk C. UNIX byl ve vyšších verzích psán v C-čku sám, což umožnilo jeho přenositelnost na další počítače (strojový kód je sám o http:// cs.wikipedia.org/ wiki/ C_(programovací_jazyk ) Pro projekt „Cesta k vědě“ (veda.gymjs.net) vytvořil V. Pospíšil ( [email protected] ) a A. Šulc. Modifikace a šíření dokumentu podléhá licenci CC-BY-SA. Evropský sociální fond Praha & EU: Investujeme do vaší budoucnosti
Transcript
Page 1: Programovací jazyk C

Programovací jazyk C

Ken Thompson 1943 -

Dennis MacAlistair Ritchie 1941 -

Počítač PDP-7

Operační systém UNIX byl vyvinut v sedmdesátých letech pro společnost Bell Telephone Laboratories (divize AT&T) na počítači PDP-7. V souvislosti s UNIXem byl také vyvinut programovací jazyk C. UNIX byl ve vyšších verzích psán v C-čku sám, což umožnilo jeho přenositelnost na další počítače (strojový kód je sám o sobě zpravidla nepřeno-sitelný).

http://cs.wikipedia.org/wiki/C_(programovací_jazyk)

Pro projekt „Cesta k vědě“ (veda.gymjs.net) vytvořil V. Pospíšil ([email protected]) a A. Šulc. Modifikace a šíření dokumentu podléhá licenci CC-BY-SA.

Evropský sociální fond Praha & EU: Investujeme

do vaší budoucnosti

Page 2: Programovací jazyk C

Programovací jazyk C

GNU Compiler Collection (zkráceně GCC) je sada kompilátorů vytvořených v rámci projektu GNU. Původně se jednalo pouze o překladač programovacího jazyka C (a zkratka tehdy znamenala GNU C Compiler), později byly na stejném společném základě vytvořeny překladače jazyků C++, Fortran, Ada a dalších.

Původním autorem GCC je Richard Stallman, který ho roku 1987 vytvořil jako jednu ze základních částí svého projektu GNU; dnes projekt zastřešuje nadace Free Software Foundation. GCC je šířen pod licencí GNU GPL a stal se již de facto standardním kompilátorem v open source operačních systémech unixového typu, ale používá se i v některých komerčních operačních systémech, např. na Mac OS X. Existují také jeho portace pro Microsoft Windows (např. mingw).

http://en.wikipedia.org/wiki/GNU_Compiler_Collection

Richard Matthew Stallman 1953 -

Page 3: Programovací jazyk C

První progámek a jeho překlad

Programů, které zajišťují překlad zdrojáku do strojového kódu a sestavení jednotlivých přeložených souborů do jednoho je více. My budeme používat GNU překladač gcc.

Jako příklad uveďme jednoduchý prográmek, který je uložen v souboru hello.c .

#include <stdio.h>#include <stdlib.h>

int main( void ){ printf( "Hello, world!\n" );} /*main*/

Co která řádka konkrétněji znamená později, program jen vypíše text „Hello, world!“ na obrazovku.

Povšimněte si použití syntax highlight, který je běžný u většiny textových editorů.

Překlad i linkování se zařídí příkazem g++ (který je součástí systému gcc)

g++ -c hello.cg++ -o hello hello.o

Výsledkem bude binární soubor hello.o a spustitelný program hello.

Page 4: Programovací jazyk C

Skripty pro překlady

Je-li souborů se zdrojovým kódem více, je výhodné použít skript Makefile nebo další nástroje jako CMake (www.cmake.org) nebo SCons (www.scons.org).

Přeložený modul 1

Přeložený modul 2

Přeložený modul 3

Program ve strojovém

kódu

#define itemDIRECTORY "directory"#define itemVOTES "votes"#define itemLIST "list"#define itemFILE "file"#define itemOUTPUT "output"#define itemUNVALID "unvalid"

#define MAX_NR_OF_CANDIDATES 100#define cand2D( x, y ) ( MAX_NR_OF_CANDIDATES*(y) + (x) )

class CElectionProcessor { public: CElectionProcessor( char *descFileName, CCategory *cat = NULL, CEntryList *ent = NULL, CVoteList *vot = NULL); ~CElectionProcessor( void ); void WriteElectionSetup( FILE *targetFILE = stdout ); int Run( FILE *out );

char *GetVoteListFileName( void ) { return voteListFileName; };

char *GetMailDirectory( void ) { return mailDirectory; };

char *GetOneMailFileName( void ) { return oneMailFileName; };

char *GetMailFileNameList( void ) { return mailFileNameList; };

char *GetOutputFileName( void ) { return outputFileName; };

Zdrojový kód 1

Zdrojový kód 2

Zdrojový kód 3Překlad (compile)

Sestavování (link)

Překlad jednoho souboru = jedno zavolání překlada-če. Velké programy mají obvykle stovky zdrojových souborů.

Překládat lze navíc něko-lika režimy (pro debug-ging, pro různé typy počí-tačů), nebo jenom změně-né části kvůli úspoře času. To nelze ručně.

Page 5: Programovací jazyk C

Skripty pro překlady

Přeložený modul 1

Přeložený modul 2

Přeložený modul 3

Program ve strojovém

kódu

#define itemDIRECTORY "directory"#define itemVOTES "votes"#define itemLIST "list"#define itemFILE "file"#define itemOUTPUT "output"#define itemUNVALID "unvalid"

#define MAX_NR_OF_CANDIDATES 100#define cand2D( x, y ) ( MAX_NR_OF_CANDIDATES*(y) + (x) )

class CElectionProcessor { public: CElectionProcessor( char *descFileName, CCategory *cat = NULL, CEntryList *ent = NULL, CVoteList *vot = NULL); ~CElectionProcessor( void ); void WriteElectionSetup( FILE *targetFILE = stdout ); int Run( FILE *out );

char *GetVoteListFileName( void ) { return voteListFileName; };

char *GetMailDirectory( void ) { return mailDirectory; };

char *GetOneMailFileName( void ) { return oneMailFileName; };

char *GetMailFileNameList( void ) { return mailFileNameList; };

char *GetOutputFileName( void ) { return outputFileName; };

Zdrojový kód 1

Zdrojový kód 2

Zdrojový kód 3

Překlad (compile)

Sestavování (link)

Dynamicky linkovatelná

knihovna

(.dll ve windows, .so pod

linuxem)

za běhu programu

Je-li souborů se zdrojovým kódem více, je výhodné použít skript Makefile nebo další nástroje jako CMake (www.cmake.org) nebo SCons (www.scons.org).

Page 6: Programovací jazyk C

Skripty pro překlady

Nyní se podívejme, jak by se překládal a linkoval program, který má více zdrojových souborů. Řekněme, že struktura zdrojáků je následující:

/inc/main.h

/src/main.cpp

/inc/module.h

/src/module.cpp

main.o

module.o

binary

překlad linkování

Překlad i linkování se zařídí příkazem g++ následovně

g++ -c /src/module.cg++ -c /src/main.cg++ -o binary main.o module.o

Vidíme, že složitost příkazů narůstá. Pokud by zdrojáků bylo dvacet, byl by překlad již velmi komplikovaná záležitost.

Page 7: Programovací jazyk C

Skripty pro překlady - Makefile

Práci nám usnadní program make. Předpokládejme strukturu

/inc/main.h

/src/main.cpp/inc/source1.h

/src/source1.cpp

main.o

source1.o

binary

/inc/source2.h

/src/source2.cpp source2.o

binary: main.o source1.o source2.og++ $(CFLAGS) -o binary main.o source1.o

source2.o

main: ./inc/main.h ./src/main.cppg++ $(CFLAGS) -c ./src/main.cpp

source1: ./inc/source1.h ./src/source1.cppg++ $(CFLAGS) -c ./src/source1.cpp

source2: ./inc/source2.h ./src/source2.cppg++ $(CFLAGS) -c ./src/source2.cpp

clean:rm –f *.o *.d

Skript pro make se jmenuje Makefile. Popisuje, které soubory se mají jak překládat a říká programu make, co má přeložit, pokud se některý soubor změní. Například byl-li změněn od posledního překla-du pouze soubor source2.h, program make provede pouze příkaz g++ na šestém řádku.

Příkaz make lze zavolat přímo se jménem modulu, např.

make source1

a pak se provede pouze příslušná část skriptu.

Pozn.: před příkazy g++ (případně jinými) v jednotlivých oddílech skriptu musí být znak „tabelátor“.

Page 8: Programovací jazyk C

Skripty pro překlady - Odkazy

Podrobější informace o Makefile na webu (EN):

http://en.wikipedia.org/wiki/Make

http://www.gnu.org/software/make/manual/html_node/index.html

http://www.opussoftware.com/tutorial/TutMakefile.htm

Příklad složitějšího skriptu Makefile:

http://veda.gymjs.net/

Alternativní nástroje k Makefile

CMake - http:// www.cmake.org/

SCons - http:// www.scons.org/

Page 9: Programovací jazyk C

Základní syntax

2)1)

4)3)

7)

5)

6)

1) Direktiva preprocesoru (začíná znakem #). Zde je vnořen soubor stdio.h

2) Deklarace funkce. Speciálně funkce main je vstupní bod programu.

3) Blok příkazů (např. tělo funkce). Začíná znakem { a končí znakem } .

4) Deklarace proměnné. Zde je deklarována proměnná count, která má celočíselný typ int.

5) Cyklus typu for. Proběhne 500x .

6) Příkaz vykonaný v každém průběhu cyklu.

7) Konec funkce, návratová hodnota je 0.

Page 10: Programovací jazyk C

Komentáře

#include <stdio.h>

int main( void )

{ // Funkce main je vstupní bod programu

int count;

// Deklarace řídící proměnné cyklu

for( count = 1; count <= 500; count++ ) printf( “I will not throw paper airplanes in class. \n“ );

// Cykl proběhne 500x a vypíše zprávu

return 0;

// Konec programu, návratová hodnota je 0

} /*main*/

Komentáře dovolují programátorovi vpisovat do zdrojového kódu poznámky, díky nimž se kód stane přehlednějším. Lze do nich také vpisovat například „TO DO“ poznámky nebo dočasně vyřadit část kódu pro potřeby testování.

// …….. komentář až do konce řádku

/* */ … cokoliv mezi těmito dvojznaky je komentář

Dobrá rada : Vždy pište komentáře i do sebejednodušších programů v poměru 1 řádek kódu ku 1 řádku komentáře. Umožní to orientovat se v programu komukoliv, kdo ho dostane po vás, nebo i vám samotným po delší době.

Page 11: Programovací jazyk C

Typy a deklarace proměnných

Typ Popis Velikost Rozsah

char Znak nebo velmi malé celé číslo

1 byte signed : -128 až 127 unsigned : 0 to 255

short int short

Malé celé číslo 2 byty signed : -32768 až 32767 unsigned : 0 to 65535

int long int

Celé číslo 4 byty signed : -2147483648 až 2147483647 unsigned : 0 až 4294967295

long long int long long

Velké celé číslo 8 bytů signed : -moc až + moc unsigned : 0 až SPOUSTA

bool Booleaovská hodnota. Může být buď true nebo false.

1 byte true nebo false

float Reálné číslo s jednoduchou přesností

4 byty +/- 3.4e +/- 38 (~7 číslic)

double long double

Reálné číslo s dvojitou přesností

8 bytů +/- 1.7e +/- 308 (~15 číslic)

wchar_t Rozšířený znak 2 or 4 byty 1 rozšířený znak

int cislo; // Deklarace celočíselné proměnné. Její hodnota je náhodná.

int cislo = 1 ; // Deklarace celočíselné proměnné. Její hodnota je jedna.

float pi = 3.1415; // Deklarace reálné proměnné, její hodnota je 3.1415

double hodnota = 2.56e-5; // Deklarace reálné proměnné, její hodnota je 0.0000256

Page 12: Programovací jazyk C

Lexikální rozsah platnosti

#include <stdio.h>

int main( void )

{

int pocet = 50;

for( int count = 1; count <= pocet; count++ )

{

int druhaMocnina;

int dalsiCislo;

druhaMocnina = count * count;

printf( “Druha mocnina %i je %i \n“, count, druhaMocnina );

dalsiCislo = count * pocet;

printf( “%i * %i = %i \n“, count, pocet, dalsiCislo );

} /*for*/

pocet = 100;

dalsiCislo = 20;

} /*main*/

Deklarace proměnné je platná pouze ve svém bloku kódu (a všech vnořených)

Proměnná count je viditelná pouze v bloku for cyklu.

Proměnné druhaMocnina a dalsiCislo jsou viditelné pouze v bloku for cyklu. Jejich použití za tímto blokem vyvolá chybu při překladu.

Proměnná pocet je viditelná v celém bloku funkce main (a tedy i uvnitř for cyklu).

Page 13: Programovací jazyk C

Lexikální rozsah platnosti

#include <stdio>

int main( void )

{

int dalsiCislo = 500;

printf( “Cyklus brzy zacne, dalsiCislo = %i \n“, dalsiCislo );

for( int count = 1; count <= 5; count++ )

{

int dalsiCislo = count + 1;

printf( “count = %i, dalsiCislo = %i \n“, count, dalsiCislo );

} /*for*/

printf( “Cyklus probehl. dalsiCislo = %i \n“, dalsiCislo );

} /*main*/

Deklarace proměnné je platná pouze ve svém bloku kódu (a všech vnořených). Pokud je ve vnořeném bloku deklarována další proměnná téhož jména, je výše deklarovaná proměnná dočasně překryta. Přístupná bude po opuštění vnořeného bloku.

Cyklus brzy zacne, dalsiCislo = 500

count = 1, dalsiCislo = 2

count = 2, dalsiCislo = 3

count = 3, dalsiCislo = 4

count = 4, dalsiCislo = 5

count = 5, dalsiCislo = 6

Cyklus probehl, dalsiCislo = 500Dobrá rada : Jména proměnných držte unikátní v celém logickém celku (tělo jedné procedury, modul). Překrytí jmen proměnných jsou zbytečná a vedou k častým chybám.

Page 14: Programovací jazyk C

Lexikální rozsah platnosti

Proměnné je možné deklarovat i na globální úrovni, tj. kdekoliv ve zdrojovém kódu mimo těla funkcí (vč. funkce main). Ty jsou pak viditelné kdekoliv od své deklarace.

Dobrá rada: Deklaraci globálních proměnných je třeba si dobře rozmyslet speciálně v případech, kdy se program skládá z více souborů se zdrojovým kódem!

zdroják1.c

int jakesiCislo;

zdroják2.c

float jakesiCislo;

PROBLÉM

Page 15: Programovací jazyk C

Konstanty

const int pathWidth = 100;

const char tabulator = '\t' ;

const double pi = 3.1415 ;

#define pathWidth 100

#define tabulator '\t'

#define pi 3.1415

Deklarace proměnné uvozená klíčovým slovem const se považuje za hodnotu určenou pouze ke čtení. Jakýkoliv pokus o zápis do takto deklarované proměnné vyústí v chybu při překladu.

Konstanty je možné definovat i pomocí direktivy preprocesoru define. Před vlastním překladem preprocesor fyzicky nahradí každý výskyt jména konstanty udanou hodnotou. O direktivách preprocesoru bude pojednáno podrobně později.

Zápis číselných konstant je dovolen v desítkové, osmičkové a šestnáctkové soustavě. Osmičkové číslo začína nulou, šestnáctkové dvojznakem 0x. Reálná čísla mohou být zapsána v exponenciální formě (se základem deset).

75 // Dekadicky

0113 // Osmičkově

0x4B // Šestnáctkově

1.5e-12 // Reálné číslo

Page 16: Programovací jazyk C

Konstanty

'z‚

'p'

"Hello world"

"How do you do?"

Pro zápis znakových a řetězcových konstant se používají jednoduché a dvojité uvozovky. Znakové i řetězcové konstanty mohou obsahovat speciální znaky (znak se zpětným lomítkem, viz tabulka vlevo dole). Pro práci s řetězcovými konstantami platí následující pravidla :

\n Nový řádek

\rNávrat kurzoru na začátek řádky

\t Tabulátor

\v Vertikální tabulátor

\b Backspace

\fPosun na novou stránku (form/page feed)

\a Pípnutí

\' Jednoduchá uvozovka (')

\" Dvojitá uvozovka (")

\? Otazník (?)

\\ Zpětné lomítko (\)

" Retezec, který je dlouhy a nevejde se \ na jednu radku"

"toto tvori" "jeden cely" "retezec" "znaku"

L"Toto je řetězec rozšířených znaků : α, β, €, £ …"

Dlouhý řetězec lze na více řádek rozdělit pomocí zpětného lomítka.

Lze spojit za sebou několik řetězcových konstant

Řetězec rozšířených znaků je označen znakem L.

Pozor, tato pravidla neplatí pro práci s řetězcovými proměnnými (viz později)!

Page 17: Programovací jazyk C

Složené typy – statická pole

Pole je vektor (soubor, souhrn, posloupnost - jak chcete) proměnných stejného typu. Tyto proměnné ovšem nejsou samostatně pojmenované, pouze oindexované a jsou přístupné přes jméno celého pole a příslušný index. Statické pole (tedy takové, u nějž je počet prvků znám již při překladu), se deklaruje takto:

typ jméno [n]

kde n je počet prvků. Schéma příslušné struktury lze zobrazit například takto:

0. prvek 1. prvek 2. prvek 3. prvek 4. prvek ... (n-2). prvek (n-1). prvek

Pole má n prvků indexovaných od nuly.

int poleCisel [10];

int jinePoleCisel [5] = { 1, 2, 3, 4, 5 } ;

float realnePole [4] = { 0.1, 1.2, 2.3, 3.4 } ;

float spatne [3] = { 0.1, 0.2, 0.3, 0.4, 0.5 } ;

Příklady na deklaraci statických polí. prvky pole lze již při deklaraci naplnit specifikova-nými hodnotami pomocí složených závorek. Poslední z příkladů vyvolá chybu při překladu.

poleCisel [3] = 12 ;

jinePoleCisel [0] = poleCisel[3] + poleCisel[4] ;

realnePole[4] = 1.5 ;

realnePole[ poleCisel[3] ] = 10;

Příklady na přístup do statických polí. Předposlední z příkladů vyvolá (pravděpodobně) chybu při překladu, poslední pak buď chybu za běhu nebo způsobí zcela

iracionální a náhodné chování programu.

Page 18: Programovací jazyk C

Složené typy – Výčty

enum jméno typu { hodnota1, hodnota2, hodnota3, . . } jméno proměnné;

Výčty jsou ordinární (celočíselné) typy, které mohou ale obsahovat pouze vybrané hodnoty. Tyto hodnoty jsou nějak pojmenované.

Například pro jména měsíců by bylo možné deklarovat výčet mesic_typ, který obsahuje jména měsíců. S těmito jmény pak lze pracovat jako s hodnotami proměnné.

Pozn.: mesic je ve skutečnosti celočíselná proměnná a jména měsíců jsou zástupná jména pro celá čísla. Defaultově je první položka výčtu nula, lze to ovšem změnit přiřazením.

enum mesic_typ { leden, unor, brezen, duben, kveten, cerven, cervenec, srpen, zari, rijen, listopad, prosinec } mesic;

mesic = leden;mesic = unor;if( mesic == brezen ) JaroJeTady();

enum barvy_typ { bila = 1, modra = 2, cervena = 4, zelena = 8, cerna = 16 } barva;

Barvy ve druhém příkladu mají jako hodnoty přiřazené mocniny dvou a je tedy možné je použít jako flags (viz binární operátory).

Page 19: Programovací jazyk C

Složené typy - Struktury

Datová struktura je skupina proměnných shromážděná pod jedním jménem. Syntax její deklarace vypadá následovně:

struct jméno typu { typ člen 1; typ člen 2; typ člen 3; ...} jméno proměnné;

Typy jednotlivých členů můžou být libovolné základní typy nebo další složené (struktura, výčet, pole). K jednotlivým členům proměnné typu struktura se přistupuje pomocí operátoru "." (tečka):

struct complex { float Re; float Im;} A, B, C;

A.Re = 10.1;A.Im = -3;

V příkladu nalevo jsou vytvořeny tři proměnné A, B a C jako struktury představující komplexní číslo (obsahují dvě reálná čísla, jedno pro reálnou a druhé pro imaginární část). Do proměnné A je poté uložena hodnota 10.1 - 3i .

Page 20: Programovací jazyk C

Složené typy - Struktury

Se strukturou lze nakládat stejně jako se základními typy, například vytvářet pole:

#include <stdio.h>

struct film_typ{ char Nazev[100]; int Rok; } filmy [10];

int main ( void ) { for ( int n = 0; n < 3; n++ ) { printf( "Zadejte nazev : " ); scanf( "%s", filmy[n].Nazev ); printf( "Zadejte rok: " ); scanf( "%u", & filmy[n].Rok ); } printf( "\nZadal jste nasledujici tri filmy : \n" ); for ( int n = 0; n < 3; n++ ) printf( "%s (%i) \n", filmy[n].Nazev, filmy[n].Rok ); }

Zadejte nazev : Blade_Runner

Zadejte rok : 1982

Zadejte Nazev : Matrix

Zadejte rok : 1999

Zadejte nazev : Taxi_Driver

Zadejte rok : 1976

Zadal jste nasledujici tri filmy :

Blade_Runner (1982)

Matrix (1999)

Taxi_Driver (1976)

Page 21: Programovací jazyk C

Přetypování

Převod výrazu či proměnné jednoho typu na typ jiný se nazývá přetypování (type-casting). U některých typů provádí překladač přetypování sám - tzv. implicitní přetypování :

short a = 1024; // Proměnná typu short má délku 2 byte int b ; // Proměnná typu int má délku 4 byteb = a; // Pokud přiřazujeme do proměnné typu int proměnnou typu short, dva byty proměné // short se zkopírují a dva zbývající byty proměnné int vynulují.

V některých případech je ale překladači nutné říct, co přesně má na co přetypovat. Zejména je to třeba při práci s ukazateli, ale i některé základní typy to vyžadují. Přetypování lze v zásadě zapsat dvěma způsoby:

int a = 1024; float b;

b = (float) a; // Starší způsob vyhovující normě ANSI-Cb = float (a); // Přetypování na způsob volání funkce

a = (int) b;a = int (b);

Převod celého čísla na reálné je relativně komplikovaná záležitost, protože organizace jednotlivých bitů ve float a int jsou zcela odlišné, ačkoliv celková délka je v obou případech stejná (4 byte).

Page 22: Programovací jazyk C

Operátory

Máme-li deklarované proměnné, chceme s nimi také nějak operovat. Pro tento účel C integruje tzv. operátory. Na rozdíl od mnoha jiných jazyků nejsou operátory pojmenovány slovy, ale symboly, které se skládají nikoliv z písmen, ale jiných, na klávesnici běžně dostupných znaků. Operátorů je poměrně velký počet.

Přiřazovací operátor (=)

a = 5;

a = b;

Příkaz vlevo uloží do proměnné a hodnotu 5 v prvním případě, respektive obsah proměnné b v druhém. Výraz nalevo od rovnítka se označuje jako lvalue (left value), výraz napravo jako rvalue (right value). Lvalue musí vždy být proměnná, zatímco rvalue může být jakákoliv kombinace proměnných, konstant či výsledů jiných operací.

Přiřazování se vždy děje zprava doleva – hodnota proměnné vlevo se změní až po vyhodnocení výrazu napravo. Předchozí hodnota je v tomtéž okamžiku zapomenuta.

int a, b; // Deklarace, hodnoty a a b jsou nyní neurčené

a = 5; // Do a je uložena hodnota 5

b = 4; // Do b je uložena hodnota 4

a = b; // Do a je uložena hodnota b, tedy 4

b = 15; // Do b je uloženo 15, v a je stále 4 (předchozí rovnítko již // nehraje žádnou roli

Přiřazení není matematická

rovnost!

Page 23: Programovací jazyk C

Operátory

Aritmetické operátory ( +, -, *, /, % )

Aritmetické operátory fungují tak, jak od nich čekáme (součet, rozdíl, násobek, podíl). Podle typu operandů je operátor dělení celočíselný nebo reálný. Operátor % (modulo) je definován pouze pro celá čísla a celočíselné proměnné a vrací zbytek po dělení :

int a, b; // Deklarace, hodnoty a a b jsou nyní neurčené

a = 5 + 3; // Do a je uložena hodnota 8

b = 5 / 3; // Do b je uložena hodnota 1

a = 6 / 3; // Do a je uložena hodnota 2

b = 7 % 3; // Do a je uložena hodnota 1

float x = 6.2 / 12.8; // Do x je uložena hodnota 0.484375

x = a - b; // Do x je uložena hodnota a – b = 2 – 1 = 1. Zde dochází k automatickému // přetypování z celočíselného typu na reálný.

Pozn. : Lvalue jednoho operátoru může být rvalue (nebo částí rvalue) jiného operátoru. Jsou tedy dovoleny konstrukce typu :

a = 5 + (b = 3);

a = b = c = d = 6;

Podobné konstrukce mohou být užitečné třeba v cyklech, obecně je ale lepší se jim vyhnout, neboť znepřehledňují program.

Page 24: Programovací jazyk C

Operátory

Bitové operátory ( ^, &, |, ~ )

Bitové operátory provádějí logické operace s jednotlivými bity operandů (celočíselných). Označíme-li 0 za false a 1 za true, pak:

0 ^ 0 = 00 ^ 1 = 11 ^ 0 = 11 ^ 1 = 0

0 | 0 = 00 | 1 = 11 | 0 = 11 | 1 = 1

0 & 0 = 00 & 1 = 01 & 0 = 01 & 1 = 1

bitovéAND(konjunkce)

bitovéOR(disjunkce)

bitovéexkluzivní OR respektive XOR(nonekvivalence)

Operace jsou provedeny s každý bitem operandů zvlášť, tedy například:

Bitová konjunkce a & b

1 1 0 0 1 0 0 0 200

1 1 0 1 1 0 0 1 217

1 1 0 0 1 0 0 0 200

Bitová nonekvivalence a ^ b

1 1 0 0 1 0 0 0 200

1 1 0 1 1 0 0 1 217

0 0 0 1 0 0 0 1 17

Bitová disjunkce a | b

1 1 0 0 1 0 0 0 200

1 1 0 1 1 0 0 1 217

1 1 0 1 1 0 0 1 217

Bitová negace (doplněk) pak převrátí smysl všech bitů, tj:

Bitová negace ~a

1 1 0 0 1 0 0 0 200

0 0 1 1 0 1 1 1 55

Page 25: Programovací jazyk C

Operátory

Bitové operátory ( ^, &, |, ~ )Bitové operátory ve spojení s celočíselnými proměnnými mohou tvořit účinnou a úspornou metodu ukládání skupin binárních stavů (flags). Máme-li například v experimentu částicové fyziky řadu subdetektorů (řekněmě 32) u nichž je pro každý event nutné uložit stav (zanznamenal/nezaznamenal průchod částice). Pokud bychom použili 32 proměnných typu bool, tato data by pro každý event zabrala 32 byte a pro milion událostí 32 MB, což je hodně.

Přitom stav včech detektorů lze úsporně uložit do 4 byte, pokud každý bit celočíselné proměnné typu int přiřadíme právě jednomu detektoru :

1

1

0

1

0

0

1

0. bit

1. bit

2. bit

3. bit

4. bit

30. bit

31. bit

int detektory;

detektory = detektory | 4 ; // Přiřazení stavu 3. detektoru „ZÁSAH“

detektory = detektory & (~4) ; // Přiřazení stavu 3. detektoru „NIC“

stav = detektory & 4 ; // Zjištění stavu 3. detektoru

1 1 0 1 0 … 0 1

0 0 1 0 0 … 0 0

1 1 1 1 0 … 0 1

OR

=

1 1 1 1 0 … 0 1

1 1 0 1 1 … 1 1

1 1 0 1 0 … 0 1

AND

=NOT

NIC ZÁSAH

Obecně pro n-tý dektektor nahradíme konstantu 4 konstantou 2n.

Page 26: Programovací jazyk C

Operátory

Relační operátory Relační operátory slouží pro porovnávání číselných hodnot. Bez rozsáhlejších úprav (přetěžování) je nelze aplikovat na žádné jiné. Jejich výsledek je buď 0 (false) nebo 1 (true) :

a == b // a je rovno b

a != b // a není rovno b

a < b // a je menší než b

a <= b // a je menší nebo rovno b

a > b // a je větší než b

a >= b // a je větší nebo rovno b

Logické operátory ( &&, ||, ! ) Logické operátory slouží pro spojování podmínek (logických výrazů) do větších celků. Operandy mohou být proměnné typu bool nebo celá čísla, kde pak 0 je považována za false a cokoliv jiného za true. Výsledek je booleanovská hodnota. Nepleťe si je s bitovými operátory!

P && Q // Vrací true jsou-li P i Q pravdivé

P || Q // Vrací true je-li alespoň jeden z P a Q pravdivý

! P // Vrací true je-li P nepravdivý

Page 27: Programovací jazyk C

Operátory

Inkrementace a dekrementace ( ++, -- )Operátor ++ zvýší hodnotu operandu o jedna, -- ji o jedna sníží. Existují v prefixové a postfixové variantě. Efekty jsou následující (předpokládejme, že hodnota b před operací je 10) :

b++ // Zvětší obsah b o jedna, tj. potom b je 11

a = b++ // a je rovno 10, b je rovno 11

a = ++b // a je rovno 11, b je rovno 11

b-- // Zmenší obsah b o jedna, tj potom b je 9

a = b-- // a je rovno 10, b je rovno 9

a = --b // a je rovno 9, b je rovno 9

Bitové posuny ( <<, >> ) Operátor posune bity celočíselné proměnné vlevo či vpravo o udaný počet míst a zbytek doplní nulami. Tedy například

char a = 64;

char b = a << 3;

0. 1. 2. 3. 4. 5. 6. 7.

0 0 0 0 0 0 1 0

0 0 0 1 0 0 0 0

char a = 133;

char b = a >> 2;

0. 1. 2. 3. 4. 5. 6. 7.

1 0 1 0 0 0 1 1

0 0 1 0 1 0 0 0

648

13320

Page 28: Programovací jazyk C

Podmínky

Podmínky slouží pro rozhodování a větvení programu jejich syntax je následující:

if (podmínka) příkaz 1; else příkaz 2 ;

Část else je nepovinná. Tedy například

if ( a >= 0 )

printf( “Cislo a je nezaporne“ );

else

printf( “Cislo a je zaporne“ );

Jako podmínka může být libovolná booleanovská hodnota (nebo i celočíselná, kde 0 je považována za false, cokoliv jiného za true ). Na místě příkazu 1 a 2 nemusí být jen jediný příkaz, ale také příkazový blok :

if ( a >= 0 ){ printf( “Cislo a je nezaporne \n“ ); printf( “Absolutni hodnota cisla a je %i \n“, cislo );}else{ printf( “Cislo a je zaporne“ ); printf( “Absolutni hodnota cisla a je %i \n“, - cislo );}

POZOR! Záměna operátoru == za operátor = v podmínce je zdrojem velmi častých a těžko objevitelných chyb:

int a = 0;if( a = 2 ) printf( “a je dvojka“ );

vytiskne hlášení „a je dvojka“ i přes to, že v a očekáváme hodnotu 0!

CVIČĚNÍ : výpočet kvadratické rovnice

Page 29: Programovací jazyk C

for cyklus

For cyklus slouží pro opakované provádění příkazu nebo bloku příkazů.

for ( inicializace; test; akce ) příkaz;

V částech inicializace, a akce může být teoreticky jakýkoliv platný příkaz nebo výraz, v části podmínka pak booleanovský výraz. Je-li tento výraz platný, cykl pokračuje, není-li cykl končí. Příkaz inicializace se provede jednou na začátku cyklu, příkaz akce se pak provede pokaždé na konci každého průběhu.

Je obvyklé, že jednotlivé části se používají následovně:

Inicializace …. deklarace řídící proměnné cyklu (celočíselná hodnota)

Podmínka …… porovnání řídící proměnné s nějakou konstantou či předem známou hodnotou

Akce …………. inkrementace či dekrementace řídící proměnné

for ( int loop = 0; loop < 10; loop++ ) printf( “Ahoj! “ ); // Vytiskne 10x řetězec „Ahoj“

for ( int loop = 0; loop < 10; loop++ ) { // Vytiskne čísla od nuly do devíti a jejich druhé mocniny printf( “loop = %i \n“, loop ); printf( “loop ^2 = %i \n“, loop * loop );} /*for( loop )*/

Page 30: Programovací jazyk C

while cyklus

While cyklus slouží pro opakované provádění příkazu nebo bloku příkazů.

while( podmínka ) příkaz;

Slouží zejména v případě, kdy není zcela jasné, kolik průběhů bude cykl mít. Příkaz (nebo blok příkazů) se vykonává tak dlouho, dokud je podmínka platná. Může být použito například při načítání řádek ze souboru. Shcematicky :

while ( ! soubor.JeNaKonci() ) { soubor.NactiData(); ZpracujData(); }

Cyklus bude tak dlouho načítat a zpracovávat data, dokud nenarazí na konec souboru. Pak skončí. Funkční příklad zde:

int count; // Deklarace promene poctu opakovaniprintf( “Zadejte počet opakovani : “ );scanf( “%i“, &count ); // Počet zada uzivatel z klavesnicewhile ( count > 0 ) // Cykl probiha dokud je počet kladny{ printf( “%i, “ ); // Tiskne se hlaseni o zbyvajicim poctu count--; // Počet se snizuje o jedna }printf( “ZAZEH!“ ); // Hlaseni o konci cyklu

Pozn. : zadá-li uživatel nulu nebo záporné číslo, cykl neproběhne ani jednou!

Page 31: Programovací jazyk C

do-while cyklus

Do-while cyklus slouží pro opakované provádění příkazu nebo bloku příkazů.

do příkaz; while( podmínka ) ;

Od samotného cyklu while se liší pouze v jediném – testovací podmínka je až na konci, takže tělo cyklu se minimálně jednou provede.

char z = 'a';

do

{

printf( “%c“, z );

z++;

} while ( z <= 'z‚ ); // Vytiskne pismena od a do z .

Pozn. : všimněte si, že znaková konstanta se dá použít na místě číselné hodnoty. V takovém případě překladač nahradí znak jeho ASCII hodnotou.

Page 32: Programovací jazyk C

Příkazy break a continue

V souvislosti s cykly jsou zavedeny dva příkazy, a to break a continue. První z nich okamžitě ukončí provádění cyklu, druhý z nich ukončí provádění průběhu. Jejich použití a rozdíly jsou zřejmé z následujících příkladů:

int loop = 0;while( true ){ // Vytiskne lichá čísla mezi nulou a dvacítkou if( loop == 20 ) break; if( (loop % 2) == 0 ) continue; printf( “%i, “, loop ); loop++;} /*while*/

for ( int loop = 0; loop < 20; loop++ ) { // Vytiskne lichá čísla mezi nulou a dvacítkou if( (loop % 2) == 0 ) continue; printf( “%i, “, loop );} /*for( loop )*/

V obou případech bude stejný výstup: 1, 3, 5, 7, 9, 11, 13, 15, 17, 19,

CVIČĚNÍ : výpočet faktoriálu resp. součet řady

Page 33: Programovací jazyk C

Výběrový příkaz switch-case

switch (výraz) { case konstanta 1: skupina příkazů 1; break;

case konstanta 2 : skupina příkazů 2; break;

. . .

default: def. skupina příkazů break; }

Příkaz switch umožňuje větvení podle více hodnot celočíselných výrazů nebo výčtů. Výraz se vyhodnotí a jestliže odpovídá některé hodnotě case, skočí vykonávání na řádek, kde se case nachází. Jestliže neodpovídá ani jedna hodnota skočí na příkaz default (pokud je definován). Příaz(y) se vykonávají dokud nenarazí na break, po němž se přesune chod programu za složenou závorku.

switch ( akce ) { case 1: printf( “Provadim akci 1 … \n“ ); break;

case 2 : printf( “Provadim akci 2 … \n“ ); break;

case 3 : printf( “Provadim akci 3 … \n“ ); break;

default: printf( “Tuto akci neznam! \n“ ); break; }

Zapomenete-li v nějaké sekci case napsat na konci break, provádění programu „proleze“

do další sekce. To lze v mnoha případech udělat i záměrně.

Page 34: Programovací jazyk C

Základy vstupu a výstupu

int printf( const char *format, ... )

Funkce printf , implementována v knihovně stdio, zavádí standardní metodu pro formátovaný výstup na stdout. Může mít libovolný počet argumentů, první z nich je řetězec udávající formátování. Na tomto řetězci konkrétně záleží, kolik argumentů jakého typu musí funkce mít.

char *jmeno = "Bobiku"; int vek = 21; printf( "Ahoj %s, jsi %d let stary (a bude hur).\n", jmeno, vek );

Ahoj Bobiku, jsi 21 let stary (a bude hur).

Ve formátovacím řetězci jsou jednak normální znaky, jednak speciální sekvence, které jsou před tiskem nahrazeny obsahem proměnných. Pokud nesouhlasí počet a typ sekvencí s počtem a typem proměnných, výstup je nedefinovaný. Funkce vrátí celkový počet vytištěných znaků (nebo záporné číslo v případě chyby).

Page 35: Programovací jazyk C

Základy vstupu a výstupu

Kód Příslušná proměnná

%c Znak

%d Celočíselná proměnná se znaménkem

%i Celočíselná proměnná se znaménkem

%e Exponenciální zápis reálného čísla s malým „e“

%E Exponenciální zápis reálného čísla s velkým „E“

%f Reálné číslo

%g Automatický výběr variant %e nebo %f (co je kratší)

%G Automatický výběr variant %E nebo %f (co je kratší)

%o Celé číslo v osmičkovém zápisu

%s Řetězec

%u Celé číslo bez znaménka

%x Celé číslo bez znaménka v šestnáctkovém zápisu, malé znaky „a“ až „f“

%X Celé číslo bez znaménka v šestnáctkovém zápisu, velké znaky „A“ až „F“

%p Ukazatel

Tabulka shrnuje nejčastěji používané formátovací znaky. Mezi procento a písmeno lze vložit číslo, které udává šířku výstupu :

int vek = 21; printf( "Je mi %10u let.\n", vek );

printf( "Je mi %010u let.\n", vek );

Je mi 21 let.

Je mi 0000000021 let.

Page 36: Programovací jazyk C

Základy vstupu a výstupu

U reálných čísel lze přidat počet platných desetinných míst takto :

int pi = 3.1415926535; printf( "pi = %f \n", pi );

printf( "pi = %8f \n", pi );

printf( "pi = %8.3f \n", pi );

pi = 3.141592653

pi = 3.141592

pi = 3.141

Počet znaků lze zadat záporný, pak se zarovnává doleva :

char *jmeno = „Bobik"; printf( "Dnesni menu : %10s !\n", jmeno );

printf( "Dnesni menu : %-10s !\n", jmeno );

Dnesni menu : Bobik !

Dnesni menu : Bobik !

Počet mezer lze zadat i z programu pomocí znaku "*" :

for( int i = 1; i < 4; i++ ) printf( "->%*i \n", 2*i, i ); -> 1

-> 2

-> 3

Page 37: Programovací jazyk C

Základy vstupu a výstupu

int scan( const char *format, ... )

Pro načítání hodnot je definována funkce scanf. Parametry má obdobné jako printf - řídící řetězec určuje konkrétní počet a typ proměnných, do kterých se bude načítat. Pokud řídící řetězec obsahuje cokoliv jiného než sekvence začínající "%", musí se tyto znaky objevit na vstupu (což je pro uživatele matoucí a není to doporučeno).int i;float f;double d;  printf( "Zadejte cele cislo: " );scanf( "%d", &i );   printf( "Zadejte realne cislo: " );scanf( "%f", &f );   printf( "Zadejte realne cislo s dvojitou presnosti: " ); scanf( "%lf", &d );  printf( "Zadal jste %d, %f, and %f\n", i, f, d );

Povšimněte si sekvence "%lf", která načítá reálné číslo s dvojitou přesností a také znaku "&" před jmény proměnných ve scanf. To jsou dereference, tj. funkci se předává nikoliv samotná proměnná, ale ukazatel na ni (o ukazatelích později) - parametr je předán odkazem.

CVIČĚNÍ : výpočet různých řad závislých na parametru dle výběru uživatele

Page 38: Programovací jazyk C

Základy vstupu a výstupu

int fprintf( FILE *output, const char *format, ... )

int fscanf( FILE *input, const char *format, ... )

Funkce printf a scanf operovaly se standardním výstupem a vstupem (stdout, stdin). Existují ale varianty těchto funkcí, které se chovají zcela shodně, výstup resp. vstup mají ale přesměrovaný na libovolný soubor. Jsou to funkce fprintf a fscanf. Aby bylo možné pracovat se souborem na disku, je třeba jej nejprve otevřít. K tomu slouží funkce fopen.

FILE * fopen( const char *fname, const char *mode );

Fopen má dva argumenty. První z nich popisuje cestu k souboru na disku, druhý z nich pak režim, ve kterém bude soubor otevřen.

Mód Význam Existuje Neexistuje

“r” Otevře pro čtení Čte od začátku Chyba

“w” Vytvoří pro zápis Přepíše obsah Vytvoří nový

“a” Připojí pro zápis Píše na konec Vytvoří nový

“r+“ Otevře pro čtení i zápis Čte/píše od začátku Chyba

“w+“ Vytvoří pro čtení i zápis Přepíše obsah Vytvoří nový

“a+“ Připojí pro čtení i zápis Píše na konec Vytvoří nový

POZOR! Pro MS Windows jsou v cestách k souborům zpětná lomítka a je tedy třeba je zadávat dvojitě:

"C:\\Tmp\\soubor.txt"

Page 39: Programovací jazyk C

Základy vstupu a výstupu

int fprintf( FILE *output, const char *format, ... )

int fscanf( FILE *input, const char *format, ... )

Funkce printf a scanf operovaly se standardním výstupem a vstupem (stdout, stdin ). Existují ale varianty těchto funkcí, které se chovají zcela shodně, výstup resp. vstup mají ale přesměrovaný na libovolný soubor. Jsou to funkce fprintf a fscanf. Aby bylo možné pracovat se souborem na disku, je třeba jej nejprve otevřít. K tomu slouží funkce fopen.

FILE * fopen( const char *fname, const char *mode );

FILE je struktura definovaná v knihovně stdio popisující otevřený soubor. O její obsah se běžný programátor nemusí starat, pouze ukazatel na ni používá jako typ proměnné, která "obsahuje" soubor a předává ji jako argument všem příslušným funkcím. O ukazatelích později. Po ukončení práce se souborem je třeba jej zavřít :

fclose( FILE * fileDescriptor );

Bez zavolání tohoto příkazu se neuloží obsah bufferů a navíc bude soubor nadále zablokovaný pro jakýkoliv další proces!

Page 40: Programovací jazyk C

Základy vstupu a výstupu

#include <stdio.h>int main( void ){ char *polePrijmeni[] = { "Novak", "Jirousek", "Chudy", "Posedly" } ; char *poleJmen[] = { "Josef", "Jan", "Petr", "Maniak" } ; int poleVeku[] = { 27, 35, 16, 1024 } ; // Defininice tri statickych poli s informacemi

FILE *vystup = fopen( "seznam.txt", "w" ); // Vytvari se novy soubor pro vystup (pokud uz existuje, premaze se)

fprintf( vystup, "Jmeno Prijmeni Vek\n" ); fprintf( vystup, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); // Tiskne se hlavicka tabulky

for( int i = 0; i < 4; i++ ) fprintf( vystup, "%10s %10s %5i \n", poleJmen[i], polePrijmeni[i], poleVeku[i] ); // Tisknou se radky tabulky

fclose( vystup ); // Vystupni soubor je zavren

} /*main*/

Page 41: Programovací jazyk C

Základy vstupu a výstupu

#include <stdio.h>int main( void ){ char jmeno[100], prijmeni[100]; int vek; FILE *vstup = fopen( "seznam.txt", "r" ); // Otvira se soubor pro vstup

printf( "Jmeno Prijmeni Vek\n" ); printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); // Tiskne se hlavicka tabulky na obrazovku

fscanf( vstup, "Jmeno Prijmeni Vek\n" ); fscanf (vstup, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); // Preskoci hlavicku v souboru

while( ! feof( vstup ) ) { // Nacte se obsah souboru a vytiskne jako tabulka na obrazovku fscanf( vstup, "%s%s%i\n", jmeno, prijmeni, &vek ); printf( "%10s %10s %5i \n", jmeno, prijmeni, vek ); } /*while*/

fclose( vstup ); // Zavira se vstupni soubor

} /*main*/CVIČĚNÍ : zápis a načítání různých seznamů z/do souborů

Page 42: Programovací jazyk C

Funkce

typ jméno( parametr1, parametr2, ... ) { příkazy }

Funkce je část kódu, která se používá často a opakovaně na více místech. Bylo by nesmírně "neekonomické" opisovat kód znovu a znovu tam, kde je jej třeba. Proto je možné tuto část kódu zapouzdřit a nějak jej pojmenovat a ve zbytku programu už jen tímto jménem "volat". Obecná definice funkce vypadá následovně :

typ ................ typ návratové hodnotyjméno ............ jméno funkceparametr# ..... hodnoty, které se do funkce předávajípříkazy ........... samotný blok kódu

#include <stdio.h>

int soucet( int a, int b ) { int r; r = a + b; return r; } /*soucet*/

void main ( void ) { int z; z = soucet( 5, 3 ); z = soucet( z, 2 ); printf( "5 + 3 + 2 = %i\n", z );} /*main*/

Klíčové slovo return ukončuje provádění funkce a vrací řízení provádění programu těsně za volání funkce. Funkce nemusí vracet řádnou hodnotu, pak se na místo typu použije void a po klíčovém slovu return nebude uvedena žádná hodnota či proměnná. Stejně tak není nutné, aby měla nějaké vstupní parametry (rovněž se použije void).

Page 43: Programovací jazyk C

Funkce – argumenty předávané hodnotou

Argumenty funkce jsou v jejím těle přístupné jako deklarované proměnné. Předávání může probíhat dvěma způsoby : hodnotou a odkazem.

#include <stdio.h>int pracujSCisly( int a, int b ) { a++; b--; printf( "pracujSCisly : a = %i, b = %i\n", a, b ); return a + b;} /*pracujSCisly*/

void main ( void ) { int arg1 = 10; int arg2 = 15; printf( "arg1 = %i, arg2 = %i\n", arg1, arg2 ); int z = pracujSCisly( arg1, arg2 ); printf( "arg1 = %i, arg2 = %i, z = %i\n", arg1, arg2, z );} /*main*/

Argument předaný hodnotou se v těle funkce chová jako nezávisle deklarovaná proměnná s hodnotou přiřazenou při deklaraci. Jakákoliv práce s ním neovlivní hodnoty proměnných v nadřazeném bloku, odkud byla funkce zavolána. arg1 = 10, arg2 = 15

pracujSCisly: a = 11, b = 14

arg1 = 10, arg2 = 15, z = 25

Page 44: Programovací jazyk C

Funkce – argumenty předávané odkazem

Argumenty funkce jsou v jejím těle přístupné jako deklarované proměnné. Předávání může probíhat dvěma způsoby : hodnotou a odkazem.

#include <stdio.h>int pracujSCisly( int & a, int & b ) { a++; b--; printf( "pracujSCisly : a = %i, b = %i\n", a, b ); return a + b;} /*pracujSCisly*/

void main ( void ) { int arg1 = 10; int arg2 = 15; printf( "arg1 = %i, arg2 = %i\n", arg1, arg2 ); int z = pracujSCisly( arg1, arg2 ); printf( "arg1 = %i, arg2 = %i, z = %i\n", arg1, arg2, z );} /*main*/

Argument předaný odkazem naopak zůstává neustále svázán s proměnnou v nadřazeném bloku (předává se vlastně ukazatel). Argument předaný hodnotou se v definici funkce označuje znakem "&" za typem argumentu. arg1 = 10, arg2 = 15

pracujSCisly: a = 11, b = 14

arg1 = 11, arg2 = 14, z = 25

Page 45: Programovací jazyk C

Funkce – defaultové parametry

Pro pohodlí programátora je možné nastavit defaultové hodnoty argumentů. Ty se pak při volání funkce vůbec nemusejí uvádět:

#include <stdio.h>void tiskniDvojiciCisel( int x = -1, int y = +1 ) { printf( "[ %i, %i ] ", x, y ); return;} /*tiskniDvojiciCisel*/

void main ( void ) { tiskniDvojiciCisel( 10, 10 ); tiskniDvojiciCisel( 5 ); tiskniDvojiciCisel();} /*main*/

Defaultová hodnota se do definice funkce přidá pomocí rovnítka (jako přiřazovací příkaz). Hodnoty se do parametrů vkládají při překladu. Z tohoto důvodu je nutné, aby všechny parametry s defaultovými hodnotami byly až za parametry bez nich, v opačném případě by překladač nevěděl, co má dělat.

[ 10, 10 ] [ 5, 1 ] [ -1, 1 ]

povoleno : void tiskniDvojiciCisel( int x = -1, int y = +1 ) {} povoleno : void tiskniDvojiciCisel( int x, int y = +1 ) {} zakázáno : void tiskniDvojiciCisel( int x = -1, int y ) {}

Page 46: Programovací jazyk C

Funkce - přetěžování

Je možné definovat několik funkcí které mají stejné jméno a liší se jen argumenty. To je dobré v okamžiku, kdy mají tyto funkce provádět stejné věci pro různé typy argumentů. Například :

int secti( int x, int y ) { return x + y; }double secti( double x, int y ) { return x + (double)y; }double secti( double x, double y ) { return x + y; }

void main ( void ) { int a = 1; int b = 2; double c = 3.1415; double d = 2.7;

int resInt; double resDoub;

resInt = secti( a, b ); resDoub = secti( a, c ); resDoub = secti( c, d )

} /*main*/

První volání secti zavolá první z definovaných funkcí, druhé druhou a třetí poslední definovanou funkci. Přitom se všechny jmenují stejně a ze jména je jasné, že provádí aritmetickou operaci sčítání.

Která funkce se kdy přesně volá rozhoduje překladač na základě typů argumentů.

Pozn.: nelze deklarovat stejně se jmenující funkce se stejnými argumenty, lišící se pouze typem navrácené hodnoty.

Page 47: Programovací jazyk C

Funkce – rekurzivní volání

Prohlédni( adresář )

Načti seznam souborů

Obsahuje seznam hledaný

soubor ?

Vrať adresář + soubor

Načti seznam podadresářů

Cykl přes podadresáře

(podadr)

Volej Z = Prohlédni (adresář + podadr )

Z je rovno " " Vrať Z

Vrať " "

ANO

NE

ANO

NE

Funkce může volat sama sebe. To je velice mocný nástroj pro řešení některého typu úloh - tzv. rekurzivní volání. Jako příklad takové úlohy může být vyhledávání souboru určitého jména v adresářové struktuře. Základní algoritmus takového vyhledávání zobrazuje vývojový diagram vlevo. Předpokládáme, že jméno hledaného souboru je známé na všech úrovních volání funkce. Argument funkce Prohlédni je adresář, ve kterém se má s hledáním začít. Její návratová hodnota je buď plná cesta k souboru, nebo prázdný řetězec (" ") v případě, že soubor nebyl nalezen.

Page 48: Programovací jazyk C

Funkce – rekurzivní volání

Jako příklad funkčního kódu je zde výpočet faktoriálu (byť pro tuto úlohu je rekurze zbytečná):

#include <stdio.h>

int SpoctiFaktorial( int n ){ if( n <= 1 ) return 1; return n * SpoctiFaktorial( n - 1 );}

void main ( void ) { int N; printf( "Zadejte cele cislo : " ); scanf( "%u", &N ); printf( "Faktorial cisla %u je %u. \n", N, SpoctiFaktorial( N ) ); }

Všimněte si volání funkce SpoctiFaktorial primo z funkce printf - zde je jako argument printf použita návratová hodnota funkce SpoctiFaktorial.

Page 49: Programovací jazyk C

Složené typy – klíčové slovo typedef

Je-li v programu třeba deklarovat více proměnných stejného složeného typu na různých místech, je nepraktické vypisovat typ znovu a znovu. Proto v C existuje klíčové slovo typedef, které překladači říká, aby si daný typ zapamatoval pod určeným jménem. Toto jméno je pak možné použít kdykoliv na deklaraci proměnné.

typedef struct complex { float Re; float Im;};

complex Secti( complex clen1, complex clen 2 ){ complex soucet; soucet.Re = clen1.Re + clen2.Re; soucet.Im = clen1.Im + clen2.Im; return soucet;}

void TiskniComplex ( complex cislo ){ if( cislo.Im < 0.0 ) printf( "%f - %fi", cislo.Re, abs( cislo.Im ) ); else printf( "%f + %fi", cislo.Re, cislo.Im );}

V příkladu je definován komplexní typ complex, který je pak v následujících funkcích použit jako typ argumentů a návratové hodnoty (o tom, jak se přesně volají funkce a o ostatních věcech použitých v příkladu později).

complex A, B, C;

A.Re = 10; A.Im = -3; B.Re = -5; B.Im = 5;

C = Secti( A, B );TiskniComplex( A); TiskniComplex( B );TiskniComplex( C );

10 - 3i

-5 + 5i

5 + 2i

CVIČĚNÍ : základní aritmetické operace se strukturou complex

Page 50: Programovací jazyk C

Ukazatele

3.1415PI AF35:010B

AF35:010BukazatelNaPI AF35:01BC

Jméno proměnné

Adresa proměnné

Obsah proměnné

V C existuje speciální třída proměnných, které mají za úkol uschovávat odkazy na jiné proměnné. Říká se jim ukazatele. Ukazatele uschovávají nikoliv nějakou hodnotu, nýbrž odkaz do paměti (adresu), na níž lze patřičnou hodnotu nalézt. Schematicky :

PI uchovává hodnotu 3.1415

ukazatelNaPI uchovává adresu v paměti, na které

je uložena hodnota PI

Pomocí ukazatelů se v C předávají parametry odkazem, deklarují pole, alokuje paměť pro větší data, pracuje s řetězci a podobně. Některé jazyky tento mechanizmus programátorům neumožňují, protože je potenciálním zdrojem chyb a problémů, nicméně C se díky němu vyznačuje obrovskou flexibilitou.Ukazatel na proměnnou nějakého typu deklarujeme pomocí znaku * za typem: float PI; // Proměnná typu float

float * ukazatelNaPI; // Proměnná typu ukazatel na float

Page 51: Programovací jazyk C

Ukazatele - reference a dereference

Pro snažší práci s ukazateli jsou definovány dva operátory. Operátor & (reference) a operátor * (dereference). Operátor reference vrací odkaz na proměnnou, které mu byla dána jako vstup, tj. :

float PI = 3.1415; // Proměnná typu float, obsahuje hodnotu 3.1415float * ukazatelNaPi = & PI; // Proměnná typu ukazatel na float, obsahuje odkaz na PI

Naopak operátor dereference vrací hodnotu proměnné, na níž ukazuje ukazatel, který mu byl dán jako vstup:

float realnaPromenna = 1024;float * ukazatel = &realnaPromenna;*ukazatel = 2048;printf ( "Realna promenna ma hodnotu %f \n", realnaPromenna );

#include <stdio.h>void tiskniDvojiciCisel( int x, int y ) { printf( "[ %i, %i ] ", x, y ); }

void main ( void ) { int a = 3; int b = 4; int * pa = &a; int * pb = &b; tiskniDvojiciCisel( *pa, *pb ) int * pa = &b; int * pb = &a; tiskniDvojiciCisel( *pa, *pb )} /*main*/

[ 3, 4 ] [ 4, 3 ]

Ukazatel je samozřejmě standardní proměnná a během programu je možné libovolně měnit na co ukazuje.

Realna promenna ma hodnotu 2048

Page 52: Programovací jazyk C

Ukazatelová aritmetika

Pro ukazatele jsou definovány standardní aritmetické operátory +, - a operátory inkrementace a dekrementace ++, --. Operátory ovšem nemění hodnotu proměnné, na kterou ukazatel míří, ale mění cílovou polohu ukazatele!

1024 156 -103 98 515 1547 2048 ...

Paměť obsahující po sobě jdoucí celá čísla

ukazatelNaInt++

Postupné inkrementace

ukazatele

ukazatelNaInt

*ukazatelNaInt == 1024

ukazatelNaInt

*ukazatelNaInt == 156

ukazatelNaInt

*ukazatelNaInt == -103

ukazatelNaInt

*ukazatelNaInt == 98

ukazatelNaInt

*ukazatelNaInt == 515

ukazatelNaInt

*ukazatelNaInt == 1547

Uvedený příklad je pro operátor ++, ostatní fungují obdobně. Například ukazatelNaInt + 3 vrátí ukazatel na paměťovou buňku o tři dále k vyšším adresám, ukazatelNaInt - 2 vrátí ukazatel na buňku o dvě zpět k nižším adresám.Velikost posunu závisí na velikosti typu ukazatele. Pro int * je velikost buňky 4 byte, pro double * je velikost buňky 8 byte, pro char * je tato velikost 1 byte.

Page 53: Programovací jazyk C

Ukazatelová aritmetika

#include <stdio.h>int strlen( char * string ) { int len = 0; while( *string != 0 ) { len++; string++; } return len;}

void main ( void ) { char * str1 = "Ahoj, jak se mas?"; char * str2 = "Je to vsechno na ... ehm ..."; printf ( "Delka retezce \"%s\" je %i znaku.", str1, strlen(str1) ); printf ( "Delka retezce \"%s\" je %i znaku.", str2, strlen(str2) );}

Ukazatelové aritmetiky se využívá například ve funkci počítající délku řetězce strlen. V C je řetězec tvořen posloupností znaků zakončených znakem s ASCII hodnotou 0 (o řetězcích podrobně později). Délka řetězce je tedy spočtena tak, že se postupně posouvá ukazatel od začátku řetězce po jednotlivých znacích a dokud neukazuje na nulový znak (terminátor), přičítá se jednička k celkové délce.

Delka retezce "Ahoj, jak se mas?" je 17 znaku.

Delka retezce "Je to vsechno na ... ehm ..." je 28 znaku.

Page 54: Programovací jazyk C

Ukazatelová aritmetika

Operátor "-" je zaveden také pro dva ukazatele jako operandy. Vrácen je rozdíl poloh ukazatelů v buňkách, tj.

1024 156 -103 98 515 1547 2048 ...

Paměť obsahující po sobě jdoucí celá čísla

Dva různé ukazatele

ukazatelNaInt1

*ukazatelNaInt1 == 1024

ukazatelNaInt2

*ukazatelNaInt2 == 2048

ukazatelNaInt2 - ukazatelNaInt1 == 6

int strlen( char * string ) { char *ptr = string; while( *ptr != 0 ) ptr ++; return ptr - string;}

Funkci strlen lze modifikovat za využití operátoru "-" ve výše zmíněné formě na o něco málo rychlejší variantu (v každém průběhu cyklu je o jednu inkrementaci méně).

Page 55: Programovací jazyk C

Ukazatel na void a NULL

Lze deklarovat ukazatel bez specifického určení typu, na který ukazuje. Takový ukazatel se deklaruje pomocí klíčového slova void:

void * ukazatel; // Ukazatel bez určeného typu

S takovým ukazatelem se zachází, jako by velikost jeho příslušné paměťové buňky byla 1 byte. Nelze jej jednoduše dereferencovat bez explicitního přetypování. Pokud překladači výslovně neřekneme, co se pod ukazatelem typu void * zrovna skrývá, nemá šanci to sám zjistit:

float PI = 3.1415;void * ukazatel = &PI; // Správně - ukazatel nyní směřuje na obsah proměnné PI float promenna = * ukazatel; // Špatně - překladač neví, na co ukazatel směřuje a zahlásí // chybu při překladuint celeCislo = * (int *) ukazatel; // Správně - překladač bude hodnotu uloženou ve 4 byte pod // ukazatelem považovat za celé číslo a umístí ji do // proměnné celeCislo. Tato hodnota ovšem nebude mít s // konstantou pi nic společného.

Do každého ukazatele lze uložit speciálně nadefinovanou hodnotu NULL - takový ukazatel pak neukazuje na nic.

int* ukazatel = NULL;*ukazatel = 100;

SEGMENTATIONVIOLATION

Page 56: Programovací jazyk C

Ukazatelové špeky

Ukazatele jsou mocný nástroj, ale také se dá s jejich pomocí nadělat mraky chyb.#include <stdio.h>void main ( void ) { short celeCislo = 2048; float realneCislo = 3.1415;

int *ukazatelNaCeleCislo = &celeCislo; *ukazatelNaCeleCislo = 545282125;

printf ( "cele cislo : %i, realne cislo : %f.", celeCislo , realneCislo );}

Cele cislo : 22605 , realne cislo : 3.142609

Výsledná chyba je ovšem závislá na architektuře, překladači a dokonce na nastavení překladače. Obecně špatné nastavení pozic ukazatelů respektive chyba ve velikosti objektů, na které ukazují, způsobí v lepším případě pád programu, v horším případě nečekané a na první pohled naprosto nelogické chování programu. Odstranit takovou chybu je vskutku detektivní práce.

Page 57: Programovací jazyk C

Správa paměti

ŽÁDOSTI PROGRAMÁTORA

VO

NÍ F

UN

KC

Í

Statické proměnné

Zde se skladují lokální

proměnné deklarované v tělech funkcí

Dynamické proměnné

Zde se alokuje paměť pro

větší datové celky na žádost programátora

Paměť je z pohledu aplikace v C rozdělena na tři části. Jednak základní statická paměť, kde je uložen kód aplikace spolu s globálními proměnnými, potom zásobník, kde se skladují lokální proměnné deklarované v tělech funkcí a nakonec hromada.

Paměť na zásobníku se

přiřazuje automaticky, o

paměť na hromadě je třeba požádat.

Page 58: Programovací jazyk C

Správa paměti - zásobník

#include <stdio>

int funkceB( int clen1, int clen2 ) { int kVraceni = (clen1 + clen2 ) / 2; return kVraceni;}

double funkceA( int a, int b ) { double cislo = 3.1415; int vysledek; vysledek = funkceB( a, b ); return vysledek * cislo;}

void main ( void ) { double val = funkceA( 2, 4) ; printf ( "X = %f", val );}

8B - val : ?

4B - a : 2

4B - b : 4

8B - cislo : 3.1415

4B - vysledek : ?

4B - clen1 : 2

4B - clen2 : 4

4B - kVraceni : ?4B - kVraceni : 3

4B - vysledek : 3

8B - val : 9.4245

ZÁSOBNÍK

Stack overflow

Tato chyba může nastat například při chybě v rekurzivním algoritmu

Page 59: Programovací jazyk C

Správa paměti – dynamická alokace

HROMADA

Aplikace1

Aplikace2

Aplikace3

void *data

char *poleZnaku

int *poleCisel

void *seznam

char *retezec1

int *pole

char *retezec2

char *retezec3

float *matice

Hromada je společná pro všechny aplikace, o korektní

přiřazování paměti a ochranu paměti přiřazené aplikaci se

stará systém. Programátor musí v programu o přidělení

dynamické paměti na hromadě žádat a musí ji po ukončení

práce uvolnit.

Page 60: Programovací jazyk C

Správa paměti – operátory new a delete

Žádost o přidělení paměti na hromadě podává programátor pomocí operátoru new.

ukazatel = new typukazatel = new typ [počet prvků]

Po zavolání operátoru new vytvoří systém v paměti místo na proměnou udaného typu (nebo na zadaný počet proměnných daného typu v řadě za sebou). Není-li paměti dostatek nebo nastala nějaká jiná chyba, je do proměnné ukazatel vložena hodnota NULL. V opačném případě je do něj uložena adresa prvního prvku.

Příkaz k uvolnění paměti vydává programátor pomocí operátoru delete.

delete ukazatel delete [ ] ukazatel

Pozn.: pravděpodobnost přeplnění hromady je díky systému virtuální paměti poměrně malá, může ovšem nastat při nějaké programátorské chybě (např. alokace paměti v nekonečném cyklu).

Page 61: Programovací jazyk C

#include <stdlib.h>#include <stdio.h>

int main (){ int pocet,n; int * ukazatel; printf( "Kolik chcete zadat cisel? " ); scanf( "%u", &pocet ); ukazatel = new int [pocet]; if ( ukazatel == NULL) printf( "Chyba - nelze alokovat pamet" ); else { for ( n = 0; n < pocet; n++ ) { printf( "Zadejte cislo : " ); scanf( "%u", ukazatel + n ); } printf( "Bylo zadano : " ); for ( n = 0; n < pocet; n++ ) printf( "%i, ", ukazatel [n] ); delete [ ] p; } }

Zde je typický příklad na použití dynamicky alokované paměti. Program má za úkol uschovat nějaké množství čísel - ale během překladu není jasné, kolik jich bude. Patřičná paměť je na čísla vyhrazena až poté, co uživatel zadá jejich počet.

Všimněte si použití ukazatelové aritmetiky při načítání jednotlivých čísel z klávesnice a také možnosti pracovat s typovým ukazatelem u jako s polem (zadáním indexu do hranatých závorek) :

*( u + index )u [index] ekvivalentní

Správa paměti – operátory new a delete

Page 62: Programovací jazyk C

int * cislo = (int *) malloc( sizeof( int ) );float * vektor = (int *) calloc( 3, sizeof( float ) );

free( cislo );free( vektor );

Správa paměti v ANSI-C

Operátory new a delete jsou specialitou jazyka C++. Dnes už těžko narazíte na překladač, který by přeložil C a C++ nikoliv, ale pro pořádek - v C jsou pro práci s hromadou definovány následující funkce:

void * malloc ( size_t size );

void * calloc ( size_t num, size_t size );

void * realloc ( void * ptr, size_t size );

void free ( void * ptr )

Funkce malloc je ekvivalentní jednoduchému operátoru new - alokuje místo pro jednu proměnnou o velikosti size bytů (či prostě alokuje paměť o dané velikosti). Funkce calloc je pak ekvivalent operátoru new [] - alokuje místo pro num proměnných o velikosti size. Obě funkce vrací ukazatel na void, tj. programátor si ho podle potřeby musí dále přetypovat. Funkce free pak zajišťuje uvolnění alokované paměti.

int * cislo = new int;float * vektor = new float [3];

delete cislo;delete [] vektor;

Funkce realloc zvětší velikost již jednou alokované paměti a je-li to nutné, překopíruje obsah na jinou adresu (kterou vrátí).

Page 63: Programovací jazyk C

Složené typy – Ukazatele na struktury

Dynamická alokace a ukazatele se často používají spolu se strukturami - struktura je kompaktní datový objekt, vhodný pro databázové algoritmy a počet alokovaných struktur aplikace je zpravidla dán uživatelským vstupem. Pro jednodušší přístup ke členům struktury na kterou je pouze odkaz (ukazatel) je zaveden operátor "->".

#include <stdio.h>#include <stdlib.h>

typedef struct complex { float Re; float Im; }

void main( void ){ complex *complexVektor = new complex[100];

complex *ukazatel = complexVektor ; for( int n = 0; n < 100; n++ ) { ukazatel -> Re = ( (float)rand() ) / ( (float)RAND_MAX ); ukazatel -> Im = ( (float)rand() ) / ( (float)RAND_MAX ); ukazatel ++; }

for( int n = 0; n < 100; n++ ) TiskniComplex( complexVektor[n] ); delete [ ] complexVektor; }

Program naplní pole o 100 prvcích náhodnými komplexními čísly o reálné i imaginární části z intervalu <0, 1> a pak je vytiskne (funkce TiskniComplex viz průsvitka 21). Všimněte si přetypování celočíselných hodnot rand() a RAND_MAX na reálné typy ještě před dělením - kdyby tomu tak nebylo, dělení by se bralo jako celočíselné a výsledek by byl vždy nulový!

Page 64: Programovací jazyk C

Ukazatelové špeky 2

Je-li členem struktury nějaký ukazatel, pak při dealokaci původní struktury musíte dealokovat i paměť, na kterou ukazuje - jinak dojde k zapomenutí odkazu a část paměti zůstane alokovaná až do konce aplikace (memory leak).

CVIČĚNÍ : práce se spojovým seznamem (buňka je nějaká struktura)

typedef struct complex { float Re; float Im; }typedef struct dveCisla { complex *A; complex *B; }

void main( void ){ dveCisla *promenna = new dveCisla; dveCisla->A = new complex; dveCisla->B = new complex; . . .

delete dveCisla; }

typedef struct complex { float Re; float Im; }typedef struct dveCisla { complex *A; complex *B; }

void main( void ){ dveCisla *promenna = new dveCisla; dveCisla->A = new complex; dveCisla->B = new complex;

. . .

delete dveCisla->A; delete dveCisla->B; delete dveCisla; }

Š P A T N Ě

D O B Ř E

Page 65: Programovací jazyk C

Složené typy – dynamická pole

Jak bylo v posledním příkladu naznačeno, operátor new [ ] vrací paměť vyhrazenou pro určitý počet prvků daného typu. Také bylo ukázáno, že za pomoci operátoru indexace "[ ]" lze k těmto prvkům přistupovat jako k prvkům statického pole. To proto, že každé pole je určeno ukazatelem na první prvek v paměti a indexace probíhá pomocí operátoru "[ ]" - tj. statická pole a prostor alokovaný pomocí new [ ] (dynamická pole) se principiálně nijak neliší.

int statInt [10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }int * dynInt;

dynInt = (int *) new int [10];

for( int n = 0; n < 10; n++ ) dynInt[n] = statInt[9 - n];for( int n = 0; n < 10; n++ ) printf( "%i, ", synInt[n] );

delete [ ] dynInt;

*( u + index )u [index] ekvivalentní

Pozn. : Operátor "[]" je definován pomocí ukazatelové aritmetiky.

Dynamická pole mají oproti statickým tu výhodu, že nepotřebují znát svou velikost již během překladu. Navíc statická pole jsou vytvářena na zásobníku a ten je na rozdíl od hromady poměrně malý, takže není vhodný pro velká pole.

Page 66: Programovací jazyk C

Předávání polí jako argumenty

Je-li třeba předat pole jako argument nějaké funkce, musí být předány dvě věci : ukazatel na první prvek a celková délka pole. V C (na rozdíl například od C#) toto není automatizováno a programátor se o to musí postarat sám:

#include <stdio.h>

void KopirujPole( int *odkud, int odkudN, int *kam, int kamN ){ int prvku; if( odkudN > kamN ) prvku = kamN; else prvku = odkudN;

for( int n = 0; n < prvku; n ++ ) kam[n] = odkud[n];}

void main( void ){ int statInt [10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } int * dynInt = (int *) new int [20];

KopirujPole( statInt, 10, dynInt, 20 ); for( int n = 0; n < 20; n++ ) printf( "%i, ", dynInt[n] );

delete [] dynInt;}

Na výstupu z tohoto programu budou číslice jedna až deset, následované dalšími deseti náhodnými čísly. Procedura KopirujPole zkopírovala celé statické pole do dynamického, které je ale dvakrát větší. V druhých deseti prvcích dynamického pole zůstaly čísla, která byla v paměti před alokací - což je zcela náhodné smetí. Zde je také vidět nutnost inicializace důležitých proměnných!

Co se stane, když zde místo dvacítky

bude pětka?

Page 67: Programovací jazyk C

Složené typy - Řetězce v C

Řetězec v ANSI-C je pole znaků, zakončené znakem s ASCII hodnotou 0. Pole může být statické nebo dynamické - jak již bylo zmíněno, mezi těmito druhy polí není principiální rozdíl. Zapíšeme-li do zdrojového kódu řetězcovou konstantu, překladač jí zavede jako statické pole při startu programu. Znak nula na konci (terminátor) je důležitý, neboť s řetězcem se běžně nepředává jeho délka - všechny procedury pracují se znaky řetězce tak dlouho, dokud nenarazí na nulu.

int strlen( char * string ) { char *ptr = string; while( *ptr != 0 ) ptr ++; return ptr - string;}

Např. funkci strlen se předává pouze ukazatel na první znak řetězce, ne však jeho délka. Se znaky se pracuje tak dlouho, až přijde nula.

Z předchozí definice řetězce je zřejmé, že práce s nimi není nic jednoduchého. Např. v BASICu lze řetězce běžně sčítat :

LET A$ = "Ahoj, "LET B$ = ", jak se mas?"PRINT A$ + "Tomasi" + B$

Což v ANSI-C tak snadno nejde (je třeba použít specializované funkce).

Page 68: Programovací jazyk C

Složené typy - Řetězce v C

#include <stdio.h>

char * SectiRetezce( char *prvni, char *druhy ){ int delka1 = strlen( prvni ); int delka2 = strlen( druhy );

char *soucet = new char [delka1 + delka2 + 1];

int index = 0; for( int n = 0; n < delka1; n++ ) soucet[n] = prvni[n]; for( int n = 0; n < delka2; n++ ) soucet[n + delka1] = druhy[n];

soucet[ delka1 + delka2 ] = 0; return soucet;}

void main( void ){ char *mujRetezec = SectiRetezce( "Ahoj, Tomasi, ", "jak se mas?" ); printf( "%s\n", mujRetezec ); delete [] mujRetezec;}

Zde je příklad funkce, která sečte dva řetězce. Všimněte si, že místo pro nový řetězec musí být alokováno - a tudíž po použití i zrušeno!

Podobné funkce jsou ovšem již napsány v knihov-ně string,viz např. :

http://www.cppreference.com/wiki/c/string/start

CVIČĚNÍ : základní řetězcové operace (sčítání, vyhledávání apod.)

Page 69: Programovací jazyk C

Direktivy preprocesoru

Preprocesor je program, který projede zdrojový kód ještě před překladem a udělá v něm nějaké úpravy. V první řadě odstraní komentáře, ale jeho úloha může být o mnoho složitější - například může rozhodnout o tom, které části kódu budou překládány a které ne (například v závislosti na architektuře). Preprocesor reaguje na speciální značky ve zdrojovém kódu, které začínají znakem #. Například :

complex *ukazatel;ukazatel = new complex;

. . .

#ifdef _DEBUG printf( "Re = %f, Im = %f [ukazatel = 0x%X]\n", ukazatel->Re, ukazatel->Im );#else printf( "Re = %f, Im = %f\n", ukazatel->Re, ukazatel->Im );#endif

Je-li definována direktiva preprocesoru "_DEBUG", vytiskne se reálná i imaginární část komplexního čísla, na které míří ukazatel spolu s adresou tohoto čísla. To má význam při ladění programu. Při běžném použití ("_DEBUG" není definována) se tiskne jen samotné číslo. Preprocesor na základě podmínky #ifdef _DEBUG vymaže ze zdrojového kódu příslušnou část, než jej pošle překladači.

Page 70: Programovací jazyk C

Direktivy preprocesoru

#define

#define radky 10#define sloupce 5#define matice( mat, x, y ) ( (mat)[ (y)*sloupce + (x) ] )

float *2Dpole = new float [ radky * sloupce ];for( int m = 0; m < radky; m++ ) for( int n = 0; n < sloupce; n++ ) matice( 2Dpole, n, m ) = ( (float)rand() ) ) / ( (float) MAX_RAND );

Direktiva #define dovoluje nadefinovat a pojmenovat nějaký symbol či makro. Preprocesor pak všechny výskyty tohoto jména nahradí příslušným symbolem.

float *2Dpole = new float [ 10 * 5 ];for( int m = 0; m < radky; m++ ) for( int n = 0; n < sloupce; n++ ) ( (2Dpole)[ (m)*5 + (n) ] = ( (float) rand() ) ) / ( (float) MAX_RAND );

#define PI 3.1415

float frekvence = 10.5;float omega = 2 * PI * frekvence;

float frekvence = 10.5;float omega = 2 * 3.1415 * frekvence;

Závorky kolem argumentů makra nejsou povinné, ale je bezpečnější je tam udělat pro případ, že by do argumentu přišel nějaký složitější výraz.

Pozn. : je možné definovat pouze jméno

bez hodnoty - pak se dá použít např v #ifdef .

Page 71: Programovací jazyk C

Direktivy preprocesoru

#ifdef, #ifndef, #elif, #else, #endifPodmíněné direktivy dovolují na základě definovaných symbolů pomocí #define určit, které části kódu se budou překládat a které nikoliv. Typicky se tak určuje verze pro ladění či pro uživatele, kód specifický pro různé architektury či různé verze programu.

#define _DEBUG

complex *ukazatel;ukazatel = new complex;

. . .

#ifdef _DEBUG printf( "Re = %f, Im = %f [ukazatel = 0x%X]\n", ukazatel->Re, ukazatel->Im );#else printf( "Re = %f, Im = %f\n", ukazatel->Re, ukazatel->Im );#endif

Page 72: Programovací jazyk C

Direktivy preprocesoru

#ifdef, #ifndef, #elif, #else, #endifPodmíněné direktivy dovolují na základě definovaných symbolů pomocí #define určit, které části kódu se budou překládat a které nikoliv. Typicky se tak určuje verze pro ladění či pro uživatele, kód specifický pro různé architektury či různé verze programu.

#define Win_x32// #define Win_x64// #define Linux

#ifdef Win_x32 printf ( "Verze aplikace \"tojsemtovymnouk\" pro 32 bitovou verzi Windows ... \n" );#elif Win_x64 printf ( "Verze aplikace \"tojsemtovymnouk\" pro 64 bitovou verzi Windows ... \n" );#elif Linux printf ( "Verze aplikace \"tojsemtovymnouk\" pro Linux ... \n" );#else printf ( "Verze aplikace \"tojsemtovymnouk\" pro nespecifikovanou architekturu ... \n" );#endif

Jak uvidíme později, tento mechanizmus je také běžnou součástí ochrany před vícenásobným vložením hlaviček (soubory typu *.h) do zdrojových kódů.

Page 73: Programovací jazyk C

Direktivy preprocesoru

#errorPomocí direktivy #error lze uměle vyvolat chybu při překladu. To je užitečné pro potřeby ladění. Předchozí příklad by šel modifikovat třeba takto:

// #define Win_x32// #define Win_x64// #define Linux

#ifdef Win_x32 printf ( "Verze aplikace \"tojsemtovymnouk\" pro 32 bitovou verzi Windows ... \n" );#elif Win_x64 printf ( "Verze aplikace \"tojsemtovymnouk\" pro 64 bitovou verzi Windows ... \n" );#elif Linux printf ( "Verze aplikace \"tojsemtovymnouk\" pro Linux ... \n" );#else#error Je nutne specifikovat architekturu!#endif

Chyba při překladu nastane, pokud programátor zapomene definovat (resp. odkomentovat) jeden z definovaných symbolů Win_x32, Win_x64, Linux.

Page 74: Programovací jazyk C

Direktivy preprocesoru

#includeDirektiva na místo sebe zkopíruje obsah zadaného souboru.

#include "hlavicka.h" // Prohledá aktuální adresář a vloží na toto místo celý soubor hlavicka.h

#include <stdlib>#include <stdlib.h> // Prohledá seznam knihoven C/C++ a vloží na toto místo hlavičku příslušné // standardní knihovny

Předdefinovaná makra preprocesoru__LINE__ ............................... je nahrazeno číslem řádku

__FILE__ .............................. je nahrazeno jménem souboru se zdrojovým kódem

__DATE__ ............................. je nahrazeno aktuálním datem

__TIME__ ............................... je nahrazeno aktuálním časem

__FUNCTION__ .................... je nahrazeno jménem funkce (pouze pro GCC)

__PRETTY_FUNCTION__ ..... je nahrazeno jménem funkce včetně rozlišení jejího typu (pouze pro GCC)

Page 75: Programovací jazyk C

Direktivy preprocesoru

#define TestMemError( ptr ) \ { \ if( (ptr) == NULL ) { \ fprintf( stderr, "Chyba alokace, soubor %s, řádek %i !\n" , __FILE__, __LINE__ ); \ exit( - 1); \ } \}

void main( void ){ int * poleInt = new int [100000]; TestMemError( poleInt ); float * poleFloat = new float [100000]; TestMemError(poleFloat ); float * poleChar = new char [1000000]; TestMemError(poleChar ); . . . }

Zde je příklad na použití maker a předdefinovaných proměnných preprocesoru. Není-li dost paměti na alokaci velkých polí, program by normálně spadl s jakousi nesrozumitelnou hláškou a výpisem. Takhle napíše, proč byl ukončen a kde přesně k chybě došlo. Všimněte si definice většího makra na více řádků za pomoci zpětného lomítka. Povšimněte si také výpisu chybové hlášky na standardní chybový výstup (stderr).

Page 76: Programovací jazyk C

Modulární programování

Je obvyklé, že větší program má zdrojový kód o mnoha tisících či dokonce desetitisících řádek. Kdyby toto všechno bylo v jednom souboru, byly by jakékoliv úpravy obtížné a orientace těžká. Navíc bývá obvyklé, že více programů má společné součásti (vstupně/výstupní operace, práce s diskem, výpočetní rutiny apod.). Z těchto důvodů je moudré rozdělit program do více souborů podle tematických celků.

Modul pro V/V operace

Modul pro práci s HDD

Modul pro výpočty

Celá aplikace. Každý soubor (modul) se překládá zvlášť.

Problém : každý modul potřebuje volat funkce z ostatních. Jak ale překladač ví, které funkce jsou ve kterém modulu?

Hlavní modul (fce main)

Odpověď : Zavedením hlaviček a možnosti deklarovat funkci, aniž bylo definováno její tělo.

operaceVV.hoperaceVV.c

praceHDD.hpraceHDD.c

vypocty.hvypocty.c aplikace.c

Page 77: Programovací jazyk C

Modulární programování

Hlavičky (soubory *.h) obsahují pouze deklarace funkcí, deklarace externích proměnných, definice typů a případné symboly preprocesoru. Vlastní těla funkcí obsahují soubory typu *.c .

typedef struct complex { float Re; float Im; };

complex Secti( complex clen1, complex clen 2 );void TiskniComplex ( complex cislo );

void main( void ){ complex A, B; A.Re = 1; A.Im = 2; B.Re = 3; B.Im = 4; complex C = Secti( A, B ); TiskniComplex( C );}

complex Secti( complex clen1, complex clen 2 ){ complex soucet; soucet.Re = clen1.Re + clen2.Re; soucet.Im = clen1.Im + clen2.Im; return soucet;}

void TiskniComplex ( complex cislo ){ if( cislo.Im < 0.0 ) printf( "%f - %fi", cislo.Re, abs( cislo.Im ) ); else printf( "%f + %fi", cislo.Re, cislo.Im );}

Na příkladu vlevo je vidět rozdíl mezi deklarací a definicí funkce. Deklarace obou použitých funkcí jsou bezprostřed-ně za definicí typu complex. Říkají překladači, že takovéto funkce existují a jsou definovány někde jinde. Proto je lze ihned použít ve funkci main. Překladač se při překladu souboru nestará o to, zda funkce opravdu někde existují - to udělá na závěr linker.

Page 78: Programovací jazyk C

Modulární programování

Předchozí zdrojový kód lze rozdělit například do těchto tří souborů. V complex.h je definice typu complex a deklarace funkcí, které s ním pracují. V souboru complex.c jsou definice funkcí, které pracují s komplexními čísly. Musí zde být direktivou #include vnořený soubor complex.h, protože samotné hlavičky se nepřekládají (pouze *.c soubory). Nakonec v aplikace.c je funkce main, která modul complex využívá.

complex.c#include "complex.h"

complex Secti( complex clen1, complex clen 2 ){ complex soucet; soucet.Re = clen1.Re + clen2.Re; soucet.Im = clen1.Im + clen2.Im; return soucet;}

void TiskniComplex ( complex cislo ){ if( cislo.Im < 0.0 ) printf( "%f - %fi", cislo.Re, abs( cislo.Im ) ); else printf( "%f + %fi", cislo.Re, cislo.Im );}

typedef struct complex { float Re; float Im; };

complex Secti( complex clen1, complex clen 2 );void TiskniComplex ( complex cislo );

complex.h

#include "complex.h"

void main( void ){ complex A, B; A.Re = 1; A.Im = 2; B.Re = 3; B.Im = 4; complex C = Secti( A, B ); TiskniComplex( C );}

aplikace.c

Page 79: Programovací jazyk C

Modulární programování

Podobný mechanizmus jako pro funkce (deklarace/definice) existuje pro proměnné. Globální proměnné lze deklarovat v hla-vičkách pomocí klíčového slova extern. Pozor! Když na extern zapomenete a prsknete proměnnou do hlavičky jen tak, vyloží si to překladač, jako že má definovat proměnnou tohoto jména v každém modulu, který se na complex odkazuje. A to je samozřejmě něco úplně jiného, než byl původní záměr ...complex.c

#include "complex.h"

complex A, B;

complex Secti( complex clen1, complex clen 2 ){ . . .}

void TiskniComplex ( complex cislo ){ . . . }

typedef struct complex { float Re; float Im; };

complex Secti( complex clen1, complex clen 2 );void TiskniComplex ( complex cislo );

extern complex A;extern complex B;

complex.h

#include "complex.h"

void main( void ){ A.Re = 1; A.Im = 2; B.Re = 3; B.Im = 4; complex C = Secti( A, B ); TiskniComplex( C );}

aplikace.c

Page 80: Programovací jazyk C

Modulární programování

V případě složitějších programů se nezřídka stává, že dochází k vícenásobným či dokonce rekurzivním vnořením hlaviček. To je ovšem nežádoucí a je proto třeba zavést nějaký ochranný mechanizmus. V C je to standardně použití direktiv preprocesoru #define a #ifndef následujícím způsobem:

#ifndef soubor1_H#define soubor1_H

. . . deklarace . . .

#endif

soubor1.h

#ifndef soubor2_H#define soubor2_H

#include "soubor1.h". . . deklarace . . .

#endif

soubor2.h

#include "soubor1.h" #include "soubor2.h"

#include "soubor1.h" #include "soubor2.h"

void main( void ) { . . . }

main.c

Ačkoliv jsou v tomto kódu (nesmyslné) požadavky na vícenásobná vnoření téže hlavičky, efektivně se každá hlavička dostane do každého modulu jen jednou.

CVIČĚNÍ : převedení předchozích složitějších cvičení do modulů

Page 81: Programovací jazyk C

Zdroje

• http://www.cppreference.com/

• http://www.cplusplus.com/

• http://www.cplusplus.com/doc/tutorial/

• http://www.cplusplus.com/reference/

• http://www.faqs.org/docs/Linux-HOWTO/C++Programming-HOWTO.html

• http://www.cpptalk.net/

Page 82: Programovací jazyk C

Shrnutí

• Úvod

• Překlad a linkování, skript Makefile

• Základní syntax, komentáře

• Typy a deklarace proměnných, lexikální rozsah platnosti

• Konstanty

• Složené typy - statická pole, výčty a struktury

•přetypování

• Operátory

• Podmínky, cykly, větvení

• Základy vstupu a výstupu

• Funkce (definice, předávání parametrů, přetěžování, rekurzivní volání)

• Klíčové slovo typedef

• Ukazatele a ukazatelová aritmetika

• Správa paměti, alokace dynamických struktur

• Složené typy - dynamická pole a řetězce

• Direktivy preprocesoru

• Modulární programování


Recommended