Je "Céčko" opravdu příliš složité?

1. 2. 1998

Sdílet

Mezi programátory se lze často setkat s názorem, že programování v jazyce C (natožpak v C++) je natolik složité, ...





Mezi programátory se lze často setkat s názorem, že programování

v jazyce C (natožpak v C++) je natolik složité, že je daleko

lepší naučit se a používat raději nějaký jiný jazyk.

V následujícím malém příkladu se pokusím ukázat, že s trochou

logického uvažování lze pochopit i na první pohled poněkud

nejasný zápis a posléze jej i aplikovat ve vlastní tvorbě.

Podívejme se tedy například na jedno zajímavé použití operátoru

inkrementace (++). Třeba takto lze v C++ kopírovat typický

řetězec zakončený nulou:



void cpy(char* r, const char* s) {



while(*r++ = *s++);



}



Právě v zápise typu while (*r++ = *s++); tkví oblíbenost jazyka

C/C++, přestože je více než trochu záhadný pro toho, kdo

v C příliš neprogramuje. Jelikož tento druh zápisu není ve

zdrojových textech ničím neobvyklým, jistě si zaslouží i naše

bližší zkoumání.

Postaví-li nás znenadání někdo před problém kopírování řetězce,

první způsob, který nás napadne, může například používat řetězec

jako pole znaků:



int delka = strlen(s);



for (int i=0; i<=delka; i++) r[i] = s[i];



Uvedený zápis však zavání neefektivitou: řetězec je ukončen nulou

a k zjištění jeho velikosti je nutné jej prohledat od začátku,

takže vlastně řetězec přečteme dvakrát – jednou při zjišťování

délky, podruhé při vlastním kopírování. Ve smyslu tohoto poznatku

zkusíme tohle:



for (int i = 0; s[i] != 0; i++) r[i] = s[i];



r[i] = 0; // a zakončíme nulou



Proměnná i, použitá jako index pole, může být klidně vynechána,

protože r a s jsou ukazatele.



while (*s != 0) {



*r = *s;



r++; // posun v 1. řetězci



s++; // posun v 2. řetězci



}



*r = 0; // nezapomeneme zakončit nulou



Víme také, že postfixové operace umožňují napřed použít hodnotu

a potom ji teprve zvýší:



while (*s != 0) {



*r++ = *s++;



}



*r = 0; // a opět nezbytná nula



Dále si musíme uvědomit, že hodnota *r++ = *s++ je *s. Snadno

tedy kód upravíme:



while ((*r++ == *s++) != 0) { }



Zde vidíme, že *s je nulové až po překopírování do *r, takže jsme

mohli vyloučit i dodatečné zapisování koncové nuly. Tuto verzi

nadto můžeme ještě zjednodušit tím, že si uvědomíme nepotřebnost

prázdného bloku příkazů, a že !=0 je nadbytečné, neboť každý

výsledek podmíněného výrazu se vždy porovnává s nulovou hodnotou.

S nemalým překvapením tedy zjišťujeme, že finální verze while

(*r++ = *s++); se shoduje s kódem, který jsme se rozhodli

analyzovat.

Nakonec však zbývá to nejdůležitější, totiž zda je tento tvar

srozumitelnější než verze předchozí a jaké má časové a paměťové

nároky na svoji činnost. Pomineme-li první verzi s použitím

strlen(), pak jsou tyto verze v podstatě ekvivalentní, konečný

výsledek bude záviset na konkrétním překladači a na architektuře

vašeho počítače. (Nejúčinnější verzí by měla být standardní

funkce na kopírování řetězců int strcpy(char* , const char*) ze

<string.h>.) Hodnocení srozumitelnosti pak záleží na vašich

znalostech, ale vezmeme-li v potaz eleganci kódu, jasně vítězí

krátký jednořádkový příkaz před daleko méně přehlednými verzemi.




Autor článku