Operatory arytmetyczne i przypisania
Cztery działania, reszta z dzielenia i kilka skrótów, bez których nie obejdzie się żaden program. Poznasz też pułapkę, na którą nabiera się niemal każdy początkujący: dlaczego 7 / 2 daje w C# trzy, a nie trzy i pół.
Czego się dziś nauczysz
- Wykonasz cztery działania arytmetyczne na zmiennych
- Wyjaśnisz, czym różni się dzielenie całkowite od zmiennoprzecinkowego
- Użyjesz operatora
%do sprawdzania podzielności i rozbijania liczb - Odróżnisz
i++od++ii wskażesz, kiedy to ma znaczenie - Zapiszesz działanie skrótem
+=zamiast pełną formą - Ustalisz kolejność wykonywania działań w złożonym wyrażeniu
Cztery działania i reszta
Operator to symbol wykonujący działanie. Wartości, na których działa, nazywamy operandami.
int wynik = 15 + 4;
// ^^ ^ ^
// | | └── drugi operand
// | └── operator
// └── pierwszy operand
| Operator | Nazwa | Przykład | Wynik |
|---|---|---|---|
+ | dodawanie | 15 + 4 | 19 |
- | odejmowanie | 15 - 4 | 11 |
* | mnożenie | 15 * 4 | 60 |
/ | dzielenie | 15 / 4 | 3 — uwaga! |
% | reszta z dzielenia (modulo) | 15 % 4 | 3 |
int a = 15;
int b = 4;
Console.WriteLine($"{a} + {b} = {a + b}"); // 19
Console.WriteLine($"{a} - {b} = {a - b}"); // 11
Console.WriteLine($"{a} * {b} = {a * b}"); // 60
Console.WriteLine($"{a} / {b} = {a / b}"); // 3
Console.WriteLine($"{a} % {b} = {a % b}"); // 3
Zwróć uwagę na dwie ostatnie linie: 15 / 4 daje 3, a nie 3,75. To nie błąd — to zamierzone zachowanie, które omawiamy w następnej sekcji.
Operatory jednoargumentowe
int liczba = 5;
int dodatnia = +liczba; // 5 — plus jednoargumentowy, praktycznie nieużywany
int ujemna = -liczba; // -5 — zmienia znak na przeciwny
Console.WriteLine($"{dodatnia} {ujemna}"); // 5 -5
Dzielenie całkowite i reszta
Najważniejsza zasada tej lekcji
Gdy oba operandy są liczbami całkowitymi, wynik dzielenia też jest liczbą całkowitą. Część ułamkowa jest odrzucana, a nie zaokrąglana.
Console.WriteLine(7 / 2); // 3 — nie 3,5 i nie 4
Console.WriteLine(9 / 2); // 4 — nie 4,5
Console.WriteLine(1 / 2); // 0 — nie 0,5!
Console.WriteLine(-7 / 2); // -3 — ucięcie w stronę zera
Żeby dostać wynik z częścią ułamkową, wystarczy, żeby choć jedna strona była typu double:
int suma = 7;
int ile = 2;
Console.WriteLine(suma / ile); // 3 ✗ oba całkowite
Console.WriteLine(7.0 / 2); // 3,5 ✓ literał z kropką
Console.WriteLine((double)suma / ile); // 3,5 ✓ rzutowanie zmiennej
Console.WriteLine(suma / (double)ile); // 3,5 ✓ obojętne, którą stronę
Rzutowanie musi być przed dzieleniem, nie po
double x = (double)(suma / ile); nie pomoże. Nawias każe najpierw wykonać dzielenie całkowite — wynik 3 — i dopiero tę trójkę zamienić na 3,0. Reszta przepadła, zanim zdążyliśmy cokolwiek rzutować.
Operator % — reszta z dzielenia
Modulo zwraca to, co zostaje po podzieleniu. Wygląda niepozornie, a jest jednym z najczęściej używanych operatorów w praktyce.
Console.WriteLine(17 % 5); // 2 bo 17 = 3*5 + 2
Console.WriteLine(20 % 5); // 0 dzieli się bez reszty
Console.WriteLine(3 % 5); // 3 mniejsza od dzielnika, więc cała jest resztą
Do czego naprawdę służy modulo
| Zadanie | Zapis | Jak to działa |
|---|---|---|
| Czy liczba jest parzysta? | liczba % 2 == 0 | Parzyste dzielą się przez 2 bez reszty. |
| Czy podzielna przez 3? | liczba % 3 == 0 | Ta sama zasada dla dowolnego dzielnika. |
| Ostatnia cyfra liczby | liczba % 10 | Reszta z dzielenia przez 10 to zawsze cyfra jedności. |
| Cyfra dziesiątek | liczba / 10 % 10 | Najpierw obcinamy jedności, potem bierzemy nową ostatnią cyfrę. |
| Zawijanie w zakresie | (indeks + 1) % dlugosc | Po ostatnim elemencie wraca do zera — przydatne przy karuzelach i kolejkach. |
| Godziny z minut | minuty / 60 i minuty % 60 | Dzielenie daje pełne godziny, reszta — minuty. |
int liczba = 4738;
int jednosci = liczba % 10; // 8
int dziesiatki = liczba / 10 % 10; // 3
int setki = liczba / 100 % 10; // 7
int tysiace = liczba / 1000 % 10; // 4
Console.WriteLine($"{tysiace} {setki} {dziesiatki} {jednosci}"); // 4 7 3 8
Dzielenie przez zero
| Sytuacja | Co się dzieje |
|---|---|
liczby całkowite, np. a / b gdy b to 0 | Program przerywa działanie z błędem DivideByZeroException. |
liczby zmiennoprzecinkowe, np. 1.0 / 0.0 | Program działa dalej, a wynikiem jest ∞ (nieskończoność). |
0.0 / 0.0 | Wynikiem jest NaN — „nie-liczba”. |
Skąd ta różnica
Typy zmiennoprzecinkowe mają w standardzie zarezerwowane wartości specjalne na nieskończoność i „nie-liczbę”. Typy całkowite ich nie mają, więc nie da się w nich zapisać żadnego sensownego wyniku — stąd błąd.
W praktyce oznacza to, że przed dzieleniem przez zmienną warto sprawdzić, czy nie jest zerem. Jak to zrobić, poznasz w lekcji o instrukcjach warunkowych.
Zwiększanie i zmniejszanie o jeden
Dodanie jedynki do zmiennej zdarza się tak często, że język ma na to osobny skrót.
int licznik = 5;
licznik = licznik + 1; // sposób pełny
licznik += 1; // sposób skrócony
licznik++; // sposób najkrótszy — wszystkie trzy robią to samo
Analogicznie licznik-- odejmuje jeden.
i++ czy ++i?
Oba zwiększają zmienną o jeden. Różnią się tym, co zwracają, gdy użyjesz ich w większym wyrażeniu.
i++ — najpierw użyj
Zwraca starą wartość, a dopiero potem zwiększa zmienną.
++i — najpierw zwiększ
Najpierw zwiększa zmienną, a potem zwraca nową wartość.
int a = 5;
int wynikA = a++; // wynikA dostaje 5, dopiero potem a staje się 6
Console.WriteLine($"a = {a}, wynikA = {wynikA}"); // a = 6, wynikA = 5
int b = 5;
int wynikB = ++b; // b staje się 6, wynikB dostaje 6
Console.WriteLine($"b = {b}, wynikB = {wynikB}"); // b = 6, wynikB = 6
a = 6, wynikA = 5
b = 6, wynikB = 6
W praktyce ta różnica prawie nigdy nie ma znaczenia
Jeśli piszesz licznik++; jako osobną instrukcję — a tak jest w 99% przypadków — obie wersje zachowują się identycznie, bo nikt nie korzysta ze zwróconej wartości.
Różnica pojawia się dopiero wtedy, gdy wciśniesz ++ w środek większego wyrażenia. Nie rób tego. Zapis tablica[i++] = i++ * 2; jest legalny, ale nieczytelny i niemal zawsze oznacza błąd w projekcie kodu. Rozbij taki kod na osobne linie.
Przypisania złożone
Wzorzec „weź zmienną, coś z nią zrób, wynik włóż z powrotem” jest tak częsty, że dla każdego działania istnieje skrót.
| Skrót | Znaczy dokładnie to samo co | Przykład |
|---|---|---|
x += 5 | x = x + 5 | dopisanie do sumy |
x -= 5 | x = x - 5 | odjęcie od stanu konta |
x *= 2 | x = x * 2 | podwojenie |
x /= 4 | x = x / 4 | podzielenie |
x %= 3 | x = x % 3 | zostawienie samej reszty |
int liczba = 10;
liczba += 5; // 15
liczba -= 3; // 12
liczba *= 2; // 24
liczba /= 4; // 6
liczba %= 4; // 2
Console.WriteLine(liczba); // 2
// Działa też dla tekstu
string zdanie = "Ala";
zdanie += " ma kota";
Console.WriteLine(zdanie); // Ala ma kota
Drobna różnica, o której warto wiedzieć
Skróty złożone wykonują rzutowanie automatycznie. Zapis bajt += 1; zadziała, a rozpisany bajt = bajt + 1; — nie, bo dodawanie daje wynik typu int, którego nie da się bez pytania wcisnąć z powrotem do byte. Wracaliśmy do tego w lekcji 06.
Kolejność wykonywania działań
C# stosuje te same reguły co matematyka: najpierw nawiasy, potem mnożenie i dzielenie, na końcu dodawanie i odejmowanie.
| Kolejność | Operatory | Uwaga |
|---|---|---|
| 1 — pierwsze | ( ) | nawiasy wyprzedzają wszystko |
| 2 | ++ -- - (jednoargumentowy) | działają na jednej wartości |
| 3 | * / % | modulo ma taki sam priorytet jak mnożenie |
| 4 | + - | — |
| 5 — ostatnie | = += -= … | przypisanie wykonuje się na samym końcu |
Console.WriteLine(2 + 3 * 4); // 14 bo 3*4 policzy się pierwsze
Console.WriteLine((2 + 3) * 4); // 20 nawias zmienia kolejność
Console.WriteLine(10 - 4 - 3); // 3 od lewej: (10-4)-3
Console.WriteLine(20 / 4 * 2); // 10 od lewej: (20/4)*2, nie 20/(4*2)
Console.WriteLine(10 + 6 % 4); // 12 bo 6%4 wykonuje się przed dodawaniem
Nawias nic nie kosztuje
Nie musisz uczyć się całej tabeli na pamięć. Gdy masz wątpliwość, postaw nawiasy — program będzie działał tak samo szybko, a kod stanie się jednoznaczny dla każdego, kto go przeczyta.
double srednia = (a + b + c) / 3.0; czyta się lepiej niż wersja bez nawiasów, nawet gdyby dawała ten sam wynik.
Operator + dla tekstu
Ten sam znak plus, użyty na napisach, skleja je ze sobą. Nazywamy to konkatenacją.
string imie = "Anna";
string nazwisko = "Kowalska";
string pelne = imie + " " + nazwisko;
Console.WriteLine(pelne); // Anna Kowalska
// Można sklejać tekst z liczbą — liczba sama zamieni się na tekst
int wiek = 17;
Console.WriteLine("Wiek: " + wiek); // Wiek: 17
Klasyczna pułapka: kiedy plus dodaje, a kiedy skleja
Decyduje typ operandów i kolejność od lewej do prawej:
Console.WriteLine("Suma: " + 2 + 3); // Suma: 23 ✗ nie 5!
Console.WriteLine("Suma: " + (2 + 3)); // Suma: 5 ✓ nawias ratuje
Console.WriteLine(2 + 3 + " to suma"); // 5 to suma ✓ tu liczby są pierwsze
W pierwszej linii C# najpierw skleja "Suma: " z liczbą 2, dostaje tekst "Suma: 2", a potem dokleja do niego 3. W trzeciej linii kolejność jest odwrotna: najpierw dodaje 2 i 3, bo obie są liczbami, i dopiero wynik skleja z tekstem.
Interpolacja rozwiązuje ten problem raz na zawsze
Zamiast żonglować nawiasami, używaj zapisu ze znakiem $. Wewnątrz klamer wyrażenie liczy się normalnie, a dopiero wynik trafia do tekstu:
Console.WriteLine($"Suma: {2 + 3}"); → Suma: 5
Dlatego w tym kursie prawie zawsze używamy interpolacji, a sklejania plusem tylko wtedy, gdy naprawdę łączymy dwa napisy.
Klasa Math — gotowe działania
Pierwiastek, potęga i zaokrąglanie nie mają własnych operatorów. Zamiast nich używa się metod z klasy Math. Jest ona statyczna, więc nie tworzy się jej obiektu — wywołujemy metody przez nazwę klasy.
| Metoda | Zwraca | Co robi |
|---|---|---|
Math.Abs(x) | ten sam typ | Wartość bezwzględna — usuwa znak minus. Math.Abs(-7) → 7. |
Math.Pow(x, y) | double | Podnosi x do potęgi y. Math.Pow(2, 10) → 1024. |
Math.Sqrt(x) | double | Pierwiastek kwadratowy. Math.Sqrt(25) → 5. Dla liczby ujemnej zwraca NaN. |
Math.Round(x, n) | double | Zaokrągla do n miejsc po przecinku. Bez drugiego argumentu — do liczby całkowitej. |
Math.Floor(x) | double | Zaokrągla w dół: Math.Floor(4.9) → 4. |
Math.Ceiling(x) | double | Zaokrągla w górę: Math.Ceiling(4.1) → 5. |
Math.Max(a, b) | ten sam typ | Większa z dwóch liczb. |
Math.Min(a, b) | ten sam typ | Mniejsza z dwóch liczb. |
Math.PI | double | Stała π ≈ 3,14159265358979. To pole, nie metoda — bez nawiasów. |
double promien = 3.0;
double pole = Math.PI * promien * promien;
double obwod = 2 * Math.PI * promien;
Console.WriteLine($"Pole: {Math.Round(pole, 2)}"); // 28,27
Console.WriteLine($"Obwod: {Math.Round(obwod, 2)}"); // 18,85
Console.WriteLine(Math.Pow(2, 10)); // 1024
Console.WriteLine(Math.Sqrt(144)); // 12
Console.WriteLine(Math.Abs(-15)); // 15
Console.WriteLine(Math.Max(7, 12)); // 12
Uwaga na Math.Pow przy liczbach całkowitych
Math.Pow zawsze zwraca double, nawet dla Math.Pow(5, 3). Rzutowanie wyniku na int bywa zawodne, bo obliczenia zmiennoprzecinkowe mogą dać 124,99999999999999 zamiast równych 125 — a rzutowanie ucina część ułamkową, dając 124.
Gdy potęgujesz liczby całkowite i zależy ci na dokładnym wyniku, bezpieczniej użyć (int)Math.Round(Math.Pow(5, 3)) albo po prostu pomnożyć liczbę przez siebie.
Kasa samoobsługowa — wydawanie reszty
Klient płaci banknotem za zakupy. Program ma policzyć resztę i rozmienić ją na jak najmniejszą liczbę nominałów. To klasyczne zastosowanie pary / i %.
// Wszystko liczymy w groszach, żeby uniknąć ułamków
int doZaplatyGrosze = 4737; // 47,37 zl
int wplataGrosze = 10000; // 100,00 zl
int resztaGrosze = wplataGrosze - doZaplatyGrosze;
Console.WriteLine($"Do zaplaty: {doZaplatyGrosze / 100},{doZaplatyGrosze % 100:D2} zl");
Console.WriteLine($"Wplata: {wplataGrosze / 100},{wplataGrosze % 100:D2} zl");
Console.WriteLine($"Reszta: {resztaGrosze / 100},{resztaGrosze % 100:D2} zl");
Console.WriteLine();
// Rozmieniamy resztę na nominały — od największego do najmniejszego
int pozostalo = resztaGrosze;
int banknoty50 = pozostalo / 5000; pozostalo %= 5000;
int banknoty20 = pozostalo / 2000; pozostalo %= 2000;
int banknoty10 = pozostalo / 1000; pozostalo %= 1000;
int monety5 = pozostalo / 500; pozostalo %= 500;
int monety2 = pozostalo / 200; pozostalo %= 200;
int monety1 = pozostalo / 100; pozostalo %= 100;
int grosze = pozostalo;
Console.WriteLine("Wydac:");
Console.WriteLine($" 50 zl x {banknoty50}");
Console.WriteLine($" 20 zl x {banknoty20}");
Console.WriteLine($" 10 zl x {banknoty10}");
Console.WriteLine($" 5 zl x {monety5}");
Console.WriteLine($" 2 zl x {monety2}");
Console.WriteLine($" 1 zl x {monety1}");
Console.WriteLine($" gr x {grosze}");
Do zaplaty: 47,37 zl
Wplata: 100,00 zl
Reszta: 52,63 zl
Wydac:
50 zl x 1
20 zl x 0
10 zl x 0
5 zl x 0
2 zl x 1
1 zl x 0
gr x 63
Omówienie
| Fragment | Dlaczego tak |
|---|---|
| Liczenie w groszach | Zamiast 47.37 trzymamy 4737. Dzięki temu wszystkie działania są na liczbach całkowitych i nie ma mowy o zgubionym groszu. To standardowa sztuczka w systemach kasowych i bankowych. |
resztaGrosze / 100 |
Dzielenie całkowite daje pełne złotówki — tu właśnie chcemy ucięcia części ułamkowej. |
resztaGrosze % 100 |
Reszta z dzielenia przez 100 to grosze. Razem z poprzednim daje pełną kwotę. |
:D2 |
Format wymuszający dwie cyfry z zerem wiodącym. Bez niego 3 grosze wypisałyby się jako 52,3 zamiast 52,03. |
pozostalo %= 5000; |
Po odliczeniu banknotów pięćdziesięciozłotowych zostawiamy tylko resztę. Kolejny nominał pracuje już na tym, co zostało. |
| Kolejność nominałów | Idziemy od największego do najmniejszego. Odwrotna kolejność dałaby poprawną kwotę, ale wydaną w samych groszach. |
Na co uważać
| Kod | Problem |
|---|---|
double s = suma / ile; |
Oba operandy całkowite — reszta przepada przed przypisaniem. Trzeba rzutować jeden z nich: (double)suma / ile. |
double s = (double)(suma / ile); |
Rzutowanie po nawiasie, czyli za późno. Dzielenie już się wykonało całkowicie. |
"Suma: " + 2 + 3 |
Da Suma: 23. Potrzebny nawias albo, lepiej, interpolacja. |
x = x++; |
Wygląda niewinnie, a nie zmienia wartości x. Przypisanie nadpisuje wynik zwiększenia starą wartością. Wystarczy x++;. |
int i = 10 / 0; |
Dzielenie przez stałe zero to błąd kompilacji CS0020, a nie wyjątek. Przy zmiennej byłby wyjątek w czasie działania. |
int p = Math.Pow(2, 3); |
CS0266 — Math.Pow zwraca double. Potrzebne rzutowanie. |
Math.PI() |
PI to pole, nie metoda. Bez nawiasów. |
int x = 5; x =+ 3; |
Odwrócone znaki. To nie jest +=, tylko przypisanie liczby +3 — x będzie miało 3, a nie 8. Kompilator nie zgłosi błędu. |
Zadania
Zapisz na kartce wynik każdej linii, a dopiero potem uruchom program.
Console.WriteLine(10 / 3);
Console.WriteLine(10 % 3);
Console.WriteLine(10.0 / 3);
Console.WriteLine(2 + 3 * 4 - 1);
Console.WriteLine((2 + 3) * (4 - 1));
Console.WriteLine("Wynik: " + 5 + 5);
Console.WriteLine(100 % 10 % 3);
Mając liczbę sekund (np. 9876), wypisz ją w formacie godz:min:sek. Użyj wyłącznie operatorów / i % — bez gotowych metod do formatowania czasu. Wynik ma postać 2:44:36; wyrównywanie do dwóch cyfr nie jest tu wymagane. Sprawdź program dla 9876, 60, 59 i 0 sekund.
Dla liczby czterocyfrowej (np. 4738) oblicz sumę jej cyfr oraz iloczyn. Nie używaj pętli ani konwersji na tekst — same operatory.
Dla prostokąta o bokach 7,5 i 3,2 oblicz pole, obwód i długość przekątnej. Wyniki zaokrąglij do dwóch miejsc po przecinku.
Program ma policzyć średnią z trzech ocen i pole koła. Nie działa poprawnie — znajdź i popraw cztery usterki. Uwaga: nie wszystkie z nich zatrzymają kompilację.
int ocena1 = 5, ocena2 = 4, ocena3 = 4;
double srednia = ocena1 + ocena2 + ocena3 / 3;
Console.WriteLine("Srednia: " + srednia);
double promien = 2.5;
int poleKola = Math.PI * promien * promien;
Console.WriteLine("Pole: " + poleKola);
Masz dwie zmienne całkowite, na przykład int a = 7; i int b = 3;. Zamień ich wartości miejscami — po wykonaniu programu a ma mieć 3, a b siedem — nie tworząc żadnej trzeciej zmiennej. Wolno użyć wyłącznie dodawania i odejmowania.
Sprawdź swoje rozwiązanie dla par (7, 3), (0, 5) i (−4, 9). Następnie zastanów się, co się stanie, gdy obie liczby są bliskie górnej granicy typu int — i zapisz odpowiedź w komentarzu.
Co trzeba zapamiętać
- int / int wykonuje dzielenie całkowite z obcięciem w stronę zera. Rzutuj przed dzieleniem, aby dostać wynik ułamkowy.
- % zwraca resztę z dzielenia. Dla ujemnej lewej strony reszta może być ujemna, więc nie jest zawsze dodatnim modulo.
- Dzielenie całkowite i decimal przez zero zgłasza wyjątek. Dla float/double może dać nieskończoność albo NaN (np. 0.0 / 0.0).
- i++ zwraca wartość sprzed zwiększenia, a ++i po zwiększeniu.
- Przypisania złożone, np. +=, łączą operację i przypisanie; w ogólności uwzględniają też reguły konwersji typu lewej strony.
- Operator + może dodawać liczby albo łączyć tekst. Interpolacja ułatwia czytelne wypisywanie.
- Math.Pow, Math.Sqrt i Math.Round są metodami biblioteki .NET. Math.PI jest stałą, bez nawiasów wywołania.
- Math.Round domyślnie zaokrągla dokładne połówki do najbliższej liczby parzystej.