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--;
return;/*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*/
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;
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;
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;
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 S i deinkrementacji zmiennej i
if(!i) //gdy
warto�� zmiennej i 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*/
/*drukuj kolejno napotykane pod wskazaniem p. znaki �a�cucha znakowego*/}
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;
cp=h;/* do zmiennej pomocniczej wska�nikowej przypisz pocz�tek �a�cucha tekstu jawnego*/
while(*p)/* zmiennej pomocniczej wska�nikowej przypisz pocz�tek �a�cucha klucza szyfrowania*/
{/*dop�ki nie napotkany b�dzie koniec �a�cucha tekstu jawnego*/
i++;/*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*/
p++;/*inkrementacja indeksu bie��cej kom�rki tablicy szyfr */
cp++;/*inkrementacja wska�nika na bie��cy znak tekstu jawnego*/
if(!*cp) cp=h;/*inkrementacja wska�nika na bie��cy znak klucza szyfrowania*/
}/*je�li nast�pi koniec �a�cucha klucza szyfrowania, to powt�rnie przypisz zmiennej wska�nikowj cp pocz�tek �a�cucha klucza szyfrowania*/
cp=h;/*przypisz do warto�ci zmiennej wska�nikowej p. pocz�tek tekstu zaszyfrowanego*/
while(*p)/* przypisz powt�rnie pocz�tek �a�cucha znakowego klucza do zmiennej wsk. cp. */
{/* dop�ki nie napotkany b�dzie koniec �a�cucha tekstu zaszyfrowanego*/
i++; /*inkrementacja zmiennej indeksu tablicy jawny *//*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 */
}/*warunkowo, przypisz powt�rnie zmiennej wska�nikowej cp., pocz�tek ���cucha klucza deszyfracji*/
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