Si v UNIX 2.18. CHto oznachayut opisaniya? int i; // celoe. int *pi; // ukazatel' na celoe. int *api[3]; // massiv iz 3h uk-lej na celye. int (*pai)[3]; // ukazatel' na massiv iz 3h celyh. // mozhno opisat' kak int **pai; int fi(); // funkciya, vozvrashchayushchaya celoe. int *fpi(); // f-ciya, vozvr. uk-l' na celoe. int (*pfi)(); // uk-l' na f-ciyu, vozvrashchayushchuyu celoe. int *(*pfpi)(); // uk-l' na f-ciyu, vozvr. uk-l' na int. int (*pfpfi())(); // f-ciya, vozvrashchayushchaya ukazatel' na // "funkciyu, vozvrashchayushchuyu celoe". int (*fai())[3]; // f-ciya, vozvr. uk-l' na massiv // iz 3h celyh. inache ee // mozhno opisat' kak int **fai(); int (*apfi[3])(); // massiv iz 3h uk-lej na funkcii, // vozvrashchayushchie celye. Peremennye v Si opisyvayutsya v formate ih ispol'zovaniya. Tak opisanie int (*f)(); oznachaet, chto f mozhno ispol'zovat' v vide int value; value = (*f)(1, 2, 3 /* spisok argumentov */); Odnako iz takogo sposoba opisaniya tip samoj opisyvaemoj peremennoj i ego smysl dovol'no neochevidny. Privedem priem (pozaimstvovannyj iz zhurnala "Communications of the ACM"), pozvolyayushchij proyasnit' smysl opisaniya. Opisanie na Si perevoditsya v opisa- nie v stile yazyka Algol-68. Dalee ref TIP oznachaet "ukazatel' na TIP" proc() TIP "funkciya, vozvrashchayushchaya TIP" array of TIP "massiv iz elementov TIPa" x: TIP "x imeet tip TIP" Privedem neskol'ko primerov, iz kotoryh yasen i sposob preobrazovaniya: int (*f())(); oznachaet (*f())() : int *f() : proc() int f() : ref proc() int f : proc() ref proc() int to est' f - funkciya, vozvrashchayushchaya ukazatel' na funkciyu, vozvrashchayushchuyu celoe. int (*f[3])(); oznachaet (*f[])() : int *f[] : proc() int f[] : ref proc() int f : array of ref proc() int f - massiv ukazatelej na funkcii, vozvrashchayushchie celye. Obratno: opishem g kak ukaza- tel' na funkciyu, vozvrashchayushchuyu ukazatel' na massiv iz 5i ukazatelej na funkcii, vozv- rashchayushchie ukazateli na celye. A. Bogatyrev, 1992-95 - 91 - Si v UNIX g : ref p() ref array of ref p() ref int *g : p() ref array of ref p() ref int (*g)() : ref array of ref p() ref int *(*g)() : array of ref p() ref int (*(*g)())[5] : ref p() ref int *(*(*g)())[5] : p() ref int (*(*(*g)())[5])(): ref int *(*(*(*g)())[5])(): int int *(*(*(*g)())[5])(); V Si nevozmozhny funkcii, vozvrashchayushchie massiv: proc() array of ... a tol'ko proc() ref array of ... Samo nazvanie tipa (naprimer, dlya ispol'zovaniya v operacii privedeniya tipa) polucha- etsya vycherkivaniem imeni peremennoj (a takzhe mozhno opustit' razmer massiva): g = ( int *(*(*(*)())[])() ) 0; 2.19. Napishite funkciyu strcat(d,s), pripisyvayushchuyu stroku s k koncu stroki d. Otvet: char *strcat(d,s) register char *d, *s; { while( *d ) d++; /* ishchem konec stroki d */ while( *d++ = *s++ ); /* strcpy(d, s) */ return (d-1); /* konec stroki */ } Cikl, pomechennyj "strcpy" - eto naibolee kratkaya zapis' operatorov do{ char c; c = (*d = *s); s++; d++; } while(c != '\0'); Na samom dele strcat dolzhen po standartu vozvrashchat' svoj pervyj argument, kak i funk- ciya strcpy: char *strcat(d,s) register char *d, *s; { char *p = d; while( *d ) d++; strcpy(d, s); return p; } |ti dva varianta demonstriruyut, chto funkciya mozhet byt' realizovana raznymi sposobami. Krome togo vidno, chto vmesto standartnoj bibliotechnoj funkcii my mozhem opredelit' svoyu odnoimennuyu funkciyu, neskol'ko otlichayushchuyusya povedeniem ot standartnoj (kak vozv- rashchaemoe znachenie v 1-om variante). 2.20. Napishite programmu, kotoraya ob容dinyaet i raspechatyvaet dve stroki, vvedennye s terminala. Dlya vvoda strok ispol'zujte funkciyu gets(), a dlya ih ob容dineniya - strcat(). V drugom variante ispol'zujte sprintf(result,"%s%s",s1,s2); 2.21. Modificirujte predydushchuyu programmu takim obrazom, chtoby ona vydavala dlinu (chislo simvolov) ob容dinennoj stroki. Ispol'zujte funkciyu strlen(). Privedem nes- kol'ko versij realizacii strlen: /* Pri pomoshchi indeksacii massiva */ A. Bogatyrev, 1992-95 - 92 - Si v UNIX int strlen(s) char s[]; { int length = 0; for(; s[length] != '\0'; length++); return (length); } /* Pri pomoshchi prodvizheniya ukazatelya */ int strlen(s) char *s; { int length; for(length=0; *s; length++, s++); return length; } /* Pri pomoshchi raznosti ukazatelej */ int strlen(register char *s) { register char *p = s; while(*p) p++; /* ishchet konec stroki */ return (p - s); } Raznost' dvuh ukazatelej na odin i tot zhe tip - celoe chislo: esli TYPE *p1, *p2; to p2 - p1 = celoe chislo shtuk TYPE lezhashchih mezhdu p2 i p1 esli p2 = p1 + n to p2 - p1 = n |ta raznost' mozhet byt' i otricatel'noj esli p2 < p1, to est' p2 ukazyvaet na bolee levyj element massiva. 2.22. Napishite operator Si, kotoryj obrubaet stroku s do dliny n bukv. Otvet: if( strlen(s) > n ) s[n] = '\0'; Pervoe sravnenie voobshche govorya izlishne. Ono napisano lish' na tot sluchaj, esli stroka s koroche, chem n bukv i hranitsya v massive, kotoryj takzhe koroche n, t.e. ne imeet n- ogo elementa (poetomu v nego nel'zya proizvodit' zapis' priznaka konca). 2.23. Napishite funkcii preobrazovaniya stroki, soderzhashchej izobrazhenie celogo chisla, v samo eto chislo. V dvuh raznyh variantah argument-adres dolzhen ukazyvat' na pervyj bajt stroki; na poslednij bajt. Otvet: #define isdigit(c) ('0' <= (c) && (c) <= '9') int atoi(s) register char *s; { register int res=0, neg=0; for(;;s++){ switch(*s){ case ' ': case '\t': continue; case '-': neg++; case '+': s++; } break; } while(isdigit(*s)) res = res * 10 + *s++ - '0'; return( neg ? -res : res ); } int backatoi(s) register char *s; { int res=0, pow=1; while(isdigit(*s)){ A. Bogatyrev, 1992-95 - 93 - Si v UNIX res += (*s-- - '0') * pow; pow *= 10; } if(*s == '-') res = -res; return res; } 2.24. Mozhno li dlya zaneseniya v massiv s stroki "hello" napisat' char s[6]; s = "hello"; ili char s[6], d[] = "hello"; s = d; Otvet: net. Massivy v Si nel'zya prisvaivat' celikom. Dlya peresylki massiva bajt nado ispol'zovat' funkciyu strcpy(s,d). Zdes' zhe my pytaemsya izmenit' adres s (imya massiva - eto adres nachala pamyati, vydelennoj dlya hraneniya massiva), sdelav ego ravnym adresu bezymyannoj stroki "hello" (ili massiva d vo vtorom sluchae). |tot adres yavlyaetsya konstantoj i ne mozhet byt' izmenen! Zametim odnako, chto opisanie massiva s inicializaciej vpolne dopustimo: char s[6] = "hello"; ili char s[6] = { 'h', 'e', 'l', 'l', 'o', '\0' }; ili char s[] = "hello"; ili char s[] = { "hello" }; V etom sluchae kompilyator rezerviruet pamyat' dlya hraneniya massiva i raspisyvaet ee bajtami nachal'nogo znacheniya. Obratite vnimanie, chto stroka v dvojnyh kavychkah (esli ee rassmatrivat' kak massiv bukv) imeet dlinu na edinicu bol'she, chem napisano bukv v stroke, poskol'ku v konce massiva nahoditsya simvol '\0' - priznak konca, dobavlennyj kompilyatorom. Esli by my napisali char s[5] = "hello"; to kompilyator soobshchil by ob oshibke, poskol'ku dliny massiva (5) nedostatochno, chtoby razmestit' 6 bajt. V tret'ej stroke primera napisano s[], chtoby kompilyator sam pos- chital neobhodimuyu dlinu massiva. Nakonec, vozmozhna situaciya, kogda massiv bol'she, chem hranyashchayasya v nem stroka. Togda "lishnee" mesto soderzhit kakoj-to musor (v static-pamyati iznachal'no - bajty \0). char s[12] = "hello"; soderzhit: h e l l o \0 ? ? ? ? ? ? V programmah tekstovoj obrabotki pod "dlinoj stroki" obychno ponimayut kolichestvo bukv v stroke NE schitaya zakryvayushchij bajt '\0'. Imenno takuyu dlinu schitaet standartnaya funkciya strlen(s). Poetomu sleduet razlichat' takie ponyatiya kak "(tekushchaya) dlina stroki" i "dlina massiva, v kotorom hranitsya stroka": sizeof(s). Dlya napisannogo vyshe primera eti znacheniya ravny sootvetstvenno 5 i 12. Sleduet takzhe otlichat' massivy ot ukazatelej: char *sp = "bye bye"; sp = "hello"; budet vpolne zakonno, poskol'ku v dannom sluchae sp - ne imya massiva (t.e. konstanta, ravnaya adresu nachala massiva), a ukazatel' (peremennaya, hranyashchaya adres nekotoroj oblasti pamyati). Poskol'ku ukazatel' - eto peremennaya, to ee znachenie izmenyat' mozhno: v dannom sluchae sp snachala soderzhala adres bezymyannogo massiva, v kotorom nahoditsya "bye bye"; zatem my zanesli v sp adres bezymyannogo massiva, hranyashchego A. Bogatyrev, 1992-95 - 94 - Si v UNIX stroku "hello". Zdes' ne proishodit kopirovaniya massiva, a proishodit prosto prisva- ivanie peremennoj sp novogo znacheniya adresa. Predosterezhem ot vozmozhnoj nepriyatnosti: char d[5]; char s[] = "abcdefgh"; strcpy(d, s); Dliny massiva d prosto ne hvatit dlya hraneniya takoj dlinnoj stroki. Poskol'ku eto nichem ne kontroliruetsya (ni kompilyatorom, ni samoj strcpy, ni vami yavnym obrazom), to pri kopirovanii stroki "izbytochnye" bajty zapishutsya posle massiva d poverh drugih dannyh, kotorye budut isporcheny. |to privedet k nepredskazuemym effektam. Nekotorye vozmozhnosti dlya kontrolya za dlinoj strok-argumentov vam dayut funkcii strncpy(d,s,len); strncat(d,s,len); strncmp(s1,s2,len). Oni peresylayut (sravnivayut) ne bolee, chem len pervyh simvolov stroki s (strok s1, s2). Posmotrite v dokumenta- ciyu! Napishite funkciyu strncmp (sravnenie strok po pervym len simvolam), posmotrev na funkciyu strncpy: char *strncpy(dst, src, n) register char *dst, *src; register int n; { char *save; for(save=dst; --n >= 0; ) if( !(*dst++ = *src++)){ while(--n >= 0) *dst++ = '\0'; return save; } return save; } Otmet'te, chto strncpy obladaet odnim nepriyatnym svojstvom: esli n <= strlen(src), to stroka dst ne budet imet' na konce simvola '\0', to est' budet nahodit'sya v nekor- rektnom (ne kanonicheskom) sostoyanii. Otvet: int strncmp(register char *s1, register char *s2, register int n) { if(s1 == s2) return(0); while(--n >= 0 && *s1 == *s2++) if(*s1++ == '\0') return(0); return((n < 0)? 0: (*s1 - *--s2)); } 2.25. V chem oshibka? #include <stdio.h> /* dlya putchar */ char s[] = "We don't need no education"; main(){ while(*s) putchar(*s++); } Otvet: zdes' s - konstanta, k nej neprimenima operaciya ++. Nado napisat' char *s = "We don't need no education"; sdelav s ukazatelem na bezymyannyj macciv. Ukazatel' uzhe mozhno izmenyat'. 2.26. Kakie iz privedennyh konstrukcij oboznachayut odno i to zhe? A. Bogatyrev, 1992-95 - 95 - Si v UNIX char a[] = ""; /* pustaya stroka */ char b[] = "\0"; char c = '\0'; char z[] = "ab"; char aa[] = { '\0' }; char bb[] = { '\0', '\0' }; char xx[] = { 'a', 'b' }; char zz[] = { 'a', 'b', '\0' }; char *ptr = "ab"; 2.27. Najdite oshibki v opisanii simvol'noj stroki: main() { char mas[] = {'s', 'o', 'r', 't'}; /* "sort" ? */ printf("%s\n", mas); } Otvet: stroka dolzhna konchat'sya '\0' (v nashem sluchae printf ne obnaruzhiv simvola konca stroki budet vydavat' i bajty, nahodyashchiesya v pamyati posle massiva mas, t.e. musor); inicializirovannyj massiv ne mozhet byt' avtomaticheskim - trebuetsya static: main() { static char mas[] = {'s', 'o', 'r', 't', '\0'}; } Zametim, chto main(){ char *mas = "sort"; } zakonno, t.k. sama stroka zdes' hranitsya v staticheskoj pamyati, a inicializiruetsya lish' ukazatel' na etot massiv bajt. 2.28. V chem oshibka? Programma sobiraetsya iz dvuh fajlov: a.c i b.c komandoj cc a.c b.c -o ab a.c b.c --------------------------------------------------- int n = 2; extern int n; char s[] = "012345678"; extern char *s; main(){ f(){ f(); s[n] = '+'; printf("%s\n", s ); } } Otvet: delo v tom, chto tipy (char *) - ukazatel', i char[] - massiv, oznachayut odno i to zhe tol'ko pri ob座avlenii formal'nogo parametra funkcii: f(char *arg){...} f(char arg[]){...} eto budet lokal'naya peremennaya, soderzhashchaya ukazatel' na char (t.e. adres nekotorogo bajta v pamyati). Vnutri funkcii my mozhem izmenyat' etu peremennuyu, naprimer arg++. Dalee, i (char *) i char[] odinakovo ispol'zuyutsya, naprimer, oba eti tipa mozhno indeksirovat': arg[i]. No vne funkcij oni ob座avlyayut raznye ob容kty! Tak char *p; eto skalyarnaya peremennaya, hranyashchaya adres (ukazatel'): -------- ------- p:| *--|----->| '0' | char -------- | '1' | char ... A. Bogatyrev, 1992-95 - 96 - Si v UNIX togda kak char a[20]; eto adres nachala massiva (a vovse ne peremennaya): ------- a:| '0' | char | '1' | char ... V nashem primere v fajle b.c my ob座avili vneshnij massiv s kak peremennuyu. V rezul'- tate kompilyator budet interpretirovat' nachalo massiva s kak peremennuyu, soderzhashchuyu ukazatel' na char. ------- s:| '0' | \ eto budet vosprinyato kak | '1' | / adres drugih dannyh. | '2' | ... I indeksirovat'sya budet uzhe |TOT adres! Rezul'tat - obrashchenie po nesushchestvuyushchemu adresu. To, chto napisano u nas, ekvivalentno char s[] = "012345678"; char **ss = s; /* s - kak by "massiv ukazatelej" */ /* pervye bajty s interpretiruyutsya kak ukazatel': */ char *p = ss[0]; p[2] = '+'; My zhe dolzhny byli ob座avit' v b.c extern char s[]; /* razmer ukazyvat' ne trebuetsya */ Vot eshche odin analogichnyj primer, kotoryj poyasnit vam, chto proishodit (a zaodno poka- zhet poryadok bajtov v long). Primer vypolnyalsya na IBM PC 80386, na kotoroj sizeof(char *) = sizeof(long) = 4 a.c b.c --------------------------------------------------- char s[20] = {1,2,3,4}; extern char *s; main(){ f(){ /*pechat' ukazatelya kak long */ f(); printf( "%08lX\n", s ); } } pechataetsya 04030201. 2.29. CHto napechataet programma? static char str1[ ] = "abc"; static char str2[4]; strcpy( str2, str1 ); /* mozhno li napisat' str2 = str1; ? */ printf( str1 == str2 ? "ravno":"ne ravno" ); Kak nado pravil'no sravnivat' stroki? CHto na samom dele sravnivaetsya v dannom pri- mere? Otvet: sravnivayutsya adresa massivov, hranyashchih stroki. Tak A. Bogatyrev, 1992-95 - 97 - Si v UNIX char str1[2]; char str2[2]; main(){ printf( str1 < str2 ? "<":">"); } pechataet <, a esli napisat' char str2[2]; char str1[2]; to napechataetsya >. 2.30. Napishite programmu, sprashivayushchuyu vashe imya do teh por, poka vy ego pravil'no ne vvedete. Dlya sravneniya strok ispol'zujte funkciyu strcmp() (ee realizaciya est' v glave "Mobil'nost'"). 2.31. Kakie znacheniya vozvrashchaet funkciya strcmp() v sleduyushchej programme? #include <stdio.h> main() { printf("%d\n", strcmp("abc", "abc")); /* 0 */ printf("%d\n", strcmp("ab" , "abc")); /* -99 */ printf("%d\n", strcmp("abd", "abc")); /* 1 */ printf("%d\n", strcmp("abc", "abd")); /* -1 */ printf("%d\n", strcmp("abc", "abe")); /* -2 */ } 2.32. V kachestve itoga predydushchih zadach: pomnite, chto v Si stroki (a ne adresa) nado sravnivat' kak if( strcmp("abc", "bcd") < 0) ... ; if( strcmp("abc", "bcd") == 0) ... ; vmesto if( "abc" < "bcd" ) ... ; if( "abc" == "bcd" ) ... ; i prisvaivat' kak char d[80], s[80]; strcpy( d, s ); vmesto d = s; 2.33. Napishite programmu, kotoraya sortiruet po alfavitu i pechataet sleduyushchie klyuche- vye slova yazyka Si: int char double long for while if 2.34. Vopros ne sovsem pro stroki, skoree pro cikl: chem ploha konstrukciya? char s[] = "You're a smart boy, now shut up."; int i, len; for(i=0; i < strlen(s); i++) putchar(s[i]); Otvet: v sootvetstvii s semantikoj Si cikl razvernetsya primerno v A. Bogatyrev, 1992-95 - 98 - Si v UNIX i=0; LOOP: if( !(i < strlen(s))) goto ENDLOOP; putchar(s[i]); i++; goto LOOP; ENDLOOP: ; Zamet'te, chto hotya dlina stroki s ne menyaetsya, strlen(s) vychislyaetsya na KAZHDOJ itera- cii cikla, sovershaya lishnyuyu rabotu! Bor'ba s etim takova: for(i=0, len=strlen(s); i < len; i++ ) putchar(s[i]); ili for(i=0, len=strlen(s); len > 0; i++, --len ) putchar(s[i]); Analogichno, v cikle while( i < strlen(s))...; funkciya tozhe budet vychislyat'sya pri kazhdoj proverke usloviya! |to, konechno, otnositsya k lyuboj funkcii, ispol'zuemoj v uslovii, a ne tol'ko k strlen. (No, razumeetsya, sluchaj kogda funkciya vozvrashchaet priznak "nado li prodolzhat' cikl" - sovsem drugoe delo: takaya funkciya obyazana vychislyat'sya kazhdyj raz). 2.35. CHto napechataet sleduyushchaya programma? #include <stdio.h> main(){ static char str[] = "Do vstrechi v bufete"; char *pt; pt = str; puts(pt); puts(++pt); str[7] = '\0'; puts(str); puts(pt); puts(++pt); } 2.36. CHto napechataet sleduyushchaya programma? main() { static char name[] = "Konstantin"; char *pt; pt = name + strlen(name); while(--pt >= name) puts(pt); } 2.37. CHto napechataet sleduyushchaya programma? char str1[] = "abcdef"; char str2[] = "xyz"; main(){ register char *a, *b; a = str1; b = str2; while( *b ) *a++ = *b++; printf( "str=%s a=%s\n", str1, a ); a = str1; b = str2; A. Bogatyrev, 1992-95 - 99 - Si v UNIX while( *b ) *++a = *b++; printf( "str=%s a=%s\n", str1, a ); } Otvet: str=xyzdef a=def str=xxyzef a=zef 2.38. CHto pechataet programma? char *s; for(s = "Sitroen"; *s; s+= 2){ putchar(s[0]); if(!s[1]) break; } putchar('\n'); 2.39. CHto napechataet programma? Rassmotrite prodvizhenie ukazatelya s, ukazatelej - elementov massiva strs[]. Razberites' s poryadkom vypolneniya operacij. V kakih sluchayah ++ izmenyaet ukazatel', a v kakih - bukvu v stroke? Narisujte sebe kartinku, izobrazha- yushchuyu sostoyanie ukazatelej - ona pomozhet vam rasputat' eti spagetti. Udelite razboru etogo primera dostatochnoe vremya! #include <stdio.h> /* opredelenie NULL */ /* Latinskij alfavit: abcdefghijklmnopqrstuvwxyz */ char *strs[] = { "abcd","ABCD","0fpx","159", "hello","-gop","A1479",NULL }; main(){ char c, **s = strs, *p; c = *++*s; printf("#1 %d %c %s\n", s-strs, c, *s); c = **++s; printf("#2 %d %c %s\n", s-strs, c, *s); c = **s++; printf("#3 %d %c %s\n", s-strs, c, *s); c = ++**s; printf("#4 %d %c %s\n", s-strs, c, *s); c = (**s)++; printf("#5 %d %c %s\n", s-strs, c, *s); c = ++*++*s; printf("#6 %d %c %s\n", s-strs, c, *s); c = *++*s++; printf("#7 %d %c %s %s\n", s-strs, c, *s, strs[2]); c = ++*++*s++; printf("#8 %d %c %s %s\n", s-strs, c, *s, strs[3]); c = ++*++*++s; printf("#9 %d %c %s\n", s-strs,c,*s); c = ++**s++; printf("#10 %d %c %s\n",s-strs,c,*s); p = *s; c = ++*(*s)++; printf("#11 %d %c %s %s %s\n",s-strs,c,*s,strs[6],p); c = ++*((*s)++); printf("#12 %d %c %s %s\n", s-strs, c, *s, strs[6]); c = (*++(*s))++; printf("#13 %d %c %s %s\n", s-strs, c, *s, strs[6]); for(s=strs; *s; s++) printf("strs[%d]=\"%s\"\n", s-strs, *s); putchar('\n'); } Pechataetsya: A. Bogatyrev, 1992-95 - 100 - Si v UNIX #1 0 b bcd strs[0]="bcd" #2 1 A ABCD strs[1]="ABCD" #3 2 A 0fpx strs[2]="px" #4 2 1 1fpx strs[3]="69" #5 2 1 2fpx strs[4]="hello" #6 2 g gpx strs[5]="iop" #7 3 p 159 px strs[6]="89" #8 4 6 hello 69 #9 5 h hop #10 6 i A1479 #11 6 B 1479 1479 B1479 #12 6 2 479 479 #13 6 7 89 89 Uchtite, chto konstrukciya char *strs[1] = { "hello" }; oznachaet, chto v strs[0] soderzhitsya ukazatel' na nachal'nyj bajt bezymyannogo massiva, soderzhashchego stroku "hello". |tot ukazatel' mozhno izmenyat'! Poprobujte sostavit' eshche podobnye primery iz *, ++, (). 2.40. CHto pechataet programma? char str[25] = "Hi, "; char *f(char **s){ int cnt; for(cnt=0; **s != '\0'; (*s)++, ++cnt); return("ny" + (cnt && (*s)[-1] == ' ') + (!cnt)); } void main(void){ char *s = str; if( *f(&s) == 'y') strcat(s, "dude"); else strcat(s, " dude"); printf("%s\n", str); } CHto ona napechataet, esli zadat' char str[25]="Hi,"; ili char str[25]=""; 2.41. V chem sostoit oshibka? (Lyubimaya oshibka nachinayushchih) main(){ char *buf; /* ili char buf[]; */ gets( buf ); printf( "%s\n", buf ); } Otvet: pamyat' pod stroku buf ne vydelena, ukazatel' buf ne proinicializirovan i smot- rit neizvestno kuda. Nado bylo pisat' naprimer tak: char buf[80]; ili char mem[80], *buf = mem; Obratite na etot primer osoboe vnimanie, poskol'ku, opisav ukazatel' (no nikuda ego ne napraviv), novichki uspokaivayutsya, ne zabotyas' o vydelenii pamyati dlya hraneniya dan- nyh. Ukazatel' dolzhen ukazyvat' na CHTO-TO, v chem mozhno hranit' dannye, a ne "viset'", ukazyvaya "pal'cem v nebo"! Zapis' informacii po "visyachemu" ukazatelyu razrushaet pamyat' programmy i privodit k skoromu (no chasto ne nemedlennomu i potomu tainstvennomu) krahu. A. Bogatyrev, 1992-95 - 101 - Si v UNIX Vot programma, kotoraya takzhe ispol'zuet neinicializirovannyj ukazatel'. Na mashine SPARCstation 20 eta programma ubivaetsya operacionnoj sistemoj s diagnostikoj "Segmentation fault" (SIGSEGV). |to kak raz i znachit obrashchenie po ukazatelyu, ukazy- vayushchemu "pal'cem v nebo". main(){ int *iptr; int ival = *iptr; printf("%d\n", ival); } 2.42. Dlya polucheniya stroki "Life is life" napisana programma: main(){ char buf[ 60 ]; strcat( buf, "Life " ); strcat( buf, "is " ); strcat( buf, "life" ); printf( "%s\n", buf ); } CHto okazhetsya v massive buf? Otvet: v nachale massiva okazhetsya musor, poskol'ku avtomaticheskij massiv ne iniciali- ziruetsya bajtami '\0', a funkciya strcat() pripisyvaet stroki k koncu stroki. Dlya isp- ravleniya mozhno napisat' *buf = '\0'; pered pervym strcat()-om, libo vmesto pervogo strcat()-a napisat' strcpy( buf, "Life " ); 2.43. Sostav'te makroopredelenie copystr(s1, s2) dlya kopirovaniya stroki s2 v stroku s1. 2.44. Sostav'te makroopredelenie lenstr(s) dlya vychisleniya dliny stroki. Mnogie sovremennye kompilyatory sami obrashchayutsya s podobnymi korotkimi (1-3 opera- tora) standartnymi funkciyami kak s makrosami, to est' pri obrashchenii k nim generyat ne vyzov funkcii, a podstavlyayut tekst ee tela v mesto obrashcheniya. |to delaet ob容ktnyj kod neskol'ko "tolshche", no zato bystree. V rasshirennyh dialektah Si i v Si++ kompilya- toru mozhno predlozhit' obrashchat'sya tak i s vashej funkciej - dlya etogo funkciyu sleduet ob座avit' kak inline (takie funkcii nazyvayutsya eshche "intrinsic"). 2.45. Sostav'te rekursivnuyu i nerekursivnuyu versii programmy invertirovaniya (zer- kal'nogo otobrazheniya) stroki: abcdef --> fedcba. 2.46. Sostav'te funkciyu index(s, t), vozvrashchayushchuyu nomer pervogo vhozhdeniya simvola t v stroku s; esli simvol t v stroku ne vhodit, funkciya vozvrashchaet -1. Perepishite etu funkciyu s ukazatelyami, chtoby ona vozvrashchala ukazatel' na pervoe vhozhdenie simvola. Esli simvol v stroke otsutstvuet - vydavat' NULL. V UNIX System-V takaya funkciya nazyvaetsya strchr. Vot vozmozhnyj otvet: char *strchr(s, c) register char *s, c; { while(*s && *s != c) s++; return *s == c ? s : NULL; } A. Bogatyrev, 1992-95 - 102 - Si v UNIX Zamet'te, chto p=strchr(s,'\0'); vydaet ukazatel' na konec stroki. Vot primer ispol'- zovaniya: extern char *strchr(); char *s = "abcd/efgh/ijklm"; char *p = strchr(s, '/'); printf("%s\n", p==NULL ? "bukvy / net" : p); if(p) printf("Indeks vhozhdeniya = s[%d]\n", p - s ); 2.47. Napishite funkciyu strrchr(), ukazyvayushchuyu na poslednee vhozhdenie simvola. Otvet: char *strrchr(s, c) register char *s, c; { char *last = NULL; do if(*s == c) last = s; while(*s++); return last; } Vot primer ee ispol'zovaniya: extern char *strrchr(); char p[] = "wsh"; /* etalon */ main(argc, argv) char *argv[];{ char *s = argv[1]; /* proveryaemoe imya */ /* poprobujte vyzyvat' * a.out csh * a.out /bin/csh * a.out wsh * a.out /usr/local/bin/wsh */ char *base = (base = strrchr(s, '/')) ? base+1 : s; if( !strcmp(p, base)) printf("Da, eto %s\n" , p); else printf("Net, eto %s\n", base); /* eshche bolee izoshchrennyj variant: */ if( !strcmp(p,(base=strrchr(s,'/')) ? ++base : (base=s)) ) printf("Yes %s\n", p); else printf("No %s\n", base); } 2.48. Napishite makros substr(to,from,n,len) kotoryj zapisyvaet v to kusok stroki from nachinaya s n-oj pozicii i dlinoj len. Ispol'zujte standartnuyu funkciyu strncpy. Otvet: #define substr(to, from, n, len) strncpy(to, from+n, len) ili bolee korrektnaya funkciya: A. Bogatyrev, 1992-95 - 103 - Si v UNIX char *substr(to, from, n, len) char *to, *from; { int lfrom = strlen(from); if(n < 0 ){ len += n; n = 0; } if(n >= lfrom || len <= 0) *to = '\0'; /* pustaya stroka */ else{ /* dlina ostatka stroki: */ if(len > lfrom-n) len = lfrom - n; strncpy(to, from+n, len); to[len] = '\0'; } return to; } 2.49. Napishite funkciyu, proveryayushchuyu, okanchivaetsya li stroka na ".abc", i esli net - pripisyvayushchuyu ".abc" k koncu. Esli zhe stroka uzhe imeet takoe okonchanie - nichego ne delat'. |ta funkciya polezna dlya generacii imen fajlov s zadannym rasshireniem. Sde- lajte rasshirenie argumentom funkcii. Dlya sravneniya konca stroki s so strokoj p sleduet ispol'zovat': int ls = strlen(s), lp = strlen(p); if(ls >= lp && !strcmp(s+ls-lp, p)) ...sovpali...; 2.50. Napishite funkcii vstavki simvola c v ukazannuyu poziciyu stroki (s razdvizhkoj stroki) i udaleniya simvola v zadannoj pozicii (so sdvizhkoj stroki). Stroka dolzhna izmenyat'sya "na meste", t.e. nikuda ne kopiruyas'. Otvet: /* udalenie */ char delete(s, at) register char *s; { char c; s += at; if((c = *s) == '\0') return c; while( s[0] = s[1] ) s++; return c; } /* libo prosto strcpy(s+at, s+at+1); */ /* vstavka */ insert(s, at, c) char s[], c; { register char *p; s += at; p = s; while(*p) p++; /* na konec stroki */ p[1] = '\0'; /* zakryt' stroku */ for( ; p != s; p-- ) p[0] = p[-1]; *s = c; } 2.51. Sostav'te programmu udaleniya simvola c iz stroki s v kazhdom sluchae, kogda on vstrechaetsya. Otvet: A. Bogatyrev, 1992-95 - 104 - Si v UNIX delc(s, c) register char *s; char c; { register char *p = s; while( *s ) if( *s != c ) *p++ = *s++; else s++; *p = '\0'; /* ne zabyvajte zakryvat' stroku ! */ } 2.52. Sostav'te programmu udaleniya iz stroki S1 kazhdogo simvola, sovpadayushchego s kakim-libo simvolom stroki S2. 2.53. Sostav'te funkciyu scopy(s,t), kotoraya kopiruet stroku s v t, pri etom simvoly tabulyacii i perevoda stroki dolzhny zamenyat'sya na special'nye dvuhsimvol'nye posledo- vatel'nosti "\n" i "\t". Ispol'zujte switch. 2.54. Sostav'te funkciyu, kotoraya "ukorachivaet" stroku, zamenyaya izobrazheniya specsim- volov (vrode "\n") na sami eti simvoly ('\n'). Otvet: extern char *strchr(); void unquote(s) char *s; { static char from[] = "nrtfbae", to [] = "\n\r\t\f\b\7\33"; char c, *p, *d; for(d=s; c = *s; s++) if( c == '\\'){ if( !(c = *++s)) break; p = strchr(from, c); *d++ = p ? to[p - from] : c; }else *d++ = c; *d = '\0'; } 2.55. Napishite programmu, zamenyayushchuyu v stroke S vse vhozhdeniya podstroki P na stroku Q, naprimer: P = "ura"; Q = "oj"; S = "ura-ura-ura!"; Rezul'tat: "oj-oj-oj!" 2.56. Krome funkcij raboty so strokami (gde predpolagaetsya, chto massiv bajt zaversha- etsya priznakom konca '\0'), v Si predusmotreny takzhe funkcii dlya raboty s massivami bajt bez ogranichitelya. Dlya takih funkcij neobhodimo yavno ukazyvat' dlinu obrabatyvae- mogo massiva. Napishite funkcii: peresylki massiva dlinoj n bajt memcpy(dst,src,n); zapolneniya massiva simvolom c memset(s,c,n); poiska vhozhdeniya simvola v massiv memchr(s,c,n); sravneniya dvuh massivov memcmp(s1,s2,n); Otvet: #define REG register char *memset(s, c, n) REG char *s, c; { REG char *p = s; while( --n >= 0 ) *p++ = c; return s; } char *memcpy(dst, src, n) REG char *dst, *src; REG int n; { REG char *d = dst; A. Bogatyrev, 1992-95 - 105 - Si v UNIX while( n-- > 0 ) *d++ = *src++; return dst; } char *memchr(s, c, n) REG char *s, c; { while(n-- && *s++ != c); return( n < 0 ? NULL : s-1 ); } int memcmp(s1, s2, n) REG char *s1, *s2; REG n; { while(n-- > 0 && *s1 == *s2) s1++, s2++; return( n < 0 ? 0 : *s1 - *s2 ); } Est' takie standartnye funkcii. 2.57. Pochemu luchshe pol'zovat'sya standartnymi funkciyami raboty so strokami i pamyat'yu (strcpy, strlen, strchr, memcpy, ...)? Otvet: potomu, chto oni obychno realizovany postavshchikami sistemy |FFEKTIVNO, to est' napisany ne na Si, a na assemblere s ispol'zovaniem specializirovannyh mashinnyh komand i registrov. |to delaet ih bolee bystrymi. Napisannyj Vami ekvivalent na Si mozhet ispol'zovat'sya dlya povysheniya mobil'nosti programmy, libo dlya vneseniya popravok v standartnye funkcii. 2.58. Rassmotrim programmu, kopiruyushchuyu stroku samu v sebya: #include <stdio.h> #include <string.h> char string[] = "abcdefghijklmn"; void main(void){ memcpy(string+2, string, 5); printf("%s\n", string); exit(0); Ona pechataet abababahijklmn. My mogli by ozhidat', chto kusok dliny 5 simvolov "abcde" budet skopirovan kak est': ab[abcde]hijklmn, a poluchili ab[ababa]hijklmn - cikliches- koe povtorenie pervyh dvuh simvolov stroki... V chem delo? Delo v tom, chto kogda oblasti istochnika (src) i poluchatelya (dst) perekryvayutsya, to v nekij moment *src beretsya iz UZHE perezapisannoj ranee oblasti, to est' isporchennoj! Vot programma, illyustriruyushchaya etu problemu: A. Bogatyrev, 1992-95 - 106 - Si v UNIX #include <stdio.h> #include <string.h> #include <ctype.h> char string[] = "abcdefghijklmn"; char *src = &string[0]; char *dst = &string[2]; int n = 5; void show(int niter, char *msg){ register length, i; printf("#%02d %s\n", niter, msg); length = src-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('S'); putchar('\n'); printf("\t...%s...\n", string); length = dst-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('D'); putchar('\n'); } void main(void){ int iter = 0; while(n-- > 0){ show(iter, "pered"); *dst++ = toupper(*src++); show(iter++, "posle"); } exit(0); } Ona pechataet: A. Bogatyrev, 1992-95 - 107 - Si v UNIX #00 pered S ...abcdefghijklmn... D #00 posle S ...abAdefghijklmn... D #01 pered S ...abAdefghijklmn... D #01 posle S ...abABefghijklmn... D #02 pered S ...abABefghijklmn... D #02 posle S ...abABAfghijklmn... D #03 pered S ...abABAfghijklmn... D #03 posle S ...abABABghijklmn... D #04 pered S ...abABABghijklmn... D #04 posle S ...abABABAhijklmn... D Otrezki NE perekryvayutsya, esli odin iz nih lezhit libo celikom levee, libo celikom pravee drugogo (n - dlina oboih otrezkov). dst src src dst ######## @@@@@@@@ @@@@@@@@ ######## dst+n <= src ili src+n <= dst dst <= src-n ili dst >= src+n Otrezki perekryvayutsya v sluchae ! (dst <= src - n || dst >= src + n) = (dst > src - n && dst < src + n) Pri etom opasen tol'ko sluchaj dst > src. Takim obrazom opasnaya situaciya opisyvaetsya usloviem src < dst && dst < src + n (esli dst==src, to voobshche nichego ne nado delat'). Resheniem yavlyaetsya kopirovanie "ot A. Bogatyrev, 1992-95 - 108 - Si v UNIX hvosta k golove": void bcopy(register char *src, register char *dst, register int n){ if(dst >= src){ dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else{ while(n-- > 0) *dst++ = *src++; } } Ili, ogranichivayas' tol'ko opasnym sluchaem: void bcopy(register char *src, register char *dst, register int n){ if(dst==src || n <= 0) return; if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else memcpy(dst, src, n); } Programma #include <stdio.h> #include <string.h> #include <ctype.h> char string[] = "abcdefghijklmn"; char *src = &string[0]; char *dst = &string[2]; int n = 5; void show(int niter, char *msg){ register length, i; printf("#%02d %s\n", niter, msg); length = src-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('S'); putchar('\n'); printf("\t...%s...\n", string); length = dst-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('D'); putchar('\n'); } A. Bogatyrev, 1992-95 - 109 - Si v UNIX void main(void){ int iter = 0; if(dst==src || n <= 0){ printf("Nichego ne nado delat'\n"); return; } if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0){ show(iter, "pered"); *dst-- = toupper(*src--); show(iter++, "posle"); } }else while(n-- > 0){ show(iter, "pered"); *dst++ = toupper(*src++); show(iter++, "posle"); } exit(0); } Pechataet A. Bogatyrev, 1992-95 - 110 - Si v UNIX #00 pered S ...abcd