C/C++ (4) - Funkce printf

Dnes se podíváme na printf, nejznámější funkci ze standardní knihovny.

4.10.2004 10:00 | Jan Němec | přečteno 88872×

Ukazatele a pár drobnosti

Než se pustíme do funkce printf, je třeba znát několik věcí, které jsme ještě neprobrali. Nejdůležitější jsou ukazatele (anglicky pointer), céčkové programy na nich stojí a hlavně padají. Ukazatel je fyzicky číslo, adresa v paměti. Jeho velikost záleží na architektuře, nejčastěji je stejně velký jako int, obvykle 4 byty, ale spoléhat se na to nelze. V Céčku má ukazatel vždy nějaký typ, například ukazatel na int není totéž, co ukazatel na double. Pokud typ neznáme, používá se ukazatel na void. Ukazatel se definuje pomocí hvězdičky.

int *pi;       /* proměnná pi je ukazatel na int */
void *pv;      /* proměnná pv je ukazatel na void */

Je běžným zvykem pojmenovávat proměnné typu ukazatel tak, aby začínali písmenem p (p jako pointer), někteří programátoři přidávají k úvodnímu p ještě podtržítko, například p_i, p_v a podobně.

Všechny objekty v paměti včetně funkcí mají adresu. U proměnných se dá zjistit operátorem &. Inverzním operátorem je stará známá hvězdička.

int i, j;    /* dvě proměnné typu int */
int *pi;     /* ukazatel na int */

i = 1;       /* do i dáme 1 */
pi = &i;     /* do pi dáme adresu i */
j = *pi;     /* do j dáme obsah paměti (chápaný jako int), na niž
                ukazuje pi, tedy obsah i, tedy 1 */ 

Jeden typ ukazatele už známe z minula. Je to řetězec, typ char *. Pokud napíšeme "řetězec", vyhradí překladač v datovém segmentu programu celkem alespoň osm bytů pro písmena řetězce a ukončovací nulu (připomínám: byte s číselnou hodnotou 0, nikoli znak '0'), výraz má typ const char *, je to tedy pointer na char a ukazuje na byte, který obsahuje úvodní 'ř'. Klíčové slovo const znamená, že obsah paměti, na niž pointer ukazuje nesmí být měněn.

V jazyku C přijde do kontaktu s ukazateli i začátečník. Důvodem je kromě řetězců také volání funkcí s parametry, ty se totiž předávají výhradně hodnotou. Ukážeme si to na příkladu, chceme napsat funkci, která vynuluje proměnnou.

void nula_spatne(int promenna) {
  promenna = 0;
}

void nula_dobre(int *ppromenna) {
  *ppromenna = 0;
}

int main(void) {
  int i = 1;
  nula_spatne(i);
  /* v i je pořád jedna */
  nula_dobre(&i);
  /* teprve teď je v i nula */
  return i;
}

Při volání funkce nula_spatne se vytvoří kopie proměnné i a do této kopie se vloží nula. Po skončení funkce kopie zmizí a původní proměnná zůstala nezměněná. Ve funkci nula_dobre máme v proměnné ppromenna adresu původní proměnné i, takže zápisem do paměti skutečně změníme hodnotu i a změnu nijak neovlivní ani zánik parametru ppromenna po skončení funkce. V některých jazycích (hlavně Pascal a C++) lze napsat funkci nula_dobre syntaxí podobnou nula_spatne, říká se tomu parametr předaný odkazem, ale Céčko tuto možnost nenabízí.

Funkce printf

V jazykách s vysokou teoretickou hodnotou a nízkou praktickou použitelností (Prolog) jsou výstupy takzvaně zadarmo. To znamená, že po skončení výpočtu se sám od sebe vypíše výsledek na obrazovku ve formátu, který vyhovuje autorovi normy jazyka. V jazykách vhodných pro výuku programování (Pascal) existuje magická formulka (writeln) s přehlednou, ale speciální syntaxí a touto formulkou (schválně neříkám funkcí) vypíše programátor libovolný počet hodnot různého typu. Moderní objektově orientované jazyky (Java) se snaží s problémem vypořádat pomocí metody, které převede cokoli na řetězec. Céčko je starý nízkoúrovňový jazyk pro praxi, a proto výstup řeší obyčejná funkce neobyčejné síly, která umožní výpis libovolného počtu hodnot základních typů na obrazovku ve formátu, který si určí programátor. Daní za to je mírně obtížnější syntaxe.

#include <stdio.h>

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

Funkce printf je ze stdio.h, vrací počet vypsaných znaků nebo záporné číslo v případě chyby. Zajímavější než návratová hodnota, která se v programech většinou ignoruje, jsou její parametry. Prvním je formátovací řetězec, který se až na případné speciální znaky vypíše na obrazovku, následuje libovolný počet (třeba i 0) dalších parametrů. To se v C značí třemi tečkami. Kolik je parametrů, jakého jsou typu a jak a kam se mají vypsat se uvede ve formátovacím řetězci. Řídícím znakem je '%', pokud jej chcete vypsat, musí se zdvojit. Celá řídící sekvence má syntaxi

%[příznaky][minimální šířka][přesnost][modifikátor typu]typ

a typicky popisuje vložení jednoho parametru funkce printf. Téměř vždy znamená výskyt jedné řídící sekvence jeden další nepovinný parametr při volání printf. Běžně užívané typy jsou:

OznačeníTyp parametru a interpretace
iint, znaménkové číslo v desítkové soustavě
o, u, x, Xunsigned, nezáporné celé číslo v osmičkové, desítkové, šestnáctkové (s ciframi abcd nebo ABCD) soustavě
edouble, v exponenciální formě
fdouble, ve formě s desetinou tečkou
cint, zkonvertuje se na unsigned char reprezentující jeden znak
sconst char *, nulou ukončený řetězec
pvoid *, adresa paměti, číslo v šestnáctkové soustavě

Ukážeme si několik jednoduchých příkladů, všechny řídící sekvence z formátovacího řetězce budou v základním formátu %typ.

int i = -125;
const char *s = "Nějaký text";
double d = 31.4159;

/* 1. Nejjednodušší příklad bez nepovinných parametrů, */
printf("Ahoj světe\n");
/* 2. který zvládne i funkce puts, ta navíc odřádkuje. */
puts("Ahoj světe");
/* 3. Výpis čísla typu int */
printf("Minus sto dvacet pět je %i\n", i);
/* 4. Zdvojení vypisovaného procenta */
printf("Jedeme na %i%%\n", 100);
/* 5. Výpis znaku, jeho ASCII kódu ve dvou soustavách */
printf("Znak '%c' má ASCII kód %i, šestnáctkově %x\n",
  'm', 'm', (unsigned) 'm');
/* 6. Výpis řetězce, ukazatele a znaku */
printf("Řetězec s obsahuje \"%s\", je na adrese %p"
  " a začíná písmenem '%c'\n", s, (const void *) s, (int) *s);
/* 7. Výpis reálných čísel */
printf("10 pi je asi %f, což se dá napsat i jako %e\n", d, d);
/* 8. Funkce printf vrací, kolik toho vytiskla. */
printf("Vnořená printf vypsala %i znaků\n", printf("12345\n"));

Příklad vytiskne na standardní výstup následující text:

Ahoj světe
Ahoj světe
Minus sto dvacet pět je -125
Jedeme na 100%
Znak 'm' má ASCII kód 109, šestnáctkově 6d
Řetězec s obsahuje "Nějaký text", je na adrese 0x8048540 a začíná písmenem 'N'
Číslo pi je asi 31.415900, což se dá napsat i jako 3.141590e+01
12345
Vnořená printf vypsala 6 znaků

První příklad je velmi jednoduchý, vypíšeme jen jednoduchý řetězec bez řídících znaků. Funkce printf narozdíl od puts sama neodřádkuje, proto jsem musel přidat znak konce řádku do řetězce. Ve třetím příkladu je vidět výpis parametru typu int pomocí %i. Tím parametrem nemusí být jen proměnná, ale třeba konstanta (4), výsledek volání funkce (8) nebo obecně jakýkoli výraz příslušného typu. Řídících sekvencí se znakem procento může být ve volání více (5), (6) a (7), všechny běžně užívané a zde uvedené sekvence zkonzumují po jednom parametru. Důležité je, aby byly parametry správného typu. Pokud to není splněno, musíme je přetypovat (5), (6). V pátém případě využívám skutečnosti, že znaková konstanta není typu char, ale int (je to divné, ale je tomu tak). V případě %x je lepší 'm', přetypovat z intu na vyžadovaný typ unsigned, ačkoli na normálních platformách mají stejnou fyzickou reprezentaci v paměti a příklad bude fungovat i bez přetypování. V šestém případě podobně měním const char * na const void * spíše pro formu (neumím si představit platformu, kde by ukazatele na různé typy měly různé reprezentace v paměti). O něco větší smysl má přetypování charu na int (6), nicméně vzhledem ke způsobu předávání parametrů funkcím není na normálních platformách nezbytné. Vyzkoušejte si ale třeba bez přetypování vypsat double pomocí %i nebo naopak int pomocí %f a budete možná nepříjemně překvapeni.

Pokračování příště

Vidíte, že funkce printf je mocný nástroj a to jsme jenom na začátku. V příštím díle výklad dokončím. Zatím si můžete za domácí úkol přečíst manuálovou stránku k printf.

man 3 printf

Většině věcí byste už měli rozumět.

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=407