Řetězce. Často potřebujeme v řetězci přepsat nějaké znaky. Nemůžeme ale zapsat např.

Podobné dokumenty
Pole a Funkce. Úvod do programování 1 Tomáš Kühr

9. lekce Úvod do jazyka C 4. část Funkce, rekurze Editace, kompilace, spuštění Miroslav Jílek

Základy programování (IZP)

10 Práce s řetězci - pokračování

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:

Základy programování (IZP)

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:

Programování v jazyce C pro chemiky (C2160) 7. Ukazatele, Funkce pro práci s řetězci

for (i = 0, j = 5; i < 10; i++) { // tělo cyklu }

IMPLEMENTACE OPERAČNÍHO SYSTÉMU LINUX DO VÝUKY INFORMAČNÍCH TECHNOLOGIÍ JAZYK C

for (int i = 0; i < sizeof(hodnoty) / sizeof(int); i++) { cout<<hodonoty[i]<< endl; } cin.get(); return 0; }

Program převod z desítkové na dvojkovou soustavu: /* Prevod desitkove na binarni */ #include <stdio.h>

Pole a kolekce. v C#, Javě a C++

- jak udělat konstantu long int: L long velka = 78L;

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

PB161 Programování v jazyce C++ Přednáška 4

8. lekce Úvod do jazyka C 3. část Základní příkazy jazyka C Miroslav Jílek

Více o konstruktorech a destruktorech

Formátové specifikace formátovací řetězce

Strukturu lze funkci předat: (pole[i])+j. switch(výraz) velikost ukazatele

přetížení operátorů (o)

Struktura programu v době běhu

Pointery II. Jan Hnilica Počítačové modelování 17

Martin Flusser. Faculty of Nuclear Sciences and Physical Engineering Czech Technical University in Prague. October 17, 2016

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Základy PERLu snadno a rychle

Dynamická vícerozměrná pole. Základy programování 2 Tomáš Kühr

Střední škola pedagogická, hotelnictví a služeb, Litoměříce, příspěvková organizace

Aplikace Embedded systémů v Mechatronice. Michal Bastl A2/713a

Úvod do programovacích jazyků (Java)

Př. další použití pointerů

IAJCE Přednáška č. 8. double tprumer = (t1 + t2 + t3 + t4 + t5 + t6 + t7) / 7; Console.Write("\nPrumerna teplota je {0}", tprumer);

PROMĚNNÉ, KONSTANTY A DATOVÉ TYPY TEORIE DATUM VYTVOŘENÍ: KLÍČOVÁ AKTIVITA: 02 PROGRAMOVÁNÍ 2. ROČNÍK (PRG2) HODINOVÁ DOTACE: 1

Programování v jazyce C pro chemiky (C2160) 4. Textové řetězce, zápis dat do souboru

DUM 07 téma: Proměnné, konstanty a pohyb po buňkách ve VBA

8 Třídy, objekty, metody, předávání argumentů metod

Základy C++ I. Jan Hnilica Počítačové modelování 18

Lekce 9 IMPLEMENTACE OPERAČNÍHO SYSTÉMU LINUX DO VÝUKY INFORMAČNÍCH TECHNOLOGIÍ JAZYK C

7. Datové typy v Javě

Pokročilé programování v jazyce C pro chemiky (C3220) Operátory new a delete, virtuální metody

Šifrování/Dešifrování s použitím hesla

Úvod do jazyka C. Ing. Jan Fikejz (KST, FEI) Fakulta elektrotechniky a informatiky Katedra softwarových technologií

1.1 Struktura programu v Pascalu Vstup a výstup Operátory a některé matematické funkce 5

Ukazatele a pole. Chceme-li vyplnit celé pole nulami, použijeme prázdný inicializátor: 207 Čárka na konci seznamu inicializátorů

Jazyk C++, některá rozšíření oproti C

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Příklad : String txt1 = new String( Ahoj vsichni! ); //vytvoří instanci třídy String a přiřadí ji vnitřní hodnotu Ahoj vsichni!

PB161 Programování v C++ Proudy pro standardní zařízení Souborové proudy Paměťové proudy Manipulátory

Lekce 6 IMPLEMENTACE OPERAČNÍHO SYSTÉMU LINUX DO VÝUKY INFORMAČNÍCH TECHNOLOGIÍ JAZYK C

Základy programování (IZP)

IPA - Lab.1 Úvod do programování v ASM

Jazyk C práce se soubory. Jan Hnilica Počítačové modelování 16

Funkce, intuitivní chápání složitosti

III/2 Inovace a zkvalitnění výuky prostřednictvím ICT

Programování v jazyce C pro chemiky (C2160) 5. Čtení dat ze souboru

Základy programování (IZP)

Skripta ke školení. Základy VBA. vypracoval: Tomáš Herout. tel:

6. lekce Úvod do jazyka C knihovny datové typy, definice proměnných základní struktura programu a jeho editace Miroslav Jílek

Preprocesor. Karel Richta a kol. katedra počítačů FEL ČVUT v Praze. Karel Richta, Martin Hořeňovský, Aleš Hrabalík, 2016

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

Standardní algoritmy vyhledávací.

Pokročilé programování v jazyce C pro chemiky (C3220) Statické proměnné a metody, šablony v C++

IUJCE 07/08 Přednáška č. 4. v paměti neexistuje. v paměti existuje

Práce se soubory. Úvod do programování 2 Tomáš Kühr

Úvod do programování. Lekce 1

Paměť počítače. alg2 1

7 Formátovaný výstup, třídy, objekty, pole, chyby v programech

Téma 8: Konfigurace počítačů se systémem Windows 7 IV

Např.: // v hlavičkovém souboru nebo na začátku // programu (pod include): typedef struct { char jmeno[20]; char prijmeni[20]; int rok_nar; } CLOVEK;

Struktury a dynamická paměť

Programovací jazyk Pascal

Práce se soubory. Základy programování 2 Tomáš Kühr

Programování v C++ 1, 5. cvičení

Algoritmizace a programování

Práce s binárními soubory. Základy programování 2 Tomáš Kühr

Standardní vstup a výstup

IB111 Úvod do programování skrze Python Přednáška 7

Správné vytvoření a otevření textového souboru pro čtení a zápis představuje

5 Přehled operátorů, příkazy, přetypování

Ukazatele, dynamická alokace

Úvod do programování. Lekce 5

Datové typy v Javě. Tomáš Pitner, upravil Marek Šabo

Základy algoritmizace a programování

Úvod do programování 6. hodina

Řídicí příkazy KAPITOLA 3. Vstup znaků z klávesnice

Obsah. Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15

Úvod do programování

Algoritmizace a programování

Maturitní otázky z předmětu PROGRAMOVÁNÍ

2 Základní funkce a operátory V této kapitole se seznámíme s použitím funkce printf, probereme základní operátory a uvedeme nejdůležitější funkce.

Operační systémy. Cvičení 3: Programování v C pod Unixem

Programování v C++ 1, 1. cvičení

- znakové konstanty v apostrofech, např. a, +, (znak mezera) - proměnná zabírá 1 byte, obsahuje kód příslušného znaku

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

Úvod do programování - Java. Cvičení č.4

Programovanie v jazyku C - funkcie a makra

ZÁPOČTOVÝ TEST. Zpracoval Vilém Závodný, #include "stdafx.h" #include "stdio.h"

MQL4 COURSE. By Coders guru -8- Proměnné

EVROPSKÝ SOCIÁLNÍ FOND. Úvod do PHP PRAHA & EU INVESTUJEME DO VAŠÍ BUDOUCNOSTI

Transkript:

Řetězce tl;dr Čtení a zápis textu v C: char data[16]; printf("zadej text: "); scanf("%s", data); printf("zadal jsi: %s", data); Tento příklad vám stačí pro základní práci s řetězci, bez dalších podrobností se ale setkáte s celou řadou překvapení vedoucích k nečekaným chybám, které jsou co možná nejstručněji zmíněny níže. Je to rozvedeno na celých 5 stránek, pro které nepoužívejte rychločtení, ale opravdu si uvědomte obsah každé věty. Není to jen o práci s textem, popisuje se zde i základní práce s pamětí, která vám umožní proniknout do nízkoúrovňového programování. Ke zdůvodnění, proč se tím vůbec prokousávat, když vám stačí ty tři řádky zmíněné výše, viz poslední 2 stránky v kapitole Použití, kde je vysvětlena hranice mezi programy, které je lepší psát v javascriptu a mezi říší jazyka C. Implementace řetězců V předchozím díle jsme se setkali s řetězcovými literály (např. "text"). Ty jsou uloženy ve statické paměti, tj. jsou vytvořeny na začátku programu a uvolněny až na konci programu. Jsou také konstantní, tj. nelze je měnit. Tyto řetězce (strings) můžeme uložit do paměti typu const char*. Hvězdička na konci znamená ukazatel, což je typ proměnné, který obsahuje adresu. K hodnotě takové proměnné přistoupíme operátorem * (nezaměňujte tyto dva rozdílné významy). Shrňme si, jak je to s adresami a hodnotami hodnotových proměnných a ukazatelů: hodnota adresa int x x &x const char* y *y y Často potřebujeme v řetězci přepsat nějaké znaky. Nemůžeme ale zapsat např. char* str = "login"; // varování, nekonstantní str ukazuje na konstantu protože řetězcový literál je konstantní a proměnná str zde značí nekonstantní ukazatel. Některé architektury (např. Arduino) ukládají konstantní paměť do jiného segmentu než nekonstantní (např. do ROM). Pokus o změnu této paměti skončí běhovou chybou, proto většina kompilátorů při kompilaci vygeneruje varování. Měnitelné řetězce lze uložit jako pole znaků. Lze tedy zapsat char str[] = "login"; // v pořádku

Pokud proměnnou definujeme při deklaraci, není nutné psát do hranatých závorek velikost pole, určí se automaticky z pravé strany. V tomto případě se nevytváří řetězcový literál, je to jen zkrácený zápis následujícího char str[] = {'l', 'o', 'g', 'i', 'n', '\0'; Výraz ve složených závorkách se nazývá inicializátor, podrobněji ho probereme u struktur. Všechny řetězce v C jsou ukončeny znakem '\0', což je znak NULL s ASCII indexem 0 (lze ho tedy zapsat i jako 0. Nezaměňujte se znakem '0' (číslo 0), který má ASCII index 48. Díky tomu můžeme řetězec znak po znaku procházet bez jakýchkoliv dodatečných informací (např. bez znalosti jeho délky): for(char* i=str; *i!=0; i++) printf("%c", *i); Na začátku cyklu vytvoříme nekonstantní ukazatel i (typu char*), jehož hodnotu (*i) tiskneme. Poté zvětšíme adresu o 1 (pole se vždy vytváří na souvislém bloku paměti) a tento postup opakujeme tak dlouho, dokud je hodnota i různá od 0. Tímto způsobem bychom mohli zjistit délku řetězce, případně též voláním funkce strlen(řetězec). Je možné také psát for(const char* i=str; *i!=0; i++) printf("%c", *i); Zde je označena jako konstantní paměť, na kterou ukazatel ukazuje, ukazatel je nekonstantní, proto ho můžeme měnit příkazem i++. Návod, jak se v této konstantnosti vyznat, se skládá ze dvou kroků: 1. pokud je const na začátku, dáme ho na druhé místo (const char* je tedy totéž, co char const*), je tomu tak proto, že jiné jazyky vyžadují const jako první. 2. výraz čteme zprava doleva, tedy char const* je ukazatel (*) na konstantní (const) paměť typu char char* const je konstantní ukazatel na nekonstantní paměť char char const* const je konstantní ukazatel na konstantní paměť char Je jedno, z jaké strany hvězdičky dáme mezeru, následující výrazy jsou ekvivalentní: char* x char *x char*x char * x char* x zdůrazňuje, že proměnná x je typu ukazatel na char. char *x zdůrazňuje, že ukazatel x ukazuje na paměť typu char. Pozor pouze na následující: char* x,y; je totéž, co char* x; char y; char *x,*y je totéž, co char* x; char* y;

Následující ukázky kódu upozorňují na rozdíl mezi řetězcovým literálem a polem znaků: ukázka 1 const char* gethello() { const char* result = "hello"; // result[0] = 'H'; // chyba, result ukazuje na konstantní paměť return result; // v pořádku, result ukazuje na statickou paměť const char* x = gethello(); puts(x); // v pořádku ukázka 2 char* gethello() { char result[] = "hello"; result[0] = 'H'; // v pořádku return result; // vracíme odkaz na paměť, která bude ihned uvolněna! const char* x = gethello(); // x ukazuje na uvolněnou paměť puts(x); // může fungovat, mohou se vypsat divné znaky, může spadnout ukázka 3 char* gethello() { static char result[] = "hello"; result[0] = 'H'; return result; const char* x = gethello(); // v pořádku, gethello vrací statickou paměť puts(x); // Hello char* y = gethello(); y[0] = 'B'; puts(x); // Bello (x a y ukazují na tutéž paměť) Všimněte si v ukázce č. 3, že přestože jsme x označili jako ukazatel na konstantní paměť, tato paměť byla změněna, protože y je ukazatel na nekonstantní paměť, která ukazuje na totéž místo. Načítání řetězců Funkci scanf (scan formatted) lze použít i k načítání řetězců pomocí symbolu %s, vyzkoušejte si: int x; char str[16]; scanf("%d %s", &x, str); // zadejte např. 15 jablek while(getchar()!='\n'); // vyprázdnění vstupního bufferu printf("%s %d", str, x); // vypíše jablek 15 str je referenční proměnná (odkaz), obsahuje adresu, a proto při načítání před ní neuvádíme &. Uvedený příklad má následující skryté chyby: Pokud uživatel zadá víc než 15 znaků, zapíše se výsledek do nealokované paměti. To lze ošetřit číslem za %: scanf("%15s", str) - načte max 15 znaků (poslední se doplní ukončovací 0). %s čte znaky až do první mezery. To lze ošetřit výčtovým typem v hranatých závorkách, např. scanf("%[ab ]", str) - načítá do pouze znaky a, b a mezeru. Lépe

lze použít negativní výčet specifikovaný prvním znakem stříšky (circumflex) ^: scanf("%15[^\n]", str) - načte max. 15 znaků včetně mezer (čte všechny znaky krom konce řádky (který se při načítání z konzole stejně ve vstupu neobjeví). Kopírování řetězců Při použití operátoru = na řetězce se kopíruje pouze odkaz, ne jejich obsah - tzv. mělká kopie (shallow copy). Pro hluboké kopírování (deep copy) je nutno do nového místa zkopírovat vše znak po znaku. To lze také funkcí strcpy z knihovny string.h: const char* src = "hello"; char* dest1 = src; // shallow copy // dest1[0] = 'H'; // chyba: dest ukazuje na src a ta je konstantní // char dest2[6] = src; // chyba: takto pole inicializovat nelze char dest2[6]; for(char* i=src, *j=dest2; *i!=0; ++i, ++j) *j = *i; // deep copy *j = 0; // nezapomeňme na ukončovací nulu for(char* i=src, *j=dest2; *i!=0 ;) *j++ = *i++; // deep copy ;) #include <string.h> strcpy(dest2, src); // také deep copy, srozumitelný zápis Srovnejte kopírování do dest1 s ukázkou č. 3 výše. Zde je src vytvořena jako konstantní, v ukázce jsme ji vytvořili jako nekonstantní (konstantní byl pouze ukazatel). Porovnávání řetězců Použitím operátoru == se porovnává adresa, ne hodnota řetězců. Pro porovnávání hodnoty je třeba porovnat znak po znaku nebo použít funkci strcmp (string compare), která vrací nulu, pokud se řetězce rovnají, pozor na řetězcové literály. ukázka 1 char str1[] = "hello"; char str2[] = "hello"; printf("%d", str1==str2); // vždy 0 str1 a str2 se nacházejí na různém paměťovém místě ukázka 2 char str1[] = "hello"; char str2[] = "hello"; printf("%d", strcmp(str1,str2)==0 ); // 1 Zde je hluboké porovnávání, obsahy řetězců se rovnají. ukázka 3 const char* str1 = "hello"; const char* str2 = "hello"; printf("%d", str1==str2); // může být 1! V tomto případě kompilátor může (nemusí!) vytvořit konstantu "hello" na jednom paměťovém místě a str1 i str2 na ně odkázat.

Prohledávání řetězců Pro nalezení znaku v řetězci můžeme použít funkci strchr(řetězec,znak) (string search character), která vrací ukazatel na nalezený znak. Všechny pozice znaku ve slově najdeme: const char* str = "yabadabadoo"; for(const char* i=strchr(str,'a'); i!=0; i=strchr(i+1,'a')) { printf("podslovo %s na pozici %d\n", i, i-str); podslovo abadabadoo na pozici 1 podslovo adabadoo na pozici 3 podslovo abadoo na pozici 5 podslovo adoo na pozici 7 Obdobně lze použít funkci strrchr (string search reverse character) pro prohledávání od konce a funkci strstr pro hledání podřetězců v řetězci. Spojování a rozdělování řetězců Potřebujeme-li řetězec zkrátit, stačí na příslušnou pozici vložit ASCII 0: char str[] = "hello world"; // 12 bytů v paměti str[5] = 0; puts(str); // hello Za koncem řetězce máme ještě vyhrazeno 5 bytů paměti (plus jeden navíc na ukončovací nulu), můžeme tedy připojit další část (pomocí cyklu nebo funkcí strcat string concatenate): strcat(str, " baby"); puts(str); // hello baby Při navazování uživatelského vstupu je třeba mít na paměti, že jeho délka nemusí být známá. Lze pak využít funkci strncat, která kopíruje pouze n znaků a nepřipojuje ukončovací nulu: char str[12] = "hello "; // rezervováno dalších 5 znaků printf("login: "); char login[16]; scanf("%15s", login); while(getchar()!='\n'); // login dlouhý až 15 znaků strncat(str, login, 5); // připojíme nejvýše 5 znaků z loginu str[11] = 0; // standardní ukončení řetězce puts(str); Obdobně bychom při spojování řetězců neznámé délky měli použít funkci strncpy místo strcpy. Pro rozdělování řetězců lze použít funkci strtok(str,znaky) (string tokenize), která najde výskyt některého znaku v řetězci znaky a nahradí ho \0. Pokud je str 0, prohledává se o 1 byte vpravo od posledního nahrazení. Funkce vrací ukazatel na další část řetězce a 0 po skončení řetězce: char str[] = "one two three four"; for(char* i=strtok(str," "); i!=0; i=strtok(0," ")) puts(i); one two three four

Použití Ve srovnání s vyššími jazyky (tj. takovými, které jsou vzdálenější hardwaru) jako Javascript či PHP je práce s řetězci v C náročnější (musíme myslet na jejich bytovou reprezentaci v paměti). Na druhou stranu program v C je mnohem hospodárnější: pokud ve vyšších jazycích spojujete dva řetězce, vyhradí se nová paměť, kam se oba řetězce nakopírují pokud dělíme řetězec, vyhradí se nová paměť pro všechny podřetězce Pokud tedy pracujete na architekturách, kde paměť není problém (např. na PC), ušetříte si práci, použijete-li vyšší jazyk. Ale i zde vám při nevhodném kódu může paměť dojít, např. <script> var a = "a"; for(var i=0; i<100000; ++i) { a+= "a"; document.write(a); // znemožnění optimalizace na pozadí </script> Tento kód vyhradí 1 byte pro znak "a", poté vyhradí 2 byty pro řetězec "aa", kam řetězce spojí, poté vyhradí 3 byty atd. Celkem tedy potřebujeme řádově 1 + 2 + 3 + 99999 + 100000 bytů paměti, což je 100000 * 99999 / 2, tedy minimálně 5 GB paměti (další paměť potřebuje prohlížeč - interpret pro svůj běh, zkuste si spustit správce úloh, pak spusťte tento skript v prohlížeči a sledujte, jak paměť roste. Občas se možná její větší blok uvolní díky virtuální paměti a garbage collectoru, o tom více ve čtvrťáku.). Zkuste si také stopnout, jak dlouho vašemu nabušenému procesoru trvá vykonat tento jednoduchý program, a přitom sto tisíc není až tak velké číslo. Zkuste si podobný program v C: void main() { char str[100001] = {; // vyplní celý blok paměti \0 for(int i=0; i<100000; ++i) { str[i] = 'a'; puts(str); Program také poběží dlouho díky faktu, že příkazová řádka Windows není na tak dlouhý výstup dělaná. Potřebujeme však pouze 100 kb paměti, která za běhu už dále neroste. Aby byla demonstrace přesvědčivější, použijeme pro optimalizaci funkce jádra OS 1 : #include <windows.h> void main() { HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE); COORD start = {1,1; // novou hodnotu vypíšeme vždy od začátku konzole DWORD out; 1 nemusíte umět - zde jen pro účely demonstrace bez dalších podrobností (existuje řada dalších optimalizací)

char str[100001] = {; for(int i=1; i<100000; ++i) { str[i] = 'a'; WriteConsoleOutputCharacter(stdout,str,i,start,&out); Na mém stroji trval výpočet 44 sekund za využití jednoho jádra procesoru. Javascript puštěný v Opeře za využití všech 4 jader za stejnou dobu spadl poté, co spotřeboval veškerou dostupnou paměť (cca 7 GB). Na problém tohoto typu narazíte, pokud budete např. chtít javascriptem vygenerovat nějakou rozsáhlou tabulku. I v javascriptu toto můžete optimalizovat tím, že nepoužijete řetězce, ale předem alokované pole znaků. Zde už ale musíte dohledávat v technických manuálech jak a kde je javascript optimalizován, jelikož jeho interpret je pravděpodobně napsaný v C++ a méně se nadřete, pokud rovnou použijete jazyk C.