|
|
Dátové objekty 2 - stringyObsah tém.
Dátový objekt reťazec (STRING)Reťazec znakov je zvláštnym druhom jednorozmerného poľa. Sú zložené z prvkov typu char. Osobitne sa nimi zaoberáme z jedinej príčiny: Reťazce sú základom textových a databázových súborov. Reťazcové konštanty zapisujeme v programe ako postupnosť znakov uzavretých medzi úvodzovkami. V pamäti je reťazec ukončený znakom '\0', ale pri vypisovaní to nepoznať, lebo tento znak je "neviditeľný". Naopak, v prípade, že omylom zmažeme tento znak, programy pracujúce s reťazcom dovtedy budú hľadať koniec reťazca (stringu) pokiaľ náhodne nenarazia na najbližší znak '\0'. Reťazce môžeme spracovávať jednak poliami a jednak pomocou pointrov. Pointre a polia sú v podstate zameniteľné technológie. Dátový objekt POLEPole je dátový objekt zložený z prvkov rovnakého typu. Znamená to zároveň, že každý prvok poľa zaberá rovnaké miesto v pamäti a pozícia (adresa) každého prvku sa dá vypočítať z bázovej adresy (počiatočnej adresy poľa) a poradového čísla prvku. Počet prvkov poľa sa určí pri deklarácií a tým sa vymedzí veľkosť adresného miesta celého poľa. Jednorozmerné pole znakovDeklarácia poľa: Deklarácia poľa je podobná deklarácií
jednoduchej premennej s tým rozdielom, že za menom premennej uvedieme v hranatých
zátvorkách počet prvkov poľa. dátový_typ meno_poľa[rozmer];
char meno[20];
Čo vieme zistiť z deklarácie?
Počet prvkov poľa sa určí pri deklarácií. Údaj o počte prvkov je možné v deklarácií vynechať ( ak je pole definované inde) alebo v definícií (kde inicializujeme všetky prvky). Inicializácia Pri definícií je možné pole zároveň aj inicializovať. Vtom prípade nasleduje za menom poľa a hranatými zátvorkami znak rovnosti a za ním v zložených zátvorkách hodnoty, ktoré sa majú priradiť jednotlivým prvkom. V prípade stringov nahradia zložené zátvorky dvojité úvodzovky. Za platnými znakmi string sa automaticky doplní o koncový znak '\0'. Pozor na číslo v hranatých zátvorkách. Koncový znak musí byť súčasťou stringu (započítava sa!). Prvkom poľa čísel,
ktoré neinicializujeme sa zvyčajne automaticky priradí hodnota 0. V prípade
poľa znakov obsahujúceho kratší string než je dĺžka poľa (t.j. za
hodnotou '\0' do konca poľa budú voľné pozície) bude až do konca poľa
obsahovať náhodné
údaje.
char meno[20]="Jano";
je to isté ako:
char meno[20]={ 'J','a','n','o','\0' };
alebo
char meno[]="Jano"; // v tomto prípade vzniklo pole meno[5]
K jednotlivým prvkom poľa sa pristupuje pomocou
indexov, ktoré sa zapisujú do hranatých zátvoriek za menom poľa.
Index môže byť konštanta, ale aj premenná. Samotný identifikátor poľa predstavuje adresu 0-tého prvku poľa t.j. začiatok
poľa. Prvkom v poli priradzujeme hodnoty dvoma spôsobmi a to buď v cykle napr. pomocou funkcie scanf alebo priamo, vymenovaním jednotlivých hodnôt. Tento spôsob si však vyžaduje poznať hodnoty vopred napr.: int pole[10] = { 1,4,5,7,8,9,6,5,54,23}; // pole[9] == 23 int pole[] = { 1,4,5,7,8,9,6,5,54,23}; // identická definícia Pri druhom spôsobe nemusíme uvádzať počet prvkov poľa v hranatých zátvorkách Dátový objekt POINTERPredstavuje premennú určenú výlučne na adresovanie pozície dát v pracovnej pamäti. T.j. jej obsahom je vždy ADRESA. Ak chceme pracovať so samotným údajom na danej adrese, predradíme pred názov pointra referenčný znak *.
int x; // obyčajná premenná typu integer
int *p_x= &x; // pointer p_x na premennú x (&x je adresa premennej)
char *p_meno= "Jano"; // definícia pointra p_meno na typ char a string Jano\0
Pointer môže obdržať adresu už existujúcej premennej, alebo existujúceho poľa obyčajným priraďovacím príkazom. Podobne ako definícia poľa je spojená s typom premennej, aj pointer je deklarovaný na dátový typ (t.j. pointer na daný typ, resp. adresa na daný typ). Vďaka možnosti zmeny adresy v obsahu pointra sú pointre predurčené pre prácu s dynamickými dátami - t.j. pridelenie pamäti, odobranie pamäti, zmena adresy v pointri, ... Tak ako polia aj pointre majú vždy určené koľko pamäťového priestoru zaberá typ premennej na ktorú pointer ukazuje. V predošlom príklade kompilátor uložil reťazcovú konštantu "Jano" do dátovej oblasti, na koniec doplnil \0, a priradil jej začiatok do pointra p_meno. Dynamické prideľovanie adresného priestoru zabezpečuje počas behu programu príkaz malloc() - memory allocation.
char *s_dyn = NULL; // vytvorenie prázdneho pointra
s_dyn= (char*) malloc(10 * sizeof(char)); // pridelenie priestoru pre 10 znakov
strcpy(s_dyn,"Ahoj"); // naplnenie priestoru stringom "Ahoj"
s_dyn="JAJAJ"; // kompilátor uloží reťazcovú konštantu a naplní adresou
// pointer s_dyn !!! pritom sa stratí pôvodná oblasť
// posledný príkaz nie je priradenie HODNOTY "JAJAJ" ale priradenie jeho adresy
Pointer s_dyn z predošlého príkladu najprv ukazuje na pamäťovú oblasť vyčlenenú pre uloženie 10 znakov do ktorej sa skopíroval string "Ahoj". Neskôr obdrží adresu stringu "JAJAJ" (a to preto lebo tento reťazec je typu char*) , ale pritom sa stratí oblasť so stringom "Ahoj". Aby sme neprišli o časť pamäte východiskom je Uvolnenie pamäti. free ((void *) s_dyn); // uvolnenie pridelenej pamäti do HEAPU s_dyn=NULL; // korektné nastavenie ak by sme s_dyn nepotrebovali s_dyn="JAJAJ"; // teraz je to OK aj keď NULL bolo zbytočné *s_dyn='x'; // string JAJAJ sa zmenil na xAJAJ Ešte raz si dobre všimnite rozdiel medzi s_dyn="JAJAJ"; a *s_dyn='x';. Prvý príkaz hovorí. Budeme mať uložený string "JAJAJ" v dátovej oblasti. Objekt "JAJAJ" na pravej strane príkazu je vnímaný ako typ char*, a preto sa priradením naplní adresa s_dyn hodnotou ADRESY stringu "JAJAJ". Iný je prípad *s_dyn, ktorý je typu char a preto priradenie naplní cieľ t.j. pozíciu na adrese s_dyn hodnotou 'x'. String "JAJAJ" sa zmenil na "xAJAJ". Príklady pre prácu so stringomČítanie reťazca z klávesnice
char *s1=NULL;
s1=(char *)malloc(100 * sizeof(char));
scanf("%s",s1); // problém by bol s1=NULL ak by sme nepoužili malloc
s1="xxxxxxxxxxxx";
scanf("%11s",s1); // s1 ukazuje na string 11+1 do ktorého možno scanovať
Pri čítaní reťazca scanf() z klávesnice sa načíta celý úsek až po oddeľovač medzera, resp. ENTER. Na začiatku sa vynechajú biele znaky a potom sa načítajú platné znaky až po prvý biely znak, zvyšok do konca riadka sa ignoruje. Ak by sme chceli načítať riadok aj s bielymi znakmi potrebujeme použiť miesto scnaf() funkciu gets(). Format použitý na čítanie reťazca je "%11s". Načítavanie je obmedzené na 11 znakov kde sa 12. znak doplní \0. Pri načítavaní reťazca sa predpokladá, že zodpovedajúca premenná predstavuje adresu na ktorú sa uloží načítaný string. Riadkovo orientované čítanie klávesnice Ak by sme použili scanf() na reťazec "Ahoj clovece" načíta sa reťazec "Ahoj" zvyšok sa stratí. Načítanie celého riadka až po záverečný ukončovač riadku \n uskutoční gets(). char *gets(char *str); // prototyp načítania celého riadka Funkcia gets() číta celý riadok až po znak '\n' a uloží ho do reťazca str, ktorý ukončí znakom '\0'. Znak '\n' sa neukladá. Zároveň gets() vracia pointer na reťazec str. Pokiaľ bol riadok prázdny, vracia NULL a do str[0] vloží znak '\0' t.j. nulový reťazec. Tlač reťazca na obrazovku
printf("%s",s1);
Pri tlači sa uvádza najčastejšie hodnota vyjadrená premennou. Pri tlači napr. celého čísla pomocou pointra by najskôr bol použitý podobný tvar *p_i (tu *s1 ), ale pri tlači reťazca sa uvádza adresa, výnimka, pretože formát "%s" vyžaduje adresu. Nasledovný príklad ukazuje prácu s reťazcom pomocou pointra.
#include < conio.h >
#include < stdio.h >
#include < string.h >
void main(void)
{
char * str;
if ( (str= (char *) malloc(11* sizeof(char))) == NULL )
{
printf("Málo pamäte\n");
return ;
}
printf("Zadaj reťazec:");
scanf("%10s",str);
printf("Načítaný reťazec je: %s\n",str);
}
Riadkovo orientované tlačenie na obrazovku Aj keď na tlač riadka dá sa použiť klasický príkaz printf("%s \n", str) nastáva problém v menšej efektivite výsledného kódu, existuje efektívnejší príkaz: int puts(char *str); // prototyp tlačenia stringu do celého riadka Príkaz puts() vytlačí string a doplní ho znakom '\n'. V prípade že vznikne problém tlače vráti kód EOF, inak vráti nezáporné číslo.
Štandardné funkcie pre prácu s reťazcomC jazyk nedefinuje prácu s reťazcami, napriek tomu sú k dispozícii štandardné knižnice, ktoré obsahujú funkcie na prácu so stringami. Na tento účel je potrebné pripojiť hlavičkový súbor string.h príkazom:
#include < string.h >
Dĺžka reťazca - strlen
int strlen(char *s);
dlzka=strlen("Ahoj"); // vráti do dlzka hodnotu 4
Vracia dĺžku reťazca bez ukončovacieho znaku \0. Kopírovanie reťazca - strcpy char *strcpy(char *s1, char *s2); adr=strcpy(str,"Ahoj"); // v str bude "Ahoj" Skopíruje obsah reťazca s2 do reťazca s1. Vracia pointer na prvý znak reťazca s1. Spojenie reťazcov - strcat char *strcat(char *s1, char *s2); adr=strcat(str," + Nazdar"); // v str bude "Ahoj + Nazdar" Pripojí reťazec s2 k reťazcu s1. Vracia pointer na prvý znak reťazca s1. Nájdenie znaku v reťazci - strchr char *strchr(char *s, char c); adr=strchr(str,'x'); // v str "Ahoj + Nazdar" nenájde 'x' preto vráti NULL Pokiaľ sa v reťazci s nachádza znak c, potom vracia pointer na jeho prvý výskyt. Inak vráti NULL. Porovnanie dvoch reťazcov - strcmp int strcmp(char *s1, char *s2); cislo=strcmp(str1,str2); // =0 ak sú rovnaké - ak str1<str2 + str1>=str2 V prípade rovnosti vracia číslo 0, ak je str1 lexikograficky menšie než str2 vráti záporný výsledok inak kladný. Nájdenie podreťazca v reťazci - strstr char * strstr(char *s1, char *s2); adr=strstr(str1,str2); // ak nenájde vráti NULL inak adresu začiatku Pokiaľ sa v reťazci s1 nachádza podreťazec s2 vráti adresu prvého výskytu inak vráti NULL Práca s obmedzenou časťou reťazca t.j.
-n- znakov char * strncpy(char *s1, char *s2, int MAX); adr=strncpy(str,"alkoholicke",7); // z druheho stringu kopiruje MAX 7 znakov V príklade sa skopíruje do s1 reťazca 7 znakov z s2 reťazca. Ak by bol strlen(s2) <= max pridá sa na koniec \0. Práca s reťazcami odzadu - reverse
- "-r-" char *rstrchr(char *s, char c); adr=rstrchr(str,'x'); // v str "Ahoj + Nazdar" nenájde 'x' preto vráti NULL V príklade ak sa znak 'x' nevyskytne pri prehľadávaní str odzadu vracia sa NULL inak sa vráti pointer na jeho posledný výskyt (prvý odzadu). Prevody reťazcov na čísla (stdlib.h)-
int atoi(char *string); // konvertuje string na celé číslo
long atol(char *string); // konvertuje string na long - celé číslo
double atof(char *string); // konvertuje string na double - reálne číslo
intcislo=atoi("125"); // asci kódy '1','2','5','\0' vytvoria hodnotu 125
Funkcia atoi konvertuje reťazec obsahujúci číslo na premennú typu integer. Funkcia atof konvertuje reťazec obsahujúci číslo na premennú typu float.
Formátované čítanie a zápis z a do reťazcaPodobne ako funkcia printf(), alebo ešte lepšie fprintf(), určená na tlač resp. zápis do existujú funkcie sprintf() "tlačiace" do reťazca umiestneného do operačnej pamäte. Následne sa dajú použiť všetky reťazcové funkcie C jazyka. Analogicky k funkcii scnaf(), a súborovej fscanf(), existuje aj jeho obdoba sscanf(). Úlohou xscanx je zosnímať zo zdroja údaje a umiestniť ich na pozíciu uvedenej premennej. Funkcia sscanf() sníma údaje z reťazcovej premennej a umiestňuje do inej premenej. Preto kombinácia sprintf() a sscanf() vytvárajú ideálnu dvojicu na transformáciu údajov - reťazec na číslo a číslo na reťazec. Príklad: nasledovný program najprv načíta 4 hexadecimálne číslice a tieto potom skonvertuje na číslo. Následne číslo zkonvertuje na osmičkové číslo vo forme reťazca a reťazec vytlačí na obrazovku.
#include < stdio.h >
void main(void)
{
int i;
char s1[5],s2[10];
printf("Zadaj 4 hexadecimálne číslice: ");
scanf("%s",s1);
sscanf(s1,"%x",&i);
sprintf(s2,"%o",i);
printf("%s \n",s2);
}
Príklad: x
void uradnik(char * zpr, double polomer)
{
double obsah, obvod;
obsah= 3.14 * polomer * polomer ;
obvod= 3.14 * 2 * polomer ;
sprintf(zpr,"Kruh s polomerom %d má obsah %d a obvod %d \n",polomer,obsah,obvod);
}
void sef(void)
{
char sprava[120];
uradnik(sprava,5.0);
strcat(sprava,"\t\tVypracoval sef\n");
printf("Dovoľujem si predniesť správu o kruhu: \n %s",sprava);
}
Riadkovo orientovaný vstup/výstup z/do súboruČítanie riadka zo súboru Načítanie riadka zo súboru sa uskutočňuje prototyp funkcie fgets(), ktorý číta zo súboru fr až do konca riadka nanajvýš max znakov včítane znaku '\n', a uloží ho do str. Funkcia vracia pointer na str, alebo pri dosiahnutí konca súboru vráti NULL.
char *fgets(char * str, int max, FILE * fr);
Poznámky:
Príklad: Program prečíta obsah súboru DOPIS.TXT po riadkoch a vypíše jeho obsah na displej. Dĺžka načítavaného riadka je obmedzená na 80 znakov.
#include < stdio.h >
int main(void)
{
char riadok[80];
FILE *fr;
if ((fr=fopen("DOPIS.TXT","r"))==NULL)
{
printf("Súbor DOPIS.TXT sa nedá otvoriť \n");
return;
}
while(fgets(riadok,80,fr)!=NULL) printf("%s",riadok);
if (fclose(fr)==EOF)
printf("Súbor DOPIS.TXT sa nedá uzatvoriť \n");
}
Zápis riadka do súboru Ak chceme zapísať reťazec do súboru a neodriadkovať (odriadkovanie je v reťazci), potom použijeme funkciu fputs(), ktorá má nasledovný prototyp:
int fputs(char * str, FILE * fw);
Funkcia po zápise reťazca neodriadkuje ani nezapisuje do súboru ukončovací znak reťazca '\0'. Jej návratová hodnota je nezáporné číslo. V prípade neúspechu vracia EOF. Pole reťazcovPole reťazcov je jedno z najčastejšie používaných dvojrozmerných polí (vid. p_text[1][2]). Prvý rozmer je zoznam (pole) pointrov na druhý rozmer - pole znakov (string). Polia znakov (t.j. stringy) môžu mať rôzne dĺžky. Takmer každý program pracujúci s textom môže využívať pole reťazcov.
char *p_text[4] ; // definícia poľa 4 pointrov na reťazce
p_text[0]="Jahoda";
p_text[1]="Fero";
p_text[2]=(char *)malloc(6*sizeof(char)); // vyclečleň z HEAP-u 5+1
ctrcpy(p_text[3],"Marek"); // naplň pole ako kópiu
p_text[3]="Eva";
//-------------------------------- A L T E R N A T Í V N E R I E Š E N I E
char *p_pole[]={"Jahoda","Fero","Marek","Eva"};
printf("Tretí znak druhého reťazca je %c", p_text[1][2]);
Výpis obsahu hociktorého stringu môžeme realizovať jednak ako tlač znakov, jednak ako tlač celého stringu. V nasledovnom príklade prvá metóda je zameraná na tlač znaku a nasledovné dva na dva rôzne spôsoby tlačenie stringov putchar, printf, puts. Puts je výhodnejší pri porovnaní s printf, nakoľko printf je univerzálnejší a potrebuje analyzovať a transformovať tlačený obsah.
// 1. ----------------
char *p_pom=p_text[0]; // pomocný pointer na adresu znaku
// p_text[0] - adresa 0.-tého stringu
while (*p_pom != '\0') putchar(*p_pom++); //tlac znak a adresuj nasledovný
// podmienka ukončenia cyklu je nájdenie '\0'
// 2. ----------------
printf("%s \n",p_text[1]); // p_text[1] je adresa reťazca
// 3. ----------------
puts(p_text[2]); // p_text[2] je adresa reťazca
Pozor na umiestnenie niektorých operátorov pri využívaní "špecialít" C jazyka. V 4. príklade použijeme pointer na pole pointrov na reťazce (string-y). Po nastavení p_pom na adresu p_text pri jeho definícii výraz (++*p_pom) by zapríčinil posun p_pom adresy na pozíciu druhého znaku (počítajú sa od 0 t.j. p_text[0][1]) od začiatku prvého reťazca t.j. p_text[0]. Správne potrebujeme použiť výraz (*p_pom++), ktorý zaadresuje pozíciu prvého znaku druhého reťazca (t.j. p_text[1][0]).
// 4. ----------------
char **p_pom=p_text; // p_pom je pointer na pointer na znaky (pole stringov)
// p_text je adresa pola pointrov
puts(++*p_pom); // Pozor! výpisal by "ahoda" miesto "Jahoda". Zodpovedá
puts(++(*p_pom)); // mu identický príkaz inkrementuj operand na adrese
// *p_pom a potom vypíš zvyšok ako string
for(i=0 ; i<4 ; i++) // Správne vypíše obsah všetkých 4 reťazcov
puts(*p_pom++); // Výpíše reťazec a posunie sa na nasledovný
// pointer v poli pointrov (p_pom++)
Parametre "main" - vstupné parametre programuFunkcia main je vstupnou aj výstupnou bránou programu. V prípade ak je main definované ako int vracia pri ukončení programu návratovú hodnotu ktorá sa zapíše do systemovej premennej ERRORLEVEL, ktorá je čitateľná v dávkových súboroch alebo v iných programoch, ktoré štartovali našu aplikáciu. Vstupné parametre funkcie main môžu byť void, alebo presne definované nasledovným spôsobom:
int main(int argc, char * argv[])
Pri štarte vzorového programu test.exe param1 param2 obdrží program nasledovné parametre argc bude rovný 3 t.j. počet prvkov poľa pointrov na reťazce "test", "param1" a "param2" nasledovným spôsobom: argv[0] - pointer na test Príklad: Volanie programu test bude obsahovať viacero parametrov uzavretých do úvodzoviek. Obsah v úvodzovkách sa bude ponímať ako jeden reťazec aj keby obsahovali v sebe oddeľovače. test "Nazdar" ako " sa mas clovece?" argv[0] - pointer na test Vstupné parametre programov sú teda normálne reťazce a dá sa s nimi pracovať funkciami na prácu s reťazcami. V nasledovnom príklade vypíšeme vstupné parametre na obrazovke.
#include < stdio.h >
void main(int argc, char * argv[])
{
int i;
printf("Vstupný riadok má %d parametrov \n", argc);
for (i=1; i< argc; i++)
printf("%s \n", argv[i] );
}
Tu budem ešte doplňovať ... Príklad č._ _ : #include<stdio.h> #include<conio.h> void main(void) // void - znamená "žiaden typ - žiadna premenná" { ... } Tu budem ešte doplňovať ... Úlohy pre zopakovanie
|
||