Skočiť na obsah


Programovanie v jazyku C a C#

Návody pre začiatočníkov a pokročilých



Fórum: Škôlka jazyka C
- - - - -
Fotografia

7. lekcia C (Reťazce) základná škola


7 Reťazce

Reťazec (string) je špeciálny typ jednorozmerného poľa. Je vždy zložený z prvkov typu char a v podstate sa správa ako každé iné jednorozmerné pole, ale s jedným malým dodatkom. Pretože je práca s reťazcami v programoch veľmi častá, je reťazcom v C venovaná táto špeciálna kapitola. V nej sa nedozviete žiadne prevratné novinky oproti už prebraným jednorozmerným poliam, ale skôr väčšie množstvo drobných, ale uzitočných informácií, ktoré sa môžu hodiť.

7.1 Základné informácie a definovanie reťazcov

Podstatná informácia, na ktorú pri práci s reťazcami nesmieme nikdy zabudnúť, je, že reťazec je vždy ukončený znakom '\0'. Podľa tohto znaku sa teda pozná dĺžka reťazca. Tento fakt má nasledujúce dôsledky:

Reťazec môže mať ľubovlnú dĺžku, obmedzenú iba veľkosťou pamäti. Z tejto celkovej pridelenej pamäti je ale "aktívna" (práve využitá) len jej časť od začiatku až do prvého znaku '\0'. Všetky ďalšie informácie uložené až za '\0' sú pri štandardnom spracovavaní reťazcov nedostupné, pretože práca s reťazcom končí vždy dosiahnutím prvého znaku '\0'.
Pri definovaní reťazca, teda alokovaní miesta pre neho, musíme alokovať o jeden Byte viac, práve pre túto '\0'.
Pokiaľ zabudneme dať na koniec reťazca znak '\0' alebo tento znak omylom prepíšeme, považuje sa za reťazec celá nasledujúca oblasť pamäti tak dlho, pokiaľ sa niekde ďalej v pamäti tento znak neobjaví.
Predpokladajme, že reťazec str má dĺžku 10 Bytov a je v ňom uložený text "ahoj". Potom je možné sa pozrieť, ako je tento reťazec uložený v pamäti.

Obrázok

Okrem tejto zvláštnosti je reťazec normálne jednorozmerné pole, zložené však vždy z prvkov typu char. Z toho, čo už vieme o jednorozmerných poliach, je možné usúdiť, že je možné vytvoriť reťazec statický alebo dynamický čo je absolútne správna úvaha.
Ďalej bude uvedený príklad vytvorenia reťazca o 10-tich prvkoch, pričom je nutné si uvedomiť, že:

Najdlhší text, do tohoto reťazca uložený, má dĺžku 9 znakov.
Pokiaľ budeme pristupovať k jednotlivým znakom pomocou indexovania, potom najvyšší index je 8. Pretože prvok s indexom 9 je práve ukončovací '\0', a prvok s indexom 10 už neexistuje.
Ukážme si príklad alokovania polí:

char s_stat[10];		 /* staticka alokácia */



char *s_dyn;

s_dyn = (char *) malloc(10); /* dynamická alokácia */
Obidva reťazce (s_stat, s_dyn) sú si ďalej absolútne rovnocenné.

Poznámka:

Dôrazne upozorňujeme, že pre dynamický reťazec nestačí len definovať pointer, ale je nutný aj ďalší krok - alokácia pamäti. Veľmi častou chybou je, že sa na túto alokáciu zabudne, a potom sa program samozrejme zrúti, pretože pracujeme s pamäťou, ktorá nie je naša.
Pokiaľ definujeme statický reťazec, často sa využíva možnosť ju súčasne inicializovať. Inicializácia je veľmi jednoduchá, pretože sa použije už známa retazcová konštanta:
char s1[10] = "Ahoj";	 /* definícia s inicializáciou */
Možnosti inicializácie sú este ďalej, takže keď potrebujeme definovať reťazec dlhý "práve akurát" pre určitý text, nemusíme pracne počítať jednotlivé znaky. To za nás urobí prekladač, ktorý zároveň alokuje potrebné miesto v pamäti.
char s2[] = "Nazdar";
definícia s inicializáciou bez udanej dĺžky reťazca.

Pozri si nasledovný program:
http://databaza.free...Herout/pomoc1.c

Výpis programu:

Obrázok


Poznámky:

Pozorného čitateľa už určite napadlo, čo sa v tomto prípade deje s ukončovacou nulou. Prekladač ju na koniec reťazcovej konštanty doplní sám.

Na rozdiel od Pascalu nie je možné priradiť statickému reťazcu konštantu, napr.:
char str[10];

str = "Ahoj"		/* nedá sa to */
Prekladač nám vynadá. Napr. Code:Blocks takto:

error: incompatible types when assigning to type 'char[10]' from type 'char *'
Adresa poľa str totiž nie je l-hodnota a nemôže sa objaviť na ľavej strane priradzovacieho príkazu.

Možno, že ste už niekde v programe videli podobnú definíciu:
char *str = "Ahoj";
a teraz vám vŕta hlavou, ako je možné inicializovať reťazec, keď pre neho nie je nikde alokované miesto. Nemajme obavy, vsetko je v absolútnom poriadku, pretože str tu nepredstavuje dynamický reťazec, ale pointer na typ char. A tento pointer je inicializovaný adresou reťazcovej konštanty, ktorá má obsah Ahoj. Pre túto konštantu prekladač vyhradil miesto v dátovej pamäti a samozrejme ju nezabudol ukončiť znakom '\0'. V tomto prípade sa žiadna dynamická pamäť pre reťazec str nealokuje.
Ešte viac pozorného čitateľa teraz asi napadlo, ako sa teda inicializuje dynamicky vytvoreny reťazec. Prezradíme dopredu, že sa to nedá, pretože inicializácia sa uskutočňuje v dobe prekladu a alokácia dynamickej pamäti v dobe behu programu. Pamäť, ktorá bola reťazcu dynamicky pridelená pomocou funkcie malloc() musíme sami v programe naplniť. V nasledujúcich dvoch spôsoboch je ten vyššie zle a ten nižšie dobre, hoci program by fungoval pre odidva.
/* chybna verzia */										

char *s_dyn;												

s_dyn = (char *) malloc(10);								

s_dyn = "Ahoj";

/* spravna verzia */

char *s_dyn;

s_dyn = (char *) malloc(10);

strcpy(s_dyn, "Ahoj");

Čo sa vlastne stalo? V obidvoch prípadoch sme správne definovali pointer na char, alokovali sme 10 Bytov v dynamickej pamäti a túto adresu sme priradili do s_dyn. V príklade vyššie sme v dalšom príkaze priradili do s_dyn adresu reťazcovej konštanty Ahoj. Od tejto chvíle s_dyn skutočne ukazuje na tento reťazec, ale nenávratne sme stratili adresu pôvodne alokovanej dynamickej pamäti. Pokiaľ budeme chcieť tento reťazec meniť, bude to možné, ale pozor - budeme meniť reťazcovú konštantu, pre ktorú bolo pridelených iba 5 Bytov pamäti. Je veľká pravdepodobnosť, že budeme chcieť využívať celých 10 Bytov v blahej viere, že sú naše, čo samozrejme spôsobí problémy. Je možné, že sa tieto problémy neprejavia zrútenim programu, ale iba jeho zlou funkčnosťou. Reťazcová konštanta je totiž uložená v dátovej oblasti spoločne s ďalšími údajmi programu. Podarí sa nám teda zrejme prepísať údaje a nie kód programu.
V druhom prípade sme správne využili štandardnej funkcie pre prácu s reťazcami - viď ďalej, ktorá uskutočnila skopírovanie reťazcovej konštanty "Ahoj" do alokovanej pamäti znak po znaku. Znamená to, že teraz je text "Ahoj" v pamäti dvakrát. Raz v statickej pamäti ako retazcová konštanta, použitá ako parameter funkcie strcpy(), a podruhý krat v dynamickej pamäti ako obsah reťazca s_dyn. Obidva tieto texty sú ale na sebe celkom nezavislé a text v reťazci s_dyn možeme ľubovolne meniť a hlavne možeme využívať všetkých alokovaných 10 Bytov.

Ako dôkaz vyššie napísaného si pozri tento program:
http://databaza.free....Herout/pomoc.c

Výpis programu:

Obrázok


Poznámky:

Občas sa pre definíciu reťazcov používa novy typ:
typedef char *STRING;
Je nutné rozlišovať nulový pointer a nulový reťazec. Nulový pointer má hodnotu NULL (čo je väčšinou 0), zatiaľ čo nulový reťazec je reťazec, ktorého prvý znak je '\0', napr.:
char *p_null_point, *p_null_str;

p_null_point = NULL;

p_null_str = (char *) malloc(10);

p_null_str[0] = '\0';
Pozor na rozdiel medzi: "x" a 'x'
"x" je retazcová konštanta dĺžky 2 Byty (znak 'x' + znak '\0')
'x' je znaková konštanta dĺžky 1 Byte
Je asi zbytočné zdôraznovať, že všade, kde sa objavujú reťazcové konštanty (texty v úvodzovkách), je možné použiť aj reťazec, napr.:
strcpy(s1, "POKUS.TXT");

fr = fopen(s1, "r");
7.2 Práca s reťazcom

7.2.1 Čítanie reťazca z klávesnice

Najčastejšie sa uskutočňuje pomocou funkcie scanf(). Príkaz vyzerá takto:

scanf("%s", s1);
Poznámky:

Formát pre čítanie reťazca je "%s", rovnako tak ako pre tlač reťazca.
Príkaz prečíta reťazec zadaný z klávesnice tým spôsobom, že preskočí všetky biele znaky, prečíta reťazec, ktorý je ukončený bielym znakom a ulozi ho na adresu s1. Ukončovaíim bielym znakom je najčastejšie klávesa <Enter>, ale je dobré si uvedomiť, že biely znak je aj medzera. To teda prakticky znamená, že keby sme zadali na vstupnom riadku text " ahoj evo ", uložilo by sa do reťazca s1 iba slovo "ahoj" a slovo "evo" by zostalo v buffere klávesnice. Toto je veľmi častá chyba. Ak potrebujeme prečítat celý riadok, bez ohľadu na medzery, je nutné použiť napr. funkciu gets().
Možno, že vas pri podrobnom prezeraní príkazu scanf() napadlo, že tam chýba operátor &, ten tam v tomto prípade nebude, pretože s1 predstavuje sám o sebe adresu.

Čítanie reťazca v danom formáte

Tak, ako je možné zadať formát výpisu, je možné zadať aj formát čítania, aj keď sa to nepoužíva prílis často. Nasledujúci program ukazuje, ako čítať reťazce v urcitom formáte. Program riesi situáciu, keď existuje súbor PENIAZE.TXT. Do tohoto súboru boli priebežne zapisované denné príjmy firmy a výdaje v amerických dolároch. O tomto súbore vieme len to, že obsahuje celé čísla a že každé toto číslo uvádza znak "$". Ďalej vieme, že píijem je uvedený znakom "+" a výdaj znakom "-", pričom nie je vždy splnené, že znak znamienka bezprostredne predchádza číslu. Riadok v súbore PENIAZE.TXT môže vyzerať napr. takto:
+ $10 -$5- $8 +$20



#include <stdio.h>

main() {

	long int suma = 0;

	FILE *fr;

	int kolko;

	char akcia[2];



	fr = fopen("PENIAZE.TXT", "r");



	while (fscanf(fr, "%1s", akcia) != EOF) {

		fscanf(fr," $%d", &kolko);

		suma += (akcia[0] == '+') ? kolko : (-1 * kolko);

	}



	printf("Celkom: $%ld \n", suma);

	fclose(fr);

}
Poznámky:

Na konci súboru vracia scanf() hodnotu EOF.
Je dôležité, aby operátor oper bol člen vo formáte "%ls". (Teda reťazec dĺžky jeden znak). Ak by bol totiž vstup napr.:
" -$5"
potom čítanie napr. pomocou:
fscanf(fr, "%c", &c)
by prečítalo do znakovej premennej c prvú medzeru pred znakom "-".

Iný formát, napr.:
fscanf(fr, "%s", akcia)
prečíta do reťazca oper znaky "-", "$" a "5" a ukončí ich '\0', čím jednak prečíta pole akcia vyhradene len pre jeden znak a jednak sa nenačíta číslo 5 do premennej koľko.
Reťazec akcia má dĺžku 2, pretože je vždy nutné počítať s miestom pre ukončujúci nulový znak.
Medzera vo formáte " $%d" je veľmi dolezita, pretože hovorí, že sa majú preskočťt všetky predchadzajúce biele znaky.
Formát v scanf() umožňuje mnoho ďalších kúziel. Pokiaľ ich budete chcieť objavovať, doporučuje sa robiť čo najjednoduchšie pokusy.


7.2.2 Tlač reťazca na obrazovku

Najčastejšie sa uskutočňuje pomocou funkcie printf(), ale sú možné ďalšie sôosoby, ktoré sú uvedené v časti 7.4.

Príkaz vyzerá takto:
printf("%s", s1);
a tlačí znaky od adresy s1 až do okamžiku pokiaľ nenarazí na ukončovací znak '\0'.

Poznámka:

Čitateľa občas napadne, že keď sme v scanf() vynechali operátor &, mali by sme v printf() pridať dereferenčný operátor *, teda:
printf("%s", *s1);
Táto úvaha opät nie je správna. Formát "%s" totiž funkcií printf() hovorí, že má očakávať adresu a nie hodnotu. Výsledkom týchto komplikácií nech je tvrdenie, že sa reťazec číta inak, než jednoduchá premenná, ale tlačí sa úplne rovnako ako akákľvek iná premenná.

Príklad:

Program prečíta reťazec nie dlhší než 10 znakov a vypíše ho na obrazovku.
#include <stdio.h>

main() {

	char str[11];



	printf("Zadaj retazec: ");

	scanf("%s", str);

	printf("retazec je : %s \n", str);

}
Definovaná dĺžka reťazca je 11, ale reťazec nesmie presiahnuť dĺžku 10-tich znakov, pretože potom bude prepisovať nepridelenú pamäť, ležiacu za str. V týchto píipadoch je vhodnejšie čítať reťazec v obmedzenom formáte, napr.:
scanf("%10s", str);
Príklad:
Pre dynamický reťazec bude program vyzerať takto:
#include <stdio.h>

#include <stdlib.h>



main() {

	char *str;



	if ((str = (char *) malloc(11)) == NULL) {

		printf("Malo pamati");

		return;

	}



	printf("Zadaj retazec : ");

	scanf("%10s", str);

	printf("retazec je : %s \n", str);

}

7.2.3 Prístup k jednotlivým znakom reťazca

Často sa nám stane, že potrebujeme pracovať s jednotlivými znakmi reťazca. Ako už sme niekoľkokrát zdôrazňovali, reťazec je normálne jednorozmerné pole, takže prístup k jeho jednotlivým prvkom (znakom) by nám nemal robiť žiadne problémy.

Príklad:
Nasledujúca časť programu vyplni s1 hviezdičkami.

for (aj = 0; aj < 10 - 1; aj++)

	s1[aj] = '*';

s1[10-1] ='\0';				 /* ukoncenie retazca */
Poznámka:

V predchádzajúcom príklade si všimnite posledný riadok. Na ukončovací znak sa nesmie nikdy zabudnúť! Časté chyby teda sú:
for (aj = 0; aj < 10; aj++)	

	s1[aj] = '*';

for (aj = 0; aj < 10; aj++)

s1[aj] = '*';

s1[10] = '\0';

Kde sme v prvom prípade zabudli na ukončovací znak úplne a v druhom prípade sme ho zapisali do oblasti pamäti, ktorá už nie je naša. Ak je reťazec definovany ako:
char s[MAX];
potom využiteľná dĺžka je: MAX - 1


7.2.4 Štandardné funkcie pre prácu s reťazcami

Tu je opät nutné zdôrazniť, že C nedefinuje prácu s reťazcami ako sucast jazyka. Pretože sú ale práce s reťazcami veľmi časté, poskytuje jazyk C vdaka svojej štandardnej kniznice množstvo funkcií, pomocou ktorých sa pracuje s reťazcami ľahko a rýchlo. (V tomto prípade to nie je reklamný trik, pretože funkcie sú optimalizované, tak že sú skutočne rychlejšie, než keby ste si napísali vlastné). Ak chceme tieto funkcie využívať, je nutné pripojiť do našeho programu štandardný hlavičkový súbor string.h príkazom:
#include <string.h>
Po tomto prikaze možeme využívať množstvo funkcií, z ktorých ďalej uvedieme len tie najdôležitejšie vždy formou úplneho funkčného prototypu, stručného popisu a príkladu použitia.

Dĺžka reťazca
int strlen(char *s);
Vracia dĺžku reťazca s bez ukoncovacieho znaku '\0' Napr.:
strlen("ahoj")
vráti hodnotu 4

Kopírovanie reťazca
char *strcpy(char *s1, char *s2);
Skopíruje obsah reťazca s2 do s1. Vracia pointer na prvý znak reťazca s1. Napr.:
strcpy(str, "ahoj");
v str bude "ahoj"

Spojenie reťazcov
char *strcat(char *s1, *s2);
Pripojí s2 k reťazcu s1. Vracia pointer na prvý znak reťazca s1. Napr.:
strcat(str, " + nazdar");
v str bude "ahoj + nazdar"

Nájdenie znaku v reťazci

Obrázok

Pokiaľ sa znak v premennej c vyskytuje v reťazci s, potom je vráteny pointer na jeho prvý výskyt, inak je vrátená hodnota NULL. Napr.: z predchádzajúceho reťazca:

Obrázok

vráti NULL

Príklad použitia:

Obrázok

Výpis:

Obrázok

Porovnávanie dvoch reťazcov

int strcmp(char *s1, char *s2);

Vráti 0, ak sú reťazce s1 a s2 rovnaké. Vráti záporné číslo, ak je s1 lexikograficky menší než s2 a kladné číslo v opačnom prípade.

Nájdenie podreťazca v reťazci
char *strstr(char *s1, char *s2);
Nájde prvý výskyt reťazca s2 v reťazci s1 a vráti pointer na tento výskyt, alebo vráti NULL v prípade neúspechu.

Práca s obmedzenou častou reťazca

V štandardnej knižnici sú taktiež implementovné funkcie, ktoré nemusia pracovať s celým reťazcom (teda až do ukončujúceho znaku '\0'), ale iba so zadaným počtom jeho prvých znakov. Tieto funkcie vyzerajú podobne, ako funkcie vyššie uvedené, len s tým rozdielom, že majú v názve písmeno "n" - ako number. Druhým rozdielom je samozrejme ďalší formálny parameter naviac, pomocou ktorého sa predáva maximálny počet spracovavaných znakov reťazca. Funkcie spracovávajú reťazec buď do určenej dĺžky - ak je reťazec dlhší než požadovaná dĺžka spracovávania, alebo do ukončovacieho znaku '\0' - ak je reťazec v skutočnosti kratsi než požadovaná dĺžka spracovávania.

Príklad:
Ako príklad týchto funkcií uvedieme funkciu, ktorá skopíruje najviac max znakov z s2 do s1 a potom pridá znak '\0'.
char *strncpy(char *s1, char *s2, int max);
Po príkaze:
strncpy(str, "alkoholicke", 7);
bude v str reťazec: "alkohol"

Praca s reťazcom odzadu

Ďalšia množina funkcií pracuje s reťazcami "odzadu". Tieto funkcie sa opät veľmi podobajú príslušným základným funkciám, s tým rozdielom, že majú vo svojom názve písmeno "r" - ako reverzia. Rozdiel v ich práci je, že reťazec nespracovávaju od počiatočnej adresy reťazca, ale od adresy jeho koncového znaku '\0' smerom k počiatku reťazca.

Príklad:
Ako príklad týchto funkcií uvedieme funkciu, ktorá hľadá znak v premennej c v reťazci str od jeho konca. Pokiaľ sa znak v premennej c vyskytuje v reťazci str, potom je vráteny pointer na jeho posledný výskyt, inak je vrátené NULL.

Obrázok

Prevody reťazcov na čísla

V programe často potrebujeme skonvertovat číselnú hodnotu premennej na jej zodpovedajúci reťazec číslic alebo naopak. Pokiaľ sa to deje v rámci vstupov alebo výstupov, nemusíme sa týmto problémom trápit, pretože napr. funkcia scanf() a printf() to vlastne robia automaticky. Ak sa nejedná o vstupy a výstupy, možeme použiť niekoľko funkcií, ktoré uskutočňujú prevod reťazca na číslo. Poznamenajme, že funkčné prototypy týchto funkcií nie sú v súbore string.h ale v súbore stdlib.h. Pre konverziu reťazca na číslo sa používa funkcia atoi() - názov vznikol ako skratka z ASCII to integer. Jeho uplný funkčný prototyp je:
int atoi(char *string);
Táto funkcia konvertuje reťazec obsahujúci číslo na celočíslenú hodnotu typu int, ktorú vráti ako svoj funkčný parameter.
Ďalšie funkcie s podobnou činnosťou sú:
long atol(char *string);		 // konvertuje string na long

double atof(char *stirng);	 // konvertuej string na double
Príklady použitia uvedených funkcií sú v nasledujúcom programe:
http://databaza.free...unkcie_string.c

Výstup z programu:

Obrázok
Obrázok

7.3 Formátované čítanie a zápis z a do reťazca

V niektorých prípadoch by sme v programe potrebovali využiť výhod formátovaného výpisu, tak ako ho poznáme z funkcie printf(), ale nechceme tento výstupný reťazec tlačiť. V tomto prípade možeme využiť služieb funkcie sprintf(), ktorá pracuje absolútne rovnako ako funkcia printf(), ale výsledok svojej práce zapíše do reťazca. Tento reťazec je možné potom ďalej programom ľubovolne spracovávať. To môže byť v mnohych prípadoch užitočné, napr. keď viac funkcií pripravuje do spoločného bufferu správy, ktoré majú byť vytlačené, a jedna funkcia ich skutočne tlačí podľa nejakého algoritmu. Potom všetky podriadené funkcie zapisujú do reťazcov pomocou sprintf() a tieto reťazce predávajú funkcii, ktorá ich uloží do bufferu a ďalšia funkcia ich vo vhodnej chvíli zobrazí.

Poznámka:

Pokiaľ nám spôsob práce funkcie sprintf() nie je jasný, môžeme si ju predstaviť ako funkciu fprintf(), ktorá zapisuje do súboru. Funkcia sprintf() robi to iste, ale nezapisuje do súboru ale do reťazca.
Rovnako tak, ako k funkcii printf() patrí funkcia scanf() a k funkcii fprintf() funkcia fscanf(), je aj k funkcii sprintf() duálna funkcia sscanf(). Funkcia sscanf() číta do špecifikovaných premenných zo zadaného reťazca.

Ak sa zamyslíme nad funkciami sprintf() a sscanf(), prídeme na to, že vlastne významne rozširujú možnosti konverzie čísel na reťazec a naopak - viď tiež predchádzajúcu časť. Tam boli popísane len funkcie pre prevod reťazca na číslo a opačný prevod - čísla na reťazec vôbec nebol k dispozícii. Pomocou funkcií sprintf() a sscanf() môžeme tieto prevody robiť s veľkým komfortom a uskutočňovať napr. aj do/z hexadecimálnej sústavy.

Príklad:
Nasledujúci program prečíta štyri hexadecimálne číslice do reťazca s1, z tohoto reťazca ich potom skonvertuje do premennej aj. Premenna aj je najskôr prevedená na reťazec osmičkových čísel s2 a tento reťazec je potom vytlačený.

#include <stdio.h>



main() {

int aj;

char s1[5], s2[10];



printf("Zadaj 4 hexacislice : ");

scanf("%s", s1); // do s1 sa nacita hexi premenna, napr.: ffff

sscanf(s1, "%x", &aj); //v s1 sa premenna skonvertuje na dekadicke cislo

sprintf(s2, "%o", aj); // do s2 sa premenna z dekadickeho cisla skonvertuje na oktanove

printf("%s \n", s2); // vypis s2 - oktanove cislo

}

Príklad:
Funkcia uradnik() je typická funkcia, ktorá pripravuje výstupnú správu z niekoľkých položiek a túto správu vracia ako svoj parameter. Starosťou funkcie sef() je iba definovať dostatočne veľký reťazec a správne volať funkciu uradnik().

#include <stdio.h>

#include <string.h>



void uradnik(char *zpr, double polomer) {

double obsah, obvod;

obsah = 3.14 * polomer * polomer;

obvod = 2 * 3.14 * polomer;

sprintf(zpr, "Kruh o polomere %f ma obsah %f a obvod %f \n", polomer, obsah, obvod);

}

void sef(void) {

char sprava[120];

uradnik(sprava, 5.0);

strcat(sprava, "\t \tSpracoval sef \n");

printf("Dovolujem si predniest spravu o kruhu: \n %s", sprava);

}

main()

{

sef();

}


7.4 Riadkovo orientovaný vstup a výstup z terminálu

V predchádzajúcom texte boli popísane funkcie scanf() a printf(), ktoré zaisťujú vstup a výstup "jednoduchého" reťazca. Nasledujúce funkcie pracujú s reťazcom, v ktorom je uložený celý riadok.

10.4.1 Čítanie riadku z klávesnice

Funkcia scanf(), ktorá načíta reťazec zadaný z klávesnice do premennej typu reťazec, má niekedy tú nevýhodu, že si zadaný reťazec predspracuje. To znamená, že vynechá všetky biele znaky pred reťazcom a čítanie reťazca skončí na prvom bielom znaku - bez ohľadu na to, či ešte reťazec pokračuje, alebo nie. Ak by sme zadali napríklad z klávesnice reťazec: " ahoj ludia " potom by prikaz:

scanf("%s", str);
nacital do reťazca str iba "ahoj".
Z tohoto príkladu je vidieť, že keď potrebujeme načítat z klávesnice celý riadok až do ukončovacieho znaku '\n', musíme použiť inú funkciu než funkciu scanf(). Štandardná knižnica funkcií v C poskytuje funkciu gets(), ktorá požiadavok na prečítanie celého riadku naraz splňuje. Má nasledujúci funkčný prototyp:
char *gets(char *str);
Funkcia gets() (Je podobná funkcií READLN z Pascalu) číta celý riadok až do znaku '\n' a uloží ho do reťazca str, ktorý ukončí znakom '\0'. Znak '\n' sa neukladá! Zároven gets() vracia pointer na reťazec str. Pokiaľ bol riadok prázdny, vracia NULL a do s[0] dá ukončovací znak '\0', teda str bude nulový reťazec.

Tento program to ukazuje v praxi:

#include <stdio.h>



main() {

char s1[50], s2[50];



printf("Majme definovane retazce s1[50] a s2[50].\n"

		 "Zadaj celu vetu (retazec s1):\n");

scanf("%s", s1); // do s1 sa nacita len prve slovo z vety

while (getchar() != '\n')

	;

printf("Pouzili sme prikaz: scanf(\"%%s\", s1);\n");

printf("Vypis je: \"%s\", teda nie cela veta, ale iba prve slovo.\n\n", s1);

printf("Zadaj tu istu, alebo inu vetu vetu (retazec s2):\n");

gets(s2);

printf("Pouzili sme prikaz: gets(s2);\n");

printf("Vypis je: \"%s\", teda cela zadana veta.\n", s2);

}
7.4.2 Vypis riadku na obrazovku

Pokiaľ chceme na obrazovku vypísať reťazec str (pripravený skôr napr. funkciou sprintf()) ako riadok ukončený znakom '\n' možeme samozrejme použiť prikaz:

printf("%s \n", str);
ktorý funguje presne tak, ako je požadované. Malý problém je ale v menšej efektivite programu, pretože printf() musí najskôr pomocou formátovej specifikácie "%s \n" pomerne zložito zistiť, čo to má tlačiť. Funkcia puts() (Je podobná funkcii WRITELN z Pascalu) je pre tento prípad vhodnejšia, pretože nad ničim "nepremýšľa" a hneď zadaný reťazec tlačí a naviac ešte sama odriadkuje - vypíše znak '\n'. Jej funkčný prototyp je:
int puts(char *str);
Pokiaľ funkcia puts() z nejakého dôvodu nemohla pracovať, potom vráti EOF, inak vráti nezáporné číslo.

Príklad:


#include <stdio.h>



main() {

char s1[50], s2[50];



printf("Majme definovane retazce s1[50] a s2[50].\n"

		 "Zadaj celu vetu (gets(s1)<img src='http://forum.freespace.sk/public/style_emoticons//wink.png' class='bbc_emoticon' alt=';)' />:\n");

gets(s1);



sprintf(s2, "%s", s1); // do s1 sa nacita len prve slovo z vety

printf("Prikazom sprintf(s2, \"%%s\", s1); zapiseme obsah s1 do s2.\n");

printf("Prikaz: puts(s2) vypise obsah s2;\n");

printf("Vypis je: ");

puts(s2);

putchar('\n');

}
7.5 Riadkovo orientovaný vstup a výstup zo súboru

7.5.1 Čítanie riadku zo súboru

Pre vstup riadku zo súboru slúži funkcia:

char *fgets(char *str; int max, FILE *fr);
ktorá číta reťazec zo súboru fr až do konca riadku, najviac však max znakov a vrátane znaku '\n' ho uloží do reťazca str. Funkcia fgets() vracia pointer na str alebo, pri dosiahnutí konca súboru, vracia NULL.

Poznámky:

Pretože fgets() vie určiť maximálnu dĺžku čítaného riadku, môže byť užitočná aj pre čítanie z terminálu, kde táto možnosť nie je. V tomto prípade použijeme prikaz:
fgets(s, max, stdin);
Ak fgets() načíta zo súboru znak používany pre End-Of-File (EOF), potom ho taktiež uloží do reťazca.

Príklad:
Program prečíta súbor DOPIS.TXT po riadkoch a vytlačí ho na obrazovku. Dĺžka čítaneho riadku je obmedzená na 80 znakov.



#include <stdio.h>



main() {

char riadok[81];

FILE *fr;



if ((fr = fopen("DOPIS.TXT", "r")) == NULL) {

	printf("Subor DOPIS.TXT sa neda otvorit \n");

return;

}

while (fgets(riadok, 70, fr) != NULL) {

	printf("%s", riadok);

	putchar('\n');

}

if (fclose(fr) == EOF)

	printf("Subor DOPIS.TXT sa neda zatvorit \n");

}

Poznámka:

Všimnite si funkciu printf(). Bola použitá namiesto funkcie puts(), pretože funkcia fgets() načíta reťazec aj so znakom '\n'. Keby sme tento reťazec tlačili pomocou puts(), bol by za každým vypísaným riadkom jeden riadok prázdny.


7.5.2 Zapis riadku do súboru

Ak potrebujeme zapísať riadok do súboru a neodriadkovať, použijeme funkciu fputs(), ktorej funkčný prototyp je:
int fputs(char *s, FILE *fw);
Funkcia fputs() po zapísaní reťazca neodriadkuje a ani nezapisuje do súboru ukončovací znak reťazca '\0'. Jej návratovou hodnotou je nezáporné číslo. V prípade neúspechu vracia EOF.


7.6 Riadiaci reťazec formátu pre tlač

V tejto chvíli už máme dostatok vedomostí na to, aby bolo možné podrobne popísať riadiaci reťazec formátu pre funkciu printf() a jej podobné funkcie. Táto podkapitola bude skôr podobná manuálu než učebnici, avšak považujeme za vhodné ju uviesť, pretože tlač je veľmi často používana akcia.

Za každým znakom "%" v riadiacom reťazci formátu môžu byť tieto položky:
%[priznaky][sirka][.presnost][modifikator]konverzia
kde položky:
príznaky šírka presnosť modifikátor sú nepovinné - to naznačujú hranaté zátvorky.

Ďalej budú podrobne popísané všetky položky.


7.6.1 Konverzia

Jedná sa o jeden znak, ktorý musí byť vždy uvedený. Medzi ním a znakom "%" môžu byť ďalšie položky.

Obrázok

Obrázok

Obrázok

Obrázok


7.6.2 Modifikátor

Jedná sa o jeden voliteľný znak, ktorý, ak je pouzity, musí bezprostredne predchádzať znaku konverzie. Medzi ním a znakom "%" môžu byť ďalšie položky.

Obrázok


7.6.3 Šírka

Je to dekadické číslo, ktoré nastavuje minimálny počet vypisovaných znakov. Ak bude vypisované číslo väčšie než šírka, potom sa hodnota čísla vytlačí správne a na veľkosť šírka sa neberie ohľad.

Medzi šírka a znakom "%" môže byť položka príznaky a medzi šírka a položkou modifikátor môže byť položka presnosť, ktorá je od položky šírka oddelená znakom bodka ".".

Minimálny počet tlačených znakov sa dá taktiež určiť nepriamo pomocou znaku "*". V tomto prípade určuje počet znakov argument typu int, ktorý musi v zozname argumentov bezprostredne predchádzat argumentu, ktorý sa má tlačiť.

Obrázok


7.6.4 Presnosť

Je to dekadické číslo, ktoré:
Pre konverzie	d aj u o x X	nastavuje minimálny počet cifier čísla na výstupe - teda ako šírka

Pre konverzie	f e E	nastavuje počet cifier za desatinnou bodkou

Pre konverzie	g G	nastavuje maximálny počet vyznamových cifier

Pre konverziu	s	nastavuje maximálny počet tlačených znakov - vie teda "orezat" vypisovaný reťazec
Poznámka:

Pri určovaní počtu desatinných miest nezabudnite, že desatinná bodka je taktiež jeden znak.
Opäť sa dá určiť počet znakov nepriamo pomocou znakov "*". V tomto prípade určuje počet znakov argument typu int, ktorý musi v zozname argumentov bezprostredne predchádzat argumentu, ktorý sa má tlacit. Ak použijeme dve hviezdičky pre šírka a presnosť, potom musia byť v zozname argumentov dva argumety.

Obrázok


7.6.5 Príznak

Ďalej uvedené hodnoty príznak sa ako jediné z doposiaľ popísanych položiek môžu kombinovať medzi sebou. Znaky príznaku musia bezprostredne nasledovať za znakom "%".

Obrázok


7.6.6 Príklady rôznych formátov tlače

Všetko, čo bolo doposiaľ popísane vyzerá veľmi zložito. Našťastie väčšinu týchto informácií v najbližšej dobe asi nevyužijete. Vráťte sa k nim až v prípade, že budete chcieť nejakú komplikovanú tlač. Potom je vhodné urobiť sériu pokusov, na ktorých sa naučíte jednotlivé "triky" tlače používať. Aby ste mali aspoň nejakú predstavu, môžete sa skúsiť zorientovať v nasledujúcom vypise programu. Je na ňom vidieť, aký vplyv má položka príznak. Bolo vždy tlačené celé číslo o hodnote 555 a reálne číslo o hodnote 5.5, pričom položky šírka, presnosť a konverzia sú uvedené v záhlavi tabuľky. Položka modifikátor nebola použitá. Prvé dve čísla na prvom riadku boli teda vytlačené formáty:
%-+#06d	a	%-+#06o
Obrázok


Čo je dobré si uvedomiť:

Reťazec je ukončený znakom '\0' a je potrebné mať pre tento znak miesto, teda alokovať pamäť o jeden char väčšiu, než je využiteľná dĺžka reťazca.
Pokiaľ využívame funkcie pre prácu s reťazcami, je nutné pripojiť hlavičkový súbor string.h.
Ak používame funkcie pre prácu s reťazcami a ich skutočné parametre sú pointery, je potrebné pred volaním funkcie alokovať pamäť pomocou funkcie malloc(). Je nutné pracovať len s pamäťou, ktorú máme k dispozícii!


Úlohy:

1. Napíš program, ktorý bude čítať z klávesnice reťazce, uzavrie ich do rámčeku z hviezdičiek a vypíše na obrazovku, napr.:

*************************

* toto je pekny priklad *

*************************

Spoiler


2. Definuj s ako reťazec znakov dĺžky 20. Priraď mu obsah:
Toto je priklad
pomocou funkcie strcpy(). Ďalej definuj pointer na char. Alokuj dynamicky pamäť do ktorej prekopíruj reťazec s. Vytlač novo vzniknutý reťazec.
Spoiler

3. Prečítaj reťazec dĺžky max. 20 znakov a vytlač tieto znaky v jednom riadku zoradené podľa abecedy vzostupne.
Spoiler

4. Existujú slová, ktoré sa čítajú rovnako zľava aj sprava (napr. radar). Vytvor program, ktorý bude tieto slová generovať z ich prvej načítanej polovičky.
Spoiler

5. Napíš program, ktorý prečíta reťazec a v závislosti na jeho poslednom znaku vykoná:
l (L) prevod reťazca na malé písmená (lower)
u (U) prevod reťazca na veľké písmená (upper)
x (X) vzájomné prehodenie malých a veľkých písmen (exchange)

Zmeny vykonaj v reťazci, nie len na výstupe.
Spoiler

6. Definuj reťazcovú konštantu a zisti, od ktorej adresy je uložená v pamäti.
Spoiler

7. Napíš program, ktorý prečíta riadok znakov z klávesnice a potom prečíta samostatný jeden znak. Vstupný riadok opíš a všetky znaky v tomto riadku, ktoré sa zhodujú so zadaným znakom, podčiarkni pomocou znaku '*' (hviezdička). Z cvičných dôvodov použi funkciuObrázok.
Spoiler

8. Napíš program, ktorý bude na obrazovku vypisovať obsah súboru. Meno súboru a príponu súboru zadávaj oddelene z klávesnice a nezadávaj oddeľovací znak '.' (botku). Využi funkciu sprintf() a zaisti tiež, aby prípona súboru nemohla byť zadaná dlhšia ako troj písmenová.
Spoiler

9. Čítaj súbor a vypíš len tie riadky, v ktorých sa bude vyskytovať slovo zadané z klávesnice. Využi funkcie fgets() a strstr().
Spoiler

10. Pomocou funkcie fgets() vypíš počet znakov v jednotlivých riadkoch súboru. Zaisti správne chovanie aj pre prázdne riadky v súbore.
Spoiler

11. Prečítaj z klávesnice riadok pozostávajúci zo slov oddelených jednou medzerou. Pomocou funkcie Obrázok vytlač na každý riadok vždy len jedno slovo zo zadaného riadku bez úvodnej medzery.
Spoiler

12. Napíš funkciu strings(char *s1, char *s2, int i);, ktorá vloží do reťazca s1 od pozície i reťazec s2. Využite funkciu strcat(). Použi v programe.
Spoiler


13. Napíš funkciu strdel(char *s, int i, int n);, ktorá vymaže z reťazca s n znakov od pozície i vrátane. Použi v programe.
Spoiler


14. Napíš príkaz na tlač čísla Pí (ludolfovo číslo), ktoré nadefinuj ako symbolickú konštantu s presnosťou aspoň na 7 desatinných miest. Z klávesnice budeš zadávať, na koľko desatinných miest má byť toto číslo vytlačené. Použi reťazec ako riadiaci formát funkcie printf().
Spoiler
  • 0

Popis: Reťazce
- Základné informácie a definovanie reťazcov
- Čítanie reťazca z klávesnice
- Čítanie reťazca v danom formáte
- Tlač reťazca na obrazovku
- Prístup k jednotlivým znakom reťazca
- Štandardné funkcie pre prácu s reťazcami
- Dĺžka reťazca
- Kopírovanie reťazca
- Spojenie reťazcov
- Nájdenie znaku v reťazci
- Porovnavanie dvoch reťazcov
- Nájdenie podreťazca v reťazci
- Práca s obmedzenou častou reťazca
- Praca s reťazcom odzadu
- Prevody reťazcov na čísla
- Formátované čítanie a zápis z a do reťazca
- Riadkovo orientovaný vstup a výstup z terminálu
- Čítanie riadku z klávesnice
- Vypis riadku na obrazovku
- Riadkovo orientovaný vstup a výstup zo súboru
- Čítanie riadku zo súboru
- Zapis riadku do súboru
- Riadiaci reťazec formátu pre tlač
- Konverzia
- Modifikátor
- Šírka
- Presnosť
- Príznak
- Príklady rôznych formátov tlače
- Úlohy k lekcii

Poznámky:
Odkazy:


0 Komentárov


Najlepšie lekcie


Najnovšie pridané lekcie


Najnovšie komentáre


Najviac komentované lekcie


Najviac zobrazené lekcie


Náhodné lekcie


Na tejto stránke bolo užívateľ(ov) za posledných 30 minút

členov, návštevníkov