mgr in�. Artur Bernat
Rozwa�ania nad istot� j�zyka C.


Wszelkie prawa zastrze�one


SPIS TRESCI
Wprowadzenie
Poj�cie wyra�enia w j�zyku
Przyk�ady b��d�w pope�nianych przez pocz�tkuj�cych "programist�w".
Operowanie na zmiennych wska�nikowych
Typy �a�cuchowe
Pierwszy przyk�ad wykorzystania wska�nik�w i zmiennych �a�cuchowych
Drugi przyk�ad wykorzystania wska�nik�w i zmiennych �a�cuchowych
Trzeci przyk�ad wykorzystania wska�nik�w i zmiennych �a�cuchowych
Czwarty przyk�ad wykorzystania wska�nik�w i zmiennych �a�cuchowych

Wprowadzenie
    Poni�ej przedstawione, powiedzia�bym, rozwa�ania na temat mo�liwo�ci leksykalnych i wykonawczych j�zyka C, nie nale�y traktowa� jako regularnego, pe�no-wymiarowego wyk�adu, lecz raczej jako pobie�ny przegl�d co wa�niejszych element�w tego j�zyka programowania, z du�ym naciskiem k�adzionym na wszechstronne mo�liwo�ci tworzenia wyra�e� w j�zyku C oraz u�ycie wska�nik�w. Mam nadziej�, �e przytaczaj�c stron� WWW na ten temat, zaoszcz�dz� du�o trudu tym, kt�rzy zar�wno  ucz� si� j�zyka C jako drugiego j�zyka wysokiego poziomu, po opanowaniu takiego j�zyka jak Pascal lub Fortran, jak i tym, kt�rzy staraj� si� opanowa� ten j�zyk jako ich pierwszy j�zyk programowania.

Poj�cie wyra�enia w j�zyku C
Na pocz�tek om�wimy poj�cie wyra�enia w j�zyku C.
Wyra�eniem w j�zyku C mo�e by� zar�wno taka linia kodu �r�d�owego:

i+=2;                /*zwi�ksz warto�� zmiennej liczbowej i o 2*/

jak i linia kodu realizuj�ca np. cztery wzajemnie niezale�ne operacje:

i++,--j, (*x)++, y--, y--;

/*zwi�ksz warto�� i o jeden, zmniejsz warto�� j o jeden, wy�uskaj warto�� liczbow� spod wska�nika x i zwi�ksz j� o jeden, dwukrotnie powt�rz deinkrementacj� warto�ci zmiennej liczbowej y.*/

Zauwa�my, �e kolejne instrukcje w wyra�eniu przedzielone s� przecinkami, natomiast ca�o�� wyra�enia zako�czona jest �rednikiem. Jak wida� z powy�szego przyk�adu wyra�enie mo�e posiada� bardziej rozbudowan� sk�adni�, tzn. zawiera� w sobie wiele niezale�nych instrukcji. Wyra�enie mo�e poza tym wyst�powa� w j�zyku C w postaci wolnostoj�cej, tzn. do napisania dowolnego wyra�enia nie potrzeba stosowa� koniecznie operatora przypisania �="(wyra�enie wolnostoj�ce - termin w�asny autora),  wyra�enie takie mo�e wykorzystywa� tylko operator unarny (jednoargumentowy) lub nie wykorzystywa� �adnych operator�w , tak jak np. w wyra�eniu typu:

i

lub w wyra�eniu:

i++

Z tak szerokim poj�ciem wyra�enia w j�zyku C wi��e si� odpowiedni mechanizm estymacji warto�ci wyra�enia. Wszak warto�� estymowana takiego wyra�enia mo�e wp�ywa� na przebieg programu w p�tlach iteracyjnych for, do while , while oraz w instrukcji warunkowej if.
Ci z czytelnik�w, kt�rzy zadufani s� w stylu programowania j�zyka Pascal, doznaj� pewnie w tym miejscu zdziwienia. Ot� w celu przeprowadzenia dalszych wywod�w na temat estymacji wyra�e� w j�zyku C, pomocne b�dzie wprowadzenie uog�lnionego poj�cia warto�ci zmiennej logicznej. W j�zyku C, w przeciwie�stwie do Pascala, nie istnieje tak naprawd� wyspecjalizowane poj�cie zmiennej logicznej i przyjmowanych przez ni� warto�ci TRUE i FALSE. Mianowicie, warto�� dowolnego testowanego wyra�enia logicznego lub arytmetycznego umieszczonego w p�tli iteracyjnej lub w instrukcji warunkowej , r�na od zera interpretowana jest jako spe�nienie warunku logicznego, czyli warto�� TRUE. Natomiast warto�� testowanego wyra�enia logicznego lub arytmetycznego r�wna zeru oznacza niespe�nienie warunku logicznego, czyli warto�� FALSE.
T� w�a�ciwo�� j�zyka C, ( a w�a�ciwie jego wielk� zalet�, pozwalaj�c� na uproszczenie testowanych wyra�e�) mo�na zobrazowa� na przyk�adzie p�tli iteracyjnej while, wykorzystanej do obliczania warto�ci silni S z zadanej liczby ca�kowitej i. Pocz�tkuj�cy w j�zyku C, "wyra�aj�cy si�" poprawnie �programista", napisa�by pewnie:

void main()
{
 int i=7;
 int S=1;
 while(i>0)
 {
  S=S*i;
  i=i-1;
 }
 return;
}

Powy�szy program, pomimo, �e jest napisany tzw. �poprawnie", jest zwyk�� strat� kodu �r�d�owego i czasu wykonania tych�e instrukcji. Bardziej poprawna i zwi�z�a wersja tego programu mo�e wygl�da� nast�puj�co:

void main()
{
 int i=7;
 int S=1;
  while(i)        /*praca p�tli while jest podtrzymywana, dop�ki warto�� wyra�enia jest                 niezerowa.*/
      S*=i, i--;

/*wyra�enie to sk�ada si� z instrukcji przemna�aj�cej warto�� zmiennej   S przez warto�� zmiennej i oraz z instrukcji deinkrementacji warto�ci zmiennej i*/
 return;
 }

Przypomn�, �e w j�zyku C istnieje ca�a gama zwi�z�ych operator�w:

*=,/=,+=,-=,^=,itp.,

 r�wnowa�nych, w pewnych warunkach, w rezultatach wykonania operacji, odpowiednim operatorom:

*, /, +, -, ^, itp.

W przypadkach, gdy wynik jednej z tych operacji dwuargumentowych jest umieszczany pod zmienn� b�d�c� jednym z argument�w, mo�na napisa� pro�ciej i �wi�lej:

S*=i;

,zamiast:

S=S*i;         /*jest to zwyk�a nadmiarowa strata kodu �r�d�owego*/

Z pewno�ci� warto�� silni z zadanej liczby ca�kowitej  mo�na obliczy� w j�zyku C co najmniej kilkana�cie sposob�w, co zostanie udowodnione, a przy okazji zademonstrowana b�dzie elastyczno�� tworzenia wyra�e� j�zyka C
Powy�ej przedstawiony program obliczania silni mo�e by� jeszcze kr�tszy:

void main()
{
 int i=7;
 int S=1;
 while(i)
    S*=i--;

/*wyra�enie to wykorzystuje zjawisko deinkrementacji w notacji postfiksowej, tzn. po wykonaniu w�a�ciwej operacji przemna�ania warto�ci zmiennej i przez warto�� zmiennej S,  warto�� zmiennej i jest deinkrementowana.*/
 return;
}

Ciekawe co by si� sta�o, gdyby w wyra�eniu obliczaj�cym silni�, deinkrementacj� w notacji postfiksowej zamieniono na deinkrementacj� w notacji prefiksowej, tzn. w przebiegu bie��cej iteracji p�tli while najpierw nast�powa�a by operacja deinkrementacji warto�ci zmiennej i, a dopiero w drugiej kolejno�ci tak zmniejszona warto�� zmiennej i wykorzystana by�aby w operacji przemna�ania:

void main()
{
 int i=8;
 int S=1;
 while(i>1)
   S*=--i;

/*przed wykonaniem w�a�ciwej operacji przemna�ania warto�ci zmiennej i przez warto�� zmiennej S, warto�� zmiennej i jest deinkrementowana.*/
 return;
}

Program nieco si� skomplikowa�. Warto�� pocz�tkowa zmiennej i wynosi 8, a pomimo tego faktu, program b�dzie nadal liczy� silni� z 7 (5040). Dlaczego? W wyra�eniu podrz�dnym p�tli while licz�cym silni�, wyst�puje unarny(jednoargumentowy) operator deinkrementacji w notacji prefiksowej, pomniejszaj�cy o jeden warto�� zmiennej i przed ka�d� operacj� przemna�ania. �Skomplikowa�o si�" r�wnie� wyra�enie testowane w warunku p�tli while. Musimy w tym programie dba� o to, aby przy nast�pnej wykonywanej iteracji, warto�� zmiennej i nie spad�a do warto�ci 1. W przeciwnym wypadku policzona silnia S wynosi� b�dzie wynosi�... zero. A propos: testowane wyra�enie w warunku p�tli while, z wyra�enia wolnostoj�cego, przedzierzgn�o si� w wyra�enie czysto logiczne o mo�liwych warto�ciach zwracanych r�wnych albo 0(FALSE) albo 1(TRUE).
Przeanalizujmy z kolei zupe�nie inne podej�cie do problemu:

void main()
{
 int i=8;
 int S=1;
 while(--i)

/*tutaj zosta� zastosowany unarny(jednoargumentowy) operator deinkrementacji w notacji prefiksowej*/
   S*=i;
 return;
}

W programie tym po raz pierwszy spotykamy si� z problemem estymacji testowanego wyra�enia, logicznego lub arytmetycznego, w warunku p�tli while. Je�li, tak jak w tym powy�szym programie, wyst�puje operator unarny deinkrementacji w notacji prefiksowej, to warto�� wyra�enia jest poddawana estymacji dopiero po wykonaniu operacji deinkrementacji. Tak wi�c warto�� pocz�tkowa zmiennej i , w tym opisywanym przyk�adzie musi wynosi� 8, aby zosta�a prawid�owo policzona silnia z warto�ci liczby 7.
Analogicznie, je�li w wyra�eniu estymowanym warunku p�tli while u�yjemy deinkrementacji w notacji postfiksowej, to warto�� wyra�enia testowego b�dzie poddawana estymacji przed wykonaniem operacji deinkrementacji. Ilustruje to poni�szy program:

void main()
{
 int i=8;
 int S=1;
 while(i -- >1)

/*zastosowany tutaj operator unarny deinkrementacji w notacji postfiksowej, spowodowa�, �e estymacji poddawane jest najpierw wyra�enie i>1, a nast�pnie wykonywana jest operacja deinkrementacji na warto�ci zmiennej i.*/
   S*=i;
 return;
}

W powy�szym przyk�adzie warto�� pocz�tkowa zmiennej i musi by� r�wnie� ustawiona na 8, z uwagi na to, �e warto�� zmiennej i jest pomniejszana o jeden przed operacj� mno�enia.
Innym jeszcze sposobem na obliczenie silni przy u�yciu deinkrementacji z u�yciem p�tli iteracyjnej while jest rozbudowa estymowanego wyra�enia warunku p�tli while. Oto przyk�ad:

void main()
{
 int i=7;
 int S=1;
 while(S*=i,--i)     /*wyra�enie warunku p�tli while z�o�one jest z dw�ch instrukcji*/
 ;             /*tutaj wykonywana jest instrukcja pusta (symbol �rednika)*/
 return;
}

Jak wida� z powy�szego przyk�adu, w�a�ciwa operacja mno�enia i obliczania warto�ci silni dla zadanej warto�ci zmiennej i, wykonywana jest jako cz�� wyra�enia estymowanego warunku p�tli while. Wskutek takiej kompozycji p�tli iteracyjnej while w zasadniczym bloku instrukcji podrz�dnych p�tli while jest wykonywana tylko instrukcja pusta(symbol �rednika)!.
Uwaga: estymowan� warto�ci� takiego z�o�onego wyra�enia jest warto�� zwracana przez ostatnie podwyra�enie(instrukcj�) w wyra�eniu, czyli w powy�szym przyk�adzie przez podwyra�enie:

--i.

Z tego te� wzgl�du, niedopuszczalne by�oby umieszczenie nast�puj�cego wyra�enia w warunku p�tli while:

while(--i, S*=i)

oraz wyra�enia:

while(S*=i--)

W tych dw�ch przyk�adowych, b��dnie skonstruowanych, estymowanych wyra�eniach warunku p�tli while, warto�� zwracana wyra�enia nie tylko nie sugerowa�aby zako�czenia pracy p�tli w odpowiedniej iteracji, lecz prawdopodobnie p�tla taka w niesko�czonos� liczy�aby b��dn� wartos� silni..
Uwzgl�dniaj�c mo�liwo�� liczenia silni na dwa sposoby tj. przez przemna�anie tymczasowego wyniku przez wzrastaj�c� warto�� mno�nika, a drugim razem przez przemna�anie tymczasowego wyniku przez malej�c� do jedno�ci warto�� mno�nika oraz uwzgl�dniaj�c mo�liwo�� zastosowania trzech r�nych p�tli iteracyjnych tj. while, do while, for, liczb� przedstawionych powy�ej sposob�w liczenia warto�ci silni mo�na rozszerzy� do co najmniej 20 przyk�ad�w. Nie to jednak jest przedmiotem i celem tego wyk�adu. Z uwagi na specyficzne odmienno�ci p�tli for w stosunku do p�tli while powy�sze przyk�ady liczenia silni b�d� jeszcze zaimplementowane z u�yciem tej p�tli.

void main()
{
 for(int i=7,S=1; i ; i--)

/*nowsza wersja kompilatora zezwala na deklaracj� zmiennych w prawie ka�dym miejscu, np. w wyra�eniu inicjuj�cym warunek pocz�tkowy p�tli for*/
   S*=i;
 return;
}

Nie zwa�aj�c na czysto�� klasycznego j�zyka C, nowsze kompilatory tego j�zyka umo�liwiaj� deklarowanie zmiennych lokalnych dos�ownie prawie w ka�dym miejscu kodu �r�d�owego programu. Chocia� powy�szy program funkcjonuje,. nie zaleca si� stosowania takiego stylu programowania.
Jeszcze bardziej zwi�z�ym kodem �r�d�owym, tego drobnego przyk�adu obliczania silni, kt�rego sk�adni� akceptuje kompilator  np. Borland�a w wersji 3.,1 mo�e poszczy� si� poni�szy program:

void main()
{
 for(int i=7,S=1; i ; S*=i--);

/*wyra�enie trzecie p�tli for, odpowiedzialne za zmian� warto�ci zmiennej iteracyjnej, jednocze�nie jest wyra�eniem licz�cym warto�� silni S*/
 return;
}

W powy�szym przyk�adzie zastosowano deinkrementacj� w notacji postfiksowej.
Dla urozmaicenia mo�emy zastosowa� tutaj r�wnie� notacj� prefiksow� deinkrementacji, chocia� posta� p�tli for, oblicz�cej silni� z warto�ci zmiennej i, nieco si� skomplikuje:

void main()
{
 for(int i=7,S=1; i ; S*=--i+1);

/*wyra�enie trzecie p�tli for, odpowiedzialne za zmian� warto�ci zmiennej iteracyjnej, dokonuje najpierw deinkrementacji warto�ci zmiennej i, a nast�pnie wykorzystuje tak zmienion� jej warto�� w obliczaniu iloczynu S. Z tego wzgl�du niezb�dne by�o dodanie jedynki do  warto�ci zmiennej i w tym wyra�eniu*/
 return;
}

Przyk�ady b��d�w pope�nianych przez pocz�tkuj�cych "programist�w".
Na podstawie przytoczonego materia�u, celowym w tym miejscu wydaje si� przytoczenie typowych b��d�w pope�nianych przez pocz�tkuj�cych �programist�w" j�zyka C.

Pierwszy przyk�ad. Dosy� cz�sto instrukcja warunkowa if, s�u��c� do testowania prawdziwo�ci pewnego warunku, tak jak poni�ej:

if(c==5)
 /*tu nast�puje blok instrukcji podrz�dnych wykonywanych warunkowo*/

zostaje nieopatrznie u�yta w nast�puj�cy spos�b:

if(c=5)
 /*blok instrukcji podrz�dnych wykonywanych rzekomo warunkowo*/

W�wczas umieszczona w warunku instrukcji if warto�� wyniku operacji przypisania c=5 zamiast operacji por�wnania c==5, jest zawsze niezerowa, r�wna 5, co powoduje bezwarunkowe(!) wykonanie bloku instrukcji podrzednych pod instrukcj� if.

Drugi przyk�ad. Cz�sto w p�tlach iteracyjnych typu while, licz�cych np. silni� z zadanej liczby ca�kowitej, tak jak w przyk�adzie ponizej:

while(i)
 S*=i--;

przez nieuwag�, bezpo�rednio po pierwszej linii, zostaje umieszczony symbol �rednika, oznaczaj�cy instrukcj� pust�:

while(i);
 S*=i--;

Powstaje w�wczas p�tla o niesko�czonej liczbie  iteracji, wykonuj�cych instrukcj� pust�!
Zwarto�� i zwi�z�o�� j�zyka C nie pozwala na jakiekolwiek b��dne posuni�cie, a wadliwe dzia�anie algorytm�w zwi�zane z niew�a�ciwym umieszczeniem symbolu instrukcji pustej, okazuje si� zwykle trudnym do wykrycia.
Oczywi�cie, je�li komu� zale�y na stworzeniu nieko�cz�cej si� p�tli w programie napisanym w j�zyku C, to elastyczno�� zwi�zana z manipulowaniem wyra�eniami pozwala u�y� np. p�tli for o nast�puj�cej postaci:

for(; ; ;);

Uwa�am  jednak, �e praktyczniejsze wykorzystanie p�tli for z trzema pustymi wyra�eniami (tj. realizuj�cymi odpowiednio : inijcjacj� zmiennej(ych) iteracyjnej(ych), wyra�enie testowe warunku ko�cowego p�tli oraz wyra�enie modyfikacji zmiennej(nnych) iteracyjnej(nych) ) obrazuje poni�szy przyk�ad, jeszcze jednego sposobu na obliczanie silni:

int S=1;
int i=7;
void main()
{
 for(;;) //p�tla for z trzema wyra�eniami pustymi
  {
   S*=i--; //wyra�enie obliczania warto�ci silni i deinkrementacji zmiennej i
   if(!i)    //gdy warto�� zmiennej osi�gnie zero
    break; //bieg programu " wy�amie sie " z p�tli for
  }
 return;
}
Mechanizm dzia�ania p�tli for zosta� "zwolniony" z obowi�zku inicjacji i deinkrementacji warto�ci zmiennej i oraz z testowania warunku ko�cowego funkcjonowania p�tli. Funkcje te przej�� blok instrukcji podrz�dnych tej p�tli for. Jednak nie zaleca si� i nie pochwala "wyr�czania" p�tli for w obowi�zku sp�niania tych funkcji. Oczywi�cie powy�szy przyk�ad zosta� tu przytoczony tylko w celach demonstracji granic elastyczno�ci sk�adni j�zyka C.
 

Operowanie na zmiennych wska�nikowych.
W dobrym opanowaniu j�zyka C, niezb�dna jest przynajmniej podstawowa wiedza o typach wska�nikowych na zmienne wszelkiego typu.

Zmienne wska�nikowe s�u�� do przechowywania adresu (inaczej m�wi�c wskazania) na okre�lony obszar pami�ci.Deklaracja zmiennej wska�nikowej na pewien typ danych zawsze poprzedzona jest symbolem gwiazdki(asterisk symbol), np.

float *wskf;     /*zmienna wska�nikowa na zmienn� typu rzeczywistego*/
char *wskc;    /*zmienna wska�nikowa na zmienn� typu znakowego*/
int *wskn    /*zmienna wska�nikowa na zmienn� typu ca�kowitego*/

struct el
{
 char nazw[40];
 char imie[40];
}  *wskstr
    /*zmienna wska�nikowa na typ strukturalny el, zmiennej zajmuj�cej 80 bajt�w */

Warto�ci zmiennych wska�nikowych mog� ulega� w pewnych sytuacjach przypisaniu oraz innym zmianom np. wywo�anych inkrementacj� lub deinkrementacj�  zmiennej wska�nikowej.
Przy czym inkrementacja warto�ci zmiennej wska�nikowej wskazuj�cej na typ float r�ni si� zawsze od inkrementacji warto�ci zmiennej wska�nikowej wskazuj�cej inny np. zdefiniowany powy�ej typ strukturalny el. Mianowicie operacja:

wskf++

powoduje zwi�kszenie adresu wskazuj�cego na zadany obszar pami�ci o ilo�� bajt�w zajmowany przez dan� typu float, czyli o 4 bajty. Ta sama operacja inkrementacji wykonana  na warto�ci zmiennej wska�nikowej wskstr wskazuj�cej na zmienn� o typie el zdefiniowanym powy�ej, spowoduje zwi�kszenie adresu wskazywanego o wielko�� takiej struktury, czyli w tym rozwa�anym przypadku, o 80 bajt�w. Ten okre�lony �rastr" warto�ci adresu zmiennych wska�nikowych, �ci�le powi�zany z typem danej, na kt�ry wskazuje dany wska�nik, nie ujmuje oczywi�cie nic z elastyczno�ci j�zyka C. Wykorzystuj�c operator rzutowania ( ) warto�ci jednego typu danej  na warto�� innego typu danych, mo�emy przypisa� warto�� wska�nika 4-bajtowego typu float  warto�ci wska�nika 1-bajtowego typu char:

wskc=(char *) wskf;

Mamy w�wczas nierzadk� okazj� �zagl�dni�cia" do wewn�trznego formatu liczby typu float, chocia�by z u�yciem nast�puj�cych linii kodu:
 

....                /*deklaracje zmiennych wskf, wskc jak powy�ej*/
float f=5;
wskf= &f; /*tworzenie referencji, czyli adresu odnosz�cego si� do zmiennej  f*/
wskc=(char *) wskf; /*operacja rzutowania, wymuszaj�ca w trakcie operacji przypisania warto�ci wska�nika wskc warto�� wska�nika wskc, typ wskazania na zmienn� typu char.*/
for(int i=0;i<4;i++)
 printf(�%d bajt pami�ci : %d\n", i,*wskc++);
........

Rezultat wykonania tego bloku kodu b�dzie nast�puj�cy:

0 bajt pami�ci: 0
1 bajt pami�ci: 1
2 bajt pami�ci: -96
3 bajt pami�ci: 64

Ale, kto by chcia� �zagl�da�" do wewn�trznego formatu liczby typu float przechowuj�cej wyk�adnik i mantys� reprezentowanej liczby rzeczywistej? Fe!
Z typami wska�nikowymi j�zyka C, w szczeg�lny spos�b wi��e si�, znakomicie skonstruowana koncepcja typ�w tablicowych. Nazwa tablicy o przyk�adowej definicji, umieszczonej poni�ej:

char tabznak[20];

czyli tabznak jest jednocze�nie wyra�eniem zwracaj�cym adres na pocz�tek po�o�enia tej tablicy i jednocze�nie jest to adres wskazuj�cy po�o�enie pierwszej kom�rki tej tablicy tabznak[0]. Z tego te� naturalnie wynikaj�cego faktu, indeks pierwszej kom�rki dowolnej zmiennej, typu tablicowego jest r�wny zeru, a ostatni element tablicy jest wskazywany przez indeks o jeden mniejszy od ca�kowitej ilo�ci kom�rek tablicy.  Dla tablicy tabznak, 20-elementowej, zdefiniowanej jak powy�ej, lista dost�pnych kom�rek wygl�da nast�puj�co:

tabznak[0] tabznak[1] tabznak[2] tabznak[3] ... tabznak[18] tabznak[19].

Do warto�ci zmiennej przechowywanej pod adresem przechowywanym w zmiennej wska�nikowej mo�emy si� odwo�ywa� za pomoc� operatora wy�uskania  �*"(symbol asterisku). Nie nale�y myli� tego operatora z tym samym symbolem, u�ywanym w innym miejscu przy deklaracji zmiennych wska�nikowych. Operatorem dope�niaj�cym do operatora wy�uskania �*", jest operator tworzenia referencji �&" s�u��cy do tworzenia adresu odniesienia do zmiennej. Dla przyk�adu niech b�d� dane deklaracje zmiennych:

float * wskf1,wskf2;
float f=5;

W�wczas dopuszczalne s� operacje:

wskf1=&f;

/*przeprowadzana jest operacja referencji, czyli pobierania adresu odniesienia do zmiennej f*/
wskf2 = wskf1;
/*operacja przypisania warto�ci wska�nika wskf1 warto�ci wska�nika wskf2*/
printf("Warto�� zmiennej f wynosi: %f \n", *wskf2);
/* przy operacji drukowania warto�ci zmiennej f, u�yto operatora wy�uskania �*", czyli operatora dost�pu do warto�ci zmiennej wskazywanej przez warto�� wska�nika wskf2 */

Operator wy�uskania z powodzeniem mo�na u�ywa� przy operowaniu na warto�ciach kom�rek zmiennych typu tablicowego. Na przyk�ad, dla deklaracji tablicy tabznak jak powy�ej, tzn.

char tabznak[20];

poprawny jest nast�puj�cy blok kodu:

for(int i = 0; i<20; i++)
  printf(�Element nr: %d tablicy wynosi: %d \n", *(tabznak+i));

Inaczej m�wi�c wyra�enia *(tabznak+i) oraz tabznak[i] pobieraj�ce warto�� kolejnych kom�rek tablicy s� sobie r�wnowa�ne.

Typy �a�cuchowe.
Ze typami wska�nikowymi, niepodzielnie kr�luj�cymi w j�zyku C, wi��e si� spos�b operowania na zmiennych typu �a�cuchowego. Do zdefiniowania pewnego litera�u �a�cuchowego o postaci:

char *nazw= �Jan Kot";

pos�u�y� wska�nik typu �a�cuchowego  char * nazw. Taki litera� �a�cuchowy zawsze jest zako�czony znakiem pustym 0 (lub �/x0� w heksadecymalnej reprezentacji zmiennej znakowej), oznaczaj�cym koniec odczytywanego �a�cucha znakowego. Aby powy�ej zdefiniowany litera� �a�cuchowy by� w pewnym funkcjonalnym sensie r�wnowa�ny warto�ci  zmiennej tablicowej tabznak, inicjowanej w nast�puj�cy spos�b:

tabznak[0]=�J�;
tabznak[1]=�a�;
tabznak[2]=�n�;
tabznak[3]=� �;
tabznak[4]=�K�;
tabznak[5]=�o�;
tabznak[6]=�t�;

za ostatni� inicjowan� kom�rk� w tablicy, umieszczonego znaku, musi wyst�pi� znak pusty "/x0":

tabznak[7]=�\x0�;

Zalet� tak konstruowanych litera��w i zmiennych �a�cuchowych, jest na przyk�ad to, �e w parametrach przekazywanych do funkcji, nie zachodzi potrzeba kopiowania ca�ego �a�cucha (jak to zwykle mia�o miejsce w klasycznej wersji j�zyka Pascal), lecz wystarczy tylko przekaza� wskazanie na dan� zmienn� �a�cuchow�. Wszelkie funkcje biblioteczne, j�zyka C ,operuj�ce wska�nikach do �a�cuch�w znakowych, odczytuj� dany �a�cuch znakowy, a� do napotkania znaku pustego �\x0�, sygnalizuj�cego jego koniec. Oczywi�cie wracaj�c do zdefiniowanej powy�ej tablicy tabznak, mo�liwe jest bardziej bezpo�rednie zainicjowanie tej tablicy:

tabznak="Jan Kot";

Na ostatniej pozycji, za ostatnim znakiem w �a�cuchu znakowym automatycznie zostanie wpisany znak pusty �\x0� ko�cz�cy �a�cuch znakowy. Brak znaku pustego na ko�cu �a�cucha znakowego m�g�by spowodowa� nieprzewidywalne skutki wykonania programu. Taki �a�cuch znakowy jest praktycznie odczytywany a� do przypadkowego wyst�pienia znaku pustego lub wyst�pienia innego zdarzenia w systemie, kt�re zako�czy ten odczyt. Z przedstawionych powy�ej fakt�w wynika jeszcze inna mo�liwo��. Mianowicie, warto�� ka�dego wska�nika dowolnego typu np. char *, mo�e by� traktowana jako adres pocz�tkowy tablicy o typie danych zgodnym z typem wska�nika.
Na przyk�ad:

char liczba= 6;
char *wsk;
void main()
{
 wsk=&liczba;
print("Zawarto�� kolejnych kom�rek pami�ci:\n);

 for(int i =0; i<20; i++)
  printf("Kom�rka nr: %d : %d \n", i, wsk[i]);
return;
}

Poni�szy program wydrukuje poprawnie tylko zawarto�� pierwszej kom�rki tablicy tabznak, reszta zawarto�ci kom�rek przedstawia sob� przypadkow� zawarto�� 19 bajt�w pami�ci znajduj�cych si� bezpo�rednio za bajtem pami�ci zarezerwowanym dla zmiennej liczba.
PIERWSZY PRZYK�AD
Jako pierwszy powa�ny przyk�ad wprowadzaj�cy do technik pos�ugiwania si� zmiennymi wska�nikowymi, mo�e pos�u�y� kr�tki program odwracaj�cy zadany tekst.

#include<stdio.h>
#include<conio.h>
char *t="To jest prosty tekst do odwr�cenia";     /*zadany tekst do odwr�cenia*/
char *p;     /*pomocnicza zmienna wska�nika p. */
void main()
{
 clrscr();
  p=t;

/* przypisanie adresu pocz�tkowego �a�cucha znakowego do zmiennej wsk. p. */
  while(*p)
/*iteracje p�tli while b�d� kontynuowane dop�ki nie napotkany b�dzie znak pusty, ko�cz�cy odwracany �a�cuch znakowy, w�wczas wyra�enie *p. zwr�ci warto�� r�wn� zeru*/
    p++;     /*przewijanie warto�ci wska�nika p. */
/*nast�pna p�tla while wycofywania si� warto�ci wska�nika w kierunku pocz�tku �a�cucha znakowego*/
 while(p!=t)     /*dop�ki nie napotkano pocz�tku �a�cucha znakowego*/
   {
     p--;      /* zmniejszaj warto�� wska�nika p */
     printf(�%c", *p);
/*drukuj kolejno napotykane pod wskazaniem p. znaki �a�cucha znakowego*/
    }
 return;
}

Prosz� zauwa�y�, �e u�ycie instrukcji drukowania bie��cego znaku z ca�o�ci przewijanego od ko�ca �a�cucha znakowego w postaci:

printf(�% s\n",p.);

spowodowa�oby drukowanie w oddzielnych liniach coraz d�u�szych pod�a�cuch�w ca�kowitego �a�cucha  znakowego oi g�owach tych pod�a�cuch�w zbiegaj�cych si� do pocz�tku ca�ego tekstu pr�bnego.

DRUGI PRZYK�AD
Drugim przyk�adem wykorzystania operacji na zmiennych wska�nikowych jest kr�tki program dokonuj�cy szyfrowania i deszyfrowania zadanego �a�cucha znakowego. Program wykorzystuje operacj� XOR. Operacja ta realizowana za pomoc� operatora dwuargumentowego " ^ ", jest operacj� odwrotn� do samej siebie, tzn. jeden raz zaszyfrowany za pomoc� �a�cucha znakowego - klucza szyfrowania tekst, mo�e by� z powrotem odszyfrowany z u�yciem tego samego klucza. Jest to przyk�ad kryptografii z u�yciem klucza symetrycznego.

//demonstracja u�ycia wska�nik�w w
//szyfrowaniu dowolnego wybranego tekstu jawnego
//za pomoc� ci�gu liter b�d�cych kluczem szyfrowania
#include<stdio.h>
#include<conio.h>
char *t="To jest tekst gotowy do zaszyfrowania";
char *p; /*zmienna pomocnicza wska�nikowa, przegl�dania znak�w tekstu jawnego*/
char *h="XOR"; /*litera� znakowy b�d�cy kluczem szyfrowania*/
char *cp;  /*zmienna pomocnicza wska�nikowa ,przegl�dania znak�w klucza */
char szyfr[45]; /*tablica pomocnicza przechowywania tekstu zaszyfrowanego*/
char jawny[45]; /*tablica pomocnicza przechowywania tekstu odszyfrowanego*/
char i;               /* zmienna pomocnicza indeksuj�ca tablice*/
void main()
{
 clrscr();
 p=t;

/* do zmiennej pomocniczej wska�nikowej przypisz pocz�tek �a�cucha tekstu jawnego*/
 cp=h;
/* zmiennej pomocniczej wska�nikowej przypisz pocz�tek �a�cucha klucza szyfrowania*/
 while(*p)
/*dop�ki nie napotkany b�dzie koniec �a�cucha tekstu jawnego*/
 {
   szyfr[i]=*cp^*p;
/*wykonuj operacj� XOR na bie��cym znaku tekstu jawnego i bie��cym znaku �a�cucha klucza szyfrowania. Wynik szyfrowania umieszczany jest w bie��cej kom�rce tablicy szyfr*/
   i++;
/*inkrementacja indeksu bie��cej kom�rki tablicy szyfr */
   p++;
/*inkrementacja wska�nika na bie��cy znak tekstu jawnego*/
   cp++;
/*inkrementacja wska�nika na bie��cy znak klucza szyfrowania*/
   if(!*cp) cp=h;
/*je�li nast�pi koniec �a�cucha klucza szyfrowania, to powt�rnie przypisz zmiennej wska�nikowj cp pocz�tek �a�cucha klucza szyfrowania*/
 }
                            /*koniec p�tli szyfrowania*/
 szyfr[i]=0;         /*umie�� znak ko�ca �a�cucha tekstu zaszyfrowanego*/
 printf("Szyfr:%s",szyfr);     /* wydrukuj tekst zaszyfrowany*/
 i=0;                                         /* ustaw zmienn� indeksacji na warto�� pocz�tkow�*/
 p=szyfr;
/*przypisz do warto�ci zmiennej wska�nikowej p. pocz�tek tekstu zaszyfrowanego*/
 cp=h;
/* przypisz powt�rnie pocz�tek �a�cucha znakowego klucza do zmiennej wsk. cp. */
 while(*p)
/* dop�ki nie napotkany b�dzie koniec �a�cucha tekstu zaszyfrowanego*/
 {
  jawny[i]=*p^*cp;
/*wykonuj operacj� XOR na bie��cym znaku tekstu zaszyfrowanego i bie��cym znaku �a�cucha klucza deszyfracji. Wynik deszyfracji umieszczany jest w bie��cej komi�rce tablicy jawny */
  i++;                     /*inkrementacja zmiennej indeksu tablicy jawny */
  cp++;                   /*inkrementacja zmiennej wska�nikowej cp */
  p++;                     /*inkrementacja zmiennej wska�nikowej p. */
  if(!*cp)  cp=h;
/*warunkowo, przypisz powt�rnie zmiennej wska�nikowej cp., pocz�tek ���cucha klucza deszyfracji*/
 }
 jawny[i]=0;         /*wpisz znak pusty na ko�cu tablicy tekstu odszyfrowanego*/
 printf("Jawny:%s",jawny);     /* wydrukuj tekst odszyfrowany*/
 return;
}

TRZECI PRZYK�AD
Pewn� odmian� tego przyk�adu, mo�e by� wersja w kt�rej zastosowano mechanizm semafora, na zmiennej st, kt�rej warto�ci 1 i 0 ustawiane s� naprzemiennie. Co to oznacza? W pocz�tkowym przebiegu p�tli while, realizuj�cej proces szyfrowania, przy warto�ci tego semafora ustawionej na 1,  nast�puj�ce inkrementacja wska�nika cp, a� do napotkania ko�ca �a�cucha klucza. W�wczas nast�puje zmiana warto�ci semafora z 1 na 0, co w rezultacie w ka�dej nast�pnej iteracji p�tli while powoduje deinkrementacj� warto�ci wska�nika cp. Z kolei przy napotkaniu pocz�tku �a�cucha znakowego klucza warto�� zmiennej semafora st zmienia powt�rnie na 1. Efektem wprowadzonej modyfikacji, program operuje w trakcie szyfrowania, kluczem przewijanym na zasadzie ping-ponga.

//przyk�ad u�ycia wska�nik�w podobny do przyk�adu wsk2.cpp
// z ta r�nica, ze
//has�o przegl�dane jest na zasadzie ping-pongu
#include<stdio.h>
#include<conio.h>
char *t="To jest tekst do odwrocenia";
char *p;
char *h="IZK";
char *cp;
char szyfr[45];
char jawny[45];
char i;
char st=1;
void main()
{
 clrscr();
 p=t;
 cp=h;
 while(*p)
 {
   szyfr[i]=*cp^*p;
   i++;
   p++;
   if(st)
    cp++;
   else
    cp--;
   if(!*cp)
    st=0;
   if(cp==h)
    st=1;
 }
 szyfr[i]=0;
 printf("Szyfr:%s",szyfr);
 i=0;
 p=szyfr;
 cp=h;
 st=1;
 while(*p)
 {
  jawny[i]=szyfr[i]^*cp;
  i++;
  if(st)
   cp++;
  else
   cp--;
  p++;
  if(!*cp)
   st=0;
  if(cp==h)
   st=1;
 }
 jawny[i]=0;
 printf("Jawny:%s",jawny);
 return;
}

Pierwszy z tych dw�ch powy�ej przedstawionych przyk�ad�w mo�e by� nieznacznie zoptymalizowany:

//demonstracja u�ycia wska�nik�w przy okazji
//szyfrowania dowolnego wybranego tekstu jawnego
//za pomoc� dowolnego ci�gu liter has�o za pomoc�
//operacji XOR
//wersja optymalizowana
#include<stdio.h>
#include<conio.h>
char *t="To jest tekst do odwrocenia";
char *p;
char *h="IZK";
char *cp;
char szyfr[45];
char jawny[45];
char i;
void main()
{
 clrscr();
 p=t;
 cp=h;
 while(*p)
 {
   szyfr[i++]=*(cp++)^*(p++);
   if(!*cp)
    cp=h;
 }
 szyfr[i]=0;
 printf("Szyfr:%s",szyfr);
 i=0;
 p=szyfr;
 cp=h;
 while(*p)
 {
  jawny[i++]=*(p++)^*(cp++);
  if(!*cp)
   cp=h;
 }
 jawny[i]=0;
 printf("Jawny:%s",jawny);
 return;
}

W powy�szym przyk�adzie, niezale�ne instrukcje wykonywanej operacji XOR oraz �przewijania" wska�nik�w:

szyfr[i]=*cp^*p;
   i++;
   p++;
   cp++; ,

zast�piono jedn� instrukcj�:

szyfr[i++]=*(cp++)^*(p++);

wykorzystuj�c� zjawisko inkrementacji w notacji
postfiksowej, czyli inkrementowania zmiennych wska�nikowych po wykonaniu zasadniczej operacji XOR i przypisania wyniku do bie��cej kom�rki tablicy szyfr.

Rzecz jasna, program realizuj�cy podobne zadanie mo�na rozwija� na znaczn� liczb� sposob�w, dokonuj�c przy tym podobnych redukcji nadmiarowo�ci kodu �r�d�owego w j�zyku C. Trzeba jednak pami�ta�, �e wi��e si� z tym nieod��czne, sta�e pogarszanie si� dla �tw�rcy programu" czytelno�ci kodu �r�d�owego. Dla cel�w dydaktycznych dalsza ewolucja tego programu w tym kierunku zdaje si� by� bezcelow�. Po za tym program ten, przy ca�ej swojej prostocie nie jest pozbawiony banalnych b��d�w. Mianowicie w ci�gu znak�w dowolnie wybranego klucza szyfrowania, mog� si� pojawi� bie��ce znaki identyczne z bie��cymi znakami tekstu szyfrowanego. Wynikiem operacji XOR b�dzie oczywi�cie warto�� zero, co spowoduje trudno�ci w wy�wietlaniu w ca�o�ci �a�cucha tekstu zaszyfrowanego( jest to znak pusty okre�laj�cy koniec �a�cucha znakowego).
CZWARTY PRZYK�AD
Jako ostatni� ju� propozycj� rozbudowy tego programu szyfruj�cego podany zostanie wydruk programu szyfruj�cego z u�yciem trzech niezale�nych kluczy-hase�:

//demonstracja u�ycia wska�nik�w przy okazji
//szyfrowania dowolnego wybranego tekstu jawnego
//za pomoc� dowolnego ci�gu liter has�o za pomoc�
//operacji XOR
//jest to modyfikacja programu wsk5.cpp
//program ten uzywa az trzech po kolei hase� do szyfrowania tekstu jawnego
#include<stdio.h>
#include<conio.h>
char *t="To jest tekst do zaszyfrowania";
char *p;
char *h1="ALA";
char *h2="ELKA";
char *h3="MONICA";
char *cp;
char szyfr[45];
char jawny[45];
char i;
char haslo=1;
void main()
{
 clrscr();
 p=t;
 cp=h1;
 while(*p)
 {
   szyfr[i]=*(cp++)^*(p.++);
   i++;
   if(!*cp)
    switch(haslo)
    {
     case 1: {cp=h2;haslo++;};break;
     case 2: {cp=h3;haslo++;};break;
     case 3: {cp=h1;haslo=1;};break;
    }
 }
 szyfr[i]=0;
 printf("Szyfr:%s",szyfr);
 i=0;
 p=szyfr;
 cp=h;
 haslo=1;
 while(*p)
 {
  jawny[i]=*(p.++)^*(cp++);
  i++;
  if(!*cp)
   switch(haslo)
   {
     case 1: {cp=h2;haslo++;};break;
     case 2: {cp=h3;haslo++;};break;
     case 3: {cp=h1;haslo=1;};break;

   }
 }
 jawny[i]=0;
 printf("Jawny:%s",jawny);
 return;
}

Mam nadziej�, �e te powy�sze skromne uwagi na temat programowania, dotycz�ce konstruowania i funkcjonowania wyra�e� oraz dotycz�ce operacji na zmiennych wska�nikowych stan� si� pomocne w zrozumieniu wyj�tkowo�ci j�zyka C.

POWR�T DO SPISU TRESCI
POWR�T DO STRONY G��WNEJ

dnia 10.10.1998r
Data ostatniej modyfikacji 15.04.1999r.
 
 PS: do poprzedniej wersji tych�e rozwaza� na temat istoty j�zyka C zakrad�y si� pewne b��dy w przyk�adach programowych... Jest to o tyle dziwne, �e wszystkie moje przyk�ady zosta�y do tej strony  WWW wklejone ze wcze�niej sprawdzonych pod kompilatorem Borlanda 3.1 plik�w kod�w �r�d�owych tych programik�w. Jednak�e zaznaczam, �e w przysz�o�ci b�d� z uwag� �ledzi� losy moich stron WWW, co do wyst�pienia dziwnych, nadzwyczaj "inteligentych" chochlik�w drukarskich. Oczywi�cie, moje "ciche" podejrzenie pada na serwer "lwa", z kt�rego po uprzednim, finalnym zredagowaniu,  nowwsze wersje moich stron s� zwykle przekopiowywane na inne mirrory.
Ponadto, staram si� w miar� mo�liwo�ci, dostosowywa� terminologi� stosowan� w moich wyk�adach do termin�w technicznych stosowanych powszechnie w polskiej  literaturze technicznej.
mgr in�. Artur Bernat