PB071: Úvod do jazyka C - 4

Textové řetězce, manipulace se znaky

Textové řetězce

Konstanty:

Z normálních znaků:

"text"
"Muze obsahovat \t ridici znaky.\n"

Ze "širokých" znaků (jen C99!):

L"Du mußt = musíš"

Následují-li v programu dvě textové konstanty bezprostředně za sebou (oddělené "bílými znaky"), spojí je překladač do jedné konstanty. Toho lze použít pro rozdělení dlouhé konstanty do více řádků zdrojového textu.

Deklarace:

char radek[81]; /* Max. 80 znaků, za posledním znakem '\0' */
char nadpis[]="Jmeno\tPrijmeni";
wchar_t text[100]; /* V C99: Max. 99 "dlouhých" znaků + nulový znak */

Koncovou nulu obsahuje i textová konstanta (literál), takže sizeof "abcd" == 5

Manipulace s řetězci:

NE:  if(radek=="end")radek="Nějaký text"; (Nutno použít funkce)

#include <string.h>

strcpy(a,b) Zkopíruje řetězec b do řetězce a; vrací ukazatel na začátek a. Řetězec a musí být dostatečně dlouhý, aby se do něj vešla kopie b i s koncovou nulou.
strcat(a,b) Přidá řetězec b na konec řetězce a (tj. přepíše vše počínaje koncovým nulovým znakem); vrací ukazatel na začátek a. Řetězec a musí být dostatečně dlouhý, aby se do něj vešly spojené řetězce i s koncovou nulou.
strcmp(a,b) Porovná řetězce a a b; vrací číslo <0 / 0 / >0, je-li a lexikograficky menší / rovno / větší než b
strlen(a) Vrací délku řetězce a (počet znaků před ukončujícím nulovým znakem)
strchr(a,c) Hledá, zda je v řetězci a obsažen znak c. Pokud ano, vrátí ukazatel na první výskyt znaku; pokud ne, vrátí NULL
strrchr(a,c) Hledá, zda je v řetězci a obsažen znak c. Pokud ano, vrátí ukazatel na poslední výskyt znaku; pokud ne, vrátí NULL
strstr(a,b) Hledá, zda je v řetězci a obsažen podřetězec b. Pokud ano, vrátí ukazatel na začátek prvního výskytu podřetězce; pokud ne, vrátí NULL

... a mnoho dalších - viz man string

Uživatel funkcí je zodpovědný za to, že předané řetězce jsou řádně ukončeny nulovým znakem.

K prvním 3 funkcím existují ještě varianty strncpy, strncat a strncmp s přidaným celočíselným parametrem n. Ty kopírují, resp. porovnávají nanejvýš n znaků. (Pokud je zpracovávaný řetězec delší, nebude výsledek u prvních 2 funkcí ukončen nulovým znakem, nevznikne tedy regulérní céčkovský řetězec!)

Pro manipulace s řetězci složenými ze širokých znaků jsou v C99 ve wchar.h k dispozici analogické funkce, jejichž název místo str začíná wcs, např. wcscpy.

Klasifikace znaků a manipulace s jednotlivými znaky:

#include <ctype.h>

POZOR! Parametr znak ve všech následujících funkcích sice obsahuje znak, ale uložený ve veličině typu int, nikoli char

Klasifikace znaků - výsledek je nenulové číslo (true) nebo 0 (false)
isalpha(znak) Je znak písmeno (malé nebo velké)?
isdigit(znak) Je znak dekadická číslice?
isxdigit(znak) Je znak hexadecimální číslice (0-9, a-f, A-F)?
isalnum(znak) Je znak písmeno nebo číslice?
ispunct(znak) Je znak speciální (tisknutelný, ale ani písmeno ani číslice)?
isprint(znak) Je znak tisknutelný (písmeno, číslice, speciální nebo mezera)?
isgraph(znak) Má znak grafickou podobu (písmeno, číslice, speciální, ale ne mezera)?
isspace(znak) Jde o bílý znak (mezeru, tabulátor, nový řádek, návrat vozíku, vertikální tabulátor, nová stránka)?
iscntrl(znak) Jde o řídicí znak (v kódu ASCII jsou to znaky s kódem <32 nebo =127)?
isupper(znak) Je znak velké písmeno?
islower(znak) Je znak malé písmeno?
Převod znaků  - z malých písmen na velká nebo naopak (jen jediný znak, ne řetězec!)
toupper(znak) Je-li znak malé písmeno, je výsledek odpovídající písmeno velké, jinak je vrácen znak původní
tolower(znak) Je-li znak velké písmeno, je výsledek odpovídající písmeno malé, jinak je vrácen znak původní

Některé znaky mohou spadat do více kategorií, třeba '\n' splňuje současně podmínky pro isspace iscntrl, velká písmena jsou současně isalpha, isalnum, isprint, isgraph isupper, některá i isxdigit

Pro široké znaky jsou v C99 analogické funkce ve wctype.h (např iswalnum, towlower)

Pro přidání 1 znaku na konec řetězce neexistuje standardní funkce. Je to však velmi snadné - přepíšete závěrečný nulový znak přidávaným a za něj dodáte nový nulový:

int del=strlen(retez);
retez[del]='.';
retez[del+1]=0;

V řetězci samozřejmě musí být na přidaný znak místo.


 Preprocesor - 1. část

Direktivy (příkazy preprocesoru) a hlavičkové soubory

1. fáze překladu: preprocesor (zpracuje řádky začínající #, tzv. direktivy)

Direktiva #include

#include <soubor> vloží do programu uvedený standardní soubor (tzv. hlavičkový soubor s příponou .h)
#include "soubor" dtto, ale (obvykle uživatelův) soubor se hledá nejdříve v aktuálním adresáři

Nejdůležitější hlavičkové soubory:

stdio.h vstup a výstup
stdlib.h standardní knihovna funkcí jazyka C
string.h prostředky pro manipulaci s textovými řetězci
math.h matematické funkce a konstanty

Další hlavičkové soubory jsou probrány na konci přednášky.

Direktiva #define

Definuje makro.

#define MAXIMUM 1000
#define MAX2 2*MAXIMUM

Každý výskyt slova MAXIMUM se před překladem nahradí konstantou 1000 a každý výskyt slova MAX2 výrazem 2*1000

Makro může mít i parametry (jednoduchá náhrada funkcí):

#define MAX(x,y) x>y?x:y
Řetězec MAX(a-b,5) se nahradí řetězcem a-b>5?a-b:5

Tato náhrada je zcela mechanická, proto pozor:
#define KVADRAT(r) r*r
Řetězec w=1.5/KVADRAT(d-5); se nahradí otrocky w=1.5/d-5*d-5; což jistě nebylo zamýšleno :-(

Většinou pomohou závorky:
#define MAX(x,y) ((x)>(y)?(x):(y))
#define KVADRAT(r) ((r)*(r))

ale ne vždy - ani plné uzávorkování nezabrání tomu, aby KVADRAT(rand()) místo druhé mocniny náhodného čísla nevygeneroval součin dvou náhodných čísel, což je něco jiného a výsledek má jiné statistické vlastnosti. Podobně KVADRAT(n++) se rozvine jako ((n++)*(n++)) a výsledek dokonce není jednoznačně definován.

Názvy maker (zejména bez parametrů) je zvykem psát samými velkými písmeny. Je to však pouhá konvence, ne požadavek.

Místo maker s parametry se v C99 často používají tzv. "vložené" (inline) funkce. Překladač s nimi obvykle pracuje podobně jako s makry. Blíže budou probrány v jiné přednášce.

Funkce jsou v řadě případů výhodnější. Výhodou maker s parametry je však to, že jsou založena na textové úpravě zdrojového souboru. Parametry makra proto nemají žádný typ (např. výše uvedené makro MAX lze stejně dobře použít pro parametry typu char, unsigned int i třeba long double.

Definované makro lze "oddefinovat" direktivou #undef, např. #undef MAX způsobí, že ve zbytku zdrojového textu není makro MAX definováno. Makro lze rovněž definovat znovu se změněným významem.

Makra definovaná při vyvolání překladače

U mnoha překladačů včetně gcc může být makro deklarováno mimo zdrojový kód programu použitím parametru -D

Například parametr -DDEBUG má týž význam, jako kdyby na začátku zdrojového textu bylo zapsáno #define DEBUG

Mezi názvem makra a nahrazující hodnotou se v parametru -D nepíše mezera, ale rovnítko:
-DPOCET=99

Pokud jsou v parametru použity mezery a jiné speciální znaky, je nutno jej v Unixu zapsat do apostrofů:
gcc '-DTYP=unsigned long int'

Makrooperátory # a ##

V rozvoji makra lze použít operátory # a ##

Operátor # je unární. Z následujícího operandu vytvoří přidáním uvozovek textovou konstantu (literál). Toho se obvykle využívá k definici makra, které konvertuje svůj parametr na textovou konstantu:

#define text(t) #t

Zápis text(Ladici tisk) preprocesor konvertuje na "Ladici tisk"

Operátor ## je binární. Spojí oba operandy do jednoho řetězce:

#define spoj(a,b) a##b

Zápis spoj(bi,narni) preprocesor konvertuje na binarni

Komplexnější příklad:

#include <stdio.h>
#define spoj(a,b) a##b
#define deklar(prom,i) double spoj(prom,i) = i
#define text(t) #t
#define tisk(prom,i) printf(text(prom) text(i) " = %lf\n",spoj(prom,i))
int main()
{
  deklar(x,1);
  deklar(y,2);
  tisk(x,1);
  tisk(y,2);
  return 0;
}
Funkci main() preprocesor konvertuje na:
int main()
{
  double x1 = 1;
  double y2 = 2;
  printf("x" "1" " = %lf\n",x1);
  printf("y" "2" " = %lf\n",y2);
  return 0;
}
a jelikož několik textových konstant za sebou (oddělených jen bílými znaky) překladač spojí do jediné konstanty, je to ekvivalentní zápisu
int main()
{
  double x1 = 1;
  double y2 = 2;
  printf("x1 = %lf\n",x1);
  printf("y2 = %lf\n",y2);
  return 0;
}

Direktivy pro podmíněný překlad

#ifdef DEBUG
/* Tohle se přeloží pouze, pokud v předchozím textu
   bylo definováno makro DEBUG ... */
#else
/* ... a tohle zase, když definováno nebylo */
#endif
Místo
#ifdef DEBUG
#else
/* ... toto překládat jen když makro není definováno */
#endif

lze psát

#ifndef DEBUG
/* ... toto překládat jen když makro není definováno */
#endif

nebo

#if defined(DEBUG)&&!defined(THREE_D)
/* ... toto překládat jen je-li definováno makro DEBUG,
   ale není definováno THREE_D */
#endif

Složitější řetěz podmínek může použít rovněž direktivu #elif s významem else if


Předchozí Předchozí přednáška Další Další přednáška Hlavní stránka Hlavní stránka