Menu

Operatory w C#: arytmetyczne, modulo, logiczne, bitowe i priorytety

Operatory C# i ich dokładne zachowanie: dzielenie całkowite, operator modulo z liczbami ujemnymi, inkrementacja i przypisanie złożone, porównania, logika ze skracaniem obliczeń, operatory bitowe, operatory null i tabela priorytetów.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Operator przyjmuje jeden, dwa lub trzy operandy i daje wartość: a + b, !done, x ?? fallback. Operatory C# są bliskie tym z C, Javy i JavaScriptu, ale kilka zachowuje się w sposób zaskakujący dla początkujących, przede wszystkim dzielenie całkowite i znak %.

Operatory arytmetyczne

+, -, *, / i % działają na każdym typie liczbowym. Typ wyniku zależy od operandów: dwie wartości int dają int, a int i double dają double.

Wynik:

22
12
85
3
2
3.4
3.4
3
3.4

Dzielenie całkowite obcina w stronę zera. 17 / 5 daje 3, a -17 / 5 daje -3. To źródło bardzo częstego błędu: int percent = done / total * 100; daje 0 za każdym razem, gdy done < total, bo done / total już jest 0. Najpierw pomnóż (done * 100 / total) albo dziel jako double.

Dzielenie liczby całkowitej lub decimal przez zero rzuca DivideByZeroException. Dzielenie double przez zero nie rzuca wyjątku; daje nieskończoność albo NaN dla 0.0 / 0.0:

Wynik:

True
True
False
DivideByZeroException

Operator modulo i liczby ujemne

% zwraca resztę z dzielenia, a jego wynik ma znak lewego operandu. To różni go od Pythona i od matematycznego "mod", które przy dodatnim dzielniku są zawsze nieujemne:

Wynik:

1
-1
1
2
even
62 min 5 s
Thu
1.5

Używaj funkcji pomocniczej Mod zawsze, gdy lewa strona może być ujemna, a wynik jest indeksem: days[(today + offset) % 7] rzuciłoby IndexOutOfRangeException dla ujemnej sumy. Sprawdzanie liczb nieparzystych ma tę samą pułapkę: n % 2 == 1 daje false dla ujemnych liczb nieparzystych; sprawdzaj n % 2 != 0.

Przypisanie, przypisanie złożone i inkrementacja

= przypisuje. Formy złożone +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= łączą operację z przypisaniem. ++ i -- dodają lub odejmują 1 i występują w formie przedrostkowej i przyrostkowej, które różnią się zwracaną wartością:

Wynik:

14
start, step
a=5 b=7 i=7
4

Przypadek byte pokazuje pewne dziwactwo: level = level + 10; się nie kompiluje, bo byte + int to int, ale level += 10; się kompiluje, bo przypisanie złożone zawiera niejawne rzutowanie z powrotem na typ zmiennej. To rzutowanie zawija wartość przy przepełnieniu (250 + 10 daje 4).

Operatory porównania i logiczne

Operatory porównania (==, !=, <, >, <=, >=) zwracają bool. Dla stringów == porównuje tekst, a nie referencje. Operatory logiczne to ! (nie), && (i), || (lub) i ^ (alternatywa wykluczająca).

Wynik:

True
no active user
with ||:
  evaluated A
with |:
  evaluated A
  evaluated B
True

&& i || skracają obliczenia: prawy operand wykonuje się tylko wtedy, gdy może zmienić wynik. Właśnie dlatego user != null && user.IsActive jest bezpieczne. Pojedyncze & i | na wartościach logicznych zawsze obliczają obie strony, a w warunku rzadko tego chcesz.

Operatory bitowe i przesunięcia

Na typach całkowitych &, |, ^ i ~ działają na pojedynczych bitach, a << i >> je przesuwają. Używa się ich do flag, masek, haszowania i niskopoziomowych protokołów:

Wynik:

3
True
False
101
1024
125
5
-1

Dla nazwanych zestawów flag enum z atrybutem [Flags] jest czytelniejszy niż stałe całkowite, a HasFlag czyta się lepiej niż maskę. C# 11 dodał >>>, przesunięcie w prawo bez znaku, które wypełnia zerami nawet liczby ujemne.

Operatory null

Do pracy z wartościami, które mogą być null, służą trzy operatory:

Wynik:

Guest

0
5

a ?? b zwraca a, chyba że jest null, wtedy b. a?.Member zwraca null zamiast rzucać wyjątek, gdy a jest null. C# 8 dodał a ??= b, które przypisuje b do a tylko wtedy, gdy a jest null. Szczegółowo opisują je typy nullable.

Priorytet operatorów

Gdy wyrażenie łączy operatory, wyższe wiersze wiążą silniej. Operatory z tego samego wiersza grupują się od lewej do prawej, z wyjątkiem przypisania, ?? i ?:, które grupują się od prawej do lewej. (Same operandy są zawsze obliczane od lewej do prawej.)

KategoriaOperatory
Podstawowex.y, x?.y, f(x), a[i], x++, x--, new, typeof, nameof
Jednoargumentowe+x, -x, !x, ~x, ++x, --x, (T)x, await
Multiplikatywne*, /, %
Addytywne+, -
Przesunięcia<<, >>, >>>
Relacyjne i sprawdzanie typu<, >, <=, >=, is, as
Równość==, !=
Bitowe AND&
Bitowe XOR^
Bitowe OR|
Warunkowe AND&&
Warunkowe OR||
Null coalescing??
Warunkowyc ? a : b
Przypisanie i lambda=, +=, -=, ??=, => oraz pozostałe formy złożone

Dwie konsekwencje warte poznania: a + b * c najpierw mnoży, a x & mask == 0 najpierw porównuje (== wiąże silniej niż &), więc oznacza x & (mask == 0) i nie kompiluje się dla liczb całkowitych. Pisz (x & mask) == 0. Nawiasy nic nie kosztują, a intencja staje się oczywista.

Przeciążanie operatorów

Twoje własne typy mogą definiować znaczenie operatorów. W ten sposób decimal, DateTime i TimeSpan obsługują + i <:

Wynik:

33.49
True

Operatory muszą być public static, a niektóre występują w wymaganych parach: zdefiniowanie < wymaga >, a == wymaga !=. Gdy == jest zdefiniowane, kompilator ostrzega też, dopóki nie nadpiszesz pasujących Equals i GetHashCode. Przeciążaj operatory tylko tam, gdzie ich znaczenie jest oczywiste, jak przy liczbach, wektorach i pieniądzach.

Najczęściej zadawane pytania

Co robi % w C#?

% to operator reszty z dzielenia: 17 % 5 daje 2. Wynik ma znak lewego operandu, więc -7 % 3 daje -1, a nie 2 jak w Pythonie. Jeśli wynik ma być zawsze nieujemny, czego potrzebujesz przy zawijaniu indeksu, napisz ((a % n) + n) % n. Działa też na double i decimal: 7.5 % 2 daje 1.5.

Dlaczego 7 / 2 daje 3 w C#?

Gdy oba operandy są liczbami całkowitymi, / wykonuje dzielenie całkowite i odrzuca część ułamkową, obcinając w stronę zera. Żeby dostać wynik ułamkowy, zrób z jednego operandu wartość zmiennoprzecinkową lub dziesiętną: 7 / 2.0 daje 3.5, (double)a / b konwertuje przed dzieleniem, a 7m / 2 daje 3.5 jako decimal.

Jaka jest różnica między && a & w C#?

&& to warunkowe AND: jeśli lewa strona jest false, prawa nie jest obliczana. & na wartościach logicznych zawsze oblicza obie strony, a na liczbach całkowitych jest bitowym AND. W warunkach używaj && i ||, zwłaszcza w zabezpieczeniach takich jak user != null && user.IsActive, które z & rzuciłyby NullReferenceException.

Jaka jest różnica między i++ a ++i w C#?

Oba dodają 1 do i. Wyrażenie i++ daje starą wartość, a ++i nową. W osobnej linii (i++;) nie ma różnicy. Ma to znaczenie tylko wtedy, gdy wynik jest używany, jak w int a = i++;, i dlatego wiele przewodników stylu każe trzymać inkrementacje w osobnych liniach.

Jaki jest priorytet operatorów w C#?

Od najwyższego do najniższego: podstawowe (x.y, f(x), a[i], x++), jednoargumentowe (!, -, ++x, rzutowania), multiplikatywne (* / %), addytywne (+ -), przesunięcia, relacyjne (< > <= >= is as), równości (== !=), &, ^, |, &&, ||, ??, warunkowy ?: i na końcu przypisanie. W razie wątpliwości dodaj nawiasy.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ