You are on page 1of 52

Idź do Język C.

Nowoczesne
programowanie. Wydanie II
• Spis treści
• Przykładowy rozdział Autor: K. N. King
Tłumaczenie: Przemysław Szeremiota
Katalog książek ISBN: 978-83-246-2805-6
Tytuł oryginału: C Programming: A Modern Approach, 2nd Edition
Format: B5, stron: 928
• Katalog online
• Zamów drukowany
katalog
Język C żyje i ma się dobrze. Sprawdź, co nowego w wersji C99!
Twój koszyk • Jak wygląda proces standaryzacji języka?
• Jak komentować kod?
• Jak przygotować projekt programu?
• Dodaj do koszyka
Język C należy do nielicznej grupy języków, które sprawdzają się w środowiskach produkcyjnych,
a jednocześnie nadają się do nauki programowania na uczelniach wyższych. Dzięki logicznej
Cennik i informacje i przejrzystej składni, jasno określonym zasadom wykorzystania oraz ogromnym możliwościom
język ten pomimo swojego wieku cieszy się popularnością i uznaniem. Nawet dziś, kiedy na rynku
• Zamów informacje panują niepodzielnie Java oraz .NET, język C znalazł swoją niszę i świetnie ją wypełnia. Na tym polu
o nowościach żaden współczesny język nie ma z nim żadnych szans!
• Zamów cennik Kolejne wydanie książki rozszerzono między innymi o elementy zawarte w specyfikacji oznaczonej
numerem C99 (ISO 9899:1999). Co jeszcze wyróżnia tę książkę? Jej pierwsze wydanie było
wykorzystywane na kursach programowania prowadzonych przez 225 uczelni. Dzięki temu
Czytelnia zaliczana jest ona do najbardziej znaczących wydawnictw dotyczących języka C. Wydanie drugie
powiela zalety pierwszego, a dodatkowo zostało rozbudowane o jeszcze większą liczbę przykładów,
• Fragmenty książek pytań, ćwiczeń i zadań programistycznych.
online W trakcie pasjonującej lektury – zgadza się, K.N. King potrafi w ten sposób pisać o swoim ulubionym
języku – poznasz wszystkie aspekty programowania w języku C, począwszy od jego historii, poprzez
fundamentalne pojęcia funkcji, zmiennych, a skończywszy na zarządzaniu pamięcią oraz wykorzystaniu
wskaźników. „Język C. Nowoczesne programowanie. Wydanie II” to obowiązkowa pozycja dla
każdego studenta poznającego tajniki tego języka. Programiści znający język C niewątpliwie
docenią kunszt autora, a książka znajdzie zastosowanie jako przekrojowy przewodnik – taka
pozycja powinna być na półce każdego programisty!
Poznaj język C, korzystając z uznanego podręcznika!

Kontakt

Helion SA
ul. Kościuszki 1c
44-100 Gliwice
tel. 32 230 98 63
e-mail: helion@helion.pl
© Helion 1991–2010
SPIS TRECI

Wstp 19

1. WPROWADZENIE 29
1.1. Historia jzyka C 29
Pocztki 29
Standaryzacja 30
Jzyki oparte na C 31
1.2. Mocne i sabsze strony jzyka C 32
Mocne strony 33
Saboci 33
Efektywne stosowanie jzyka C 34

2. FUNDAMENTY JZYKA C 39
2.1. Piszemy prosty program 39
Wywietlamy cytat 40
Kompilacja i konsolidacja 40
Zintegrowane rodowiska programistyczne 41
2.2. Ogólna posta prostego programu w C 42
Dyrektywy preprocesora 43
Funkcje 43
Instrukcje 44
Wypisywanie cigów znakowych 45
2.3. Komentarze 46
2.4. Zmienne i przypisania 48
Typy 48
Deklaracje 48
Przypisania 49
Wypisywanie wartoci zmiennej 50
Obliczanie gabarytu przesyki 51
Inicjalizacja 52
Wypisywanie wartoci wyrae 53

5
6 Spis treci

2.5. Wczytywanie danych 53


Obliczanie gabarytu przesyki (podejcie drugie) 54
2.6. Definiowanie nazw dla staych 55
Konwersja skali Fahrenheita na skal Celsjusza 55
2.7. Identyfikatory 57
Sowa kluczowe 58
2.8. Ogólny ukad programu C 58

3. FORMATOWANIE WEJCIA-WYJCIA 69
3.1. Funkcja printf 69
Specyfikatory konwersji 70
Wykorzystanie printf do formatowania liczb 72
Znaki sterujce 73
3.2. Funkcja scanf 74
Dziaanie funkcji scanf 76
Zwyke znaki w cigu formatujcym funkcji scanf 78
Skutki mylenia printf ze scanf 79
Dodawanie uamków 79

4. WYRAENIA 85
4.1. Operatory arytmetyczne 86
Pierwszestwo i czno operatorów 87
Obliczanie cyfry kontrolnej kodu kreskowego 88
4.2. Operatory przypisania 91
Przypisania proste 91
L-wartoci 92
Przypisania zoone 93
4.3. Operatory inkrementacji i dekrementacji 94
4.4. Obliczanie wartoci wyra e 96
Kolejno obliczania podwyrae 97
4.5. Instrukcje wyra eniowe 98

5. INSTRUKCJE WYBORU 107


5.1. Wyra enia logiczne 108
Operatory relacji 108
Operatory porówna 109
Operatory logiczne 109
5.2. Instrukcja if 111
Instrukcje blokowe 112
Klauzula else 112
Kaskadowe instrukcje if 114
Obliczanie prowizji brokera giedowego 115
Problem „bezpaskiego” else 116
Wyraenia warunkowe 117
Wartoci boolowskie w C89 118
Wartoci boolowskie w C99 120
5.3. Instrukcja switch 120
Rola instrukcji break 123
Wypisywanie daty w zapisie urzdowym 124
Spis treci 7

6. INSTRUKCJE PTLI 133


6.1. Instrukcja while 134
Ptle nieskoczone 135
Wypisywanie tabeli kwadratów liczb 136
Obliczanie sumy szeregu liczb 137
6.2. Instrukcja do 137
Obliczanie liczby cyfr w liczbie cakowitej 138
6.3. Instrukcja for 139
Idiomy instrukcji for 141
Pomijanie wyrae w instrukcji for 142
Instrukcje for w C99 143
Operator przecinka 143
Wypisywanie tabeli kwadratów liczb (podejcie drugie) 144
6.4. Przerywanie ptli 146
Instrukcja break 146
Instrukcja continue 147
Instrukcja goto 148
Saldo konta 149
6.5. Instrukcja pusta 151

7. PODSTAWOWE TYPY C 161


7.1. Typy cakowite 161
Typy cakowite w C99 164
Literay cakowite 164
Literay cakowite w C99 166
Przepenienie zakresu 166
Wczytywanie i wypisywanie wartoci cakowitych 166
Sumowanie szeregu liczb cakowitych (podejcie drugie) 167
7.2. Typy zmiennoprzecinkowe 168
Literay zmiennoprzecinkowe 170
Wczytywanie i wypisywanie wartoci zmiennoprzecinkowych 170
7.3. Typy znakowe 171
Operacje na znakach 172
Znaki ze znakiem i bez znaku 173
Typy arytmetyczne 173
Znaki sterujce 174
Funkcje do manipulowania znakami 176
Wczytywanie i wypisywanie znaków funkcjami scanf i printf 176
Wczytywanie i wypisywanie znaków funkcjami getchar i putchar 177
Okrelanie dugoci komunikatu 179
7.4. Konwersja typów 180
Zwyczajne konwersje arytmetyczne 181
Konwersja przy przypisaniu 183
Niejawne konwersje w C99 184
Rzutowanie 185
7.5. Definicje typów 186
Zalety definicji typów 187
Definicje typów a przenono programów 188
7.6. Operator sizeof 189
8 Spis treci

8. TABLICE 199
8.1. Tablice jednowymiarowe 199
Indeksowanie tablic 200
Odwracanie szeregu liczbowego 202
Inicjalizacja tablicy 203
Inicjalizatory desygnowane 203
Sprawdzanie, czy liczba zawiera powtarzajce si cyfry 204
Operator sizeof dla tablic 205
Naliczanie odsetek 206
8.2. Tablice wielowymiarowe 208
Inicjalizowanie tablic wielowymiarowych 209
Stae tablicowe 210
Rozdawanie kart 211
8.3. Tablice o zmiennej liczbie elementów (C99) 212

9. FUNKCJE 223
9.1. Definiowanie i wywoywanie funkcji 223
Obliczanie rednich 224
Odliczanie 225
Wywietlanie napisu (kolejne podejcie) 226
Definicja funkcji 227
Wywoanie funkcji 229
Sprawdzanie, czy podana liczba jest liczb pierwsz 230
9.2. Deklaracja funkcji 231
9.3. Argumenty 233
Konwersje argumentów 234
Argumenty tablicowe 235
Parametry tablicowe o zmiennym rozmiarze 238
Deklaracje parametrów tablicowych ze sowem static 240
Literay tablicowe 241
9.4. Instrukcja return 242
9.5. Zako czenie programu 243
Funkcja exit 243
9.6. Rekurencja 244
Algorytm quicksort 246
quicksort 248

10. ORGANIZACJA PROGRAMU 261


10.1. Zmienne lokalne 261
Zmienne statyczne funkcji 262
Parametry 263
10.2. Zmienne zewntrzne 263
Przykad. Stos implementowany na zmiennych zewntrznych 263
Zalety i wady zmiennych zewntrznych 264
Zgadywanka liczbowa 266
10.3. Bloki 270
10.4. Zasig zmiennych 271
10.5. Organizacja programu w C 272
Sia rozdania pokerowego 273
Spis treci 9

11. WSKA NIKI 283


11.1. Zmienne wska nikowe 283
Deklarowanie zmiennych wska nikowych 284
11.2. Operator adresu i wyuskania 285
Operator adresu 285
Operator wyuskania 286
11.3. Przypisania a wska niki 287
11.4. Wska niki jako argumenty funkcji 289
Wyszukiwanie najwikszego i najmniejszego elementu tablicy 291
Ochrona argumentów za pomoc const 293
11.5. Wska niki jako wartoci zwracane 293

12. WSKA NIKI A TABLICE 301


12.1. Arytmetyka wska ników 302
Dodawanie liczby do wska nika 303
Odejmowanie liczby od wska nika 303
Odejmowanie wska nika od wska nika 304
Porównywanie wska ników 304
Wska niki do literaów tablicowych 304
12.2. Przetwarzanie tablic na bazie wska ników 305
czenie operatorów * i ++ 306
12.3. Nazwa tablicy jako wska nik 307
Odwracanie szeregu liczbowego 308
Argumenty tablicowe (ponownie) 309
Wska nik jako nazwa tablicy 311
12.4. Wska niki a tablice wielowymiarowe 311
Przetwarzanie elementów tablicy wielowymiarowej 311
Przetwarzanie wierszy tablicy wielowymiarowej 312
Przetwarzanie kolumn tablicy wielowymiarowej 313
Nazwa tablicy wielowymiarowej jako wska nik 314
12.5. Wska niki a tablice o zmiennym rozmiarze (C99) 314

13. CI GI ZNAKÓW 323


13.1. Literay napisowe 323
Znaki sterujce w literaach napisowych 324
Kontynuacja literau napisowego w nowym wierszu 324
Literay napisowe a pami programu 325
Operacje na literaach napisowych 326
Literay napisowe a literay znakowe 326
13.2. Zmienne napisowe 327
Inicjalizowanie zmiennej napisowej 328
Tablice znaków a wska niki do znaków 329
13.3. Wczytywanie i wypisywanie napisów 330
Wypisywanie napisów funkcjami printf i puts 330
Wczytywanie cigów znaków funkcjami scanf i gets 331
Wczytywanie napisów znak po znaku 333
13.4. Odwoania do pojedynczych znaków w cigu 334
13.5. Funkcje biblioteczne jzyka C 335
Funkcja strcpy (kopiowanie cigów) 336
Funkcja strlen (dugo cigu) 338
Funkcja strcat (czenie cigów) 338
10 Spis treci

Funkcja strcmp (porównywanie cigów) 339


Wypisywanie notatek kalendarzowych 340
13.6. Idiomy 343
Szukanie koca cigu 343
Kopiowanie cigu 345
13.7. Tablice cigów znaków 347
Argumenty wywoania programu 349
Weryfikacja nazw planet 351

14. PREPROCESOR 363


14.1. Jak dziaa preprocesor 363
14.2. Dyrektywy preprocesora 366
14.3. Makrodefinicje 367
Makrodefinicje proste 367
Makrodefinicje sparametryzowane 370
Operator # 373
Operator ## 374
Ogólne waciwoci makrodefinicji 375
Nawiasy w makrodefinicjach 376
Tworzenie dugich makrodefinicji 377
Makrodefinicje predefiniowane 379
Dodatkowe makrodefinicje predefiniowane w C99 380
Puste argumenty makrodefinicji 381
Makrodefinicje o zmiennej liczbie argumentów 382
Identyfikator __func__ 383
14.4. Warunkowa kompilacja kodu 384
Dyrektywy #if i #endif 384
Operator defined 385
Dyrektywy #ifdef i #ifndef 386
Dyrektywy #elif i #else 386
Zastosowania warunkowej kompilacji kodu 387
14.5. Inne dyrektywy 388
Dyrektywa #error 389
Dyrektywa #line 390
Dyrektywa #pragma 391
Operator _Pragma 391

15. DUE PROGRAMY 401


15.1. Pliki ródowe 401
15.2. Pliki nagówkowe 403
Dyrektywa #include 403
Wspólne makrodefinicje i synonimy typów 405
Wspólne prototypy funkcji 406
Wspólne deklaracje zmiennych 407
Zagniedone dyrektywy #include 409
Ochrona plików nagówkowych 410
Dyrektywy #error w plikach nagówkowych 411
15.3. Podzia programu na pliki 411
Formatowanie tekstu 412
15.4. Budowanie programu z wielu plików 419
Pliki Makefile 419
Bdy konsolidowania programu 422
Spis treci 11

Przebudowa programu 422


Definiowanie makrodefinicji na zewntrz programu 425

16. STRUKTURY, UNIE I WYLICZENIA 431


16.1. Zmienne strukturalne 431
Deklarowanie zmiennych strukturalnych 432
Inicjalizowanie zmiennych strukturowych 433
Inicjalizatory desygnowane 434
Operacje na strukturach 435
16.2. Typy strukturowe 436
Deklarowanie znacznika struktury 437
Definiowanie typu strukturowego 438
Struktury jako argumenty i wartoci zwracane funkcji 439
Literay strukturowe 440
16.3. Tablice i struktury zagnie d one 441
Struktury struktur 441
Tablice struktur 442
Inicjalizowanie tablic struktur 443
Zarzdzanie baz danych magazynu 444
16.4. Unie 450
Unie dla oszczdnoci 452
Unie jako mieszane struktury danych 454
Pole „wyrónika” w unii 455
16.5. Wyliczenia 456
Znaczniki i typy wyliczeniowe 457
Wyliczenia jako liczby cakowite 458
Wyliczenia jako wyróniki unii 459

17. ZAAWANSOWANE ZASTOSOWANIA WSKA NIKÓW 469


17.1. Dynamiczny przydzia pamici 470
Funkcje przydziau pamici 470
Wska niki puste 471
17.2. Dynamiczny przydzia cigów znaków 472
Przydzia pamici dla cigu znaków za pomoc funkcji malloc 472
Przydziay dynamiczne
w funkcjach operujcych na cigach znaków 473
Tablice cigów przydzielanych dynamicznie 474
Wypisywanie notatek kalendarzowych (podejcie drugie) 475
17.3. Tablice przydzielane dynamicznie 476
Przydzia pamici dla cigu znaków za pomoc funkcji malloc 477
Funkcja calloc 478
Funkcja realloc 478
17.4. Zwalnianie pamici 479
Funkcja free 480
Problem „wiszcych” wska ników 481
17.5. Listy elementów 481
Deklarowanie typu wza 482
Tworzenie wza listy 483
Operator -> 484
Wstawianie wza na pocztek listy 484
Przeszukiwanie listy 487
Usuwanie wza z listy 488
12 Spis treci

Listy uporzdkowane 490


Zarzdzanie baz danych magazynu (drugie podejcie) 491
17.6. Wska niki do wska ników 496
17.7. Wska niki do funkcji 497
Wska niki do funkcji w roli argumentów 497
Funkcja qsort 498
Inne zastosowania wska ników do funkcji 501
Tablice funkcji trygonometrycznych 502
17.8. Wska niki zastrze one (C99) 503
17.9. Elastyczne skadowe tablicowe (C99) 505

18. DEKLARACJE 517


18.1. Skadnia deklaracji 517
18.2. Klasy przydziau 519
Wasnoci zmiennych 519
Klasa przydziau auto 520
Klasa przydziau static 521
Klasa przydziau extern 522
Klasa przydziau register 523
Klasa przydziau funkcji 524
Podsumowanie 525
18.3. Kwalifikatory typów 526
18.4. Deklaratory 528
Rozszyfrowywanie zawiych deklaracji 529
Stosowanie synonimów typów dla uproszczenia deklaracji 531
18.5. Inicjalizatory 531
Zmienne niezainicjalizowane 533
18.6. Funkcje inline (C99) 533
Definicje rozwijane w miejscu wywoania 534
Ograniczenia funkcji rozwijanych w miejscu wywoania 536
Funkcje inline w GCC 536

19. PROJEKT PROGRAMU 545


19.1. Moduy 546
Spójno i wspózaleno 548
Rodzaje moduów 548
19.2. Ukrywanie informacji 549
Modu obsugi stosu 550
19.3. Abstrakcyjne typy danych 553
Hermetyzacja 554
Typy niepene 554
19.4. Stos jako abstrakcyjny typ danych (ADT) 555
Definiowanie interfejsu stosu w wersji ADT 555
Implementacja stosu w wersji ADT (na bazie tablicy) 557
Zmiana typu elementu w stosie w wersji ADT 559
Implementowanie stosu ADT (na bazie tablicy dynamicznej) 560
Implementowanie stosu ADT (na bazie listy) 562
19.5. Problemy projektowe przy ADT 564
Nomenklatura 564
Obsuga bdów 565
Uniwersalny typ ADT 565
ADT w nowszych jzykach programowania 566
Spis treci 13

20. PROGRAMOWANIE NISKOPOZIOMOWE 571


20.1. Operatory bitowe 571
Operatory przesuni bitowych 572
Negacja, iloczyn, suma i suma wyczajca 573
Operatory bitowe w odwoaniach
do poszczególnych bitów wartoci liczbowych 574
Operatory bitowe w odwoaniach do pól bitowych 576
Szyfrowanie XOR 577
20.2. Pola bitowe w strukturach 578
Reprezentacja pól bitowych 580
20.3. Inne niskopoziomowe techniki programistyczne 581
Definiowanie typów maszynowych 581
Unie jako perspektywy 582
Wska niki jako adresy 584
Podgld pamici 584
Kwalifikator typu volatile 586

21. BIBLIOTEKA STANDARDOWA 593


21.1. Stosowanie biblioteki standardowej 593
Nazewnictwo w bibliotece standardowej 594
Funkcje ukrywane przez makrodefinicje 595
21.2. Przegld biblioteki standardowej C89 596
Diagnostyka 596
Obsuga znaków 596
Bdy 596
Cechy typów zmiennoprzecinkowych 596
Rozmiary typów cakowitoliczbowych 596
Lokalizacja programów 597
Matematyka 597
Skoki nielokalne 597
Obsuga sygnaów 597
Zmienne listy argumentów 597
Podstawowe definicje 597
Wejcie-wyjcie 597
Narzdzia 598
Obsuga cigów znaków 598
Daty i godziny 598
21.3. Uzupenienia i zmiany w C99 598
Arytmetyka liczb zespolonych 599
rodowisko implementacji zmiennoprzecinkowej 599
Znakowe konwersje typów cakowitoliczbowych 599
Alternatywny zapis skadni C 599
Wartoci i typy logiczne 599
Typy cakowitoliczbowe 599
Matematyka na uniwersalnych typach 599
Operacje na znakach wielobajtowych 600
Narzdzia mapowania i klasyfikacji znaków wielobajtowych 600
21.4. Nagówek <stddef.h> — definicje podstawowe 600
21.5. Nagówek <stdbool.h> (C99) — typy i wartoci logiczne 601
14 Spis treci

22. WEJCIE-WYJCIE 605


22.1. Strumienie 606
Wska niki plikowe 606
Strumienie standardowe a przekierowania 607
Pliki tekstowe i pliki binarne 608
22.2. Operacje na plikach 609
Otwieranie pliku 610
Tryby dostpu do plików 611
Zamykanie pliku 612
Doczanie pliku do otwartego strumienia 613
Pobieranie nazw plików z wiersza polecenia 613
Sprawdzanie moliwoci otwarcia pliku 614
Pliki tymczasowe 615
Buforowanie plików 616
Inne operacje na plikach 618
22.3. Formatowanie wejcia-wyjcia 619
Funkcje …printf 619
Specyfikatory konwersji dla funkcji …printf 620
Zmiany specyfikatorów konwersji w C99 622
Przykady specyfikacji konwersji dla funkcji …printf 624
Funkcje …scanf 626
Cigi formatujce funkcji …scanf 627
Specyfikacje konwersji funkcji …scanf 628
Zmiany specyfikatorów konwersji w C99 631
Przykady dla funkcji scanf 631
Wykrywanie koca strumienia wejciowego i bdów 632
22.4. Wejcie-wyjcie znakowe 635
Funkcje wyjcia 635
Funkcje wejcia 636
Kopiowanie pliku 637
22.5. Wierszowe wejcie-wyjcie 638
Funkcje wyjcia 638
Funkcje wejcia 639
22.6. Blokowe wejcie-wyjcie 640
22.7. Pozycjonowanie w plikach 641
Modyfikowanie pliku rekordów bazy danych 643
22.8. Funkcje wejcia-wyjcia w pamici 644
Funkcje wyjcia 645
Funkcje wejcia 646

23. OBSUGA LICZB I DANYCH ZNAKOWYCH 659


23.1. Nagówek <float.h> — cechy typów zmiennoprzecinkowych 659
23.2. Nagówek <limits.h> — rozmiary typów cakowitych 662
23.3. Nagówek <math.h> — matematyka 664
Bdy 664
Funkcje trygonometryczne 665
Funkcje hiperboliczne 666
Funkcje wykadnicze i logarytmiczne 666
Funkcje potgowe 667
Najblisza liczba cakowita, warto bezwzgldna,
reszta z dzielenia 668
Spis treci 15

23.4. Nagówek <math.h> — matematyka (C99) 669


Standard zmiennoprzecinkowy IEEE 669
Typy 671
Makrodefinicje 671
Bdy 672
Funkcje 673
Makrodefinicje klasyfikujce 674
Funkcje trygonometryczne 675
Funkcje hiperboliczne 675
Funkcje wykadnicze i logarytmiczne 676
Funkcje potgowe i funkcje wartoci bezwzgldnej 677
Funkcje bdów i funkcje gamma 678
Funkcje zaokrglania 679
Funkcje reszty z dzielenia 680
Funkcja manipulacji 680
Funkcje maksimum, minimum i rónicy dodatniej 681
Zmiennoprzecinkowy iloczyn-suma 682
Makrodefinicje porówna 683
23.5. Nagówek <ctype.h> — obsuga znaków 684
Funkcje klasyfikacji znaków 684
Test funkcji klasyfikacji znaków 685
Funkcje mapowania wielkoci liter 686
Test funkcji zmiany wielkoci liter 687
23.6. Nagówek <string.h> — obsuga cigów znaków 687
Funkcje kopiujce 688
Funkcje czenia cigów 689
Funkcje porówna 690
Funkcje wyszukujce 691
Róne 695

24. OBSUGA BDÓW 699


24.1. Nagówek <assert.h> — diagnostyka 700
24.2. Nagówek <errno.h> — bdy 701
Funkcje perror i strerror 702
24.3. Nagówek <signal.h> — obsuga sygnaów 703
Makrodefinicje sygnaów 704
Funkcja signal 704
Predefiniowane funkcje obsugi sygnaów 705
Funkcja raise 707
Testowanie mechanizmu sygnaów 707
24.4. Nagówek <setjmp.h> — skoki nielokalne 708
Testowanie setjmp/longjmp 709

25. „MIDZYNARODÓWKA” 715


25.1. Nagówek <locale.h> — rodowiska jzykowe 716
Kategorie 716
Funkcja setlocale 717
Funkcja localeconv 719
25.2. Znaki wielobajtowe i znaki poszerzone 722
Znaki wielobajtowe 723
Znaki poszerzone 724
Unicode i uniwersalny zestaw znaków UCS 724
16 Spis treci

Kodowanie Unicode 725


Funkcje konwersji znaków poszerzonych i wielobajtowych 727
Funkcje konwersji cigów znaków poszerzonych i wielobajtowych 729
25.3. Dwuznaki i trójznaki 729
Trójznaki 730
Dwuznaki 731
Nagówek <iso646.h> — symbole alternatywne 731
25.4. Uniwersalne nazwy znaków (C99) 732
25.5. Nagówek <wchar.h> (C99) — dodatkowe narzdzia
dla znaków poszerzonych i wielobajtowych 733
Orientacja strumienia 734
Funkcje formatowanego wejcia-wyjcia
dla znaków poszerzonych 735
Funkcje wejcia-wyjcia dla znaków poszerzonych 737
Obsuga cigów znaków poszerzonych 738
Funkcja konwersji dat i godzin na cigi znaków poszerzonych 743
Dodatkowe funkcje konwersji znaków poszerzonych
i wielobajtowych 743
25.6. Nagówek <wctype.h> (C99) — klasyfikacja
znaków poszerzonych 747
Funkcje klasyfikacji znaków poszerzonych 747
Rozszerzalne funkcje klasyfikacji znaków poszerzonych 748
Funkcje zmiany wielkoci liter dla znaków poszerzonych 749
Rozszerzalne funkcje zmiany wielkoci liter
znaków poszerzonych 750

26. RÓNE 755


26.1. Nagówek <stdarg.h> — zmienna liczba argumentów 755
Wywoanie funkcji o zmiennej liczbie argumentów 758
Funkcje v…printf 758
Funkcje v…scanf 759
26.2. Nagówek <stdlib.h> — inne narzdzia 760
Funkcje konwersji liczbowych 761
Testowanie funkcji konwersji liczbowych 762
Funkcje sekwencji pseudolosowych 764
Testowanie funkcji generowania liczb pseudolosowych 765
Komunikacja ze rodowiskiem wykonawczym 766
Wyszukiwanie i sortowanie 768
Okrelanie odlegoci 769
Funkcje arytmetyki liczb cakowitych 770
26.3. Nagówek <time.h> — daty i godziny 771
Funkcje operujce na datach i godzinach 772
Funkcje konwersji dat i godzin 774
Wypisywanie daty i godziny 778

27. ROZSZERZONE OPERACJE MATEMATYCZNE W C99 787


27.1. Nagówek <stdint.h> — typy cakowite 788
Typy nagówka <stdint.h> 788
Ograniczenia typów o okrelonym rozmiarze 790
Ograniczenia pozostaych typów cakowitych 790
Makrodefinicje dla staych cakowitych 791
Spis treci 17

27.2. Nagówek <inttypes.h> — konwersje typów cakowitych 792


Makrodefinicje dla specyfikatorów konwersji 792
Funkcje obsugi najszerszych typów 793
27.3. Liczby zespolone (C99) 795
Definicja liczb zespolonych 795
Arytmetyka liczb zespolonych 797
Typy zespolone w C99 797
Operacje na wartociach zespolonych 798
Reguy konwersji dla typów zespolonych 798
27.4. Nagówek <complex.h> (C99) — arytmetyka
liczb zespolonych 800
Makrodefinicje nagówka <complex.h> 800
CX_LIMITED_RANGE 801
Funkcje nagówka <complex.h> 802
Funkcje trygonometryczne 802
Funkcje hiperboliczne 803
Funkcje wykadnicze i logarytmiczne 804
Funkcje potgowe i funkcje wartoci bezwzgldnych 804
Inne 805
Szukanie pierwiastków równania kwadratowego 805
27.5. Nagówek <tgmath.h> (C99) — matematyka bez typów 806
Makrodefinicje rozprowadzajce wywoania
funkcji matematycznych 807
Wywoania makrodefinicji rozprowadzajcych 807
27.6. Nagówek <fenv.h> (C99)
— rodowisko zmiennoprzecinkowe 810
Stany i tryby jednostki zmiennoprzecinkowej 810
Makrodefinicje nagówka <fenv.h> 811
FENV_ACCESS 811
Funkcje wyjtków zmiennoprzecinkowych 813
Funkcje zaokrglania 814
Funkcje rodowiska 815

Dodatek A Operatory jzyka C 819


Dodatek B C99 kontra C89 821
Dodatek C C89 kontra K&R 827
Dodatek D Funkcje biblioteki standardowej 831
Dodatek E Zestaw znaków ASCII 893
Bibliografia 895
Skorowidz 899
3 Formatowanie
wejcia-wyjcia
W poszukiwaniu nieosigalnego na przeszkodzie staje tylko prostota.

Do najczciej wykorzystywanych funkcji bibliotecznych jzyka C nale printf


i scanf, suce do obsugi formatowanego wejcia i wyjcia programu. W tym
rozdziale przekonasz si o moliwociach tych funkcji, ale te o koniecznej ostro-
noci w ich stosowaniu. W podrozdziale 3.1 zajmiemy si funkcj printf. Gów-
nym zagadnieniem podrozdziau 3.2 bdzie funkcja scanf. W adnym z pod-
rozdziaów nie zgbimy jednak wszystkich detali — niektóre bd musiay
poczeka do rozdziau 22.

3.1. Funkcja printf


Funkcja printf suy do wypisywania na wyjciu programu zawartoci cigu
znaków, okrelanego mianem cigu formatujcego, który moe zawiera sym-
bole zastpcze dla wartoci zmiennych wstawianych do cigu wypisywanego.
W wywoaniu funkcji printf musi si znajdowa cig formatujcy, uzupeniony
wartociami, które maj by podstawione w odpowiednie miejsca cigu na wyjciu
programu:
printf(cig-formatujcy, wyraenie1, wyraenie1, …);

Wartoci przekazywane do podstawienia do cigu formatujcego mog by sta-


ymi, zmiennymi albo caymi wyraeniami. Nie istnieje ograniczenie liczby war-
toci wypisywanych w ramach pojedynczego wywoania funkcji printf.
Cig formatujcy moe zawiera zarówno zwyczajne znaki drukowalne, jak
i tak zwane specyfikatory konwersji, rozpoczynajce si od znaku %. Specyfi-
kator konwersji to symbol zastpczy reprezentujcy warto, która ma zosta
wstawiona w dane miejsce cigu formatujcego, wraz z opisem sposobu wypisa-
nia wartoci. Informacje znajdujce si za znakiem % okrelaj sposób konwersji

69
70 Rozdzia 3. Formatowanie wejcia-wyjcia

przekazanej wartoci z jej reprezentacji wewntrznej (binarnej) na reprezentacj


drukowan (znakow) — std pojcie „specyfikatora konwersji”. Na przykad
specyfikator konwersji %d mówi, e printf ma zamieni przekazan warto
typu int z jej reprezentacji binarnej na cig znaków kolejnych cyfr. Podobnie
specyfikator %f nakazuje zamian wartoci zmiennoprzecinkowej (typu float)
na znakow.
Zwyczajne znaki zawarte w cigu formatujcym s wypisywane bez modyfi-
kacji. Specyfikatory konwersji s natomiast zastpowane przez znakowe repre-
zentacje przekazanych wartoci. We my nastpujcy przykad:
int i, j;
float x, y;

i = 10;
j = 20;
x = 43.2892f;
y = 5527.0f;

printf("i = %d, j = %d, x = %f, y = %f\n", i, j, x, y);

Takie wywoanie funkcji printf spowoduje wypisanie na wyjciu:


i = 10, j = 20, x = 43.289200, y = 5527.000000

Zwyczajne znaki w cigu formatujcym zostay po prostu skopiowane na wyjcie.


Cztery specyfikatory konwersji zostay za zastpione przez odpowiednio repre-
zentowane wartoci zmiennych i, j, x i y (w tej kolejnoci).

Specyfikatory konwersji
Specyfikatory konwersji pozwalaj programistom zachowa du doz kontroli
nad wygldem (formatem) wypisywanych cigów i wartoci. Z drugiej strony
bywaj skomplikowane i trudne do ogarnicia. Istotnie, szczegóowe opisywanie
specyfikatorów konwersji byoby na tym wstpnym etapie omówienia przed-
wczesne. Zapoznamy si wic tylko z najwaniejszymi cechami i moliwociami
dawanymi przez specyfikatory.
W rozdziale 2. zauwaylimy, e specyfikator konwersji moe zawiera infor-
macje sterujce formatowaniem wartoci. W szczególnoci zastosowalimy spe-
cyfikator %.1f, aby ograniczy liczb wypisywanych cyfr po przecinku w war-
toci typu float. Ogólniej rzecz biorc, specyfikator konwersji moe przyj
posta %m.pX albo %-m.pX, gdzie m i p to stae cakowite, a X to litera. Wartoci
m i p s opcjonalne. W przypadku nieobecnoci p nie stosuje si równie kropki
oddzielajcej m od p. W specyfikatorze konwersji %10.2f m wynosi 10, p wy-
nosi 2, a X to f. W specyfikatorze %10f m wynosi 10, p (wraz z kropk) zostao
pominite, a X to f. Za to w %.2f m zostao pominite, a p wynosi 2.
Minimalna szeroko pola m okrela minimaln liczb znaków, jaka zostanie
wypisana na wyjciu przy wypisywaniu wartoci. Jeli wypisywana warto jest
w reprezentacji znakowej krótsza ni m znaków, zostanie wyrównana do minimal-
nej szerokoci pola i do prawej strony pola (innymi sowy, przed waciw war-
to wstawiona bdzie odpowiednia liczba spacji). Na przykad specyfikator %4d
3.1. Funkcja printf 71

Standard nie wymaga od kompilatorów jzyka C sprawdzania, czy liczba specy-


fikatorów konwersji okrelona w cigu formatujcym odpowiada liczbie przeka-
zanych wartoci. Ponisze wywoanie funkcji printf posiada wicej specyfika-
torów konwersji ni wartoci do wypisania:
printf("%d %d\n", i); /*** LE ***/

Funkcja printf wypisze poprawnie warto zmiennej i, a nastpnie wypisze


drug — oczekiwan, ale nieokrelon — warto liczbow. Podobnie kopotliwy
jest nadmiar wartoci w stosunku do specyfikatorów konwersji:
printf("%d\n", i, j); /*** LE ***/

W tym przypadku funkcja printf wypisze jedynie warto i, a warto j zosta-


nie pominita.
Kompilatory nie s te zobligowane do sprawdzania, czy specyfikatory kon-
wersji odpowiadaj typom przekazywanych wartoci. Jeli programista zastosuje
nieodpowiednie specyfikatory, program moe wypisa na wyjciu kompletne
bzdury. We my dla przykadu wywoanie funkcji printf, w którym zmienna i
typu int i zmienna x typu float zostan przekazane w zej kolejnoci:
printf("%f %d\n", i, x); /*** LE ***/

Poniewa funkcja printf realizuje wytyczne z cigu formatujcego, wypisze


na wyjciu warto float, a za ni warto int. Niestety, obie wartoci jako le
zinterpretowane bd niepoprawne.

spowoduje wypisanie wartoci 123 jako ·123 (w caym biecym rozdziale


w miejsce niewidocznych spacji ilustrujcych formatowanie wartoci bd wsta-
wiane znaki ·). Z kolei kiedy wypisywana warto bdzie dusza ni m znaków,
pole zostanie automatycznie poszerzone do szerokoci potrzebnej do zmieszczenia
wartoci: specyfikator %4d dla wartoci 12345 spowoduje wypisanie na wyjciu
12345 — bez ucinania cyfr. Znak minusa przed m wymusza wyrównanie wartoci
w polu do lewej strony: specyfikator %4d dla wartoci 123 da na wyjciu 123·.
Znaczenie specyfikatora precyzji p jest trudniejsze do opisania, poniewa jego
dziaanie jest zalene od X, czyli waciwego specyfikatora konwersji. X okrela
rodzaj konwersji do przeprowadzenia przed wypisaniem wartoci. Do najpopu-
larniejszych konwersji wartoci liczbowych nale:
Q d — konwersja wartoci cakowitej do postaci dziesitnej. W takim ukadzie
p oznacza minimaln liczb cyfr do wypisania (w razie potrzeby przed wypi-
sywan liczb dopisywane s zera). Przy braku p uznaje si, e ma ono warto
1 (innymi sowy, %d jest tym samym co %.1d).
Q e — konwersja wartoci zmiennoprzecinkowej do postaci wykadnikowej
(w tzw. notacji naukowej). W takim ukadzie p okrela liczb cyfr do wypi-
sania po przecinku dziesitnym (warto domylna to 6). Dla p równego 0
cz po przecinku nie jest wywietlana w ogóle.
Q f — konwersja wartoci zmiennoprzecinkowej do postaci dziesitnej, bez
wykadnika. W tym ukadzie p ma takie samo znaczenie jak przy konwersji e.
72 Rozdzia 3. Formatowanie wejcia-wyjcia

Q g — konwersja wartoci zmiennoprzecinkowej do postaci wykadnikowej albo


dziesitnej, zalenie od rozmiaru liczby. W tym ukadzie p oznacza maksy-
maln liczb cyfr znaczcych (nie cyfr po przecinku) do wypisania. Inaczej ni
przy konwersji f, konwersja g nie bdzie wypisywaa zer po prawej stronie
wartoci. Co wicej, jeli konwertowana warto nie ma cyfr po przecinku,
konwersja g nie wypisze take symbolu przecinka.
Specyfikator konwersji g jest przydatny zwaszcza do wypisywania wartoci, dla
których rozmiar reprezentacji nie da si ustali na etapie pisania programu, oraz
wartoci z bardzo szerokich dziedzin. Konwersja g dla niezbyt wielkich i niezbyt
maych wartoci bdzie owocowaa zapisem dziesitnym. Dla wartoci bardzo
maych i bardzo duych zastosowany bdzie zapis wykadnikowy, wymagajcy
specyfikatory mniejszej liczby znaków.
dla wartoci cakowitych ¥ 7.1
specyfikatory dla wartoci
%d, %e, %f i %g to bynajmniej nie wszystkie specyfikatory konwersji. Pozo-
zmiennoprzecinkowych ¥ 7.2 stae bd stopniowo wprowadzane przy okazji omawiania kolejnych zagadnie.
specyfikatory
dla wartoci znakowych ¥ 7.3 Pena lista specyfikatorów wraz z objanieniem ich znaczenia i dziaania znajduje
specyfikatory
dla cigów znaków ¥ 13.3
si w podrozdziale 22.3.

PROGRAM Wykorzystanie printf do formatowania liczb


Poniszy program ilustruje sposób wykorzystania funkcji printf do wypisy-
wania wartoci cakowitych i zmiennoprzecinkowych w rozmaitych formatach:
tprintf.c /* Wypisuje wartoci int i float w ró nych formatach */

#include <stdio.h>

int main(void)
{
int i;
float x;

i = 40;
x = 839.21f;

printf("|%d|%5d|%-5d|%5.3d|\n", i, i, i, i);
printf("|%10.3f|%10.3e|%-10g|\n", x, x, x);

return 0;
}

Znaki | w cigach formatujcych dla funkcji printf su jedynie do roz-


dzielenia wypisywanych wartoci i zilustrowania szerokoci pól, w których s
wypisywane. W przeciwiestwie do znaków % i \ znak | nie ma specjalnego zna-
czenia dla funkcji printf. Program wypisuje na wyjciu co takiego:
|40| 40|40 | 040|
| 839.210| 8.392e+02|839.21 |

Spróbujmy przeanalizowa znaczenie i dziaanie specyfikatorów konwersji


wykorzystanych w tym programie:
3.1. Funkcja printf 73

Q %d — wypisanie i w zapisie dziesitnym przy jak najmniejszej liczbie znaków.


Q %5d — wypisanie wartoci i w zapisie dziesitnym w polu o szerokoci co
najmniej 5 znaków. Poniewa waciwa warto i ma zaledwie dwa znaki, pole
jest wypeniane trzema spacjami od lewej strony.
Q %-5d — wypisanie wartoci i w zapisie dziesitnym w polu o szerokoci co
najmniej 5 znaków. Poniewa waciwa warto i ma zaledwie dwa znaki,
pole jest wypeniane trzema spacjami po prawej stronie wartoci (warto i jest
wic wyrównana w picioznakowym polu do lewej strony).
Q %5.3d — wypisanie wartoci i w zapisie dziesitnym w polu o szerokoci
co najmniej 5 znaków i przy reprezentowaniu wartoci co najmniej trzema
cyframi. Poniewa i ma tylko dwie cyfry, przed waciw wartoci wstawiana
jest pojedyncza cyfra zero. Wynikowa trzyznakowa warto jest wypisywana
w polu o szerokoci 5 znaków, a wic jest poprzedzona dwoma spacjami (war-
to i jest wyrównana do prawej strony pola).
Q %10.3f — wypisanie wartoci x w zapisie dziesitnym z przecinkiem w polu
o szerokoci co najmniej 10 znaków, z trzema cyframi po przecinku. Poniewa
warto x zajmuje jedynie 7 znaków (trzy przed przecinkiem i trzy po prze-
cinku oraz sam przecinek), jest uzupeniana trzema spacjami z lewej strony.
Q %10.3e — wypisanie wartoci x w zapisie wykadnikowym w polu o sze-
rokoci co najmniej 10 znaków, z trzema cyframi po przecinku. Tak zapisana
warto x zajmuje 9 znaków, wic jest uzupeniona spacj z lewej strony.
Q %-10g — wypisanie wartoci x w zapisie wykadnikowym albo dziesitnym
z przecinkiem w polu o szerokoci co najmniej 10 znaków, z trzema cyframi
po przecinku. W naszym przypadku warto x zostaa zamieniona na dzie-
sitn z przecinkiem. Obecno znaku - w specyfikatorze konwersji wymusza
wyrównanie wartoci do lewej strony pola czterema spacjami za waciw
wartoci.

Znaki sterujce
Symbol \n, wykorzystywany ju w poprzednich cigach formatujcych, to przy-
kad tak zwanego znaku sterujcego (ang. escape sequence). Znaki sterujce
pozwalaj na osadzanie w cigach znaków, które zapisane inaczej byyby kopo-
tliwe dla kompilatora. Dotyczy to przede wszystkim znaków niedrukowalnych
terminali znakowych oraz znaków posiadajcych specjalne znaczenie dla samego
znaki sterujce ¥ 7.3 kompilatora (jak "). Kompletn list znaków sterujcych zamiecimy pó niej, na
razie wystarczy wykaz najwaniejszych:
\a sygna dzwonka,
\b kasowanie poprzedniego znaku (ang. backspace),
\n nowy wiersz,
\t tabulator poziomy.
Takie symbole w cigu formatujcym reprezentuj czynnoci do wykonania w cza-
sie wypisywania cigu na wyjciu programu. Otó wypisanie \a spowoduje na
wikszoci maszyn wygenerowanie d wiku brzczyka terminala; wypisanie \b
74 Rozdzia 3. Formatowanie wejcia-wyjcia

cofnie kursor o jedn pozycj; wypisanie \n przesunie kursor do nowego wiersza;


wypisanie \t przesunie kursor do nastpnej pozycji tabulatora.
Cig formatujcy moe zawiera dowoln liczb znaków sterujcych. Spójrzmy
na kolejny przykad wywoania printf z cigiem formatujcym zawierajcym
cznie sze znaków sterujcych:
printf("Towar\tCena\tData\n\tjed.\tzakupu");

Wykonanie tej instrukcji spowoduje wypisanie na wyjciu dwóch wierszy:


Towar Cena Data
jed. zakupu

Innym popularnym znakiem sterujcym jest \", który reprezentuje w cigu


znak podwójnego cudzysowu ". Poniewa sam znak " oznacza pocztek albo
koniec literau napisowego, nie moe w takiej postaci pojawi si wewntrz lite-
rau — musi zosta oznaczony jako znak sterujcy znakiem ukonika. Oto przykad:
printf("\"Ahoj!\"");

Taka instrukcja spowoduje wypisanie na wyjciu komunikatu:


"Ahoj!"

Podobna sytuacja dotyczy znaku lewego ukonika. Jeli zechcemy umieci


taki znak w wypisywanym cigu, nie moemy go wstawi wprost do literau napi-
sowego, bo kompilator zakada, e znak ukonika jest zapowiedzi znaku steru-
jcego. Aby wypisa znak \, trzeba go równie poprzedzi znakiem \, a wic
wstawi do cigu par \\:
printf("\\"); /* wypisuje na wyjciu pojedynczy znak \ */

3.2. Funkcja scanf


Tak jak funkcja printf suy do formatowania wyjcia programu, tak funkcja
scanf obsuguje formatowane wejcie. Cig formatujcy dla funkcji scanf rów-
nie moe zawiera zwyczajne znaki i specyfikatory konwersji. Konwersje obsu-
giwane przez funkcj scanf pokrywaj si z grubsza z konwersjami funkcji
printf.
W wielu przypadkach cig formatujcy funkcji scanf zawiera wycznie
specyfikatory konwersji, jak w poniszym przykadzie:
int i, j;
float x, y;

scanf("%d%d%f%f", &i, &j, &x, &y);

Zaómy, e uytkownik wprowadza na wejcie programu nastpujcy wiersz:


1 -20 .3 -4.0e3
3.2. Funkcja scanf 75

Funkcja scanf wczyta taki wiersz i rozpocznie stosowanie specyfikatorów kon-


wersji i podstawianie uzyskanych wartoci pod zmienne przekazane w wywoaniu:
1 pod i, -20 pod j, 0.3 pod x i -4000.0 pod y. Takie „upakowane” cigi
sterujce s dla funkcji scanf typowe. W przypadku funkcji printf czciej
stosuje si rozmaite „dekoracje” i napisy objaniajce, otaczajce wyprowadzane
wartoci.
Funkcja scanf (tak jak printf zreszt) zastawia na niewiadomych i nie-
ostronych uytkowników kilka wnyków. Programista stosujcy funkcj scanf
musi starannie sprawdza, czy liczba specyfikatorów konwersji odpowiada liczbie
zmiennych przekazanych w wywoaniu i czy poszczególne konwersje odpowiadaj
typom przekazanych zmiennych — podobnie jak w przypadku printf, kom-
pilator nie ma obowizku przeprowadzania takiej kontroli i wykrywania ewentu-
alnych niezgodnoci. Kolejna puapka czai si w znaku &, który zazwyczaj poprze-
dza kad zmienn przekazywan do scanf. Znak & jest zazwyczaj (cho nie
zawsze) konieczny i to programista ma obowizek pamita o jego stosowaniu.

Pominicie symbolu & przy zmiennej przekazywanej do funkcji scanf prowadzi


do nieprzewidywalnych wyników dziaania programu. Potencjalnie s to efekty
katastrofalne. Najczciej taki bd prowadzi do wyoenia si programu. W naj-
lepszym przypadku podstawienie wartoci pod zmienn bdzie nieskuteczne —
zmienna zachowa swoj poprzedni warto (co nie oznacza, e bdzie miaa jak-
kolwiek okrelon warto, jeli np. nie zostaa zainicjalizowana!). Pominicie
znaku & jest niestety czstym bdem. Niektóre kompilatory wykrywaj taki bd
i generuj komunikat z ostrzeeniem w rodzaju „argument nie jest wska nikiem”
(o wska nikach powiemy sobie w rozdziale 11.; to wanie znak & tworzy wska -
nik do zmiennej). Ostrzeenia zwizane z wywoaniami funkcji scanf trzeba trak-
towa bardzo powanie.

Wywoania funkcji scanf s efektywnym, ale potencjalnie niebezpiecznym


sposobem wczytywania danych do programów. Wielu zawodowych programi-
stów C unika funkcji scanf. Wol oni wczyta do programu cao danych
wejciowych w postaci znakowej i dopiero pó niej zamieni j na oczekiwane
wartoci liczbowe. My natomiast bdziemy korzysta ze scanf cakiem sporo,
zwaszcza w pocztkowych rozdziaach ksiki — jest to zwyczajnie najprostszy
sposób wczytywania wartoci liczbowych do programu. Trzeba tylko mie wia-
domo, e wiele programów zachowa si niepoprawnie, kiedy uytkownik wpro-
wadzi do nich nieodpowiednie dane wejciowe. Wkrótce si przekonamy, e mona
wykrywanie bdów
w scanf ¥ 22.3 zreszt skutecznie sprawdza, czy funkcji scanf udao si wczyta z wejcia
oczekiwane dane (a jeli nie, odpowiednio zareagowa, zamiast brn dalej w pro-
gram z niepoprawnymi danymi). Takie sprawdziany s jednak mao zasadne
w zakresie pocztkowych przykadów prezentowanych w ksice — nadmiernie
rozbudowayby program, który ma przecie przede wszystkim ilustrowa biece
zagadnienie.
76 Rozdzia 3. Formatowanie wejcia-wyjcia

Dziaanie funkcji scanf


Funkcja scanf robi w istocie znacznie wicej, ni dotychczas powiedziano. Jest to
w zasadzie mechanizm dopasowywania wzorców w cigach znaków, który pró-
buje pogrupowa znaki wejciowe i dopasowa je do specyfikatorów konwersji.
Dziaaniem funkcji scanf sterujemy za pomoc cigu formatujcego. Funkcja
scanf analizuje zawarto tego cigu, od lewej do prawej strony. Dla kadego
napotkanego w cigu specyfikatora konwersji próbuje w cigu wejciowym pro-
gramu zlokalizowa warto odpowiedniego typu. W czasie tego wyszukiwania
automatycznie pomija znaki odstpów. Znaleziony podcig pasujcy do specyfi-
katora konwersji jest wczytywany a do miejsca, w którym wystpi znak niepasu-
jcy do wymaga konwersji. Jeli udao si wczyta taki podcig, funkcja scanf
dokonuje konwersji i przechodzi do analizy reszty cigu formatujcego. Pierwsze
nieudane dopasowanie specyfikatora konwersji do danych wczytywanych z wej-
cia koczy dziaanie caej funkcji, bez rozpatrywania reszty cigu formatujcego
(i bez uwzgldniania reszty danych wejciowych).
W toku poszukiwania pierwszego znaku liczby funkcja scanf ignoruje
wszystkie znaki odstpów (spacje, znaki tabulacji poziomej i pionowej, znaki
wysuwu formularza i znaki nowego wiersza). Dziki temu na wejciu dane mona
podawa w jednym wierszu albo rozproszy je pomidzy rónymi wierszami. Przy
danym wywoaniu funkcji scanf:
scanf("%d%d%f%f", &i, &j, &x, &y);

uytkownik moe wprowadzi na wejcie np. trzy wiersze danych:


1
-20 .3
-4.0e3

dla funkcji scanf wejcie jest widoczne jako cigy strumie znaków:
··1¤-20···.3¤···-4.0e3¤

(w celu uwidocznienia znaków odstpów zastosowalimy znak · dla spacji i znak


¤ dla nowego wiersza). Poniewa funkcja scanf pomija znaki odstpów i szuka
przede wszystkim pocztku liczby i podcigu znaków nadajcych si do konwersji,
takie dane wejciowe mog by za jednym zamachem wczytane przez funkcj
scanf. Poniszy schemat ilustruje dziaanie funkcji scanf. Znak p oznacza tu
pomijanie biecego znaku wejcia, znak w oznacza wczytywanie znaków do bie-
cej konwersji:
··1¤-20···.3¤···-4.0e3¤
ppwpwwwpppwwppppwwwwww

Ostatni znak strumienia wejciowego, czyli pierwszy znak za ostatni skonwerto-


wan grup znaków, jest przez funkcj scanf „podgldany”, ale nie jest wczy-
tywany. Zostanie on wczytany i pominity bd skonwertowany przy nastpnym
wywoaniu scanf.
Wedug jakich regu scanf rozpoznaje liczb cakowit albo zmiennoprze-
cinkow w cigu danych wejciowych? Otó kiedy scanf ma wczyta liczb
cakowit, szuka w cigu wejciowym znaku cyfry, ewentualnie znaku + albo -.
3.2. Funkcja scanf 77

Po znalezieniu takiego znaku wczytuje wszystkie kolejne znaki a do napotkania


znaku niebdcego cyfr. Natomiast w przypadku liczby zmiennoprzecinkowej
scanf szuka:
znaku + albo - (opcjonalnie), a za nim
cigu cyfr (zawierajcego ewentualnie znak przecinka dziesitnego), a za nim
cigu wykadnika (opcjonalnie); cig wykadnika skada si z litery e (albo E),
opcjonalnego znaku (+ albo -) i co najmniej jednej cyfry.
W przypadku funkcji scanf konwersje %e, %f i %g mona stosowa zamiennie —
wszystkie trzy reprezentuj te same reguy dopasowania wartoci zmiennoprze-
cinkowej.
Kiedy scanf napotyka znak niezgodny z regu dopasowania dla biecej kon-
wersji, znak ten jest „odkadany” z powrotem do strumienia wejciowego, aby by
dostpny przy obsudze nastpnych specyfikatorów konwersji albo dla kolejnych
wywoa scanf wczytujcych kolejne wartoci. We my na przykad nastpujce
(niewtpliwie patologiczne) rozmieszczenie naszych czterech liczb wejciowych:
1-20.3-4.0e3¤

Zastosujemy takie same wywoanie scanf jak poprzednio:


scanf("%d%d%f%f", &i, &j, &x, &y);

Funkcja scanf bdzie przetwarza taki cig wejciowy nastpujco:


Q Bieca konwersja: %d. Pierwszy niepusty znak wejcia to 1. Poniewa liczba
cakowita moe zaczyna si od cyfry, znak jest akceptowany jako pierwszy
znak podcigu do dopasowania. Funkcja wczytuje nastpny znak: -. Taki znak
nie moe si pojawi wewntrz zapisu wartoci cakowitej, wic scanf
odkada znak z powrotem do strumienia i podstawia pod pierwsz zmienn
(i) warto 1.
Q Bieca konwersja: %d. Pierwszy niepusty znak wejcia to - (dozwolony);
kolejne znaki to 2, 0 i . (kropka). Liczba cakowita nie moe zawiera kropki,
wic znak jest odkadany z powrotem do strumienia wejciowego, a funkcja
podstawia pod kolejn zmienn (j) warto –20.
Q Bieca konwersja: %f. Pierwszy niepusty znak wejcia to . (dozwolony);
kolejne znaki to 3 i - (minus). Liczba zmiennoprzecinkowa nie zawiera znaku
- po znaku cyfry, wic znak - jest odkadany z powrotem do strumienia wej-
ciowego, a funkcja podstawia pod kolejn zmienn (x) warto 0.3.
Q Bieca konwersja: %f. Pierwszy niepusty znak wejcia to - (dozwolony);
kolejne znaki to 4, ., 0, e, 3 i ¤ (nowy wiersz). Liczba zmiennoprzecinkowa
nie moe zawiera znaku nowego wiersza, wic znak jest odkadany z powro-
tem do strumienia wejciowego, a funkcja podstawia pod kolejn zmienn
(y) warto –4.0×103.
W tym przykadzie funkcja scanf moga skutecznie dopasowa wszystkie kolejne
specyfikatory konwersji do cigu wejciowego. Poniewa znak nowego wiersza
nie zosta „zuyty”, bdzie pierwszym znakiem wczytywanym przy nastpnym
wywoaniu scanf.
78 Rozdzia 3. Formatowanie wejcia-wyjcia

Zwyke znaki w cigu formatujcym funkcji scanf


Zasad dopasowywania wzorców do podcigów strumienia wejciowego mona
rozszerzy, zapisujc cig formatujcy zawierajcy poza specyfikatorami konwer-
sji równie zwyke napisy. W takim przypadku pomidzy specyfikatorami kon-
wersji funkcja scanf bdzie porównywaa kolejne znaki wejcia ze znakami
cigu formatujcego. Porównanie takie odbywa si rónie, zalenie od tego, czy
cig formatujcy zawiera znaki odstpów:
Q Znaki odstpów w cigu formatujcym. Kiedy w cigu formatujcym znaj-
duj si znaki odstpu, funkcja scanf bdzie w strumieniu wejciowym wczy-
tywaa kolejne znaki tak dugo, a napotka pierwszy znak niebdcy znakiem
odstpu (ten znak zostanie „odoony” z powrotem do strumienia wejciowego).
Liczba znaków odstpu w cigu formatujcym nie musi dokadnie odpowiada
liczbie takich znaków w cigu wejciowym. Pojedynczy znak odstpu w cigu
formatujcym zostanie dopasowany do dowolnie dugiego podcigu takich
znaków w cigu wejciowym (co wicej, obecno znaku odstpu w cigu
formatujcym nie wymusza obecnoci takiego znaku w cigu wejciowym —
znak odstpu w cigu formatujcym odpowiada dowolnie dugiemu podcigowi
takich znaków w cigu wejciowym, a wic równie podcigowi zerowemu).
Q Pozostae znaki. Kiedy w cigu formatujcym znajduje si znak nienalecy
do specyfikatorów konwersji i niebdcy znakiem odstpu, funkcja scanf
porównuje go wprost z nastpnym znakiem wejcia. Jeli znaki s zgodne,
funkcja przechodzi do przetwarzania nastpnego znaku cigu wejciowego.
Przy braku zgodnoci funkcja odkada niepasujcy znak z powrotem do cigu
wejciowego i przerywa dalsze przetwarzanie cigu formatujcego.
Dla przykadu niech funkcja scanf otrzyma cig formatujcy w postaci
"%d/%d". Jeli na wejciu programu pojawi si cig:
·5/·96

funkcja scanf pominie pierwszy znak odstpu, szukajc liczby cakowitej, nastp-
nie dopasuje do %d podcig 5, dopasuje bezporednio znak /, pominie spacj
w poszukiwaniu kolejnej liczby cakowitej, a pó niej dopasuje do %d podcig 96.
Ale jeli na wejciu pojawi si:
·5·/·96

to funkcja scanf pominie pierwszy znak odstpu, szukajc liczby cakowitej,


nastpnie dopasuje do %d podcig 5, a pó niej spróbuje dopasowa do wejcia
znak /. Poniewa w biecym miejscu wejcia zamiast tego znaku znajduje si
inny (tu: znak odstpu), funkcja przerwie przetwarzanie wejcia, odkadajc spacj
z powrotem do cigu wejciowego. Na wejciu pozostanie cig ·/·96 czekajcy
na ewentualne kolejne wywoanie scanf. Aby umoliwi dopasowanie wejcia
ze spacj (spacjami) po pierwszej liczbie cakowitej, cig formatujcy powinien
mie posta "%d /%d".
3.2. Funkcja scanf 79

Skutki mylenia printf ze scanf


Wywoania funkcji printf i scanf bywaj bardzo podobne, ale w ich dziaa-
niu zachodz daleko idce rónice. Zignorowanie tych rónic moe by bardzo nie-
bezpieczne dla dziaania programu.
Jedn z czstych pomyek jest umieszczenie & przed zmienn w wywoaniu
funkcji printf:
printf("%d %d\n", &i, &j); /*** LE ***/

Na szczcie taka omyka jest stosunkowo prosta do wytropienia — w toku


dziaania programu funkcja printf wywietli „mieci” zamiast oczekiwanych
wartoci i i j.
Poniewa funkcja scanf normalnie pomija znaki odstpów przy poszuki-
waniu podcigu do dopasowania do wartoci liczbowej, rzadko kiedy pojawia si
potrzeba umieszczania w cigu formatujcym scanf czegokolwiek poza specy-
fikatorami konwersji. Nieuprawnione zaoenie, e cig formatujcy scanf
powinien cile odpowiada analogicznemu cigowi formatujcemu printf —
to kolejny czsty bd — moe doprowadzi do niepoprawnego dziaania funkcji
scanf. Zobaczmy, co si stanie, jeli w programie znajdzie si nastpujca in-
strukcja:
scanf("%d, %d", &i, &j);

Funkcja scanf bdzie najpierw szuka podcigu wartoci typu int i wpisze t
warto pod zmienn i. Potem bdzie próbowaa dopasowa w cigu wejciowym
znak przecinka. Jeli w cigu wejciowym zamiast przecinka pojawi si spacja,
dziaanie funkcji zostanie przerwane i zmienna j nie otrzyma oczekiwanej wartoci.

Cigi formatujce dla funkcji printf czsto kocz si znakiem sterujcym \n,
wymuszajcym wstawienie do wyjcia nowego wiersza. Taki sam znak na kocu
cigu formatujcego scanf to zazwyczaj zy pomys. Dla scanf znak nowego
wiersza w cigu formatujcym jest równowany ze znakiem spacji; pomija go,
szukajc nastpnego znaku niebdcego znakiem odstpu. Jeli na przykad cig
formatujcy ma posta "%d\n", scanf pominie ewentualne pocztkowe znaki
odstpu, wczyta warto cakowit, a nastpnie bdzie w cigu wejciowym szuka
nastpnego znaku innego ni odstp. W przypadku programu interaktywnego moe
to doprowadzi do „zawieszenia” programu do czasu, kiedy uytkownik wprowa-
dzi na wejcie znak inny ni odstp.

PROGRAM Dodawanie uamków


Aby zilustrowa zdolno funkcji scanf do dopasowywania wzorców w cigach
znaków, we miemy na warsztat problem wczytania uamka wprowadzanego przez
uytkownika. Uamki s zwyczajowo reprezentowane przez zapis licznik/mianownik.
Zamiast zmusza uytkownika do nieintuicyjnego wprowadzania uamka jako
dwóch osobnych liczb cakowitych, dziki funkcji scanf pozwolimy mu wpro-
wadzi uamek w klasycznej postaci. Oto program ilustrujcy t technik przy
dodawaniu dwóch uamków:
80 Rozdzia 3. Formatowanie wejcia-wyjcia

addfrac.c /* Dodawanie dwóch uamków zwykych */

#include <stdio.h>

int main(void)
{
int num1, denom1, num2, denom2, result_num, result_denom;

printf("Podaj pierwszy uamek: ");


scanf("%d/%d", &num1, &denom1);

printf("Podaj drugi uamek: ");


scanf("%d/%d", &num2, &denom2);

result_num = num1 * denom2 + num2 * denom1;


result_denom = denom1 * denom2;
printf("Suma uamków wynosi %d/%d\n",
result_num, result_denom);

return 0;
}

Przykadowa sesja z takim programem wygldaa tak:


Podaj pierwszy uamek: 5/6
Podaj drugi uamek: 3/4
Suma uamków wynosi 38/24

Zauwamy, e program nie przewiduje znormalizowania wynikowego uamka.

Pytania i odpowiedzi
*P: Widywaem specyfikatory w postaci %i, przeznaczone do wczytywania i wy-
pisywania liczb cakowitych. Czym róni si od %d (s. 71)?
O: W cigu formatujcym funkcji printf nie ma pomidzy nimi adnej rónicy.
Za to w funkcji scanf %d dopasuje wycznie liczb cakowit zapisan w postaci
dziesitnej, natomiast %i moe dopasowa take liczby cakowite zapisane
liczby ósemkowe ¥ 7.1 w innych systemach liczbowych (przy innych podstawach), na przykad liczby
liczby szesnastkowe ¥ 7.1 ósemkowe czy szesnastkowe. Otó jeli cig wejciowy zawiera przedrostek
0 (jak w 056), przy konwersji %i taki cig zostanie potraktowany jako ósemkowy.
Przedrostek 0x albo 0X (jak w 0x56) przy konwersji %i spowoduje konwersj
liczby jako wartoci szesnastkowej. Zastosowanie %i zamiast %d do wczytywania
liczb moe da zaskakujce wyniki, jeli uytkownik omykowo wprowadzi 0 przed
waciw liczb; z tego powodu zaleca si trzymanie si konwersji %d.
P: Skoro printf traktuje znak % jako pocztek specyfikatora konwersji, jak
mona wypisa na wyjciu programu znak procenta?
O: Funkcja printf wypisze na wyjciu znak % tam, gdzie w cigu formatujcym
pojawi si para przylegajcych znaków procenta (%%). Na przykad instrukcja:
Pytania i odpowiedzi 81

printf("Zysk netto: %d%%\n", profit);

moe spowodowa wypisanie:


Zysk netto: 10%
P: Znak sterujcy \t ma wymusi na printf przesunicie kursora do nastp-
nej pozycji tabulacji. Skd wiadomo, jaka jest szeroko tabulatora (s. 74)?
O: Nie wiadomo. Efekt wypisania na wyjciu znaku \t nie jest cile zdefiniowany
w standardzie jzyka C. Jest on zaleny od reakcji systemu operacyjnego na da-
nie wypisania znaku tabulacji. Zazwyczaj szeroko tabulatora to osiem znaków,
ale C sam w sobie nie daje takiej gwarancji.
P: Co zrobi funkcja scanf, jeli bdzie miaa wczyta liczb, a uytkownik
wprowadzi cig nieliczbowy?
O: Wyjanimy to na przykadzie:
printf("Podaj liczb: ");
scanf("%d", &i);

Zaómy, e uytkownik wprowadzi poprawn warto liczbow, za któr umieci


znaki inne ni cyfry:
Podaj liczb: 23bla

W takim przypadku funkcja scanf wczyta znaki 2 oraz 3 i w zmiennej i umieci


warto 23. Reszta znaków (bla) bdzie w strumieniu wejciowym czekaa na
kolejne wywoanie scanf (albo innej funkcji obsugujcej wejcie). Jeli natomiast
uytkownik wprowadzi cig niepoprawny od pierwszego znaku:
Podaj liczb: bla

zmienna i nie otrzyma wartoci wczytywanej z wejcia programu, a w strumieniu


wejciowym cig bla poczeka na kolejne wywoania funkcji wejcia.
wykrywanie bdów
Co mona zrobi w takich smutnych przypadkach? Pó niej dowiesz si, jak
w scanf ¥ 22.3 sprawdzi skuteczno wywoania funkcji scanf. Jeli wywoanie bdzie nie-
skuteczne, moemy zakoczy program albo spróbowa naprawi sytuacj, na przy-
kad odrzucajc niepoprawne wejcie i ponownie proszc uytkownika o wpro-
wadzenie danych (sposoby odrzucania danych wejciowych bd omawiane
w sekcji pyta i odpowiedzi rozdziau 22.).
P: Nie rozumiem, w jaki sposób funkcja scanf „odkada” znak z powrotem
do cigu wejciowego i potem ponownie wczytuje go z wejcia (s. 77)?
O: Okazuje si, e program nie wczytuje danych wejciowych bezporednio przy ich
wprowadzaniu. Cig wejciowy programu jest przechowywany w ukrytym buforze,
do którego odwouje si funkcja scanf. W takim ukadzie funkcja scanf moe
„odoy” znak z powrotem do bufora. Buforowanie wejcia bdzie omawiane
w rozdziale 22.
P: Jak zadziaa funkcja scanf, kiedy uytkownik umieci pomidzy liczbami
znaki przestankowe (np. przecinki)?
O: Zobaczmy to na prostym przykadzie. Zaómy, e zamierzamy wczyta funkcj
scanf par liczb:
82 Rozdzia 3. Formatowanie wejcia-wyjcia

printf("Podaj dwie liczby: ");


scanf("%d%d", &i, &j);

Jeli uytkownik wprowadzi:


4,28

funkcja scanf wczyta znak 4 i podstawi warto 4 do zmiennej i. W poszuki-


waniu nastpnego podcigu reprezentujcego liczb cakowit znajdzie przecinek.
Poniewa przecinek nie moe rozpoczyna zapisu liczby, funkcja przerwie dzia-
anie. Sam przecinek i druga wprowadzona liczba zostan do dyspozycji nastp-
nych wywoa scanf.
Oczywicie mona temu atwo zaradzi, instruujc uytkownika co do ocze-
kiwanego formatu danych wejciowych, np. nakazujc mu zawsze oddzielanie liczb
przecinkiem:
printf("Podaj dwie liczby, oddzielone przecinkiem: ");
scanf("%d,%d", &i, &j);

wiczenia
Podrozdzia 3.1 1. Co pojawi si na wyjciu po wykonaniu poniszych wywoa printf?
(a) printf("%6d,%d4", 86, 1040);
(b) printf("%12.5e", 86, 30.253);
(c) printf("%.4f", 83.162);
(d) printf("%-6.2g", .0000009979);

2. Napisz wywoanie funkcji printf wywietlajcej warto zmiennej typu float w nast-
pujcych formatach:
(a) w zapisie wykadnikowym, wyrównan do lewej w polu o szerokoci 8, z jedn cyfr
po przecinku;
(b) w zapisie wykadnikowym, wyrównan do prawej w polu o szerokoci 10, z szecioma
cyframi po przecinku;
(c) w zapisie dziesitnym z przecinkiem, wyrównan do lewej w polu o szerokoci 8, z trzema
cyframi po przecinku;
(d) w zapisie dziesitnym z przecinkiem, wyrównan do prawej w polu o szerokoci 6,
bez cyfr po przecinku.
Podrozdzia 3.2 3. Dla kadej z poniszych par cigów formatujcych scanf wska, czy oba cigi pary s sobie
równowane, czy nie. Jeli nie s, wska rónic w ich dziaaniu.
(a) "%d" " %d"
(b) "%d-%d-%d" " %d -%d -%d"
(c) "%f" "%f "
(d) "%f,%f" "%f, %f"

* 4. We my nastpujce wywoanie funkcji scanf:

wiczenia z gwiazdk s podchwytliwe — poprawna odpowied zazwyczaj jest róna


od odpowiedzi narzucajcej si na pierwszy rzut oka. Przeczytaj pytanie uwanie, dokadnie
przeled kod, w razie potrzeby powtórz lektur odpowiedniego podrozdziau. Powodze-
nia! — przyp. autora.
Zadania programistyczne 83

scanf("%d%f%d", &i, &x, &j);

Jakie bd wartoci zmiennych i, x i j po wykonaniu wywoania (zakadamy, e zmienne


i i j s zmiennymi typu int, a x jest zmienn typu float), jeli uytkownik wprowadzi:

10.3 5 6
* 5. We my nastpujce wywoanie funkcji scanf:

scanf("%f%d%f", &x, &i, &y);

Jakie bd wartoci zmiennych x, i i y po wykonaniu wywoania (zakadamy, e zmienne


x i y s zmiennymi typu float, a i jest zmienn typu int), jeli uytkownik wprowadzi:

12.3 45.6 789

6. Poka, jak mona przerobi program addfrac.c z podrozdziau 3.2, aby uytkownik móg
wprowadza uamki ze spacjami wokó znaku dzielenia /.

Zadania programistyczne
1. Napisz program, który przyjmuje na wejcie dat w postaci dd/mm/rrrr, a nastpnie
wypisuje j w formacie rrrrmmdd:

Podaj dat (dd/mm/rrrr): 17/2/2011


Podae dat 20110217

2. Napisz program formatujcy informacje o produkcie wprowadzone przez uytkownika. Sesja


z programem powinna wyglda tak:

Podaj numer towaru: 583


Podaj cen jednostkow: 13.5
Podaj dat zakupu (dd/mm/rrrr): 24/10/2010
Towar Cena Data
jed. zakupu
583 $ 13.50 24/10/2010

Numer towaru i data powinny by wyrównane do lewej. Cena jednostkowa powinna by
wyrównana do prawej. Program powinien dopuszcza kwoty do 9999,99. Podpowied : Do
wyrównania kolumn zastosuj tabulatory.

3. Ksiki s oznaczane midzynarodowym numerem ISBN (International Standard Book


Number). Numery ISBN nadawane po 1 stycznia 2007 roku skadaj si z 13 cyfr, podzie-
lonych na pi grup, np. 978-0-393-97950-3 (starsze numery ISBN miay 10 cyfr). Pierwsza
grupa (przedrostek GSI) to obecnie albo 978 albo 979. Identyfikator grupy okrela jzyk
kraju wydania (np. w krajach anglojzycznych stosuje si kody 0 i 1). Kod wydawcy
identyfikuje wydawc (393 to kod wydawnictwa W.W. Norton). Numer publikacji to numer
nadawany przez wydawc konkretnej ksice (tutaj 97950). Numer ISBN koczy si sum
kontroln umoliwiajc weryfikacj poprawnoci numeru ISBN. Napisz program, który
podzieli na grupy numer ISBN wprowadzony przez uytkownika:

Podaj numer ISBN: 978-0-393-97950-3


Przedrostek GSI: 978
Identyfikator grupy: 0
84 Rozdzia 3. Formatowanie wejcia-wyjcia

Kod wydawcy: 393


Numer publikacji: 97950
Suma kontrolna: 3

Uwaga: Liczba cyfr w kadej z grup moe by róna. Nie mona zaoy, e grupy maj
akurat takie rozmiary jak w tym przykadzie. Przetestuj program na innych, prawdziwych
numerach ISBN (znajdziesz je na okadkach ksiek i na stronach z informacjami o wydaniu).

4. Napisz program, który zapyta uytkownika o numer telefoniczny w formacie (xx)


xxx-xxxx, a potem wywietli ten numer w zapisie 0-xx xxx-xx-xx:
Podaj numer telefonu [(xx) xxx-xxxx]: (61) 817-6900
Podany numer: 0-61 817-69-00

5. Napisz program, który bdzie monitowa, aby uytkownik wprowadzi liczby od 1 do 16


(w dowolnej kolejnoci), potem wywietli te liczby czwórkami (w postaci macierzy 4×4),
a nastpnie wypisze sumy w wierszach, kolumnach i po przektnych:

Podaj liczby od 1 do 16 (w dowolnej kolejnoci):


16 3 2 13 5 10 11 8 9 6 7 12 4 15 14 1
16 3 2 13
5 10 11 8
9 6 7 12
4 15 14 1

Sumy w wierszach: 34 34 34 34
Sumy w kolumnach: 34 34 34 34
Sumy po przektnych: 34 34

Jeli sumy w wierszach, kolumnach i po przektnych s identyczne (jak w przykadzie),


to takie liczby stanowi tak zwany magiczny kwadrat. Ten magiczny kwadrat pojawi
si w 1514 roku na rycinie artysty i matematyka Albrechta Dürera (zauwa, e rodkowe
liczby w ostatnim wierszu skadaj si na dat ryciny).

6. Przerób program addfrac.c z podrozdziau 3.2 tak, aby uytkownik za jednym zamachem
wprowadza oba uamki oddzielone znakiem +:

Podaj dwa uamki oddzielone znakiem plusa: 5/6+3/4


Suma uamków wynosi 38/24
4 Wyra enia

Uywanie kalkulatorka to jeszcze nie programowanie,


a ju nie matematyka.

Jedn z waniejszych cech jzyka C jest nacisk, jaki jest w nim kadziony na wyra-
enia — inaczej „wzory”, które mówi o sposobie obliczania wartoci. Wyraenia
s tu waniejsze ni instrukcje. Najprostsze wyraenia to zmienne i stae programu.
Zmienna reprezentuje warto, która jest obliczana w czasie dziaania programu
poprzez pobranie wartoci z pamici skojarzonej ze zmienn. Staa reprezentuje
warto znan ju w czasie kompilacji. Bardziej rozbudowane wyraenia obejmuj
operandy i operatory (przy czym operatory same w sobie s równie wyraeniami).
W wyraeniu a + (b * c) widzimy zastosowanie operatora + do operandów
a oraz (b + c), natomiast obie strony operatora + (oba operandy) s penopraw-
nymi wyraeniami.
Operatory s podstawowymi narzdziami budowania wyrae, a jzyk C
posiada bardzo bogaty zbiór operatorów. Przede wszystkim C obsuguje podsta-
wowe operatory obecne w wikszoci innych jzyków programowania:
Q Operatory arytmetyczne, w tym dodawanie, odejmowanie, mnoenie i dzielenie.
Q Operatory relacji do obliczania wartoci wyrae logicznych, takich jak „i jest
wiksze od 0”.
Q Operatory logiczne do budowania warunków logicznych, jak „i jest wiksze
od 0 i jest mniejsze ni 10”.
Na tym jednak nie koniec. W jzyku C mamy do dyspozycji dziesitki innych
operatorów. Jest ich tak wiele, e bdziemy zmuszeni do ich stopniowego wpro-
wadzania na przestrzeni a dwudziestu rozdziaów tej ksiki. Opanowanie takiej
liczby operatorów wydaje si zadaniem niepomiernym, ale w istocie wikszo
z nich ma zasadnicze znaczenie dla efektywnoci programowania w jzyku C.
W tym rozdziale zajmiemy si jednymi z najwaniejszych operatorów jzyka C,
mianowicie we miemy na warsztat operatory arytmetyczne (podrozdzia 4.1), ope-
ratory przypisania (podrozdzia 4.2) oraz operatory inkrementacji i dekrementacji

85
86 Rozdzia 4. Wyraenia

(podrozdzia 4.3). W podrozdziale 4.1 zajmiemy si te pierwszestwem i cznoci


operatorów — te cechy operatorów s niezwykle istotne dla poprawnoci wyrae
obejmujcych wicej ni jeden operator. W podrozdziale 4.4 opisany zostanie spo-
sób, w jaki nastpuje obliczanie wyrae w jzyku C. Wreszcie w podrozdziale 4.5
zajmiemy si tak zwan instrukcj wyraeniow — konstrukcj sprawiajc,
e dowolne wyraenie moe odgrywa rol instrukcji w programie.

4.1. Operatory arytmetyczne


Operatory arytmetyczne — czyli operatory realizujce operacje dodawania,
odejmowania, mnoenia i dzielenia — to istne woy robocze w wikszoci jzyków
programowania. C nie jest tu wyjtkiem. List operatorów arytmetycznych jzyka
C wymienia tabela 4.1.

Tabela 4.1. Z jednym operandem Z dwoma operandami


Operatory
arytmetyczne Addytywne Multiplikatywne
+ zachowanie znaku liczby + dodawanie * mnoenie
- zmiana znaku liczby - odejmowanie / dzielenie
% reszta z dzielenia

Operatory addytywne i multiplikatywne to tak zwane operatory dwuargumen-


towe (ang. binary), poniewa operuj na dwóch operandach. Operatory jednoar-
gumentowe (ang. unary) wymagaj tylko jednego operandu:
i = +1; /* + jako operator jednoargumentowy */
j = -i; /* + jako operator jednoargumentowy (negacja) */

Jednoargumentowy operator + nie ma adnego dziaania. W rzeczy samej w spe-


cyfikacji K&R w ogóle go nie uwzgldniono. Suy gównie do zaznaczenia, e
staa liczbowa jest dodatnia.
Operatory dwuargumentowe nie powinny budzi wtpliwoci. Moe poza ope-
ratorem %, który oblicza reszt z dzielenia cakowitego. Warto wyraenia i %
j jest to reszta z cakowitego dzielenia i przez j. Na przykad wartoci wyrae-
nia 10 % 3 jest 1, a wyraenie 12 % 4 ma warto 0.
Operatory dwuargumentowe z tabeli 4.1 — z wyjtkiem operatora % — mog
by stosowane zarówno do operandów bdcych liczbami cakowitymi, jak i do
operandów zmiennoprzecinkowych; dopuszczalne jest te mieszanie typów operan-
dów. Kiedy w zasigu dziaania operatora wystpuj operandy typu int i float,
wynikiem dziaania operatora jest warto typu float. Dlatego wyraenie 9 +
2.5f daje 11,5, a 6.7f / 2 to 3.35.
Przy stosowaniu operatorów / i % naley zachowa pewn ostrono:
Q Operator dzielenia / moe dawa nieoczekiwane wyniki. Otó kiedy oba ope-
randy s wartociami cakowitymi, operator / obcina iloraz do najbliszej mniej-
szej wartoci cakowitej. Dlatego wyraenie 1 / 2 da warto 0.
4.1. Operatory arytmetyczne 87

Q Operator % wymaga, aby operandy byy wartociami cakowitymi. Jeli któ-


rykolwiek z operandów % jest wartoci inn ni cakowita, program nie da
si skompilowa.
Q Jeli prawym operandem operatora / albo % bdzie 0, dziaanie programu
niezdefiniowane
zachowanie programu ¥ 4.4 bdzie niezdefiniowane.
Q Najwiksze puapki czaj si przy stosowaniu operatorów / i % z operandami
ujemnymi. Standard C89 stwierdza, e kiedy którykolwiek z operandów jest
ujemny, wynik dzielenia moe zosta zaokrglony w gór albo w dó (np. war-
toci wyraenia -9 / 7 moe by albo –1, albo –2). Równie kiedy ope-
rand i albo j wyraenia i % j jest ujemny, wedug standardu C89 wynik
dziaania operatora jest zaleny od implementacji (np. wyraenie -9 % 7
moe da warto –2 albo 5). Z kolei w C99 wynik dzielenia cakowitego jest
zawsze zaokrglany w kierunku zera (-9 / 7 da wynik –1), a warto wyra-
enia i % j bdzie miaa znak taki jak operand i (czyli -9 % 7 to –2).

Zachowanie zalene od implementacji


Pojcie zachowania albo dziaania zale nego od implementacji bdzie si poja-
wia w naszym omówieniu na tyle czsto, e warto przedstawi je zawczasu.
W standardzie jzyka C celowo pozostawiono specyfikacj dziaania niektórych
elementów jzyka jako nie do koca sprecyzowan, z zaoeniem, e „implemen-
tacja” — czyli konkretne wcielenie kompilatora i programu konsolidujcego dla
konkretnej platformy maszynowej i systemowej — wypeni luk po swojemu.
W efekcie zachowanie programu w toku wykonania moe by róne na rónych
platformach. Przykadem zachowania zalenego od implementacji jest wanie
dziaanie operatorów / i % wedug standardu C89.
Niedoprecyzowanie niektórych elementów jzyka wydaje si dziwactwem,
a nawet dziaaniem szkodliwym, ale w istocie dobrze odzwierciedla filozofi przy-
wiecajc twórcom jzyka C. Jednym z zaoe projektowych bya przecie mo-
liwie dua wydajno programów pisanych w tym jzyku, co czsto oznacza
konieczno dostosowania implementacji poszczególnych konstrukcji do zachowa-
nia danej maszyny. Tak wic niektóre procesory, wykonujc operacj dzielenia
cakowitego –9 przez 7, daj wynik –1, a inne –2. Standard C89 zwyczajnie odzwier-
ciedla rónorodno maszynowej implementacji podstawowych operacji arytme-
tycznych.
Przy pisaniu programów najlepiej unika polegania na zachowaniach zalenych
od implementacji. Jeli nie da si takiej zalenoci unikn , warto przynajmniej
„rcznie” sprawdzi wyniki wtpliwych operacji — standard C wymaga na szczcie,
aby sposób realizacji zachowa zalenych od implementacji by udokumentowany.

Pierwszestwo i czno operatorów


Kiedy wyraenie zawiera wicej ni jeden operator, interpretacja wyraenia moe
by wtpliwa. Czy na przykad i + j * k oznacza „dodanie i do j i pomno-
enie sumy przez k”, czy moe „pomnoenie j przez k i dodanie do iloczynu i”?
Wtpliwo mona usun, stosujc nawiasy, a wic zapisujc jawnie albo (i + j)
* k, albo i + (j * k). Jzyk C co do zasady pozwala na grupowanie podwy-
rae w wyraeniach wanie za pomoc nawiasów.
88 Rozdzia 4. Wyraenia

No dobrze, ale jak zinterpretowa wyraenie bez nawiasów? Czy dla kompi-
latora i + j * k to jest (i + j) * k, czy moe jednak i + (j *k)? Tak
jak w wielu innych jzykach, w C potencjalne wtpliwoci tego rodzaju rozstrzyga
si na bazie regu pierwszestwa (albo inaczej priorytetów) operatorów (ang.
precedence). Dla operatorów arytmetycznych pierwszestwo przedstawia si tak
(od najwyszego priorytetu):
+ - (jednoargumentowe)
* / %
+ - (dwuargumentowe)
Operatory wymienione w tym samym wierszu (jak jednoargumentowe + i -)
cechuj si identycznym pierwszestwem.
Kiedy w jednym wyraeniu wystpuje wiele operatorów, moemy okreli
sposób ich interpretowania (kolejno obliczania podwyrae) przez kompilator,
posikujc si nawiasami grupujcymi podwyraenia, od operatorów o najwy-
szym priorytecie po operatory o priorytecie najniszym. Oto przykady:
i + j * k interpretuje si jako i + (j * k)
-i * -j interpretuje si jako (-i) * (-j)
+i + j / k interpretuje si jako (+i) + (j / k)
Reguy pierwszestwa operatorów nie s jednak wystarczajce do rozstrzy-
gnicia wtpliwoci co do kolejnoci obliczania podwyrae, kiedy w wyraeniu
wystpuje wiele operatorów o tym samym priorytecie. W takiej sytuacji zastoso-
wanie ma regua cznoci operatorów (ang. associativity). O operatorze mówimy,
e jest lewostronnie czny, kiedy grupuje operandy od lewej do prawej. Dwuar-
gumentowe operatory arytmetyczne (*, /, %, + i -) s czne lewostronnie, wic:
i - j - k interpretuje si jako (i - j) - k
i * j / k interpretuje si jako (i * j) / k
Operator jest prawostronnie czny, kiedy grupuje operandy od prawej strony do
lewej. Prawostronnie cznymi operatorami s jednoargumentowe operatory aryt-
metyczne (+ i -), wic:
- + i interpretuje si jako -(+i)
Reguy pierwszestwa i cznoci operatorów s bardzo wane take w innych
jzykach programowania, ale w C ich znaczenie jest szczególne. Z drugiej strony
przy liczbie operatorów dostpnych w jzyku C (jest ich niemal pidziesit!) mao
który programista jest w stanie zapamita czno i pierwszestwo wszystkich
tabela operatorów ¥ Dodatek A operatorów. Piszc program w jzyku C, warto wic mie pod rk tabelk opera-
torów i zaglda do niej w razie wtpliwoci. Mona te je eliminowa poprzez
jawne grupowanie podwyrae w nawiasach.

PROGRAM Obliczanie cyfry kontrolnej kodu kreskowego


W latach siedemdziesitych producenci dóbr szybko zbywalnych w Stanach Zjed-
noczonych i Kanadzie zaczli znakowa swoje towary, umieszczajc na nich kody
kreskowe. Taki kod kreskowy albo inaczej kod UPC (universal product code)
4.1. Operatory arytmetyczne 89

identyfikuje zarówno producenta, jak i konkretny produkt. Kady kod kreskowy


reprezentuje dwunastocyfrow liczb (wypisan zreszt najczciej pod kodem
kreskowym). Oto przykadowy kod kreskowy pizzy Stouffer:

Cyfry:
0 13800 15173 5

s jawnie wypisane pod waciwym kodem kreskowym. Pierwsza z nich okrela


typ towaru (dla wikszoci towarów jest to 0 albo 7, dla towarów waonych 2,
dla lekarstw i produktów farmaceutycznych 3, a dla kuponów 5). Pierwsza gru-
pa piciu cyfr identyfikuje producenta towaru (13800 to kod Nestle USA Frozen
Food Division, czyli dziau mroonek amerykaskiego koncernu Nestle). Druga
grupa cyfr (znów pi) to identyfikator produktu (okrelajcy midzy innymi
rozmiar opakowania). Ostatnia cyfra to cyfra kontrolna, której jedynym zadaniem
jest pomoc w weryfikowaniu poprawnoci kodów kreskowych (poprawnoci
poprzednich cyfr). Kiedy taki kod zostanie niepoprawnie zeskanowany, cyfra kon-
trolna nie bdzie si zgadza z jedenastoma pierwszymi cyframi, wic skaner
kasowy odrzuci kod.
Oto jedna z metod obliczania cyfry kontrolnej kodu kreskowego UPC:
Dodaj cyfr pierwsz, trzeci, pit, siódm, dziewit i jedenast.
Dodaj cyfr drug, czwart, szóst, ósm i dziesit.
Pomnó pierwsz sum przez 3 i dodaj j do drugiej sumy.
Od wyniku odejmij 1.
Oblicz reszt z dzielenia pomniejszonego wyniku przez 10.
Odejmij reszt z dzielenia od 9.
Na przykadzie pizzy Stouffer otrzymamy pierwsz sum o wartoci (0+3+0+1+
1+3) = 8; druga suma powinna wynosi (1+8+0+5+7) = 21. Suma iloczynu pierw-
szej sumy przez 3 i drugiej sumy daje 45. 45 odj 1 daje 44. Reszta z dzielenia
44 przez 10 to 4. Rónica 9 – 4 wynosi 5. Oto kilka innych przykadów liczbowych
kodów kreskowych UPC, które moemy sprawdzi — lepiej liczy, zamiast szuka
tych produktów w lodówkach:
Jif Creamy Peanut Butter (18 uncji): 0 51500 24128 ?
Ocean Spray Jellied Cranberry Sauce (8 uncji): 0 31200 01005 ?

Odpowiedzi znajduj si u dou strony1.

1
Brakujce cyfry kontrolne to 8 (Jif) i 6 (Ocean Spray) — przyp. autora.
90 Rozdzia 4. Wyraenia

Napiszmy program, który bdzie oblicza cyfr kontroln dla dowolnego kodu
UPC. Program bdzie wymaga od uytkownika wprowadzenia pierwszych 11
cyfr kodu UPC, a nastpnie wypisze brakujc cyfr kodu. Aby unikn omyek,
nakaemy uytkownikowi wprowadzanie cyfr kodu partiami: najpierw wprowadzi
samotn cyfr z lewej, potem grup piciu cyfr kodu producenta, a na koniec pitk
cyfr kodu produktu. Sesja z programem bdzie wygldaa mniej wicej tak:
Podaj pierwsz cyfr kodu: 0
Podaj pierwsz grup piciu cyfr kodu: 13800
Podaj nastpn grup piciu cyfr kodu: 15173
Cyfra kontrolna: 5

Zamiast wczytywa poszczególne grupy cyfr jako liczby piciocyfrowe,


bdziemy wczytywa je jako pitk liczb jednocyfrowych. Wczytanie cyfr jako
osobnych liczb bdzie dla nas potem znacznie wygodniejsze. Nie trzeba bdzie
si te martwi na przykad o to, e jedna z dwóch piciocyfrowych liczb nie
zmieci si w zmiennej int (w niektórych starszych kompilatorach graniczna war-
to typu int to 32 767). Aby wczyta pojedyncz cyfr, wykorzystamy funkcj
scanf ze specyfikatorami konwersji %1d, odpowiadajcymi liczbom jednocyfro-
wym (w zapisie dziesitnym).
upc.c /* Obliczanie cyfry kontrolnej kodu kreskowego UPC */

#include <stdio.h>

int main(void)
{
int d, i1, i2, i3, i4, i5, j1, j2, j3, j4, j5,
first_sum, second_sum, total;

printf("Podaj pierwsz cyfr kodu: ");


scanf("%1d", &d);
printf("Podaj pierwsz grup piciu cyfr kodu: ");
scanf("%1d%1d%1d%1d%1d", &i1, &i2, &i3, &i4, &i5);
printf("Podaj drug grup piciu cyfr kodu: ");
scanf("%1d%1d%1d%1d%1d", &j1, &j2, &j3, &j4, &j5);

first_sum = d + i2 + i4 + j1 + j3 + j5;
second_sum = i1 + i3 + i5 + j2 + j4;
total = 3 * first_sum + second_sum;

printf("Cyfra kontrolna: %d\n", 9 - ((total - 1) % 10));

return 0;
}

Zauwamy, e wyraenie 9 - ((total - 1) % 10) mona by zapisa


jako 9 - (total - 1) % 10, ale dodatkowa para nawiasów nie zaciemnia
wyraenia — wrcz odwrotnie, czyni je czytelniejszym.
4.2. Operatory przypisania 91

4.2. Operatory przypisania


Po obliczeniu wartoci wyraenia czsto chcemy zachowa t warto w zmiennej
do pó niejszego uycia. W jzyku C suy do tego celu operator przypisania
(ang. assignment) w postaci symbolu =. Aby dao si wygodnie aktualizowa
warto zmiennej wartoci wyraenia, C oferuje równie zestaw tak zwanych
zoonych operatorów przypisania (ang. compound assignment operators).

Przypisania proste
Efektem przypisania v = e jest obliczenie wyraenia e i skopiowanie wartoci
wyraenia do v. Jak wida na przykadach poniej, e moe by zmienn, sta albo
dowolnym wyraeniem:
i = 5; /* i ma teraz warto 5 */
j = i; /* j ma teraz warto 5 */
k = 10 * i + j; /* k ma teraz warto 55 */

Jeli e i v nie s wartociami tego samego typu, w toku realizacji przypisania war-
to e zostanie skonwertowana na typ v:
int i;
float f;

i = 72.99f; /* i ma teraz warto 72 */


f = 136; /* f ma teraz warto 136.0 */

przypisania z konwersj ¥ 7.4 Do tematu konwersji wartoci przypisywanej wrócimy nieco pó niej.
W wielu innych jzykach programowania przypisanie jest instrukcj. W jzyku
C przypisanie jest jednak operatorem, tak jak +. Innymi sowy, akt przypisania
jest wyraeniem posiadajcym warto, tak samo jak akt dodawania jest wyrae-
niem posiadajcym warto (tu równ sumie operandów). Wartoci przypisania
v = e jest warto v, obliczana ju po wykonaniu przypisania. Wartoci przy-
pisania i = 72.99f jest wic nie 72,99, ale 72.

Efekty uboczne
Zazwyczaj nie oczekuje si od operatorów, aby modyfikoway wartoci operan-
dów — w matematyce operatory nie maj przecie takich waciwoci. Dziaanie
i + j nie modyfikuje ani i, ani j, a jedynie oblicza sum i oraz j.
Wikszo operatorów jzyka C równie nie modyfikuje operandów, ale nie-
które to robi. O takich operatorach mówimy, e maj efekty uboczne, poniewa ich
dziaanie nie ogranicza si tylko do jawnego wyliczenia wartoci. Pierwszym ope-
ratorem, jaki poznajemy od strony efektów ubocznych, jest prosty operator przypi-
sania — nie tylko oblicza warto wyraenia prawego operandu, ale take mody-
fikuje warto lewego operandu. Obliczenie wartoci wyraenia i = 0 daje warto
0, a efektem ubocznym obliczenia wyraenia jest przypisanie 0 do i.
92 Rozdzia 4. Wyraenia

Poniewa przypisanie jest operatorem, moemy konstruowa acuchowe wyra-


enia z operatorami przypisania:
i = j = k = 0;

Operator = jest czny prawostronnie, wic takie przypisanie jest interpretowane


jako:
i = (j = (k = 0));

W efekcie w pierwszej kolejnoci nastpi przypisanie 0 do k, nastpnie wynik


przypisania zostanie przypisany do j, a wynik tego przypisania — do i.

Naley si wystrzega nieoczekiwanych wyników w acuchowych przypisaniach,


spowodowanych konwersj typów operandów:
int i;
float f;

f = i = 33.3f;

Zmienna i otrzyma tutaj warto 33 i przez to do zmiennej f przypiszemy 33.0,


a nie 33.3.

Zasadniczo przypisanie w postaci v = e jest dozwolone wszdzie tam,


gdzie byaby dozwolona warto typu v. W poniszym przykadzie wyraenie
j = i kopiuje warto i do zmiennej j. Nowa warto j jest potem dodawana do
1 i tak obliczona warto jest przypisywana do k:
i = 1;
k = 1 + (j = i);
printf("%d %d %d\n", i, j, k); /* wypisuje "1 1 2" */

Takie stosowanie operatora przypisania trudno jednak uzna za dobr praktyk


programistyczn. Przede wszystkim dlatego, e „zagniedone przypisania” zmniej-
szaj czytelno programu. Mog by równie przyczyn subtelnych bdów,
o których bdzie mowa w podrozdziale 4.4.

L-wartoci
Wikszo operatorów jzyka C pozwala, aby w roli operandów wystpoway
zmienne, stae albo wyraenia (równie zawierajce inne operatory). Operator
przypisania jest o tyle wyjtkowy, e wymaga, aby lewy operand by tak zwan
l-wartoci (ang. lvalue). L-warto reprezentuje obiekt przechowywany w pamici
komputera. Nie moe to by staa ani na przykad wynik porównania. L-warto-
ciami s wszystkie zmienne. Wyraenia w rodzaju 10 albo 2 * i l-wartociami
nie s. Na razie jedyne l-wartoci, które znamy, to wanie zmienne. W dalszych
rozdziaach powiemy sobie take o innych l-wartociach.
4.2. Operatory przypisania 93

Z wymagania, aby lewym operandem operatora przypisania bya l-warto,


wynika, e po lewej stronie operatora przypisania nie wolno stosowa adnych
wyrae:
12 = i; /*** LE ***/
i + j = 0; /*** LE ***/
-i = j; /*** LE ***/

Takie bdne przypisania s wykrywane przez kompilator — próba skompilowa-


nia powyszych instrukcji zaowocuje bdem kompilacji z komunikatem „invalid
lvalue in assignment” („niepoprawna l-warto w przypisaniu”).

Przypisania zoone
W programach pisanych w jzyku C czsto widzi si przypisania, które przy obli-
czaniu wartoci przypisania bazuj na poprzedniej wartoci modyfikowanej zmien-
nej. Oto przykadowe przypisanie dodajce 2 do biecej wartoci zmiennej i:
i = i + 2;

Takie i tym podobne instrukcje mona w jzyku C skraca do postaci przypisa


zoonych (ang. compound assignments). Moemy wic do analogicznej operacji
wykorzysta operator zoony +=:
i += 2; /* to samo, co i = i + 2 */

Operator += dodaje do wartoci lewego operandu warto prawego operandu.


W jzyku C mamy do dyspozycji dziewi zoonych operatorów przypisania,
w tym:
-= *= /= %=
inne operatory
przypisania ¥ 20.1 (o pozostaych zoonych operatorach przypisania powiemy sobie w jednym
z dalszych rozdziaów). Wszystkie operatory przypisa zoonych dziaaj na
podobnych zasadach:
v += e dodaje e do v i zapisuje sum w v
v -= e odejmuje e do v i zapisuje rónic w v
v *= e mnoy v przez e i zapisuje iloczyn w v
v /= e dzieli v przez e i zapisuje iloraz w v
v %= e oblicza reszt z dzielenia v przez e i zapisuje wynik v
Wcale nie oznacza to, e zapis v += e jest zupenie „tosamy” z v = v + e.
Przede wszystkim ze wzgldu na pierwszestwo operatorów i *= j + k to nie
to samo co i = i * j + k. S te rzadkie przypadki, kiedy v += e jest
róne od v = v + e, gdy v posiada efekty uboczne. Podobne zastrzeenia doty-
cz równie pozostaych operatorów przypisania.

Przy stosowaniu przypisa zoonych trzeba uwaa, eby nie zamieni miejscami
znaków w symbolu operatora. Przestawienie znaków moe doprowadzi do utwo-
rzenia wyraenia, które bdzie poprawne z punktu widzenia skadni programu
94 Rozdzia 4. Wyraenia

(kompilator nie zgosi bdu), ale niepoprawne ze wzgldu na oczekiwane dzia-


anie. Jeli na przykad zamierzamy napisa i += j, ale omykowo napiszemy
i =+ j, program jak najbardziej si skompiluje. Niestety, zamiast doda do i
warto j, wymusimy wykonanie wyraenia i = (+j), czyli skopiujemy j do i.

Operatory przypisa zoonych maj te same waciwoci co operatory przy-


pisa prostych. W szczególnoci s prawostronnie czne, wic instrukcja:
i += j += k;

oznacza:
i += (j += k);

4.3. Operatory inkrementacji i dekrementacji


Do najczciej uywanych operatorów jzyka C nale operatory inkrementacji
(dodania jedynki) i dekrementacji (odjcia jedynki). Tego rodzaju operacje mona,
rzecz jasna, zapisa za pomoc zwyczajnych operatorów arytmetycznych:
i = i + 1;
j = j - 1;

Mona te wykorzysta operatory przypisa zoonych:


i += 1;
j -= 1;

Ale C oferuje jeszcze krótszy zapis tych operacji przy uyciu operatorów ++
(inkrementacja) i -- (dekrementacja).
Na pierwszy rzut oka operatory inkrementacji i dekrementacji to wcielona
prostota: ++ dodaje jeden, a -- odejmuje jeden od operandu. Niestety, prostota
jest tu mylca. Operatory inkrementacji i dekrementacji bywaj nieoczywiste
w uyciu. Wynika to z tego, e operatory inkrementacji (++) i dekrementacji (--)
mog wystpowa w postaci przedrostkowej (np. ++i, --i) albo przyrostkowej
(np. i++, i--). Waciwy wybór rodzaju operatora silnie wpywa na poprawno
programu.
Kolejna komplikacja tkwi w fakcie, e operatory ++ i -- posiadaj efekty
uboczne (tak jak operatory przypisania) — modyfikuj warto operandu. Oblicze-
nie wyraenia ++i (inkrementacja przedrostkowa albo tzw. „przed-inkrementacja” i)
daje warto i + 1 oraz — w ramach efektu ubocznego — zwiksza i o jeden:
i = 1;
printf("i to %d\n", ++i); /* wypisuje "i to 2" */
printf("i to %d\n", i); /* wypisuje "i to 2" */

Z kolei obliczenie wyraenia i++ („po-inkrementacja”) daje warto i, ale


w ramach efektu ubocznego i jest zwikszane o 1:
4.3. Operatory inkrementacji i dekrementacji 95

i = 1;
printf("i to %d\n", i++); /* wypisuje "i to 1" */
printf("i to %d\n", i); /* wypisuje "i to 2" */

Pierwsza instrukcja z wywoaniem printf wypisze na wyjciu pierwotn war-


to i, jeszcze sprzed inkrementacji. Drugie wywoanie printf wypisze now
warto i. Jak wida, ++i oznacza „zwiksz natychmiast warto i”, a i++ ozna-
cza „daj biec warto i, a potem zwiksz i”. Co to znaczy „potem”? Stan-
dard jzyka C nie precyzuje dokadnego momentu wykonania inkrementacji ope-
ratora przyrostkowego, ale mona bezpiecznie zaoy, e i bdzie miao now
warto jeszcze przed wykonaniem nastpnej instrukcji.
Podobne waciwoci ma operator dekrementacji --:
i = 1;
printf("i to %d\n", --i); /* wypisuje "i to 0" */
printf("i to %d\n", i); /* wypisuje "i to 0" */

i = 1;
printf("i to %d\n", i--); /* wypisuje "i to 1" */
printf("i to %d\n", i); /* wypisuje "i to 0" */

Kiedy operator ++ albo -- wystpi wielokrotnie w jednym wyraeniu, warto


wyraenia i faktyczne wartoci operandów mog by trudne do ustalenia. We my
nastpujcy przykad:
i = 1;
j = 2;
k = ++i + j++;

Jakie s wartoci i, j i k po wykonaniu tych instrukcji? Poniewa zmienna i


bya inkrementowana przed uyciem jej wartoci w wyraeniu, a zmienna j bya
inkrementowana po uyciu jej wartoci w wyraeniu, powysze instrukcje s
równowane nastpujcym:
i = i + 1;
k = i + j;
j = j + 1;

wic wynikowe wartoci zmiennych i, j i k to odpowiednio 2, 3 i 4. Dla porów-


nania wykonanie instrukcji:
i = 1;
j = 2;
k = i++ + j++;

zostawi zmienne i, j i k z wartociami (odpowiednio) 2, 3 i 3.


Dla porzdku wypada dopowiedzie, e przyrostkowe wersje operatorów ++
i -- maj wyszy priorytet ni jednoargumentowe operatory +, - i s czne lewo-
stronnie. Wersje przedrostkowe maj priorytet taki sam jak jednoargumentowe
operatory +, - i s czne prawostronnie.
96 Rozdzia 4. Wyraenia

4.4. Obliczanie wartoci wyra e


W tabeli 4.2 zestawiono operatory omówione w poprzednich podrozdziaach
(podobn tabel, ale z kompletem operatorów, zawiera dodatek A). Pierwsza
kolumna okrela pierwszestwo operatorów wzgldem pozostaych operatorów
w tabeli (1 to najwikszy priorytet, 5 to priorytet najmniejszy). Ostatnia kolumna
opisuje czno operatorów.

Tabela 4.2. Priorytet Nazwa Symbol czno


Czciowa lista
operatorów jzyka C 1 inkrementacja (przyrostkowa) ++ lewostronna
dekrementacja (przyrostkowa) --
2 inkrementacja (przedrostkowa) ++ prawostronna
dekrementacja (przedrostkowa) --
jednoargumentowy plus +
jednoargumentowy minus -
3 multiplikatywne * / % lewostronna
4 addytywne + - lewostronna
5 przypisania = *= /= %= prawostronna
+= -=

Tabela 4.2 (albo jej uzupeniony odpowiednik z dodatku A) bdzie bardzo


przydatna. Zaómy, e w kodzie ródowym (na przykad cudzym) napotkamy tak
instrukcj:
a = b += c++ - d + --e / -f

Takie wyraenie byoby znacznie czytelniejsze, gdyby zawierao nawiasy gru-


pujce podwyraenia. Z pomoc tabeli 4.2 moemy atwo samodzielnie uzupeni
pogrupowa wyraenie — wystarczy znale  w wyraeniu operator o najwy-
szym priorytecie i wraz z operandami uj go w nawias. Od tej pory bdzie dla nas
pojedynczym operandem. Technik powtarzamy do momentu rozpoznania wszyst-
kich podwyrae.
W naszym przykadzie operatorem o najwyszym priorytecie jest ++, wyst-
pujcy tu jako operator przyrostkowy. Ujmujemy operator z operandami w nawias:
a = b += (c++) - d + --e / -f

Nastpny wedug pierwszestwa jest operator -- (przedrostkowy) oraz jed-


noargumentowy minus (oba maj priorytet 2.):
a = b += (c++) - d + (--e) / (-f)

Drugi znak minusa w wyraeniu ma operand po lewej stronie, wic jest operatorem
odejmowania, a nie kolejnym jednoargumentowym minusem.
Nastpnie odnajdujemy operator / (priorytet 3.):
a = b += (c++) - d + ((--e) / (-f))
4.4. Obliczanie wartoci wyrae 97

Wyraenie zawiera jeszcze dwa operatory o priorytecie 4. (wedug tabeli 4.2):


dodawanie i odejmowanie. Kiedy w wyraeniu ssiaduj ze sob dwa operatory
o równym priorytecie, naley bardzo uwanie zastosowa regu cznoci.
W naszym przykadzie - i + otaczaj d; czno dwuargumentowych operatorów
+ i - jest lewostronna, wic stawiamy nawiasy najpierw wokó odejmowania,
a potem wokó dodawania:
a = b += (((c++) - d) + ((--e) / (-f)))

Zostay ju tylko przypisania, oba przy operandzie b, wic znów trzeba si
posikowa cznoci. Operatory przypisania s czne prawostronnie (od prawej
do lewej), zatem najpierw ujmujemy w nawias podwyraenie z operatorem +=,
a potem podwyraenie z operatorem =:
(a = (b += (((c++) - d) + ((--e) / (-f)))))

W ten sposób dokonalimy penego pogrupowania wyraenia.

Kolejno obliczania podwyrae


Reguy pierwszestwa i cznoci operatorów pozwalaj na skuteczne rozbicie
dowolnie skomplikowanego wyraenia na podwyraenia — moemy dziki nim
pogrupowa poszczególne podwyraenia w nawiasy. Paradoksalnie obie reguy
nie zawsze pozwalaj na okrelenie wartoci wyraenia, która moe zalee od
kolejnoci obliczania wartoci poszczególnych podwyrae.
operatory logiczne and i or ¥ 5.1 Jzyk C nie definiuje kolejnoci obliczania wartoci podwyrae (z wyjtkiem
operator warunkowy ¥ 5.2 podwyrae angaujcych operatory logiczne and i or, operatory warunkowe
operator przecinka ¥ 5.3 i operatory przecinka). Przez to w przypadku wyraenia (a + b) * (c - d)
nie moemy mie pewnoci, czy jako pierwsze zostanie obliczone podwyraenie
(a + b), czy moe (c -d).
Wikszo wyrae ma t sam warto niezalenie od tego, w jakiej kolej-
noci zostay obliczone ich podwyraenia. Bywa jednak, e podwyraenie mody-
fikuje jeden ze swoich operandów. We my poniszy przykad:
a = 5;
c = (b = a + 2) - (a = 1);

Efekt wykonania drugiej instrukcji jest niezdefiniowany. Standard jzyka C nie


mówi nic jednoznacznego na temat wartoci tak zbudowanego wyraenia. W przy-
padku wikszoci kompilatorów zmienna c bdzie miaa warto 6 albo 2. Jeli
jako pierwsze zostanie obliczone podwyraenie (b = a + 2), b otrzyma war-
to 7, a c zostanie obliczone jako 6. Ale jeli pierwszym obliczonym podwyrae-
niem bdzie (a = 1), wtedy b otrzyma warto 3, a w c znajdzie si warto 2.

Naley unika konstruowania wyrae, w których odwoujemy si do wartoci


zmiennej, a równoczenie (w innym podwyraeniu) bazujemy na tej wartoci.
Wyraenie (b = a + 2) - (a = 1) zawiera odwoanie do wartoci a
w pierwszym podwyraeniu oraz modyfikacj wartoci do a w drugim podwyraeniu
(przez przypisanie jedynki). Niektóre kompilatory oznaczaj takie wyraenia komu-
nikatami z ostrzeeniami w rodzaju „operacja na ‘a’ moe by niezdefiniowana”.
98 Rozdzia 4. Wyraenia

Aby zapobiec tego rodzaju problemom, najlepiej unika stosowania operatora


przypisania w podwyraeniu. Zamiast tego naley wykona przypisania jako osobne
instrukcje. Na przykad nasze wtpliwe wyraenie moe zosta rozbite nastpujco:
a = 5;
b = a + 2;
a = 1;
c = b - a;

W ten sposób zagwarantujemy, e zmienna c otrzyma warto 6.


Poza operatorami przypisania mamy jeszcze inne operatory modyfikujce
warto operandu — chodzi o operatory inkrementacji i dekrementacji. Przy korzy-
staniu z tych operatorów równie trzeba uwaa, aby nie doprowadzi do powstania
wyraenia, którego poprawna warto jest zalena od konkretnej kolejnoci obliczania
podwyrae, jak tutaj, gdzie do j moe zosta przypisana jedna z dwóch wartoci:
i = 2;
j = i * i++;

Pozornie jest oczywiste, e j otrzyma warto 4. Niestety, w efekcie wykonania


takich instrukcji zmienna j moe mie równie warto 6. Oto moliwy scena-
riusz: (1) najpierw pobierany jest drugi operand (pierwotna warto i); nastpnie i
podlega inkrementacji (2) i pobierany jest drugi operand (ju nowa warto i);
(3) obliczany jest iloczyn obu operandów: liczba 6. „Pobranie” wartoci zmiennej
oznacza odczytanie wartoci z pamici skojarzonej ze zmienn. Pó niejsza zmiana
wartoci zmiennej w pamici nie wpywa ju na warto pobran, bo pobrana war-
to jest kopiowana w specjalne miejsce (tzw. rejestr) wewntrz procesora.

Niezdefiniowane zachowanie programu


Wedug standardu jzyka C instrukcje takie jak c = (b = a + 2) - (a = 1)
oraz j = i * i++ powoduj tzw. niezdefiniowane zachowanie (ang. undefined
behavior) programu (patrz podrozdzia 4.1). Kiedy program zawiera zachowania
niezdefiniowane, nie mona ju nic powiedzie o jego wykonaniu. W zalenoci od
uytego kompilatora moe si on zachowywa rónie, ale to nie wyczerpuje pojcia
„niezdefiniowanego zachowania”. Przede wszystkim program moe si w ogóle nie
skompilowa , skompilowany moe si nie uruchomi , a uruchomiony moe si
wyoy , dziaa niepoprawnie bd dawa nieznaczce wyniki (np. za kadym
uruchomieniem inne). Innymi sowy, przestaje by „programem” — zachowa nie-
zdefiniowanych trzeba wic unika jak ognia.

4.5. Instrukcje wyra eniowe


Jzyk C posiada niezwyk waciwo — tutaj ka de wyraenie moe by uyte
jako instrukcja programu. Kade wyraenie (niezalenie od jego typu i od tego, co
oblicza) moe zosta zamienione na instrukcj — wystarczy zakoczy je red-
nikiem. Na przykad na instrukcj moemy zamieni wyraenie ++i:
++i;
Pytania i odpowiedzi 99

Kiedy dochodzi do wykonania tej instrukcji, nastpuje zwikszenie wartoci


i o jeden, a pó niej pobierana jest nowa warto i (tak jakby miaa za chwil zosta
wykorzystana przy obliczaniu wyraenia nadrzdnego). Ale skoro ++i nie jest
czci wikszego wyraenia, pobrana warto jest odrzucana, a program przecho-
dzi do wykonania nastpnej instrukcji (ale inkrementacja i jest oczywicie trwaa).
Skoro warto instrukcji wyraeniowej jest odrzucana, nie ma wikszego sensu
wykorzystywanie wyrae jako instrukcji, chyba e s to wyraenia z efektami
ubocznymi; one zostan przecie wykonane mimo odrzucenia obliczonej wartoci
wyraenia. We my trzy przykady. W pierwszym do i przypisywana jest war-
to 1 — nowa warto i jest pobierana, ale zaraz odrzucana:
i = 1;

W drugim przykadzie warto i jest pobierana, ale znów nie bdzie nigdzie
wykorzystana. Za to samo i ju po pobraniu wartoci zostanie zwikszone o jeden:
i++;

W trzecim przykadzie zostanie obliczona warto wyraenia i * j - 1, ale


obliczona warto zaraz bdzie odrzucona:
i * j - 1;

Taka instrukcja nie ma adnego efektu ubocznego, nie zmienia adnego z operan-
dów, wic jest zwyczajnie bezcelowa.

Bezcelow, a wic pust instrukcj wyraeniow mona atwo popeni przez


prost literówk. Wystarczy, e zamiast:
i = j;

przypadkiem napiszemy:
i + j;

(taki bd jest prawdopodobny tym bardziej, e znaki + i = zajmuj ten sam klawisz
na klawiaturze). Niektóre kompilatory wykrywaj bezcelowe instrukcje wyrae-
niowe, generujc przy nich ostrzeenia w rodzaju „instrukcja bez efektu” („sta-
tement with no effect”).

Pytania i odpowiedzi
P: Zauwayem, e jzyk C nie posiada operatora potgowania. Jak mam pod-
nosi liczby do potgi?
O: Jeli wykadnik potgi jest niewielk dodatni liczb cakowit, potgowanie naj-
lepiej zrealizowa przez wielokrotne mnoenie (np. i * i * i dla obliczenia
szecianu i). Do obliczania potg o wykadnikach niecakowitych najlepiej wyko-
funkcja pow ¥ 23.3 rzysta funkcj pow.
100 Rozdzia 4. Wyraenia

P: Chciaem zastosowa operator % przy operandzie typu float, ale program


nie daje si skompilowa . Co mog zrobi (s. 86)?
funkcja fmod ¥ 23.3 O: Operator % wymaga operandów cakowitych. Spróbuj uy funkcji fmod.
P: Dlaczego dziaanie operatorów dzielenia (/) i reszty z dzielenia (%) dla ujem-
nych operandów jest tak zagmatwane (s. 87)?
O: Zasady dziaania tych operatorów nie s tak zagmatwane, jakby si wydawao.
W obu wersjach standardu celem jest zapewnienie, eby warto (a / b) *
b + a % b zawsze bya równa a (i faktycznie, oba standardy gwarantuj tak
zaleno, o ile tylko warto a / b jest wartoci „reprezentowaln”). Problem
polega na tym, e zaoon zaleno mona speni na dwa sposoby, przy rónych
metodach obliczania a / b i a % b. Wedug C89 albo -9 / 7 to –1 i -9 % 7
to –2 (równo jest speniona), albo -9 / 7 to –2 i -9 % 7 to 5 (i znów rów-
no jest speniona). W pierwszym przypadku (-9 / 7) * 7 + -9 % 7 daje
–1×7+–2 = –9, w drugim przypadku (-9 / 7) * 7 + -9 % 7 to –2×7+
5 = –9. Do czasu pojawienia si standardu C99 wikszo procesorów wykony-
waa ju dzielenie cakowite z obcinaniem w kierunku zera, wic tak wanie
regu dzielenia zapisano w standardzie C99 jako jedyn dozwolon warto
ilorazu z operandem ujemnym.
P: Skoro w C s l-wartoci, czy s te r-wartoci (s. 92)?
O: W rzeczy samej. L-warto jest wyraeniem, które jest dozwolone po lewej stronie
operatora przypisania; r-warto to wyraenie, które jest dozwolone po prawej
stronie. R-warto moe wic by zmienn, sta albo dowolnym wyraeniem.
W niniejszej ksice, podobnie jak w standardzie jzyka C, bdziemy trzyma
si okrelenia „wyraenie”, które jednak do dobrze oddaje istot r-wartoci.
*P: Bya mowa o tym, e v += e nie jest odpowiednikiem v = v + e, jeli v
ma efekty uboczne. Jak to rozumie (s. 93)?
O: Obliczenie wartoci v += e powoduje, e warto v jest obliczana tylko raz.
v w wyraeniu v = v + e jest obliczane dwa razy. Wic jeli w tym drugim
przypadku v posiada efekt uboczny, zostanie on wykonany dwukrotnie. W tym
przykadzie i jest inkrementowane raz:
a[i++] += 2;

ale jeli zamiast += uyjemy przypisania =, otrzymamy:


a[i++] = a[i++] + 2;

Warto i jest równoczenie pobierana i modyfikowana w obrbie jednej instruk-


cji, wic wynik wykonania takiej instrukcji jest niezdefiniowany. Jest prawdopo-
dobne, e i zostanie zwikszone dwukrotnie, ale w istocie nie mona nic pewnego
powiedzie o dziaaniu takiego programu.
P: W jakim celu w C udostpniono operatory ++ i --? Czy s one szybsz meto-
d inkrementacji i dekrementacji zmiennej, czy s jedynie wygodniejsze
(krótsze w zapisie) (s. 94)?
O: Jzyk C odziedziczy operatory ++ i -- w spadku po jzyku B Kena Thompsona.
Thompson wprowadzi te operatory, poniewa jego kompilator B najwyra niej
potrafi efektywniej przetumaczy zapis ++i ni i = i + 1. Operatory te
Pytania i odpowiedzi 101

stay si sol jzyka C (bazuje na nich wiele jego sawnych idiomów). W nowo-
czesnych kompilatorach stosowanie ++ i -- zapewne ani bardzo nie przyspieszy
programu, ani nie zmniejszy bardzo rozmiaru pliku wynikowego. Nieustajca
popularno tych operatorów wynika chyba z ich zwartoci.
P: Czy operatory ++ i -- dziaaj ze zmiennymi typu float?
O: Tak, operacje inkrementacji i dekrementacji mona stosowa do wartoci cakowi-
tych i zmiennoprzecinkowych, jednak w praktyce mao kto próbuje inkrementowa
albo dekrementowa zmienn typu float.
*P: Kiedy dokadnie nastpuje zwikszenie wartoci operandu w przypadku przy-
rostkowych wersji ++ i -- (s. 95)?
O: wietne pytanie. Niestety, nie mona na nie atwo odpowiedzie. Standard jzyka
C wprowadza pojcie tak zwanego „punktu sekwencji” i mówi, e „aktualizacja
skadowanej wartoci operandu powinna odby si pomidzy poprzednim a nastp-
nym punktem sekwencji”. W jzyku C okrelono kilka rónych punktów sekwen-
cji. Jednym z nich jest koniec instrukcji wyraeniowej — na kocu instrukcji
wyraeniowej wszystkie opó nione inkrementacje i dekrementacje powinny zosta
wykonane; nie moe doj do rozpoczcia wykonywania nastpnej instrukcji
z pominiciem tego kroku.
Niektóre operatory, o których powiemy sobie w dalszej czci ksiki (logiczny
operator and, logiczny operator or, operator warunkowy i operator przecinka),
równie stanowi punkty sekwencji. To samo dotyczy wywoa funkcji — argu-
menty wywoania funkcji musz by w peni obliczone przed wykonaniem wywo-
ania. Jeli argument wywoania jest wyraeniem zawierajcym przyrostkowy
operator ++ albo --, inkrementacja bd dekrementacja musi zosta wykonana
jeszcze przed wykonaniem wywoania funkcji.
P: Co oznacza „odrzucenie” wartoci instrukcji wyraeniowej (s. 99)?
O: Z definicji wyraenie reprezentuje warto. Jeli np. i ma warto 5, to obliczenie
wyraenia i + 1 daje warto 6. Zamiemy to wyraenie na instrukcj wyrae-
niow, dodajc rednik na kocu:
i + 1;

W ramach wykonywania tej instrukcji dochodzi do obliczenia wartoci wyraenia


i + 1. Poniewa jednak ta warto nie jest nigdzie wykorzystywana (nie zostaa
przypisana do zmiennej ani nie jest wykorzystywana jako podwyraenie) —
przepada.
P: A co z instrukcjami typu i = 1;? Nie widz, eby co tu byo tracone.
O: Pamitajmy, e przypisanie jest w jzyku C operatorem i jak kady operator gene-
ruje warto. Przypisanie:
i = 1;

powoduje zapisanie 1 w zmiennej i, ale jako wyraenie ma warto przypisania (1)


i ta wanie warto jest odrzucana. Odrzucanie wartoci wyraenia nie jest jak
wielk strat, poniewa w naszej instrukcji chodzio nam przede wszystkim o zmo-
dyfikowanie zmiennej i.
102 Rozdzia 4. Wyraenia

wiczenia
Podrozdzia 4.1 1. Napisz, co pojawi si na wyjciu programu wykonujcego ponisze instrukcje. Zaó, e
i, j i k s zmiennymi typu int:
(a) i = 5; j = 3;
printf("%d %d", i / j, i % j);
(b) i = 2; j = 3;
printf("%d", (i + 10) % j);
(c) i = 7; j = 8; k = 9;
printf("%d", (i + 10) % k / j);
(d) i = 1; j = 2; k = 3;
printf("%d", (i + 5) % (j + 2) / k);

* 2. Czy wyraenie (-i)/j bdzie miao zawsze t sam warto co -(i/j), jeli i i j s
dodatnimi wartociami cakowitymi? Uzasadnij odpowied .

3. Jaka bdzie warto poniszych wyrae wedug standardu C89 (jeli moliwa jest wicej
ni jedna warto, podaj wszystkie warianty):
(a) 8 / 5
(b) -8 / 5
(c) 8 / -5
(d) -8 / -5

4. Powtórz wiczenie 3. dla wytycznych standardu C99.

5. Jaka bdzie warto poniszych wyrae wedug standardu C89 (jeli moliwa jest wicej
ni jedna warto, podaj wszystkie warianty):
(a) 8 % 5
(b) -8 % 5
(c) 8 % -5
(d) -8 % -5

6. Powtórz wiczenie 5. dla wytycznych standardu C99.

7. Algorytm obliczania cyfry kontrolnej UPC koczy si nastpujcymi krokami:

Odejmij 1 od sumy.
Oblicz reszt z dzielenia zmniejszonej sumy przez 10.
Odejmij reszt z dzielenia od 9.

A korci, eby ten algorytm uproci nastpujco:

Oblicz reszt z dzielenia sumy przez 10.


Odejmij reszt z dzielenia od 10.

Dlaczego taka poprawka nie zadziaa?

8. Czy program upc.c bdzie wci poprawny, jeli wyraenie 9 – ((total – 1) % 10) zostanie
zastpione przez (10 - (total % 10)) % 10?
Podrozdzia 4.2 9. Napisz, co pojawi si na wyjciu programu wykonujcego ponisze instrukcje. Zaó, e i,
j i k s zmiennymi typu int:
wiczenia 103

(a) i = 7; j = 8;
i *= j + 1;
printf("%d %d", i, j);
(b) i = j = k = 1;
i += j += k;
printf("%d %d %d", i, j, k);
(c) i = 1; j = 2; k = 3;
i -= j -= k;
printf("%d %d %d", i, j, k);
(d) i = 2; j = 1; k = 0;
i *= j *= k;
printf("%d %d %d", i, j, k);

10. Napisz, co pojawi si na wyjciu programu wykonujcego ponisze instrukcje. Zaó, e
ii j s zmiennymi typu int:
(a) i = 6;
j = i += i;
printf("%d %d", i, j);
(b) i = 5;
j = (i -= 2) + 1;
printf("%d %d", i, j);
(c) i = 7;
j = 6 + (i = 2.5);
printf("%d %d", i, j);
(d) i = 2; j = 8;
j = (i = 6) + (j = 3);
printf("%d %d", i, j);
Podrozdzia 4.3 *11. Napisz, co pojawi si na wyjciu programu wykonujcego ponisze instrukcje. Zaó, e i, j
i k s zmiennymi typu int:
(a) i = 1;
printf("%d ", i++ - 1);
printf("%d", i);
(b) i = 10; j = 5;
printf("%d ", i++ - ++j);
printf("%d %d", i, j);
(c) i = 7; j = 8;
printf("%d ", i++ - --j);
printf("%d %d", i, j);
(d) i = 3; j = 4; k = 5;
printf("%d ", i++ - j++ + --k);
printf("%d %d %d", i, j, k);

12. Napisz, co pojawi si na wyjciu programu wykonujcego ponisze instrukcje. Zaó, e
i i j s zmiennymi typu int:
(a) i = 5;
j = ++i * 3 - 2;
printf("%d %d", i, j);
(b) i = 5;
j = 3 - 2 * i++;
printf("%d %d", i, j);
104 Rozdzia 4. Wyraenia

(c) i = 7;
j = 3 * i-- + 2;
printf("%d %d", i, j);
(d) i = 7;
j = 3 + --i * 2;
printf("%d %d", i, j);

13. Które z wyrae: ++i czy i++ jest dokadnie równowane wyraeniu (i += 1)? Uza-
sadnij odpowied .
Podrozdzia 4.4 14. Pogrupuj podwyraenia w nawiasy tak, aby zilustrowa sposób interpretacji poniszych
wyrae zoonych:
(a) a * b - c * d + e
(b) a / b % c / d
(c) - a - b + c - + d
(d) a * - b / c - d
Podrozdzia 4.5 15. Podaj wartoci zmiennych i i j po wykonaniu kadej z poniszych instrukcji (pocztkowa
warto i to 1, a pocztkowa warto j to 2):
(a) i += j;
(b) i--;
(c) i * j / i;
(d) i % ++j;

Zadania programistyczne
1. Napisz program, który bdzie wymaga od uytkownika wprowadzenia liczby dwucyfrowej,
a nastpnie wypisze t liczb w odwróconej kolejnoci cyfr. Sesja z programem powinna
przebiega tak:

Podaj liczb dwucyfrow: 28


Wspak: 82

2. Rozbuduj program z zadania 1. tak, aby obsugiwa liczby trzycyfrowe.

3. Przerób program z zadania 2. tak, eby program wypisywa odwrotny zapis liczby trzycy-
frowej, bez uycia operacji arytmetycznych do podziau liczby na cyfry. Wskazówka: Zaj-
rzyj do programu upc.c z podrozdziau 4.1.

4. Napisz program, który wczytuje liczb wprowadzon na wejcie i wywietla j w zapisie


ósemkowym:

Podaj liczb pomidzy 0 i 32767: 1953


W zapisie ósemkowym to: 03641

Wyjcie programu powinno by wywietlane z uyciem piciu cyfr, nawet jeli zapis
liczby nie wymaga ich tylu. Wskazówka: Aby zamieni liczb na reprezentacj ósemkow,
naley podzieli j przez osiem. Wynik to pierwsza cyfra zapisu ósemkowego (tutaj: 1).
Reszt z dzielenia naley znów podzieli przez osiem i powtarza proces tak dugo, jak dugo
reszta bdzie wiksza od 8. Ostatnia cyfra to reszta z ostatniego dzielenia (jest te prostszy
sposób, bo funkcja printf potrafi wypisywa liczby cakowite w zapisie ósemkowym —
przekonasz si o tym w rozdziale 7.).
Zadania programistyczne 105

5. Przerób program upc.c z podrozdziau 4.1 tak, aby uytkownik wprowadza 11 cyfr kodu
UPC za jednym zamachem:

Podaj 11 cyfr kodu UPC: 01380015173


Cyfra kontrolna: 5

6. W krajach europejskich stosuje si kody kreskowe z 13 cyframi (tzw. kod EAN). Kady
kod EAN koczy si cyfr kontroln (tak jak UPC). Algorytm obliczania cyfry kontrolnej
kodu EAN równie jest do podobny:

Dodaj drug, czwart, szóst, ósm, dziesit i dwunast cyfr kodu.


Dodaj pierwsz, trzeci, pit, siódm, dziewit i jedenast cyfr kodu.
Pomnó pierwsz sum przez 3 i dodaj j do drugiej sumy.
Od sumy odejmij 1.
Oblicz reszt z dzielenia pomniejszonej sumy przez 10.
Odejmij wynik od 9.

Na przykad tureckie sodycze Güllüoglu Turkish Delight Pistachio & Coconut maj kod
EAN 8691484260008. Pierwsza suma to 6+1+8+2+0+0 = 17, a druga suma to 8+9+4+4+
6+0 = 31. Suma iloczynu pierwszej sumy przez 3 i drugiej sumy daje 82. Po odjciu 1 zo-
staje 81. Reszta z dzielenia 81 przez 10 to 1. 9 – 1 daje 8. Zgadza si, cyfra kontrolna naszego
kodu to dokadnie 8. Zadanie polega na przerobieniu programu upc.c z podrozdziau 4.1
tak, aby oblicza cyfr kontroln kodu EAN. Uytkownik powinien wprowadza do programu
pierwsze 12 cyfr kodu EAN jednym cigiem:

Podaj 12 cyfr kodu EAN: 869148426000


Cyfra kontrolna: 8

You might also like