Idź do
• Spis treści
• Przykładowy rozdział
Helion SA
ul. Kościuszki 1c
44-100 Gliwice
tel. 32 230 98 63
© Helion 1991–2010
Katalog książek
Twój koszyk
Cennik i informacje
Czytelnia
Kontakt
Język C. Nowoczesne
programowanie. Wydanie II
Autor: K. N. King
Tłumaczenie: Przemysław Szeremiota
ISBN: 978-83-246-2805-6
Tytuł oryginału:
C Programming: A Modern Approach, 2nd Edition
Format: B5, stron: 928
Język C żyje i ma się dobrze. Sprawdź, co nowego w wersji C99!
• Jak wygląda proces standaryzacji języka?
• Jak komentować kod?
• Jak przygotować projekt programu?
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
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
panują niepodzielnie Java oraz .NET, język C znalazł swoją niszę i świetnie ją wypełnia. Na tym polu
żaden współczesny język nie ma z nim żadnych szans!
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
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,
pytań, ćwiczeń i zadań programistycznych.
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!
5
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
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 wyrae
96
Kolejno obliczania podwyrae
97
4.5.
Instrukcje wyraeniowe
98
5.
INSTRUKCJE WYBORU
107
5.1.
Wyraenia 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.
Zakoczenie 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. WSKANIKI
283
11.1.
Zmienne wskanikowe
283
Deklarowanie zmiennych wskanikowych
284
11.2.
Operator adresu i wyuskania
285
Operator adresu
285
Operator wyuskania
286
11.3.
Przypisania a wskaniki
287
11.4.
Wskaniki jako argumenty funkcji
289
Wyszukiwanie najwikszego i najmniejszego elementu tablicy
291
Ochrona argumentów za pomoc const
293
11.5.
Wskaniki jako wartoci zwracane
293
12. WSKANIKI A TABLICE
301
12.1.
Arytmetyka wskaników
302
Dodawanie liczby do wskanika
303
Odejmowanie liczby od wskanika
303
Odejmowanie wskanika od wskanika
304
Porównywanie wskaników
304
Wskaniki do literaów tablicowych
304
12.2.
Przetwarzanie tablic na bazie wskaników
305
czenie operatorów * i ++
306
12.3.
Nazwa tablicy jako wskanik
307
Odwracanie szeregu liczbowego
308
Argumenty tablicowe (ponownie)
309
Wskanik jako nazwa tablicy
311
12.4.
Wskaniki a tablice wielowymiarowe
311
Przetwarzanie elementów tablicy wielowymiarowej
311
Przetwarzanie wierszy tablicy wielowymiarowej
312
Przetwarzanie kolumn tablicy wielowymiarowej
313
Nazwa tablicy wielowymiarowej jako wskanik
314
12.5.
Wskaniki a tablice o zmiennym rozmiarze (C99)
314
13. CIGI 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 wskaniki 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 zagniedone
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 WSKANIKÓW
469
17.1.
Dynamiczny przydzia pamici
470
Funkcje przydziau pamici
470
Wskaniki 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” wskanikó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.
Wskaniki do wskaników
496
17.7.
Wskaniki do funkcji
497
Wskaniki do funkcji w roli argumentów
497
Funkcja qsort
498
Inne zastosowania wskaników do funkcji
501
Tablice funkcji trygonometrycznych
502
17.8.
Wskaniki zastrzeone (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
Wskaniki 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
Wskaniki 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
69
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
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. Wemy 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. Wemy 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
mniejszej liczby znaków.
%d
, %e, %f i %g to bynajmniej nie wszystkie specyfikatory konwersji. Pozo-
stae bd stopniowo wprowadzane przy okazji omawiania kolejnych zagadnie.
Pena lista specyfikatorów wraz z objanieniem ich znaczenia i dziaania znajduje
si w podrozdziale 22.3.
Wykorzystanie printf do formatowania liczb
Poniszy program ilustruje sposób wykorzystania funkcji printf do wypisy-
wania wartoci cakowitych i zmiennoprzecinkowych w rozmaitych formatach:
/* 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:
specyfikatory
dla wartoci cakowitych
¥ 7.1
specyfikatory dla wartoci
zmiennoprzecinkowych
¥ 7.2
specyfikatory
dla wartoci znakowych
¥ 7.3
specyfikatory
dla cigów znaków
¥ 13.3
PROGRAM
tprintf.c
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
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 dwiku brzczyka terminala; wypisanie \b
znaki sterujce
¥ 7.3
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 wskanikiem”
(o wskanikach 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
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.
wykrywanie bdów
w scanf
¥ 22.3
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. Wemy 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×10
3
.
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.
Dodawanie uamków
Aby zilustrowa zdolno funkcji scanf do dopasowywania wzorców w cigach
znaków, wemiemy 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:
PROGRAM
80
Rozdzia 3. Formatowanie wejcia-wyjcia
/* 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
w innych systemach liczbowych (przy innych podstawach), na przykad liczby
ó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:
addfrac.c
liczby ósemkowe
¥ 7.1
liczby szesnastkowe
¥ 7.1
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.
Co mona zrobi w takich smutnych przypadkach? Póniej dowiesz si, jak
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:
wykrywanie bdów
w scanf
¥ 22.3
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
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.
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.
Wemy 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.
Podrozdzia 3.1
Podrozdzia 3.2
*
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.
Wemy 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
85
4
Wyraenia
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 wemiemy na warsztat operatory arytmetyczne (podrozdzia 4.1), ope-
ratory przypisania (podrozdzia 4.2) oraz operatory inkrementacji i dekrementacji
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.
Z jednym operandem
Z dwoma operandami
Addytywne
Multiplikatywne
Tabela 4.1.
Operatory
arytmetyczne
+
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
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 zalenego 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.
niezdefiniowane
zachowanie programu
¥ 4.4
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
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.
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)
tabela operatorów
¥ Dodatek A
PROGRAM
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 strony
1
.
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).
/* 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.
upc.c
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 */
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.
przypisania z konwersj
¥ 7.4
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:
-= *= /= %=
(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
inne operatory
przypisania
¥ 20.1
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. Wemy
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 wyrae
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.
Priorytet
Nazwa
Symbol
czno
1
inkrementacja (przyrostkowa)
dekrementacja (przyrostkowa)
++
--
lewostronna
2
inkrementacja (przedrostkowa)
dekrementacja (przedrostkowa)
jednoargumentowy plus
jednoargumentowy minus
++
--
+
-
prawostronna
3
multiplikatywne
* / %
lewostronna
4
addytywne
+ -
lewostronna
Tabela 4.2.
Czciowa lista
operatorów jzyka C
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.
Jzyk C nie definiuje kolejnoci obliczania wartoci podwyrae (z wyjtkiem
podwyrae angaujcych operatory logiczne and i or, operatory warunkowe
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. Wemy 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”.
operatory logiczne and i or
¥ 5.1
operator warunkowy
¥ 5.2
operator przecinka
¥ 5.3
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 wyraeniowe
Jzyk C posiada niezwyk waciwo — tutaj kade 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. Wemy 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-
rzysta funkcj pow.
funkcja pow
¥ 23.3
100
Rozdzia 4. Wyraenia
P:
Chciaem zastosowa operator % przy operandzie typu float, ale program
nie daje si skompilowa. Co mog zrobi (s. 86)?
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 najwyraniej
potrafi efektywniej przetumaczy zapis ++i ni i = i + 1. Operatory te
funkcja fmod
¥ 23.3
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
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?
9.
Napisz, co pojawi si na wyjciu programu wykonujcego ponisze instrukcje. Zaó, e i,
j
i k s zmiennymi typu int:
Podrozdzia 4.2
Podrozdzia 4.1
*
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
i
i 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);
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);
Podrozdzia 4.3
*
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.
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
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.).
Podrozdzia 4.4
Podrozdzia 4.5
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