Andrej Bogatyrev. Hrestomatiya po programmirovaniyu na Si v Unix
© Copyright Andrej Bogatyrev. 1992-95
Email: abs@decart.msu.su ¡ mailto:abs@decart.msu.su
Txt version is located at ¡ http://decart.msu.su:100/~abs/
A. Bogatyrev, 1992-95 - 1 - Si v UNIX
0. Naputstvie v kachestve vstupleniya.
Um podoben zheludku.
Vazhno ne to, skol'ko ty v nego vlozhish',
a to, skol'ko on smozhet perevarit'.
V etoj knige vy najdete ryad zadach, primerov, algoritmov, sovetov i stilistiches-
kih zamechanij po ispol'zovaniyu yazyka programmirovaniya "C" (Si) v srede operacionnoj
sistemy UNIX. Zdes' sobrany etyudy raznoj slozhnosti i "shtrihi k portretu" yazyka Si.
Takzhe opisany razlichnye "podvodnye kamni" na kotoryh neredko terpyat krushenie novichki
v Si. V etom smysle etu knigu mozhno mestami nazvat' "Kak ne nado programmirovat' na
Si".
V bol'shinstve sluchaev v kachestve platformy ispol'zuetsya personal'nyj komp'yuter
IBM PC s kakoj-libo sistemoj UNIX, libo SPARCstation 20 s sistemoj Solaris 2 (tozhe
UNIX svr4), no mnogie primery bez kakih-libo izmenenij (libo s minimumom takovyh)
mogut byt' pereneseny v sredu MS DOS|=, libo na drugoj tip mashiny s sistemoj UNIX.
|to vasha VTORAYA kniga po Si. |ta kniga ne uchebnik, a hrestomatiya k uchebniku.
Ona ne yavlyaetsya ni sistematicheskim kursom po Si, ni spravochnikom po nemu, i prednaz-
nachena ne dlya odnorazovogo posledovatel'nogo prochteniya, a dlya chteniya v neskol'ko pro-
hodov na raznyh etapah vashej "zrelosti". Poetomu chitat' ee sleduet vmeste s "nastoya-
shchim" uchebnikom po Si, sredi kotoryh naibolee izvestna kniga Kernigana i Ritchi.
|ta kniga - ne POSLEDNYAYA vasha kniga po Si. Vo-pervyh potomu, chto koe-chto v yazyke
vse zhe menyaetsya so vremenem, hotya i nastal chas, kogda standart na yazyk Si nakonec
prinyat... No poyavilsya yazyk C++, kotoryj razvivaetsya dovol'no dinamichno. Eshche est'
Objective-C. Vo-vtoryh potomu, chto est' biblioteki i sistemnye vyzovy, kotorye raz-
vivayutsya vsled za razvitiem UNIX i drugih operacionnyh sistem. Sleduyushchimi vashimi
(nastol'nymi) knigami dolzhny stat' "Spravochnoe rukovodstvo": man2 (po sistemnym vyzo-
vam), man3 (po bibliotechnym funkciyam).
Moshch' yazyka Si - v sushchestvuyushchem mnogoobrazii bibliotek.
Proshu vas s pervyh zhe shagov sledit' za stilem oformleniya svoih programm. Delajte
otstupy, pishite kommentarii, ispol'zujte osmyslennye imena peremennyh i funkcij,
otdelyajte logicheskie chasti programmy drug ot druga pustymi strokami. Pomnite, chto
"lishnie" probely i pustye stroki v Si dopustimy vezde, krome izobrazhenij konstant i
imen. Programmy na Si, nabitye v odnu kolonku (kak na FORTRAN-e) ochen' tyazhelo chitat'
i ponimat'. Iz-za etogo byvaet trudno nahodit' poteryannye skobki { i }, poteryannye
simvoly `;' i drugie oshibki.
Sushchestvuet neskol'ko "shkol" oformleniya programm - priglyadites' k primeram v etoj
knige i v drugih istochnikah - i vyberite lyubuyu! Nichego strashnogo, esli vy budete
smeshivat' eti stili. No - PODALXSHE OT FORTRAN-a !!!
Programmu mozhno avtomaticheski sformatirovat' k "kanonicheskomu" vidu pri pomoshchi,
naprimer, programmy cb.
cb < NashFajl.c > /tmp/$$
mv /tmp/$$ NashFajl.c
no luchshe srazu oformlyat' programmu pravil'no.
Vydelyajte logicheski samostoyatel'nye ("zamknutye") chasti programmy v funkcii
(dazhe esli oni budut vyzyvat'sya edinstvennyj raz). Funkcii - ne prosto sredstvo
izbezhat' povtoreniya odnih i teh zhe operatorov v tekste programmy, no i sredstvo
strukturirovaniya processa programmirovaniya, delayushchee programmu bolee ponyatnoj. Vo-
pervyh, vy mozhete v drugoj programme ispol'zovat' tekst uzhe napisannoj vami ranee
funkcii vmesto togo, chtoby pisat' ee zanovo. Vo-vtoryh, operaciyu, oformlennuyu v vide
funkcii, mozhno rassmatrivat' kak nedelimyj primitiv (ot dovol'no prostogo po smyslu,
vrode strcmp, strcpy, do dovol'no slozhnogo - qsort, malloc, gets) i zabyt' o ego
vnutrennem ustrojstve (eto horosho - nado men'she pomnit').
____________________
|= MS DOS - torgovyj znak firmy Microsoft Corporation. (chitaetsya "Majkrosoft");
DOS - diskovaya operacionnaya sistema.
A. Bogatyrev, 1992-95 - 2 - Si v UNIX
Ne gonites' za kratkost'yu v ushcherb yasnosti. Si pozvolyaet poroj pisat' takie vyra-
zheniya, nad kotorymi mozhno polchasa lomat' golovu. Esli zhe ih zapisat' menee mudreno,
no chut' dlinnee - oni samoochevidny (i etim bolee zashchishcheny ot oshibok).
V sisteme UNIX vy mozhete posmotret' opisanie lyuboj komandy sistemy ili funkcii
Si, nabrav komandu
man nazvanieFunkcii
(man - ot slova manual, "rukovodstvo").
Eshche odno naputstvie: uchite anglijskij yazyk! Prakticheski vse yazyki programmirova-
niya ispol'zuyut anglijskie slova (v kachestve klyuchevyh slov, terminov, imen peremennyh
i funkcij). Poetomu luchshe ponimat' znachenie etih slov (hotya i vospriyatie ih kak
prosto nekih simvolov tozhe imeet opredelennye dostoinstva). Obratno - programmirova-
nie na Si pomozhet vam vyuchit' anglijskij.
Po razlichnym prichinam na territorii Rossii sejchas ispol'zuetsya mnogo raznyh
vos'mibitnyh russkih kodirovok. Sredi nih:
KOI-8
Istoricheski prinyataya na russkih UNIX sistemah - samaya rannyaya iz poyavivshihsya.
Otlichaetsya tem svojstvom, chto esli u nee obrezan vos'moj bit: c & 0177 - to ona
vse zhe chitaema s terminala kak transliteraciya latinskih bukv. Imenno etoj kodi-
rovkoj pol'zuetsya avtor etoj knigi (kak i bol'shinstvo UNIX-sites seti RelCom).
ISO 8859/5
|to amerikanskij standart na russkuyu kodirovku. A russkie programmisty k ee
razrabotke ne imeyut nikakogo otnosheniya. Eyu pol'zuetsya bol'shinstvo kommercheskih
baz dannyh.
Microsoft 1251
|to ta kodirovka, kotoroj pol'zuetsya Microsoft Windows. Vozmozhno, chto imenno k
etoj kodirovke pridut i UNIX sistemy (gipoteza 1994 goda).
Al'ternativnaya kodirovka dlya MS DOS
Russkaya kodirovka s psevdografikoj, ispol'zovavshayasya v MS DOS.
Kodirovka dlya Macintosh
|to velikoe "raznoobrazie" prichinyaet massu neudobstv. No, gospoda, eto Rossiya - chto
znachit - shirota dushi i absolyutnyj bardak. Relax and enjoy.
Mnogie primery v dannoj knige dany vmeste s otvetami - kak obrazcami dlya podra-
zhaniya. Odnako my nadeemsya, chto Vy uderzhites' ot iskusheniya i snachala proverite svoi
sily, a lish' potom posmotrite v otvet! Itak, chitaya primery - delajte po analogii.
A. Bogatyrev, 1992-95 - 3 - Si v UNIX
1. Prostye programmy i algoritmy. Syurprizy, sovety.
1.1. Sostav'te programmu privetstviya s ispol'zovaniem funkcii printf. Po tradicii
prinyato pechatat' frazu "Hello, world !" ("Zdravstvuj, mir !").
1.2. Najdite oshibku v programme
#include <stdio.h>
main(){
printf("Hello, world\n");
}
Otvet: raz ne ob®yavleno inache, funkciya main schitaetsya vozvrashchayushchej celoe znachenie
(int). No funkciya main ne vozvrashchaet nichego - v nej prosto net operatora return.
Korrektno bylo by tak:
#include <stdio.h>
main(){
printf("Hello, world\n");
return 0;
}
ili
#include <stdio.h>
void main(){
printf("Hello, world\n");
exit(0);
}
a uzh sovsem korrektno - tak:
#include <stdio.h>
int main(int argc, char *argv[]){
printf("Hello, world\n");
return 0;
}
1.3. Najdite oshibki v programme
#include studio.h
main
{
int i
i := 43
print ('V godu i nedel'')
}
1.4. CHto budet napechatano v privedennom primere, kotoryj yavlyaetsya chast'yu polnoj
programmy:
int n;
n = 2;
printf ("%d + %d = %d\n", n, n, n + n);
1.5. V chem sostoyat oshibki?
A. Bogatyrev, 1992-95 - 4 - Si v UNIX
if( x > 2 )
then x = 2;
if x < 1
x = 1;
Otvet: v Si net klyuchevogo slova then, usloviya v operatorah if, while dolzhny brat'sya v
()-skobki.
1.6. Napishite programmu, pechatayushchuyu vashe imya, mesto raboty i adres. V pervom vari-
ante programmy ispol'zujte bibliotechnuyu funkciyu printf, a vo vtorom - puts.
1.7. Sostav'te programmu s ispol'zovaniem sleduyushchih postfiksnyh i prefiksnyh opera-
cij:
a = b = 5
a + b
a++ + b
++a + b
--a + b
a-- + b
Raspechatajte poluchennye znacheniya i proanalizirujte rezul'tat.
1.8.
Cikl for
________________________________________________________________________________
for(INIT; CONDITION; INCR)
BODY
________________________________________________________________________________
INIT;
repeat:
if(CONDITION){
BODY;
cont:
INCR;
goto repeat;
}
out: ;
Cikl while
________________________________________________________________________________
while(COND)
BODY
________________________________________________________________________________
cont:
repeat:
if(CONDITION){
BODY;
goto repeat;
}
out: ;
A. Bogatyrev, 1992-95 - 5 - Si v UNIX
Cikl do
________________________________________________________________________________
do
BODY
while(CONDITION)
________________________________________________________________________________
cont:
repeat:
BODY;
if(CONDITION) goto repeat;
out: ;
V operatorah cikla vnutri tela cikla BODY mogut prisutstvovat' operatory break i
continue; kotorye oznachayut na nashih shemah sleduyushchee:
#define break goto out
#define continue goto cont
1.9. Sostav'te programmu pechati pryamougol'nogo treugol'nika iz zvezdochek
*
**
***
****
*****
ispol'zuya cikl for. Vvedite peremennuyu, znacheniem kotoroj yavlyaetsya razmer kateta tre-
ugol'nika.
1.10. Napishite operatory Si, kotorye vydayut stroku dliny WIDTH, v kotoroj snachala
soderzhitsya x0 simvolov '-', zatem w simvolov '*', i do konca stroki - vnov' simvoly
'-'. Otvet:
int x;
for(x=0; x < x0; ++x) putchar('-');
for( ; x < x0 + w; x++) putchar('*');
for( ; x < WIDTH ; ++x) putchar('-');
putchar('\n');
libo
for(x=0; x < WIDTH; x++)
putchar( x < x0 ? '-' :
x < x0 + w ? '*' :
'-' );
putchar('\n');
1.11. Napishite programmu s ciklami, kotoraya risuet treugol'nik:
*
***
*****
*******
*********
A. Bogatyrev, 1992-95 - 6 - Si v UNIX
Otvet:
/* Treugol'nik iz zvezdochek */
#include <stdio.h>
/* Pechat' n simvolov c */
printn(c, n){
while( --n >= 0 )
putchar(c);
}
int lines = 10; /* chislo strok treugol'nika */
void main(argc, argv) char *argv[];
{
register int nline; /* nomer stroki */
register int naster; /* kolichestvo zvezdochek v stroke */
register int i;
if( argc > 1 )
lines = atoi( argv[1] );
for( nline=0; nline < lines ; nline++ ){
naster = 1 + 2 * nline;
/* lidiruyushchie probely */
printn(' ', lines-1 - nline);
/* zvezdochki */
printn('*', naster);
/* perevod stroki */
putchar( '\n' );
}
exit(0); /* zavershenie programmy */
}
1.12. V chem sostoit oshibka?
main(){ /* pechat' frazy 10 raz */
int i;
while(i < 10){
printf("%d-yj raz\n", i+1);
i++;
}
}
Otvet: avtomaticheskaya peremennaya i ne byla proinicializirovana i soderzhit ne 0, a
kakoe-to proizvol'noe znachenie. Cikl mozhet vypolnit'sya ne 10, a lyuboe chislo raz (v
tom chisle i 0 po sluchajnosti). Ne zabyvajte inicializirovat' peremennye, voz'mite
opisanie s inicializaciej za pravilo!
int i = 0;
Esli by peremennaya i byla staticheskoj, ona by imela nachal'noe znachenie 0.
V dannom primere bylo by eshche luchshe ispol'zovat' cikl for, v kotorom vse operacii
nad indeksom cikla sobrany v odnom meste - v zagolovke cikla:
for(i=0; i < 10; i++) printf(...);
A. Bogatyrev, 1992-95 - 7 - Si v UNIX
1.13. Vspomogatel'nye peremennye, ne nesushchie smyslovoj nagruzki (vrode schetchika pov-
torenij cikla, ne ispol'zuemogo v samom tele cikla) prinyato po tradicii oboznachat'
odnobukvennymi imenami, vrode i, j. Bolee togo, vozmozhny dazhe takie kur'ezy:
main(){
int _ ;
for( _ = 0; _ < 10; _++) printf("%d\n", _ );
}
osnovannye na tom, chto podcherk v identifikatorah - polnopravnaya bukva.
1.14. Najdite 2 oshibki v programme:
main(){
int x = 12;
printf( "x=%d\n" );
int y;
y = 2 * x;
printf( "y=%d\n", y );
}
Kommentarij: v tele funkcii vse opisaniya dolzhny idti pered vsemi vypolnyaemymi opera-
torami (krome operatorov, vhodyashchih v sostav opisanij s inicializaciej). Ochen' chasto
posle vneseniya pravok v programmu nekotorye opisaniya okazyvayutsya posle vypolnyaemyh
operatorov. Imenno poetomu rekomenduetsya otdelyat' stroki opisaniya peremennyh ot
vypolnyaemyh operatorov pustymi strokami (v etoj knige eto chasto ne delaetsya dlya eko-
nomii mesta).
1.15. Najdite oshibku:
int n;
n = 12;
main(){
int y;
y = n+2;
printf( "%d\n", y );
}
Otvet: vypolnyaemyj operator n=12 nahoditsya vne tela kakoj-libo funkcii. Sleduet
vnesti ego v main() posle opisaniya peremennoj y, libo perepisat' ob®yavlenie pered
main() v vide
int n = 12;
V poslednem sluchae prisvaivanie peremennoj n znacheniya 12 vypolnit kompilyator eshche vo
vremya kompilyacii programmy, a ne sama programma pri svoem zapuske. Tochno tak zhe pro-
ishodit so vsemi staticheskimi dannymi (opisannymi kak static, libo raspolozhennymi vne
vseh funkcij); prichem esli ih nachal'noe znachenie ne ukazano yavno - to podrazumevaetsya
0 ('\0', NULL, ""). Odnako nulevye znacheniya ne hranyatsya v skompilirovannom vypolnyae-
mom fajle, a trebuemaya "chistaya" pamyat' raspisyvaetsya pri starte programmy.
1.16. Po povodu opisaniya peremennoj s inicializaciej:
TYPE x = vyrazhenie;
yavlyaetsya (pochti) ekvivalentom dlya
TYPE x; /* opisanie */
x = vyrazhenie; /* vychislenie nachal'nogo znacheniya */
A. Bogatyrev, 1992-95 - 8 - Si v UNIX
Rassmotrim primer:
#include <stdio.h>
extern double sqrt(); /* kvadratnyj koren' */
double x = 1.17;
double s12 = sqrt(12.0); /* #1 */
double y = x * 2.0; /* #2 */
FILE *fp = fopen("out.out", "w"); /* #3 */
main(){
double ss = sqrt(25.0) + x; /* #4 */
...
}
Stroki s metkami #1, #2 i #3 oshibochny. Pochemu?
Otvet: pri inicializacii staticheskih dannyh (a s12, y i fp takovymi i yavlyayutsya,
tak kak opisany vne kakoj-libo funkcii) vyrazhenie dolzhno soderzhat' tol'ko konstanty,
poskol'ku ono vychislyaetsya KOMPILYATOROM. Poetomu ni ispol'zovanie znachenij peremennyh,
ni vyzovy funkcij zdes' nedopustimy (no mozhno brat' adresa ot peremennyh).
V stroke #4 my inicializiruem avtomaticheskuyu peremennuyu ss, t.e. ona otvoditsya
uzhe vo vremya vypolneniya programmy. Poetomu vyrazhenie dlya inicializacii vychislyaetsya
uzhe ne kompilyatorom, a samoj programmoj, chto daet nam pravo ispol'zovat' peremennye,
vyzovy funkcij i.t.p., to est' vyrazheniya yazyka Si bez ogranichenij.
1.17. Napishite programmu, realizuyushchuyu eho-pechat' vvodimyh simvolov. Programma
dolzhna zavershat' rabotu pri poluchenii priznaka EOF. V UNIX pri vvode s klaviatury
priznak EOF obychno oboznachaetsya odnovremennym nazhatiem klavish CTRL i D (CTRL chut'
ran'she), chto v dal'nejshem budet oboznachat'sya CTRL/D; a v MS DOS - klavish CTRL/Z.
Ispol'zujte getchar() dlya vvoda bukvy i putchar() dlya vyvoda.
1.18. Napishite programmu, podschityvayushchuyu chislo simvolov postupayushchih so standartnogo
vvoda. Kakie dostoinstva i nedostatki u sleduyushchej realizacii:
#include <stdio.h>
main(){ double cnt = 0.0;
while (getchar() != EOF) ++cnt;
printf("%.0f\n", cnt );
}
Otvet: i dostoinstvo i nedostatok v tom, chto schetchik imeet tip double. Dostoinstvo -
mozhno podschitat' ochen' bol'shoe chislo simvolov; nedostatok - operacii s double obychno
vypolnyayutsya gorazdo medlennee, chem s int i long (do desyati raz), programma budet
rabotat' dol'she. V povsednevnyh zadachah vam vryad li ponadobitsya imet' schetchik,
otlichnyj ot long cnt; (pechatat' ego nado po formatu "%ld").
1.19. Sostav'te programmu perekodirovki vvodimyh simvolov so standartnogo vvoda po
sleduyushchemu pravilu:
a -> b
b -> c
c -> d
...
z -> a
drugoj simvol -> *
Kody strochnyh latinskih bukv raspolozheny podryad po vozrastaniyu.
1.20. Sostav'te programmu perekodirovki vvodimyh simvolov so standartnogo vvoda po
sleduyushchemu pravilu:
A. Bogatyrev, 1992-95 - 9 - Si v UNIX
B -> A
C -> B
...
Z -> Y
drugoj simvol -> *
Kody propisnyh latinskih bukv takzhe raspolozheny po vozrastaniyu.
1.21. Napishite programmu, pechatayushchuyu nomer i kod vvedennogo simvola v vos'merichnom i
shestnadcaterichnom vide. Zamet'te, chto esli vy naberete na vvode stroku simvolov i
nazhmete klavishu ENTER, to programma napechataet vam na odin simvol bol'she, chem vy nab-
rali. Delo v tom, chto kod klavishi ENTER, zavershivshej vvod stroki - simvol '\n' -
tozhe popadaet v vashu programmu (na ekrane on otobrazhaetsya kak perevod kursora v
nachalo sleduyushchej stroki!).
1.22. Razberites', v chem sostoit raznica mezhdu simvolami '0' (cifra nul') i '\0'
(nulevoj bajt). Napechatajte
printf( "%d %d %c\n", '\0', '0', '0' );
Postav'te opyt: chto pechataet programma?
main(){
int c = 060; /* kod simvola '0' */
printf( "%c %d %o\n", c, c, c);
}
Pochemu pechataetsya 0 48 60? Teper' napishite vmesto
int c = 060;
strochku
char c = '0';
1.23. CHto napechataet programma?
#include <stdio.h>
void main(){
printf("ab\0cd\nxyz");
putchar('\n');
}
Zapomnite, chto '\0' sluzhit priznakom konca stroki v pamyati, a '\n' - v fajle. CHto v
stroke "abcd\n" na konce neyavno uzhe raspolozhen nulevoj bajt:
'a','b','c','d','\n','\0'
CHto stroka "ab\0cd\nxyz" - eto
'a','b','\0','c','d','\n','x','y',z','\0'
CHto stroka "abcd\0" - izbytochna, poskol'ku budet imet' na konce dva nulevyh bajta
(chto ne vredno, no zachem?). CHto printf pechataet stroku do nulevogo bajta, a ne do
zakryvayushchej kavychki.
Programma eta napechataet ab i perevod stroki.
Vopros: chemu raven sizeof("ab\0cd\nxyz")? Otvet: 10.
1.24. Napishite programmu, pechatayushchuyu celye chisla ot 0 do 100.
1.25. Napishite programmu, pechatayushchuyu kvadraty i kuby celyh chisel.
A. Bogatyrev, 1992-95 - 10 - Si v UNIX
1.26. Napishite programmu, pechatayushchuyu summu kvadratov pervyh n celyh chisel.
1.27. Napishite programmu, kotoraya perevodit sekundy v dni, chasy, minuty i sekundy.
1.28. Napishite programmu, perevodyashchuyu skorost' iz kilometrov v chas v metry v sekun-
dah.
1.29. Napishite programmu, shifruyushchuyu tekst fajla putem zameny znacheniya simvola (nap-
rimer, znachenie simvola C zamenyaetsya na C+1 ili na ~C ).
1.30. Napishite programmu, kotoraya pri vvedenii s klaviatury bukvy pechataet na termi-
nale klyuchevoe slovo, nachinayushcheesya s dannoj bukvy. Naprimer, pri vvedenii bukvy 'b'
pechataet "break".
1.31. Napishite programmu, otgadyvayushchuyu zadumannoe vami chislo v predelah ot 1 do 200,
pol'zuyas' podskazkoj s klaviatury "=" (ravno), "<" (men'she) i ">" (bol'she). Dlya uga-
dyvaniya chisla ispol'zujte metod deleniya popolam.
1.32. Napishite programmu, pechatayushchuyu stepeni dvojki
1, 2, 4, 8, ...
Zamet'te, chto, nachinaya s nekotorogo n, rezul'tat stanovitsya otricatel'nym iz-za pere-
polneniya celogo.
1.33. Napishite podprogrammu vychisleniya kvadratnogo kornya s ispol'zovaniem metoda
kasatel'nyh (N'yutona):
x(0) = a
1 a
x(n+1) = - * ( ---- + x(n))
2 x(n)
Iterirovat', poka ne budet | x(n+1) - x(n) | < 0.001
Vnimanie! V dannoj zadache massiv ne nuzhen. Dostatochno hranit' tekushchee i predydu-
shchee znacheniya x i obnovlyat' ih posle kazhdoj iteracii.
1.34. Napishite programmu, raspechatyvayushchuyu prostye chisla do 1000.
1, 2, 3, 5, 7, 11, 13, 17, ...
A. Bogatyrev, 1992-95 - 11 - Si v UNIX
/*#!/bin/cc primes.c -o primes -lm
* Prostye chisla.
*/
#include <stdio.h>
#include <math.h>
int debug = 0;
/* Koren' kvadratnyj iz chisla po metodu N'yutona */
#define eps 0.0001
double sqrt (x) double x;
{
double sq, sqold, EPS;
if (x < 0.0)
return -1.0;
if (x == 0.0)
return 0.0; /* mozhet privesti k deleniyu na 0 */
EPS = x * eps;
sq = x;
sqold = x + 30.0; /* != sq */
while (fabs (sq * sq - x) >= EPS) {
/* fabs( sq - sqold )>= EPS */
sqold = sq;
sq = 0.5 * (sq + x / sq);
}
return sq;
}
/* tablica prostyh chisel */
int is_prime (t) register int t; {
register int i, up;
int not_div;
if (t == 2 || t == 3 || t == 5 || t == 7)
return 1; /* prime */
if (t % 2 == 0 || t == 1)
return 0; /* composite */
up = ceil (sqrt ((double) t)) + 1;
i = 3;
not_div = 1;
while (i <= up && not_div) {
if (t % i == 0) {
if (debug)
fprintf (stderr, "%d podelilos' na %d\n",
t, i);
not_div = 0;
break;
}
i += 2; /*
* Net smysla proveryat' chetnye,
* potomu chto esli delitsya na 2*n,
* to delitsya i na 2,
* a etot sluchaj uzhe obrabotan vyshe.
*/
}
return not_div;
}
A. Bogatyrev, 1992-95 - 12 - Si v UNIX
#define COL 6
int n;
main (argc, argv) char **argv;
{
int i,
j;
int n;
if( argc < 2 ){
fprintf( stderr, "Vyzov: %s chislo [-]\n", argv[0] );
exit(1);
}
i = atoi (argv[1]); /* stroka -> celoe, eyu izobrazhaemoe */
if( argc > 2 ) debug = 1;
printf ("\t*** Tablica prostyh chisel ot 2 do %d ***\n", i);
n = 0;
for (j = 1; j <= i; j++)
if (is_prime (j)){
/* raspechatka v COL kolonok */
printf ("%3d%s", j, n == COL-1 ? "\n" : "\t");
if( n == COL-1 ) n = 0;
else n++;
}
printf( "\n---\n" );
exit (0);
}
1.35. Sostav'te programmu vvoda dvuh kompleksnyh chisel v vide A + B * I (kazhdoe na
otdel'noj stroke) i pechati ih proizvedeniya v tom zhe vide. Ispol'zujte scanf i printf.
Pered tem, kak ispol'zovat' scanf, prover'te sebya: chto neverno v nizheprivedennom ope-
ratore?
int x;
scanf( "%d", x );
Otvet: dolzhno byt' napisano "ADRES ot x", to est' scanf( "%d", &x );
1.36. Napishite podprogrammu vychisleniya kornya uravneniya f(x)=0 metodom deleniya
otrezka popolam. Privedem realizaciyu etogo algoritma dlya poiska celochislennogo kvad-
ratnogo kornya iz celogo chisla (etot algoritm mozhet ispol'zovat'sya, naprimer, v mashin-
noj grafike pri risovanii dug):
/* Maksimal'noe unsigned long chislo */
#define MAXINT (~0L)
/* Opredelim imya-sinonim dlya tipa unsigned long */
typedef unsigned long ulong;
/* Funkciya, koren' kotoroj my ishchem: */
#define FUNC(x, arg) ((x) * (x) - (arg))
/* togda x*x - arg = 0 oznachaet x*x = arg, to est'
* x = koren'_kvadratnyj(arg) */
/* Nachal'nyj interval. Dolzhen vybirat'sya ishodya iz
* osobennostej funkcii FUNC */
#define LEFT_X(arg) 0
#define RIGHT_X(arg) (arg > MAXINT)? MAXINT : (arg/2)+1;
/* KORENX KVADRATNYJ, okruglennyj vniz do celogo.
* Reshaetsya po metodu deleniya otrezka popolam:
* FUNC(x, arg) = 0; x = ?
A. Bogatyrev, 1992-95 - 13 - Si v UNIX
*/
ulong i_sqrt( ulong arg ) {
register ulong mid, /* seredina intervala */
rgt, /* pravyj kraj intervala */
lft; /* levyj kraj intervala */
lft = LEFT_X(arg); rgt = RIGHT_X(arg);
do{ mid = (lft + rgt + 1 )/2;
/* +1 dlya oshibok okrugleniya pri celochislennom delenii */
if( FUNC(mid, arg) > 0 ){
if( rgt == mid ) mid--;
rgt = mid ; /* priblizit' pravyj kraj */
} else lft = mid ; /* priblizit' levyj kraj */
} while( lft < rgt );
return mid;
}
void main(){ ulong i;
for(i=0; i <= 100; i++)
printf("%ld -> %lu\n", i, i_sqrt(i));
}
Ispol'zovannoe nami pri ob®yavlenii peremennyh klyuchevoe slovo register oznachaet, chto
peremennaya yavlyaetsya CHASTO ISPOLXZUEMOJ, i kompilyator dolzhen popytat'sya razmestit' ee
na registre processora, a ne v steke (za schet chego uvelichitsya skorost' obrashcheniya k
etoj peremennoj). |to slovo ispol'zuetsya kak
register tip peremennaya;
register peremennaya; /* podrazumevaetsya tip int */
Ot registrovyh peremennyh nel'zya brat' adres: &peremennaya oshibochno.
1.37. Napishite programmu, vychislyayushchuyu chisla treugol'nika Paskalya i pechatayushchuyu ih v
vide treugol'nika.
C(0,n) = C(n,n) = 1 n = 0...
C(k,n+1) = C(k-1,n) + C(k,n) k = 1..n
n - nomer stroki
V raznyh variantah ispol'zujte cikly, rekursiyu.
1.38. Napishite funkciyu vychisleniya opredelennogo integrala metodom Monte-Karlo. Dlya
etogo vam pridetsya napisat' generator sluchajnyh chisel. Si predostavlyaet standartnyj
datchik CELYH ravnomerno raspredelennyh psevdosluchajnyh chisel: esli vy hotite poluchit'
celoe chislo iz intervala [A..B], ispol'zujte
int x = A + rand() % (B+1-A);
CHtoby poluchat' raznye posledovatel'nosti sleduet zadavat' nekij nachal'nyj parametr
posledovatel'nosti (eto nazyvaetsya "randomizaciya") pri pomoshchi
srand( chislo ); /* luchshe nechetnoe */
CHtoby povtorit' odnu i tu zhe posledovatel'nost' sluchajnyh chisel neskol'ko raz, vy
dolzhny postupat' tak:
srand(NBEG); x=rand(); ... ; x=rand();
/* i povtorit' vse snachala */
srand(NBEG); x=rand(); ... ; x=rand();
Ispol'zuemyj metod polucheniya sluchajnyh chisel takov:
A. Bogatyrev, 1992-95 - 14 - Si v UNIX
static unsigned long int next = 1L;
int rand(){
next = next * 1103515245 + 12345;
return ((unsigned int)(next/65536) % 32768);
}
void srand(seed) unsigned int seed;
{ next = seed; }
Dlya randomizacii chasto pol'zuyutsya takim priemom:
char t[sizeof(long)];
time(t); srand(t[0] + t[1] + t[2] + t[3] + getpid());
1.39. Napishite funkciyu vychisleniya opredelennogo integrala po metodu Simpsona.
/*#!/bin/cc $* -lm
* Vychislenie integrala po metodu Simpsona
*/
#include <math.h>
extern double integral(), sin(), fabs();
#define PI 3.141593
double myf(x) double x;
{ return sin(x / 2.0); }
int niter; /* nomer iteracii */
void main(){
double integral();
printf("%g\n", integral(0.0, PI, myf, 0.000000001));
/* Zamet'te, chto myf, a ne myf().
* Tochnoe znachenie integrala ravno 2.0
*/
printf("%d iteracij\n", niter );
}
A. Bogatyrev, 1992-95 - 15 - Si v UNIX
double integral(a, b, f, eps)
double a, b; /* koncy otrezka */
double eps; /* trebuemaya tochnost' */
double (*f)(); /* podyntegral'naya funkciya */
{
register long i;
double fab = (*f)(a) + (*f)(b); /* summa na krayah */
double h, h2; /* shag i udvoennyj shag */
long n, n2; /* chislo tochek razbieniya i ono zhe udvoennoe */
double Sodd, Seven; /* summa znachenij f v nechetnyh i v
chetnyh tochkah */
double S, Sprev;/* znachenie integrala na dannoj
i na predydushchej iteraciyah */
double x; /* tekushchaya abscissa */
niter = 0;
n = 10L; /* chetnoe chislo */
n2 = n * 2;
h = fabs(b - a) / n2; h2 = h * 2.0;
/* Vychislyaem pervoe priblizhenie */
/* Summa po nechetnym tochkam: */
for( Sodd = 0.0, x = a+h, i = 0;
i < n;
i++, x += h2 )
Sodd += (*f)(x);
/* Summa po chetnym tochkam: */
for( Seven = 0.0, x = a+h2, i = 0;
i < n-1;
i++, x += h2 )
Seven += f(x);
/* Predvaritel'noe znachenie integrala: */
S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven );
do{
niter++;
Sprev = S;
/* Vychislyaem integral s polovinnym shagom */
h2 = h; h /= 2.0;
if( h == 0.0 ) break; /* poterya znachimosti */
n = n2; n2 *= 2;
Seven = Seven + Sodd;
/* Vychislyaem summu po novym tochkam: */
for( Sodd = 0.0, x = a+h, i = 0;
i < n;
i++, x += h2 )
Sodd += (*f)(x);
/* Znachenie integrala */
S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven );
} while( niter < 31 && fabs(S - Sprev) / 15.0 >= eps );
/* Ispol'zuem uslovie Runge dlya okonchaniya iteracij */
return ( 16.0 * S - Sprev ) / 15.0 ;
/* Vozvrashchaem utochnennoe po Richardsonu znachenie */
}
A. Bogatyrev, 1992-95 - 16 - Si v UNIX
1.40. Gde oshibka?
struct time_now{
int hour, min, sec;
} X = { 13, 08, 00 }; /* 13 chasov 08 minut 00 sek.*/
Otvet: 08 - vos'merichnoe chislo (tak kak nachinaetsya s nulya)! A v vos'merichnyh chislah
cifry 8 i 9 ne byvayut.
1.41. Dan tekst:
int i = -2;
i <<= 2;
printf("%d\n", i); /* pechat' sdvinutogo i : -8 */
i >>= 2;
printf("%d\n", i); /* pechataetsya -2 */
Zakommentiruem dve stroki (isklyuchaya ih iz programmy):
int i = -2;
i <<= 2;
/*
printf("%d\n", i); /* pechat' sdvinutogo i : -8 */
i >>= 2;
*/
printf("%d\n", i); /* pechataetsya -2 */
Pochemu teper' voznikaet oshibka? Ukazanie: gde konchaetsya kommentarij?
Otvet: Si ne dopuskaet vlozhennyh kommentariev. Vmesto etogo chasto ispol'zuyutsya
konstrukcii vrode:
#ifdef COMMENT
... zakommentirovannyj tekst ...
#endif /*COMMENT*/
i vrode
/**/ printf("here");/* otladochnaya vydacha vklyuchena */
/* printf("here");/* otladochnaya vydacha vyklyuchena */
ili
/* vyklyucheno(); /**/
vklyucheno(); /**/
A vot deshevyj sposob bystro isklyuchit' operator (s vozmozhnost'yu vosstanovleniya) -
konec kommentariya zanimaet otdel'nuyu stroku, chto pozvolyaet otredaktirovat' takoj
tekst redaktorom pochti ne sdvigaya kursor:
/*printf("here");
*/
1.42. Pochemu programma pechataet nevernoe znachenie dlya i2 ?
A. Bogatyrev, 1992-95 - 17 - Si v UNIX
int main(int argc, char *argv[]){
int i1, i2;
i1 = 1; /* Inicializiruem i1 /
i2 = 2; /* Inicializiruem i2 */
printf("Numbers %d %d\n", i1, i2);
return(0);
}
Otvet: v pervom operatore prisvaivaniya ne zakryt kommentarij - ves' vtoroj operator
prisvaivaniya polnost'yu proignorirovalsya! Pravil'nyj variant:
int main(int argc, char *argv[]){
int i1, i2;
i1 = 1; /* Inicializiruem i1 */
i2 = 2; /* Inicializiruem i2 */
printf("Numbers %d %d\n", i1, i2);
return(0);
}
1.43. A vot "shal'noj" kommentarij.
void main(){
int n = 10;
int *ptr = &n;
int x, y = 40;
x = y/*ptr /* dolzhno byt' 4 */ + 1;
printf( "%d\n", x ); /* pyat' */
exit(0);
}
/* ili takoj primer iz zhizni - vzyat iz perepiski v Relcom */
...
cost = nRecords/*pFactor /* divided by Factor, and */
+ fixMargin; /* plus the precalculated */
...
Rezul'tat nepredskazuem. Delo v tom, chto y/*ptr prevratilos' v nachalo kommentariya!
Poetomu binarnye operacii prinyato okruzhat' probelami.
x = y / *ptr /* dolzhno byt' 4 */ + 1;
1.44. Najdite oshibki v direktivah preprocessora Si |- (vertikal'naya cherta oboznachaet
levyj kraj fajla).
____________________
|- Preprocessor Si - eto programma /lib/cpp
A. Bogatyrev, 1992-95 - 18 - Si v UNIX
|
| #include <stdio.h>
|#include < sys/types.h >
|# define inc (x) ((x) + 1)
|#define N 12;
|#define X -2
|
|... printf( "n=%d\n", N );
|... p = 4-X;
Otvet: v pervoj direktive stoit probel pered #. Diez dolzhen nahodit'sya v pervoj
pozicii stroki. Vo vtoroj direktive v <> nahodyatsya lishnie probely, ne otnosyashchiesya k
imeni fajla - preprocessor ne najdet takogo fajla! V dannom sluchae "krasota" poshla
vo vred delu. V tret'ej - mezhdu imenem makro inc i ego argumentom v kruglyh skobkah
(x) stoit probel, kotoryj izmenyaet ves' smysl makroopredeleniya: vmesto makrosa s
parametrom inc(x) my poluchaem, chto slovo inc budet zamenyat'sya na (x)((x)+1). Zametim
odnako, chto probely posle # pered imenem direktivy vpolne dopustimy. V chetvertom
sluchae pokazana harakternaya opechatka - simvol ; posle opredeleniya. V rezul'tate napi-
sannyj printf() zamenitsya na
printf( "n=%d\n", 12; );
gde lishnyaya ; dast sintaksicheskuyu oshibku.
V pyatom sluchae oshibki net, no nas ozhidaet nepriyatnost' v stroke p=4-X; kotoraya
rasshiritsya v stroku p=4--2; yavlyayushchuyusya sintaksicheski nevernoj. CHtoby izbezhat' podob-
noj situacii, sledovalo by napisat'
p = 4 - X; /* cherez probely */
no eshche proshche (i luchshe) vzyat' makroopredelenie v skobki:
#define X (-2)
1.45. Napishite funkciyu max(x, y), vozvrashchayushchuyu bol'shee iz dvuh znachenij. Napishite
analogichnoe makroopredelenie. Napishite makroopredeleniya min(x, y) i abs(x) (abs -
modul' chisla). Otvet:
#define abs(x) ((x) < 0 ? -(x) : (x))
#define min(x,y) (((x) < (y)) ? (x) : (y))
Zachem x vzyat v kruglye skobki (x)? Predpolozhim, chto my napisali
#define abs(x) (x < 0 ? -x : x )
vyzyvaem
abs(-z) abs(a|b)
poluchaem
(-z < 0 ? --z : -z ) (a|b < 0 ? -a|b : a|b )
U nas poyavilas' "dikaya" operaciya --z; a vyrazhenie a|b<0 sootvetstvuet a|(b<0), s sov-
sem drugim poryadkom operacij! Poetomu zaklyuchenie vseh argumentov makrosa v ego tele
v kruglye skobki pozvolyaet izbezhat' mnogih neozhidannyh problem. Priderzhivajtes' etogo
pravila!
Vot primer, pokazyvayushchij zachem polezno brat' v skobki vse opredelenie:
#define div(x, y) (x)/(y)
Pri vyzove
A. Bogatyrev, 1992-95 - 19 - Si v UNIX
z = sizeof div(1, 2);
prevratitsya v
z = sizeof(1) / (2);
chto ravno sizeof(int)/2, a ne sizeof(int). Variant
#define div(x, y) ((x) / (y))
budet rabotat' pravil'no.
1.46. Makrosy, v otlichie ot funkcij, mogut porozhdat' nepredvidennye pobochnye
effekty:
int sqr(int x){ return x * x; }
#define SQR(x) ((x) * (x))
main(){ int y=2, z;
z = sqr(y++); printf("y=%d z=%d\n", y, z);
y = 2;
z = SQR(y++); printf("y=%d z=%d\n", y, z);
}
Vyzov funkcii sqr pechataet "y=3 z=4", kak my i ozhidali. Makros zhe SQR rasshiryaetsya v
z = ((y++) * (y++));
i rezul'tatom budet "y=4 z=6", gde z sovsem ne pohozhe na kvadrat chisla 2.
1.47. ANSI preprocessor|- yazyka Si imeet operator ## - "sklejka leksem":
#define VAR(a, b) a ## b
#define CV(x) command_ ## x
main(){
int VAR(x, 31) = 1;
/* prevratitsya v int x31 = 1; */
int CV(a) = 2; /* dast int command_a = 2; */
...
}
Starye versii preprocessora ne obrabatyvayut takoj operator, poetomu ran'she ispol'zo-
valsya takoj tryuk:
#define VAR(a, b) a/**/b
v kotorom predpolagaetsya, chto preprocessor udalyaet kommentarii iz teksta, ne zamenyaya
ih na probely. |to ne vsegda tak, poetomu takaya konstrukciya ne mobil'na i pol'zo-
vat'sya eyu ne rekomenduetsya.
1.48. Napishite programmu, raspechatyvayushchuyu maksimal'noe i minimal'noe iz ryada chisel,
vvodimyh s klaviatury. Ne hranite vvodimye chisla v massive, vychislyajte max i min
srazu pri vvode ocherednogo chisla!
____________________
|- ANSI -