Przejdź do treści
School IT / Przestrzeń do nauki
← C# od podstawLekcja 9 z 58

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ół.

C# operatory modulo Math 45 min
CEL LEKCJI

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 ++i i 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
TEORIA

Cztery działania i reszta

Operator to symbol wykonujący działanie. Wartości, na których działa, nazywamy operandami.

Program.cs
int wynik = 15 + 4;
//          ^^   ^ ^
//          |    | └── drugi operand
//          |    └── operator
//          └── pierwszy operand
OperatorNazwaPrzykładWynik
+dodawanie15 + 419
-odejmowanie15 - 411
*mnożenie15 * 460
/dzielenie15 / 43 — uwaga!
%reszta z dzielenia (modulo)15 % 43
Program.cs
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

Program.cs
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
TEORIA

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.

Program.cs
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:

Program.cs
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.

Program.cs
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

ZadanieZapisJak to działa
Czy liczba jest parzysta?liczba % 2 == 0Parzyste dzielą się przez 2 bez reszty.
Czy podzielna przez 3?liczba % 3 == 0Ta sama zasada dla dowolnego dzielnika.
Ostatnia cyfra liczbyliczba % 10Reszta z dzielenia przez 10 to zawsze cyfra jedności.
Cyfra dziesiątekliczba / 10 % 10Najpierw obcinamy jedności, potem bierzemy nową ostatnią cyfrę.
Zawijanie w zakresie(indeks + 1) % dlugoscPo ostatnim elemencie wraca do zera — przydatne przy karuzelach i kolejkach.
Godziny z minutminuty / 60 i minuty % 60Dzielenie daje pełne godziny, reszta — minuty.
Program.cs — rozbicie liczby na cyfry
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

SytuacjaCo się dzieje
liczby całkowite, np. a / b gdy b to 0Program przerywa działanie z błędem DivideByZeroException.
liczby zmiennoprzecinkowe, np. 1.0 / 0.0Program działa dalej, a wynikiem jest ∞ (nieskończoność).
0.0 / 0.0Wynikiem 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.

TEORIA

Zwiększanie i zmniejszanie o jeden

Dodanie jedynki do zmiennej zdarza się tak często, że język ma na to osobny skrót.

Program.cs
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ść.

Program.cs
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
wynik w konsoli
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.

TEORIA

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ótZnaczy dokładnie to samo coPrzykład
x += 5x = x + 5dopisanie do sumy
x -= 5x = x - 5odjęcie od stanu konta
x *= 2x = x * 2podwojenie
x /= 4x = x / 4podzielenie
x %= 3x = x % 3zostawienie samej reszty
Program.cs
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.

TEORIA

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śćOperatoryUwaga
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
Program.cs
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.

TEORIA

Operator + dla tekstu

Ten sam znak plus, użyty na napisach, skleja je ze sobą. Nazywamy to konkatenacją.

Program.cs
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:

Program.cs
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.

METODY WBUDOWANE

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.

MetodaZwracaCo robi
Math.Abs(x)ten sam typWartość bezwzględna — usuwa znak minus. Math.Abs(-7) → 7.
Math.Pow(x, y)doublePodnosi x do potęgi y. Math.Pow(2, 10) → 1024.
Math.Sqrt(x)doublePierwiastek kwadratowy. Math.Sqrt(25) → 5. Dla liczby ujemnej zwraca NaN.
Math.Round(x, n)doubleZaokrągla do n miejsc po przecinku. Bez drugiego argumentu — do liczby całkowitej.
Math.Floor(x)doubleZaokrągla w dół: Math.Floor(4.9) → 4.
Math.Ceiling(x)doubleZaokrągla w górę: Math.Ceiling(4.1) → 5.
Math.Max(a, b)ten sam typWiększa z dwóch liczb.
Math.Min(a, b)ten sam typMniejsza z dwóch liczb.
Math.PIdoubleStała π ≈ 3,14159265358979. To pole, nie metoda — bez nawiasów.
Program.cs
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.

PRZYKŁAD Z OMÓWIENIEM

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 %.

Program.cs
// 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}");
wynik w konsoli
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

FragmentDlaczego 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.
CZĘSTE BŁĘDY

Na co uważać

KodProblem
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

Zadania

ZAD 1Przewidz wynik★☆☆

Zapisz na kartce wynik każdej linii, a dopiero potem uruchom program.

co wypisze?
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);
ZAD 2Sekundy na czas★★☆

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.

ZAD 3Suma cyfr★★☆

Dla liczby czterocyfrowej (np. 4738) oblicz sumę jej cyfr oraz iloczyn. Nie używaj pętli ani konwersji na tekst — same operatory.

ZAD 4Kalkulator geometrii★★☆

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.

ZAD 5Znajdź cztery błędy★★★

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ę.

do poprawienia
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);
ZAD 6Zamiana bez trzeciej zmiennej★★★

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.

PODSUMOWANIE

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.
Postęp zapisuje się w tej przeglądarce.