of(); double x = atof(str1);
extern long atol(); long y = atol(str2);
extern int atoi(); int i = atoi(str3);
libo
sscanf(str1, "%f", &x);
sscanf(str2, "%ld", &y); sscanf(str3,"%d", &i);
K slovu zametim, chto obratnoe preobrazovanie - chisla v tekst - udobnee vsego delaetsya
pri pomoshchi funkcii sprintf(), kotoraya analogichna printf(), no sformirovannaya eyu
stroka-soobshchenie ne vydaetsya na ekran, a zanositsya v massiv:
A. Bogatyrev, 1992-95 - 58 - Si v UNIX
char represent[ 40 ];
int i = ... ;
sprintf( represent, "%d", i );
1.117. Sostav'te programmu vychisleniya polinoma n-oj stepeni:
n n-1
Y = A * X + A * X + ... + A0
n n-1
shema (Gornera):
Y = A0 + X * ( A1 + X * ( A2 + ... + X * An )))...)
Oformite algoritm kak funkciyu s peremennym chislom parametrov:
poly( x, n, an, an-1, ... a0 );
O tom, kak eto sdelat' - chitajte razdel rukovodstva po UNIX man varargs. Otvet:
#include <varargs.h>
double poly(x, n, va_alist)
double x; int n; va_dcl
{
va_list args;
double sum = 0.0;
va_start(args); /* inicializirovat' spisok arg-tov */
while( n-- >= 0 ){
sum *= x;
sum += va_arg(args, double);
/* izvlech' sled. argument tipa double */
}
va_end(args); /* unichtozhit' spisok argumentov */
return sum;
}
main(){
/* y = 12*x*x + 3*x + 7 */
printf( "%g\n", poly(2.0, 2, 12.0, 3.0, 7.0));
}
Prototip etoj funkcii:
double poly(double x, int n, ... );
V etom primere ispol'zovany makrosy va_nechto. CHast' argumentov, kotoraya yavlyaetsya
spiskom peremennoj dliny, oboznachaetsya v spiske parametrov kak va_alist, pri etom ona
ob®yavlyaetsya kak va_dcl v spiske tipov parametrov. Zamet'te, chto tochka-s-zapyatoj posle
va_dcl ne nuzhna! Opisanie va_list args; ob®yavlyaet special'nuyu "svyaznuyu" peremennuyu;
smysl ee mashinno zavisim. va_start(args) inicializiruet etu peremennuyu spiskom fak-
ticheskih argumentov, sootvetstvuyushchih va_alist-u. va_end(args) deinicializiruet etu
peremennuyu (eto nado delat' obyazatel'no, poskol'ku inicializaciya mogla byt' svyazana s
konstruirovaniem spiska argumentov pri pomoshchi vydeleniya dinamicheskoj pamyati; teper'
my dolzhny unichtozhit' etot spisok i osvobodit' pamyat'). Ocherednoj argument tipa TYPE
izvlekaetsya iz spiska pri pomoshchi
TYPE x = va_arg(args, TYPE);
Spisok argumentov prosmatrivaetsya sleva napravo v odnom napravlenii, vozvrat k
A. Bogatyrev, 1992-95 - 59 - Si v UNIX
predydushchemu argumentu nevozmozhen.
Nel'zya ukazyvat' v kachestve tipov char, short, float:
char ch = va_arg(args, char);
poskol'ku v yazyke Si argumenty funkcii takih tipov avtomaticheski rasshiryayutsya v int,
int, double sootvetstvenno. Korrektno budet tak:
int ch = va_arg(args, int);
1.118. Eshche ob odnoj lovushke v yazyke Si na PDP-11 (i v kompilyatorah byvayut oshibki!):
unsigned x = 2;
printf( "%ld %ld",
- (long) x,
(long) -x
);
|tot fragment napechataet chisla -2 i 65534. Vo vtorom sluchae pri privedenii k tipu
long byl rasshiren znakovyj bit. Vstroennaya operaciya sizeof vydaet znachenie tipa
unsigned. Podumajte, kakov budet effekt v sleduyushchem fragmente programmy?
static struct point{ int x, y ;}
p = { 33, 13 };
FILE *fp = fopen( "00", "w" );
/* vpered na dlinu odnoj struktury */
fseek( fp, (long) sizeof( struct point ), 0 );
/* nazad na dlinu odnoj struktury */
/*!*/ fseek( fp, (long) -sizeof( struct point ), 1 );
/* zapisyvaem v nachalo fajla odnu strukturu */
fwrite( &p, sizeof p, 1, fp );
/* zakryvaem fajl */
fclose( fp );
Gde dolzhen nahodit'sya minus vo vtorom vyzove fseek dlya polucheniya ozhidaemogo rezul'-
tata? (Dannyj primer mozhet vesti sebya po-raznomu na raznyh mashinah, voprosy kasayutsya
PDP-11).
1.119. Obratimsya k ukazatelyam na funkcii:
void g(x){ printf("%d: here\n", x); }
main(){
void (*f)() = g; /* Ukazatel' smotrit na funkciyu g() */
(*f)(1); /* Staraya forma vyzova funkcii po ukazatelyu */
f (2); /* Novaya forma vyzova */
/* V oboih sluchayah vyzyvaetsya g(x); */
}
CHto pechataet programma?
typedef void (*(*FUN))(); /* Popytka izobrazit'
rekursivnyj tip typedef FUN (*FUN)(); */
FUN g(FUN f){ return f; }
void main(){
FUN y = g(g(g(g(g))));
if(y == g) printf("OK\n");
A. Bogatyrev, 1992-95 - 60 - Si v UNIX
}
CHto pechataet programma?
char *f(){
return "Hello, user!";
}
g(func)
char * (*func)();
{
puts((*func)());
}
main(){
g(f);
}
Pochemu bylo by neverno napisat'
main(){
g(f());
}
Eshche analogichnaya oshibka (posmotrite pro funkciyu signal v glave "Vzaimodejstvie s
UNIX"):
#include <signal.h>
f(){ printf( "Good bye.\n" ); exit(0); }
main(){
signal ( SIGINT, f() );
...
}
Zapomnite, chto f() - eto ZNACHENIE funkcii f (t.e. ona vyzyvaetsya i nechto vozvrashchaet
return-om; eto-to znachenie my i ispol'zuem), a f - eto ADRES funkcii f (ran'she eto
tak i pisalos' &f), to est' metka nachala ee mashinnyh kodov ("tochka vhoda").
1.120. CHto napechataet programma? (Primer posvyashchen ukazatelyam na funkcii i massivam
funkcij):
int f(n){ return n*2; }
int g(n){ return n+4; }
int h(n){ return n-1; }
int (*arr[3])() = { f, g, h };
main(){
int i;
for(i=0; i < 3; i++ )
printf( "%d\n", (*arr[i])(i+7) );
}
1.121. CHto napechataet programma?
extern double sin(), cos();
main(){ double x; /* cc -lm */
for(x=0.0; x < 1.0; x += 0.2)
printf("%6.4g %6.4g %6.4g\n",
(x > 0.5 ? sin : cos)(x), sin(x), cos(x));
}
to zhe v variante
A. Bogatyrev, 1992-95 - 61 - Si v UNIX
extern double sin(), cos();
main(){ double x; double (*f)();
for(x=0.0; x < 1.0; x += 0.2){
f = (x > 0.5 ? sin : cos);
printf("%g\n", (*f)(x));
}
}
1.122. Rassmotrite chetyre realizacii funkcii faktorial:
n! = 1 * 2 * ... * n
ili n! = n * (n-1)! gde 0! = 1
Vse oni illyustriruyut opredelennye podhody v programmirovanii:
/* CIKL (ITERACIYA) */
int factorial1(n){ int res = 1;
while(n > 0){ res *= n--; }
return res;
}
/* PROSTAYA REKURSIYA */
int factorial2(n){
return (n==0 ? 1 : n * factorial2(n-1));
}
/* Rekursiya, v kotoroj funkciya vyzyvaetsya rekursivno
* edinstvennyj raz - v operatore return, nazyvaetsya
* "hvostovoj rekursiej" (tail recursion) i
* legko preobrazuetsya v cikl */
/* AVTOAPPLIKACIYA */
int fi(f, n) int (*f)(), n;
{ if(n == 0) return 1;
else return n * (*f)(f, n-1);
}
int factorial3(n){ return fi(fi, n); }
/* REKURSIYA S NELOKALXNYM PEREHODOM */
#include <setjmp.h>
jmp_buf checkpoint;
void fact(n, res) register int n, res;
{ if(n) fact(n - 1, res * n);
else longjmp(checkpoint, res+1);
}
int factorial4(n){ int res;
if(res = setjmp(checkpoint)) return (res - 1);
else fact(n, 1);
}
1.123. Napishite funkciyu, pechatayushchuyu celoe chislo v sisteme schisleniya s osnovaniem
base. Otvet:
A. Bogatyrev, 1992-95 - 62 - Si v UNIX
printi( n, base ){
register int i;
if( n < 0 ){ putchar( '-' ); n = -n; }
if( i = n / base )
printi( i, base );
i = n % base ;
putchar( i >= 10 ? 'A' + i - 10 : '0' + i );
}
Poprobujte napisat' nerekursivnyj variant s nakopleniem otveta v stroke. Prive-
dem rekursivnyj variant, nakaplivayushchij otvet v stroke s i pol'zuyushchijsya analogom funk-
cii printi: funkciya prints - takaya zhe, kak printi, no vmesto vyzovov putchar(nechto);
v nej napisany operatory
*res++ = nechto;
i rekursivno vyzyvaetsya konechno zhe prints. Itak:
static char *res;
... tekst funkcii prints ...
char *itos( n, base, s )
char *s; /* ukazyvaet na char[] massiv dlya otveta */
{
res = s; prints(n, base); *res = '\0';
return s;
}
main(){ char buf[20]; printf( "%s\n", itos(19,2,buf); }
1.124. Napishite funkciyu dlya pobitnoj raspechatki celogo chisla. Imejte v vidu, chto
chislo soderzhit 8 * sizeof(int) bit. Ukazanie: ispol'zujte operacii bitovogo sdviga i
&. Otvet:
printb(n){
register i;
for(i = 8 * sizeof(int) - 1; i >= 0; --i)
putchar(n & (1 << i) ? '1':'0');
}
1.125. Napishite funkciyu, sklonyayushchuyu sushchestvitel'nye russkogo yazyka v zavisimosti ot
ih chisla. Naprimer:
printf( "%d kirpich%s", n, grammar( n, "ej", "", "a" ));
Otvet:
char *grammar( i, s1, s2, s3 )
char *s1, /* prochee */
*s2, /* odin */
*s3; /* dva, tri, chetyre */
{
i = i % 100;
if( i > 10 && i <= 20 ) return s1;
i = i % 10;
if( i == 1 ) return s2;
if( i == 2 || i == 3 || i == 4 )
return s3;
return s1;
}
A. Bogatyrev, 1992-95 - 63 - Si v UNIX
1.126. Napishite operator printf, pechatayushchij chisla iz intervala 0..99 s dobavleniem
nulya pered chislom, esli ono men'she 10 :
00 01 ... 09 10 11 ...
Ispol'zujte uslovnoe vyrazhenie, format.
Otvet:
printf ("%s%d", n < 10 ? "0" : "", n);
libo
printf ("%02d", n );
libo
printf ("%c%c", '0' + n/10, '0' + n%10 );
1.127. Predosterezhem ot odnoj oshibki, chasto dopuskaemoj nachinayushchimi.
putchar( "c" ); yavlyaetsya oshibkoj.
putchar( 'c' ); verno.
Delo v tom, chto putchar trebuet argument - simvol, togda kak "c" - STROKA iz odnogo
simvola. Bol'shinstvo kompilyatorov (te, kotorye ne proveryayut prototipy vyzova stan-
dartnyh funkcij) NE obnaruzhit zdes' nikakoj sintaksicheskoj oshibki (kstati, oshibka eta
- semanticheskaya).
Takzhe oshibochny operatory
printf ( '\n' ); /* nuzhna stroka */
putchar( "\n" ); /* nuzhen simvol */
putchar( "ab" ); /* nuzhen simvol */
putchar( 'ab' ); /* oshibka v bukvennoj konstante */
char c; if((c = getchar()) == "q" ) ... ;
/* nuzhno pisat' 'q' */
Otlichajte stroku iz odnogo simvola i simvol - eto raznye veshchi! (Podrobnee ob etom -
v sleduyushchej glave).
1.128. Ves'ma chastoj yavlyaetsya oshibka "promah na edinicu", kotoraya vstrechaetsya v
ochen' mnogih i raznoobraznyh sluchayah. Vot odna iz vozmozhnyh situacij:
int m[20]; int i = 0;
while( scanf( "%d", & m[i++] ) != EOF );
printf( "Vveli %d chisel\n", i );
V itoge i okazhetsya na 1 bol'she, chem ozhidalos'. Razberites' v chem delo.
Otvet: argumenty funkcii vychislyayutsya do ee vyzova, poetomu kogda my dostigaem
konca fajla i scanf vozvrashchaet EOF, i++ v vyzove scanf vse ravno delaetsya. Nado napi-
sat'
while( scanf( "%d", & m[i] ) != EOF ) i++;
1.129. Zamechanie po stilistike: pri vyvode soobshcheniya na ekran
printf( "Hello \n" );
probely pered \n dostatochno bessmyslenny, poskol'ku na ekrane nikak ne otobrazyatsya.
Nado pisat' (ekonomya pamyat')
printf( "Hello\n" );
A. Bogatyrev, 1992-95 - 64 - Si v UNIX
Edinstvennyj sluchaj, kogda takie probely znachimy - eto kogda vy vyvodite tekst inver-
siej. Togda probely otobrazhayutsya kak svetlyj fon.
Eshche nepriyatnee budet
printf( "Hello\n " );
poskol'ku koncevye probely okazhutsya v nachale sleduyushchej stroki.
1.130. printf - interpretiruyushchaya funkciya, t.e. rabotaet ona dovol'no medlenno. Poe-
tomu vmesto
char s[20]; int i;
...
printf( "%c", s[i] ); i printf( "\n" );
nado vsegda pisat'
putchar( s[i] ); i putchar( '\n' );
poskol'ku printf v konce-koncov (sdelav vse preobrazovaniya po formatu) vnutri sebya
vyzyvaet putchar. Tak sdelaem zhe eto srazu!
1.131. To, chto parametr "format" v funkcii printf mozhet byt' vyrazheniem, pozvolyaet
delat' nekotorye udobnye veshchi. Naprimer:
int x; ...
printf( x ? "znachenie x=%d\n" : "x raven nulyu\n\n", x);
Format zdes' - uslovnoe vyrazhenie. Esli x!=0, to budet napechatano znachenie x po for-
matu %d. Esli zhe x==0, to budet napechatana stroka, ne soderzhashchaya ni odnogo %-ta. V
rezul'tate argument x v spiske argumentov budet prosto proignorirovan. Odnako, nap-
rimer
int x = ... ;
printf( x > 30000 ? "%f\n" : "%d\n", x);
(chtoby bol'shie x pechatalis' v vide 31000.000000) nezakonno, poskol'ku celoe chislo
nel'zya pechatat' po formatu %f ni v kakih sluchayah. Edinstvennym sposobom sdelat' eto
yavlyaetsya yavnoe privedenie x k tipu double:
printf("%f\n", (double) x);
Budet li zakonen operator?
printf( x > 30000 ? "%f\n" : "%d\n",
x > 30000 ? (double) x : x );
Otvet: net. Uslovnoe vyrazhenie dlya argumenta budet imet' "starshij" tip - double. A
znachenie tipa double nel'zya pechatat' po formatu %d. My dolzhny ispol'zovat' zdes'
operator if:
if( x > 30000 ) printf("%f\n", (double)x);
else printf("%d\n", x);
1.132. Napishite funkciyu, pechatayushchuyu razmer fajla v udobnom vide: esli fajl men'she
odnogo kilobajta - pechatat' ego razmer v bajtah, esli zhe bol'she - v kilobajtah (i
megabajtah).
#define KBYTE 1024L /* kilobajt */
#define THOUSAND 1024L /* kb. v megabajte */
A. Bogatyrev, 1992-95 - 65 - Si v UNIX
void tellsize(unsigned long sz){
if(sz < KBYTE) printf("%lu bajt", sz);
else{
unsigned long Kb = sz/KBYTE;
unsigned long Mb = Kb/THOUSAND;
unsigned long Dec = ((sz % KBYTE) * 10) / KBYTE;
if( Mb ){
Kb %= THOUSAND;
printf( Dec ? "%lu.%03lu.%01lu Mb." : "%lu.%lu Mb.",
Mb, Kb, Dec );
} else
printf( Dec ? "%lu.%01lu Kb.":"%lu Kb.", Kb, Dec);
}
putchar('\n');
}
1.133. Dlya pechati strok ispol'zujte
printf("%s", string); /* A */
no ne printf(string); /* B */
Esli my ispol'zuem variant B, a v stroke vstretitsya simvol '%'
char string[] = "abc%defg";
to %d budet vosprinyato kak format dlya vyvoda celogo chisla. Vo-pervyh, sama stroka %d
ne budet napechatana; vo-vtoryh - chto zhe budet pechatat'sya po etomu formatu, kogda u
nas est' lish' edinstvennyj argument - string?! Napechataetsya kakoj-to musor!
1.134. Pochemu operator
char s[20];
scanf("%s", s); printf("%s\n", s);
v otvet na vvod stroki
Pushkin A.S.
pechataet tol'ko "Pushkin"?
Otvet: potomu, chto koncom teksta pri vvode po formatu %s schitaetsya libo \n, libo
probel, libo tabulyaciya, a ne tol'ko \n; to est' format %s chitaet slovo iz teksta.
CHtenie vseh simvolov do konca stroki, (vklyuchaya probely) dolzhno vyglyadet' tak:
scanf("%[^\n]\n", s);
%[^\n] - chitat' lyubye simvoly, krome \n (do \n)
\n - propustit' \n na konce stroki
%[abcdef] - chitat' slovo,
sostoyashchee iz perechislennyh bukv.
%[^abcde] - chitat' slovo iz lyubyh bukv,
krome perechislennyh (prervat'sya po bukve iz spiska).
Pust' teper' stroki vhodnoj informacii imeyut format:
Frejd Zigmund 1856 1939
Pust' my hotim schityvat' v stroku s familiyu, v celoe y - god rozhdeniya, a prochie polya
- ignorirovat'. Kak eto sdelat'? Nam pomozhet format "podavlenie prisvaivaniya" %*:
scanf("%s%*s%d%*[^\n]\n",
s, &y );
A. Bogatyrev, 1992-95 - 66 - Si v UNIX
%* propuskaet pole po formatu, ukazannomu posle *, ne zanosya ego znachenie ni v kakuyu
peremennuyu, a prosto "zabyvaya" ego. Tak format
"%*[^\n]\n"
ignoriruet "hvost" stroki, vklyuchaya simvol perevoda stroki.
Simvoly " ", "\t", "\n" v formate vyzyvayut propusk vseh probelov, tabulyacij,
perevodov strok vo vhodnom potoke, chto mozhno opisat' kak
int c;
while((c = getc(stdin))== ' ' || c == '\t' || c == '\n' );
libo kak format
%*[ \t\n]
Pered chislovymi formatami (%d, %o, %u, %ld, %x, %e, %f), a takzhe %s, propusk
probelov delaetsya avtomaticheski. Poetomu
scanf("%d%d", &x, &y);
i
scanf("%d %d", &x, &y);
ravnopravny (probel pered vtorym %d prosto ne nuzhen). Neyavnyj propusk probelov ne
delaetsya pered %c i %[... , poetomu v otvet na vvod stroki "12 5 x" primer
main(){ int n, m; char c;
scanf("%d%d%c", &n, &m, &c);
printf("n=%d m=%d c='%c'\n", n, m, c);
}
napechataet "n=12 m=5 c=' '", to est' v c budet prochitan probel (predshestvovavshij x),
a ne x.
Avtomaticheskij propusk probelov pered %s ne pozvolyaet schityvat' po %s stroki,
lidiruyushchie probely kotoryh dolzhny sohranyat'sya. CHtoby lidiruyushchie probely takzhe schity-
valis', sleduet ispol'zovat' format
scanf("%[^\n]%*1[\n]", s);
v kotorom modifikator dliny 1 zastavlyaet ignorirovat' tol'ko odin simvol \n, a ne
VSE probely i perevody strok, kak "\n". K sozhaleniyu (kak pokazal eksperiment) etot
format ne v sostoyanii prochest' pustuyu stroku (sostoyashchuyu tol'ko iz \n). Poetomu mozhno
sdelat' global'nyj vyvod: stroki nado schityvat' pri pomoshchi funkcij gets() i fgets()!
1.135. Eshche para slov pro scanf: scanf vozvrashchaet chislo uspeshno prochitannyh im dannyh
(obrabotannyh %-ov) ili EOF v konce fajla. Neudacha mozhet nastupit', esli dannoe vo
vhodnom potoke ne sootvetstvuet formatu, naprimer stroka
12 quack
dlya
int d1; double f; scanf("%d%lf", &d1, &f);
V etom sluchae scanf prochtet 12 po formatu %d v peremennuyu d1, no slovo quack ne otve-
chaet formatu %lf, poetomu scanf prervet svoyu rabotu i vydast znachenie 1 (uspeshno pro-
chel odin format). Stroka quack ostanetsya nevostrebovannoj - ee prochitayut posleduyushchie
vyzovy funkcij chteniya; a sejchas f ostanetsya neizmenennoj.
1.136. Si imeet kvalifikator const, ukazyvayushchij, chto znachenie yavlyaetsya ne peremen-
noj, a konstantoj, i popytka izmenit' velichinu po etomu imeni yavlyaetsya oshibkoj. Vo
mnogih sluchayah const mozhet zamenit' #define, pri etom eshche yavno ukazan tip konstanty,
chto polezno dlya proverok kompilyatorom.
A. Bogatyrev, 1992-95 - 67 - Si v UNIX
const int x = 22;
x = 33; /* oshibka: konstantu nel'zya menyat' */
Ispol'zovanie const s ukazatelem:
Ukazuemyj ob®ekt - konstanta
const char *pc = "abc";
pc[1] = 'x'; /* oshibka */
pc = "123"; /* OK */
Sam ukazatel' - konstanta
char *const cp = "abc";
cp[1] = 'x'; /* OK */
cp = "123"; /* oshibka */
Ukazuemyj ob®ekt i sam ukazatel' - konstanty
const char *const cpc = "abc";
cpc[1] = 'x'; /* oshibka */
cpc = "123"; /* oshibka */
Ukazatel' na konstantu neobhodimo ob®yavlyat' kak const TYPE*
int a = 1;
const int b = 2;
const int *pca = &a; /* OK, prosto rassmatrivaem a kak konstantu */
const int *pcb = &b; /* OK */
int *pb = &b; /* oshibka, tak kak togda vozmozhno bylo by napisat' */
*pb = 3; /* izmenit' konstantu b */
1.137. Standartnaya funkciya bystroj sortirovki qsort (algoritm quick sort) imeet
takoj format: chtoby otsortirovat' massiv elementov tipa TYPE
TYPE arr[N];
nado vyzyvat'
qsort(arr,/* CHto sortirovat'? Ne s nachala: arr+m */
N, /* Skol'ko pervyh elementov massiva? */
/* mozhno sortirovat' tol'ko chast': n < N */
sizeof(TYPE),/* Ili sizeof arr[0] */
/* razmer odnogo elementa massiva*/
cmp);
gde
int cmp(TYPE *a1, TYPE *a2);
funkciya sravneniya elementov *a1 i *a2. Ee argumenty - ADRESA dvuh kakih-to elementov
sortiruemogo massiva. Funkciyu cmp my dolzhny napisat' sami - eto funkciya, zadayushchaya
uporyadochenie elementov massiva. Dlya sortirovki po vozrastaniyu funkciya cmp() dolzhna
vozvrashchat' celoe
< 0, esli *a1 dolzhno idti ran'she *a2 <
= 0, esli *a1 sovpadaet s *a2 ==
> 0, esli *a1 dolzhno idti posle *a2 >
Dlya massiva strok elementy massiva imeyut tip (char *), poetomu argumenty funkcii
imeyut tip (char **). Trebuemomu usloviyu udovletvoryaet takaya funkciya:
A. Bogatyrev, 1992-95 - 68 - Si v UNIX
char *arr[N]; ...
cmps(s1, s2) char **s1, **s2;
{ return strcmp(*s1, *s2); }
(Pro strcmp smotri razdel "Massivy i stroki"). Zametim, chto v nekotoryh sistemah
programmirovaniya (naprimer v TurboC++ |-) vy dolzhny ispol'zovat' funkciyu sravneniya s
prototipom
int cmp (const void *a1, const void *a2);
i vnutri nee yavno delat' privedenie tipa:
cmps (const void *s1, const void *s2)
{ return strcmp(*(char **)s1, *(char **)s2); }
ili mozhno postupit' sleduyushchim obrazom:
int cmps(char **s1, char **s2){
return strcmp(*s1, *s2);
}
typedef int (*CMPS)(const void *, const void *);
qsort((void *) array, ..., ..., (CMPS) cmps);
Nakonec, vozmozhno i prosto ob®yavit'
int cmps(const void *A, const void *B){
return strcmp(A, B);
}
Dlya massiva celyh goditsya takaya funkciya sravneniya:
int arr[N]; ...
cmpi(i1, i2) int *i1, *i2;
{ return *i1 - *i2; }
Dlya massiva struktur, kotorye my sortiruem po celomu polyu key, goditsya
struct XXX{ int key; ... } arr[N];
cmpXXX(st1, st2) struct XXX *st1, *st2;
{ return( st1->key - st2->key ); }
Pust' u nas est' massiv long. Mozhno li ispol'zovat'
long arr[N]; ...
cmpl(L1, L2) long *L1, *L2;
{ return *L1 - *L2; }
Otvet: okazyvaetsya, chto net. Funkciya cmpl dolzhna vozvrashchat' celoe, a raznost' dvuh
long-ov imeet tip long. Poetomu kompilyator privodit etu raznost' k tipu int (kak
pravilo obrubaniem starshih bitov). Pri etom (esli long-chisla byli veliki) rezul'tat
mozhet izmenit' znak! Naprimer:
main(){
int n; long a = 1L; long b = 777777777L;
n = a - b; /* dolzhno by byt' otricatel'nym... */
printf( "%ld %ld %d\n", a, b, n );
}
____________________
|- TurboC - kompilyator Si v MS DOS, razrabotannyj firmoj Borland International.
A. Bogatyrev, 1992-95 - 69 - Si v UNIX
pechataet 1 777777777 3472. Funkciya sravneniya dolzhna vyglyadet' tak:
cmpl(L1, L2) long *L1, *L2; {
if( *L1 == *L2 ) return 0;
if( *L1 < *L2 ) return (-1);
return 1;
}
ili
cmpl(L1, L2) long *L1, *L2; {
return( *L1 == *L2 ? 0 :
*L1 < *L2 ? -1 : 1 );
}
poskol'ku vazhna ne velichina vozvrashchennogo znacheniya, a tol'ko ee znak.
Uchtite, chto dlya ispol'zovaniya funkcii sravneniya vy dolzhny libo opredelit' funk-
ciyu sravneniya do ee ispol'zovaniya v qsort():
int cmp(...){ ... } /* realizaciya */
...
qsort(..... , cmp);
libo predvaritel'no ob®yavit' imya funkcii sravneniya, chtoby kompilyator ponimal, chto eto
imenno funkciya:
int cmp();
qsort(..... , cmp);
...
int cmp(...){ ... } /* realizaciya */
1.138. Pust' u nas est' dve programmy, pol'zuyushchiesya odnoj i toj zhe strukturoj dannyh
W:
a.c b.c
-------------------------- ------------------------------
#include <fcntl.h> #include <fcntl.h>
struct W{ int x,y; }a; struct W{ int x,y; }b;
main(){ int fd; main(){ int fd;
a.x = 12; a.y = 77; fd = open("f", O_RDONLY);
fd = creat("f", 0644); read(fd, &b, sizeof b);
write(fd, &a, sizeof a); close(fd);
close(fd); printf("%d %d\n", b.x, b.y);
} }
CHto budet, esli my izmenim strukturu na
struct W { long x,y; };
ili
struct W { char c; int x,y; };
v fajle a.c i zabudem sdelat' eto v b.c? Budut li pravil'no rabotat' eti programmy?
Iz nablyudaemogo mozhno sdelat' vyvod, chto esli dve ili neskol'ko programm (ili
chastej odnoj programmy), razmeshchennye v raznyh fajlah, ispol'zuyut obshchie
- tipy dannyh (typedef);
- struktury i ob®edineniya;
- konstanty (opredeleniya #define);
- prototipy funkcij;
to ih opredeleniya luchshe vynosit' v obshchij include-fajl (header-fajl), daby vse prog-
rammy priderzhivalis' odnih i teh zhe obshchih soglashenij. Dazhe esli eti soglasheniya so
A. Bogatyrev, 1992-95 - 70 - Si v UNIX
vremenem izmenyatsya, to oni izmenyatsya vo vseh fajlah sinhronno i kak by sami soboj. V
nashem sluchae ispravlyat' opredelenie struktury pridetsya tol'ko v include-fajle, a ne
vyiskivat' vse mesta, gde ono napisano, ved' pri etom nemudreno kakoe-nibud' mesto i
propustit'!
W.h
-----------------------
struct W{ long x, y; };
a.c b.c
-------------------------- ------------------
#include <fcntl.h> #include <fcntl.h>
#include "W.h" #include "W.h"
struct W a; struct W b;
main(){ ... main(){ ...
printf("%ld...
Krome togo, vynesenie obshchih fragmentov teksta programmy (opredelenij struktur, kons-
tant, i.t.p.) v otdel'nyj fajl ekonomit nashi sily i vremya - vmesto togo, chtoby nabi-
vat' odin i tot zhe tekst mnogo raz v raznyh fajlah, my teper' pishem v kazhdom fajle
edinstvennuyu stroku - direktivu #include. Krome togo, ekonomitsya i mesto na diske,
ved' programma stala koroche! Fajly vklyucheniya imeyut suffiks .h, chto oznachaet
"header-file" (fajl-zagolovok).
Sinhronnuyu perekompilyaciyu vseh programm v sluchae izmeneniya include-fajla mozhno
zadat' v fajle Makefile - programme dlya koordinatora make|-:
all: a b
echo Zapusk a i b
a ; b
a: a.c W.h
cc a.c -o a
b: b.c W.h
cc b.c -o b
Pravila make imeyut vid
cel': spisok_celej_ot_kotoryh_zavisit
komanda
komanda opisyvaet chto nuzhno sdelat', chtoby izgotovit' fajl cel' iz fajlov
spisok_celej_ot_kotoryh_zavisit. Komanda vypolnyaetsya tol'ko esli fajl cel' eshche ne
sushchestvuet, libo hot' odin iz fajlov sprava ot dvoetochiya yavlyaetsya bolee "molodym"
(svezhim), chem celevoj fajl (smotri pole st_mtime i sisvyzov stat v glave pro UNIX).
1.139. Programma na Si mozhet byt' razmeshchena v neskol'kih fajlah. Kazhdyj fajl vystu-
paet v roli "modulya", v kotorom sobrany shodnye po naznacheniyu funkcii i peremennye.
Nekotorye peremennye i funkcii mozhno sdelat' nevidimymi dlya drugih modulej. Dlya etogo
nado ob®yavit' ih static:
- Ob®yavlenie peremennoj vnutri funkcii kak static delaet peremennuyu staticheskoj
(t.e. ona budet sohranyat' svoe znachenie pri vyhode iz funkcii) i ogranichivaet ee
vidimost' predelami dannoj funkcii.
- Peremennye, opisannye vne funkcij, i tak yavlyayutsya staticheskimi (po klassu
pamyati). Odnako slovo static i v etom sluchae pozvolyaet upravlyat' vidimost'yu etih
peremennyh - oni budut vidimy tol'ko v predelah dannogo fajla.
- Funkcii, ob®yavlennye kak static, takzhe vidimy tol'ko v predelah dannogo fajla.
- Argumenty funkcii i lokal'nye (avtomaticheskie) peremennye funkcii i tak sushchest-
vuyut tol'ko na vremya vyzova dannoj funkcii (pamyat' dlya nih vydelyaetsya v steke
____________________
|- Podrobnoe opisanie make smotri v dokumentacii po sisteme UNIX.
A. Bogatyrev, 1992-95 - 71 - Si v UNIX
pri vhode v funkciyu i unichtozhaetsya pri vyhode) i vidimy tol'ko vnutri ee tela.
Argumenty funkcii nel'zya ob®yavlyat' static:
f(x) static x; { x++; }
nezakonno.
Takim obrazom vse peremennye i funkcii v dannom fajle delyatsya na dve gruppy:
- Vidimye tol'ko vnutri dannogo fajla (lokal'nye dlya modulya). Takie imena ob®yav-
lyayutsya s ispol'zovaniem klyuchevogo slova static. V chastnosti est' eshche "bolee
lokal'nye" peremennye - avtomaticheskie lokaly funkcij i ih formal'nye argumenty,
kotorye vidimy tol'ko v predelah dannoj funkcii. Takzhe vidimy lish' v predelah
odnoj funkcii staticheskie lokal'nye peremennye, ob®yavlennye v tele funkcii so
slovom static.
- Vidimye vo vseh fajlah (global'nye imena).
Global'nye imena obrazuyut interfejs modulya i mogut byt' ispol'zovany v drugih modu-
lyah. Lokal'nye imena izvne modulya nedostupny.
Esli my ispol'zuem v fajle-module funkcii i peremennye, vhodyashchie v interfejs
drugogo fajla-modulya, my dolzhny ob®yavit' ih kak extern ("vneshnie"). Dlya funkcij opi-
sateli extern i int mozhno opuskat':
// fajl A.c
int x, y, z; // global'nye
char ss[200]; // glob.
static int v, w; // lokal'nye
static char *s, p[20]; // lok.
int f(){ ... } // glob.
char *g(){ ... } // glob.
static int h(){ ... } // lok.
static char *sf(){ ... } // lok.
int fi(){ ... } // glob.
// fajl B.c
extern int x, y;
extern z; // int mozhno opustit'
extern char ss[]; // razmer mozhno opustit'
extern int f();
char *g(); // extern mozhno opustit'
extern fi(); // int mozhno opustit'
Horoshim tonom yavlyaetsya napisanie kommentariya - iz kakogo modulya ili biblioteki impor-
tiruetsya peremennaya ili funkciya:
extern int x, y; /* import from A.c */
char *tgetstr(); /* import from termlib */
Sleduyushchaya programma sobiraetsya iz fajlov A.c i B.c komandoj|=
____________________
|= Mozhno zadat' Makefile vida
CFLAGS = -O
AB: A.o B.o
cc A.o B.o -o AB
A.o: A.c
cc -c $(CFLAGS) A.c
B.o: B.c
cc -c $(CFLAGS) B.c
i sobirat' programmu prosto vyzyvaya komandu make.
A. Bogatyrev, 1992-95 - 72 - Si v UNIX
cc A.c B.c -o AB
Pochemu kompilyator soobshchaet "x dvazhdy opredeleno"?
fajl A.c fajl B.c
-----------------------------------------
int x=12; int x=25;
main(){ f(y) int *y;
f(&x); {
printf("%d\n", x); *y += x;
} }
Otvet: potomu, chto v kazhdom fajle opisana global'naya peremennaya x. Nado v odnom iz
nih (ili v oboih srazu) sdelat' x lokal'nym imenem (isklyuchit' ego iz interfejsa
modulya):
static int x=...;
Pochemu v sleduyushchem primere kompilyator soobshchaet "_f dvazhdy opredeleno"?
fajl A.c fajl B.c
----------------------------------------------------
int x; extern int x;
main(){ f(5); g(77); } g(n){ f(x+n); }
f(n) { x=n; } f(m){ printf("%d\n", m); }
Otvet: nado sdelat' v fajle B.c funkciyu f lokal'noj: static f(m)...
Hot' v odnom fajle dolzhna byt' opredelena funkciya main, vyzyvaemaya sistemoj pri
zapuske programmy. Esli takoj funkcii nigde net - kompilyator vydaet soobshchenie "_main
neopredeleno". Funkciya main dolzhna byt' opredelena odin raz! V fajle ona mozhet naho-
dit'sya v lyubom meste - ne trebuetsya, chtoby ona byla samoj pervoj (ili poslednej)
funkciej fajla|=.
1.140. V chem oshibka?
fajl A.c fajl B.c
----------------------------------------------------
extern int x; extern int x;
main(){ x=2; f(){
f(); printf("%d\n", x);
} }
Otvet: peremennaya x v oboih fajlah ob®yavlena kak extern, v rezul'tate pamyat' dlya nee
nigde ne vydelena, t.e. x ne opredelena ni v odnom fajle. Uberite odno iz slov
extern!
1.141. V chem oshibka?
fajl A.c fajl B.c
----------------------------------------------------
int x; extern double x;
... ...
Tipy peremennyh ne sovpadayut. Bol'shinstvo kompilyatorov ne lovit takuyu oshibku, t.k.
kazhdyj fajl kompiliruetsya otdel'no, nezavisimo ot ostal'nyh, a pri "sklejke" fajlov v
____________________
|= Esli vy pol'zuetes' "novym" stilem ob®yavleniya funkcij, no ne ispol'zuete proto-
tipy, to sleduet opredelyat' kazhduyu funkciyu do pervogo mesta ee ispol'zovaniya, chtoby
kompilyatoru v tochke vyzova byl izvesten ee zagolovok. |to privedet k tomu, chto main()
okazhetsya poslednej funkciej v fajle - ee ne vyzyvaet nikto, zato ona vyzyvaet kogo-to
eshche.
A. Bogatyrev, 1992-95 - 73 - Si v UNIX
obshchuyu vypolnyaemuyu programmu komponovshchik znaet lish' imena peremennyh i funkcij, no ne
ih tipy i prototipy. V rezul'tate programma normal'no skompiliruetsya i soberetsya, no
rezul'tat ee vypolneniya budet nepredskazuem! Poetomu ob®yavleniya extern tozhe polezno
vynosit' v include-fajly:
fajl proto.h
------------------
extern int x;
fajl A.c fajl B.c
------------------ ------------------
#include "proto.h" #include "proto.h"
int x; ...
to, chto peremennaya x v A.c okazyvaetsya opisannoj i kak extern - vpolne dopustimo,
t.k. v moment nastoyashchego ob®yavleniya etoj peremennoj eto slovo nachnet prosto ignoriro-
vat'sya (lish' by tipy v ob®yavlenii s extern i bez nego sovpadali - inache oshibka!).
1.142. CHto pechataet programma i pochemu?
int a = 1; /* primer Bjarne Stroustrup-a */
void f(){
int b = 1;
static int c = 1;
printf("a=%d b=%d c=%d\n", a++, b++, c++);
}
void main(){
while(a < 4) f();
}
Otvet:
a=1 b=1 c=1
a=2 b=1 c=2
a=3 b=1 c=3
1.143. Avtomaticheskaya peremennaya vidima tol'ko vnutri bloka, v kotorom ona opisana.
CHto napechataet programma?
/* fajl A.c */
int x=666; /*glob.*/
main(){
f(3);
printf(" ::x = %d\n", x);
g(2); g(5);
printf(" ::x = %d\n", x);
}
g(n){
static int x=17; /*vidima tol'ko v g*/
printf("g::x = %2d g::n = %d\n", x++, n);
if(n) g(n-1); else x = 0;
}
/* fajl B.c */
extern x; /*global*/
f(n){ /*lokal funkcii*/
x++; /*global*/
{ int x; /*lokal bloka*/
x = n+1; /*lokal*/
A. Bogatyrev, 1992-95 - 74 - Si v UNIX
n = 2*x; /*lokal*/
}
x = n-1; /*global*/
}
1.144. Funkciya, kotoraya
- ne soderzhit vnutri sebya staticheskih peremennyh, hranyashchih sostoyanie processa
obrabotki dannyh (funkciya bez "pamyati");
- poluchaet znacheniya parametrov tol'ko cherez svoi argumenty (no ne cherez global'nye
staticheskie peremennye);
- vozvrashchaet znacheniya tol'ko cherez argumenty, libo kak znachenie funkcii (cherez
return);
nazyvaetsya reenterabel'noj (povtorno vhodimoj) ili chistoj (pure). Takaya funkciya
mozhet parallel'no (ili psevdoparallel'no) ispol'zovat'sya neskol'kimi "potokami" obra-
botki informacii v nashej programme, bez kakogo-libo nepredvidennogo vliyaniya etih
"potokov obrabotki" drug na druga. Pervyj punkt trebovanij pozvolyaet funkcii ne
zaviset' ni ot kakogo konkretnogo processa obrabotki dannyh, t.k. ona ne "pomnit"
obrabotannyh eyu ranee dannyh i ne stroit svoe povedenie v zavisimosti ot nih. Vtorye
dva punkta - eto trebovanie, chtoby vse bez isklyucheniya puti peredachi dannyh v funkciyu
i iz nee (interfejs funkcii) byli perechisleny v ee zagolovke. |to lishaet funkciyu
"pobochnyh effektov", ne predusmotrennyh programmistom pri ee vyzove (programmist
obychno smotrit tol'ko na zagolovok funkcii, i ne vyiskivaet "tajnye" svyazi funkcii s
programmoj cherez global'nye peremennye, esli tol'ko eto special'no ne ogovoreno).
Vot primer ne reenterabel'noj funkcii:
FILE *fp; ... /* global'nyj argument */
char delayedInput ()
{
static char prevchar; /* pamyat' */
char c;
c = prevchar;
prevchar = getc (fp);
return c;
}
A vot ee reenterabel'nyj ekvivalent:
char delayedInput (char *prevchar, FILE *fp)
{
char c;
c = *prevchar;
*prevchar = getc (fp);
return c;
}
/* vyzov: */
FILE *fp1, *fp2; char prev1, prev2, c1, c2;
... x1 = delayedInput (&prev1, fp1);
x2 = delayedInput (&prev2, fp2); ...
Kak vidim, vse "zapominayushchie" peremennye (t.e. prevchar) vyneseny iz samoj funkcii i
podayutsya v nee v vide argumenta.
Reenterabel'nye funkcii nezavisimy ot ostal'noj chasti programmy (ih mozhno skopi-
rovat' v drugoj programmnyj proekt bez izmenenij), bolee ponyatny (poskol'ku vse zat-
ragivaemye imi vneshnie peremennye perechisleny kak argumenty, ne nado vy