Operatory porównania, logiczne i bitowe

Zanim program podejmie jakąkolwiek decyzję, musi coś porównać. Ta lekcja pokazuje, jak zadawać komputerowi pytania typu „czy większe”, „czy równe” i jak łączyć takie pytania w bardziej złożone warunki. Na końcu zaglądamy pod maskę — do operacji na pojedynczych bitach.

C# bool && || operacje bitowe 50 min
CEL LEKCJI

Czego się dziś nauczysz

  • Zbudujesz wyrażenie logiczne z operatorów porównania
  • Porównasz dwa napisy z uwzględnieniem wielkości liter i bez niej
  • Połączysz kilka warunków operatorami &&, || i !
  • Wyjaśnisz, czym jest ocena skrócona i jak chroni przed błędem
  • Uprościsz zaprzeczony warunek prawami De Morgana
  • Odczytasz zapis dwójkowy liczby i wykonasz na niej operację bitową
TEORIA

Operatory porównania

Każdy z nich bierze dwie wartości i zwraca true albo false — czyli wartość typu bool.

OperatorZnaczeniePrzykładWynik
==równe5 == 5true
!=różne5 != 3true
>większe5 > 3true
<mniejsze5 < 3false
>=większe lub równe5 >= 5true
<=mniejsze lub równe3 <= 5true
Program.cs
int wiek = 17;

bool czyPelnoletni = wiek >= 18;      // false
bool czyNastolatek = wiek >= 13;      // true

Console.WriteLine(czyPelnoletni);      // False
Console.WriteLine(wiek != 17);         // False
Console.WriteLine(wiek * 2 > 30);      // True — najpierw mnożenie, potem porównanie

Jeden znak równości to co innego niż dwa

= przypisuje wartość. == porównuje. To dwie zupełnie różne operacje i mylenie ich jest klasycznym błędem początkującego.

Dobra wiadomość: w C# ten błąd zwykle nie przejdzie przez kompilator. Zapis if (x = 5) zgłosi błąd, bo przypisanie zwraca liczbę, a warunek wymaga wartości bool. W językach C i C++ taki kod kompiluje się bez słowa i psuje program.

Porównania działają też na znakach

Typ char jest porównywany według numeru znaku w tablicy Unicode. Dlatego 'a' < 'b' daje true, a 'Z' < 'a' również — wielkie litery mają w tej tablicy niższe numery niż małe.

TEORIA

Porównywanie tekstu

Operator == na napisach porównuje zawartość, a nie miejsce w pamięci. To wygodne i działa dokładnie tak, jak się spodziewasz.

Program.cs
string a = "Anna";
string b = "Anna";
string c = "ANNA";

Console.WriteLine(a == b);      // True  — ta sama treść
Console.WriteLine(a == c);      // False — inna wielkość liter!

Wielkość liter ma znaczenie

Dla komputera "Anna" i "ANNA" to dwa różne napisy. Przy porównywaniu danych wpisanych przez użytkownika prawie zawsze chcesz to zignorować — inaczej ktoś, kto wpisze TAK zamiast tak, dostanie zły wynik.

Porównanie ignorujące wielkość liter

Program.cs
string odpowiedz = "TAK";

// Sposób 1 — sprowadzamy oba napisy do małych liter
bool zgoda1 = odpowiedz.ToLower() == "tak";

// Sposób 2 — mówimy wprost, że wielkość liter nas nie interesuje (zalecany)
bool zgoda2 = string.Equals(odpowiedz, "tak", StringComparison.OrdinalIgnoreCase);

Console.WriteLine(zgoda1);      // True
Console.WriteLine(zgoda2);      // True
SposóbKiedy używać
.ToLower() == Krótszy i czytelniejszy dla początkującego. Zupełnie wystarczy do zadań szkolnych i na egzaminie.
string.Equals(..., OrdinalIgnoreCase) Poprawniejszy: nie tworzy nowego napisu w pamięci i nie zależy od ustawień regionalnych. W niektórych językach — na przykład tureckim — zamiana wielkości liter działa inaczej niż byśmy się spodziewali, a ten zapis jest na to odporny.

Napisy porównujemy tylko na równość

Operatory < i > nie działają na napisach — kompilator zgłosi błąd. Żeby ustalić, który napis jest wcześniej w kolejności alfabetycznej, używa się metody string.Compare(a, b): zwraca liczbę ujemną, zero albo dodatnią. Wrócimy do niej w bloku o tekstach.

TEORIA

Porównywanie liczb z przecinkiem

Wracamy do problemu z lekcji 06. Typ double zapisuje ułamki w przybliżeniu, więc porównanie na równość bywa zawodne.

Program.cs
double a = 0.1 + 0.2;
double b = 0.3;

Console.WriteLine(a == b);                              // False ✗

double tolerancja = 0.000001;
Console.WriteLine(Math.Abs(a - b) < tolerancja);        // True  ✓
Typ danychJak porównywać na równość
int, long, char, bool== — bez zastrzeżeń
decimal== — bez zastrzeżeń, bo liczy dziesiętnie
double, floatprzez tolerancję: Math.Abs(a - b) < 0.000001
string== albo string.Equals z OrdinalIgnoreCase

Operatory <, >, <= i >= na typie double działają normalnie — problem dotyczy tylko sprawdzania dokładnej równości.

TEORIA

Operatory logiczne — łączenie warunków

Rzadko wystarcza jeden warunek. Zwykle pytamy o kilka rzeczy naraz: „czy jest pełnoletni i ma prawo jazdy”.

OperatorNazwaZwraca true, gdy…
&&koniunkcja (I)oba warunki są prawdziwe
||alternatywa (LUB)przynajmniej jeden warunek jest prawdziwy
!negacja (NIE)warunek jest fałszywy — odwraca wartość

Tabele prawdy

ABA && BA || B!A
truetruetruetruefalse
truefalsefalsetruefalse
falsetruefalsetruetrue
falsefalsefalsefalsetrue

Jak to zapamiętać bez tabeli

&& jest wymagające — wystarczy jeden fałsz, żeby całość była fałszywa.
|| jest wyrozumiałe — wystarczy jedna prawda, żeby całość była prawdziwa.

Program.cs
int wiek = 20;
bool maPrawoJazdy = true;
bool maSamochod = false;

// Wszystkie warunki muszą być spełnione
bool mozeProwadzic = wiek >= 18 && maPrawoJazdy;
Console.WriteLine(mozeProwadzic);                      // True

// Wystarczy jeden
bool maPojazd = maSamochod || wiek < 18;
Console.WriteLine(maPojazd);                           // False

// Negacja
Console.WriteLine(!maSamochod);                        // True

// Warunek złożony — przedział
int temperatura = 22;
bool przyjemnie = temperatura >= 18 && temperatura <= 25;
Console.WriteLine(przyjemnie);                         // True

Przedziału nie zapiszesz tak jak w matematyce

Matematyczny zapis 18 <= t <= 25 nie działa w C#. Kompilator policzyłby najpierw 18 <= t, dostał wartość bool, a potem próbował porównać ją z liczbą 25 — i zgłosił błąd.

Przedział zapisujemy zawsze jako dwa osobne warunki połączone &&: t >= 18 && t <= 25.

TEORIA

Ocena skrócona

C# nie sprawdza warunków dłużej, niż to konieczne. Gdy wynik jest już przesądzony, reszta wyrażenia w ogóle się nie wykonuje.

&& przerywa na pierwszym fałszu

Skoro jeden warunek jest fałszywy, całość na pewno będzie fałszywa. Po co sprawdzać dalej?

|| przerywa na pierwszej prawdzie

Skoro jeden warunek jest prawdziwy, całość na pewno będzie prawdziwa.

To nie jest tylko optymalizacja — to mechanizm, który pozwala pisać bezpieczny kod:

Program.cs
int dzielnik = 0;
int liczba = 100;

// ✓ Bezpiecznie — gdy dzielnik jest zerem, druga część się NIE wykona
bool wynik = dzielnik != 0 && liczba / dzielnik > 10;
Console.WriteLine(wynik);     // False, bez żadnego błędu

// ✗ Odwrotna kolejność przerwie program
// bool zle = liczba / dzielnik > 10 && dzielnik != 0;
//            └── DivideByZeroException, zanim dojdziemy do sprawdzenia

Zasada praktyczna

Warunek zabezpieczający stawiaj zawsze jako pierwszy. „Czy dzielnik nie jest zerem” przed dzieleniem, „czy tekst nie jest pusty” przed sięganiem po znak. Kolejność warunków w C# ma znaczenie i można ją świadomie wykorzystać.

Istnieją też operatory bez skracania

Pojedyncze & i | na wartościach bool działają podobnie, ale zawsze sprawdzają obie strony. W praktyce prawie nigdy się ich tak nie używa — ich głównym zastosowaniem są operacje bitowe, o których za chwilę. Zapamiętaj: przy warunkach zawsze podwójne && i ||.

TEORIA

Kolejność działań i prawa De Morgana

KolejnośćOperatoryUwaga
1!negacja wykonuje się najwcześniej
2* / % + -arytmetyka przed porównaniami
3< > <= >=
4== !=równość po pozostałych porównaniach
5&&koniunkcja przed alternatywą
6||
Program.cs
bool a = true, b = false, c = true;

// && wiąże mocniej niż ||, więc to wyrażenie...
bool wynik = a || b && c;

// ...kompilator czyta tak:
bool toSamo = a || (b && c);      // true

Console.WriteLine(wynik == toSamo);     // True

Nawiasy przy mieszaniu && i ||

Gdy w jednym warunku pojawiają się oba operatory, zawsze stawiaj nawiasy — nawet jeśli akurat nie są konieczne. Wyrażenie (wiek >= 18 && maPrawoJazdy) || jestInstruktorem czyta się od razu, a bez nawiasów trzeba się zatrzymać i przypomnieć sobie priorytety.

Prawa De Morgana

Dwie reguły, które pozwalają wnieść zaprzeczenie do środka nawiasu. Przy okazji zamieniają && na || i odwrotnie.

Program.cs
// Prawo pierwsze:  !(A && B)  ==  !A || !B
!(pada && zimno)        ⟷   !pada || !zimno

// Prawo drugie:    !(A || B)  ==  !A && !B
!(weekend || swieto)    ⟷   !weekend && !swieto

Po polsku brzmi to zupełnie naturalnie:

Zapis zaprzeczonyTo samo bez nawiasu
Nieprawda, że pada i jest zimnoAlbo nie pada, albo nie jest zimno
Nieprawda, że weekend lub świętoNie weekend i nie święto

Po co to komu

Przydaje się, gdy chcesz odwrócić warunek, żeby stał się czytelniejszy. Zamiast if (!(wiek >= 18 && maZgode)) można napisać if (wiek < 18 || !maZgode) — to samo znaczenie, a od razu widać, w jakich sytuacjach warunek jest spełniony.

TEORIA

Operatory bitowe

Do tej pory traktowaliśmy liczbę jako całość. Operatory bitowe pozwalają pracować na pojedynczych bitach jej zapisu dwójkowego.

Przypomnienie: liczba 12 zapisana dwójkowo to 1100, bo 12 = 8 + 4. Liczba 10 to 1010, bo 10 = 8 + 2.

OperatorNazwaDziałanie na każdej parze bitów
&iloczyn bitowy (AND)1 tylko wtedy, gdy oba bity są 1
|suma bitowa (OR)1, gdy przynajmniej jeden bit jest 1
^różnica symetryczna (XOR)1, gdy bity są różne
~negacja bitowa (NOT)odwraca każdy bit — jednoargumentowy
<<przesunięcie w lewoprzesuwa bity w lewo, dopisując zera
>>przesunięcie w prawoprzesuwa bity w prawo
Program.cs
int a = 12;      // 1100
int b = 10;      // 1010

Console.WriteLine(a & b);     //  8   1000  — jedynka tylko na pozycji, gdzie obie miały 1
Console.WriteLine(a | b);     // 14   1110  — jedynka tam, gdzie choć jedna miała 1
Console.WriteLine(a ^ b);     //  6   0110  — jedynka tam, gdzie bity się różnią

// Podgląd zapisu dwójkowego
Console.WriteLine(Convert.ToString(a, 2));        // 1100
Console.WriteLine(Convert.ToString(a & b, 2));    // 1000

Przesunięcia bitowe

Program.cs
int x = 5;          // 101

Console.WriteLine(x << 1);    // 10   1010   przesunięcie w lewo o 1 = mnożenie przez 2
Console.WriteLine(x << 3);    // 40   101000 przesunięcie o 3 = mnożenie przez 8
Console.WriteLine(x >> 1);    //  2   10     przesunięcie w prawo = dzielenie całkowite przez 2

Przesunięcie to mnożenie i dzielenie przez potęgi dwójki

Przesunięcie w lewo o n pozycji mnoży liczbę przez 2n. Przesunięcie w prawo — dzieli całkowicie. Kiedyś była to popularna sztuczka optymalizacyjna, bo procesor wykonywał ją szybciej niż mnożenie.

Dziś tego nie robimy. Współczesne kompilatory same zamieniają x * 8 na przesunięcie, gdy to się opłaca, a zapis x * 8 jest dużo czytelniejszy. Operatory bitowe stosuje się tam, gdzie naprawdę chodzi o bity: przy obsłudze sprzętu, formatach plików, maskach uprawnień i kompresji.

Typowe zastosowanie: maska bitowa

Jednym bajtem można zapisać osiem niezależnych informacji typu tak/nie. Każdy bit to jedna flaga.

Program.cs
// Każde uprawnienie to inny bit
const int Odczyt  = 1;    // 001
const int Zapis   = 2;    // 010
const int Usuwanie = 4;   // 100

// Nadajemy dwa uprawnienia naraz — suma bitowa
int uprawnienia = Odczyt | Zapis;          // 011 = 3
Console.WriteLine(uprawnienia);            // 3

// Sprawdzamy, czy dane uprawnienie jest włączone — iloczyn bitowy
bool mozeCzytac = (uprawnienia & Odczyt) != 0;
bool mozeUsuwac = (uprawnienia & Usuwanie) != 0;

Console.WriteLine(mozeCzytac);             // True
Console.WriteLine(mozeUsuwac);             // False

// Dodajemy uprawnienie
uprawnienia = uprawnienia | Usuwanie;      // 111 = 7
Console.WriteLine(uprawnienia);            // 7

Nie myl & z &&

To dwa różne operatory. && łączy warunki logiczne i skraca ocenę. & działa na bitach liczb. Jeden znak różnicy, a zupełnie inne znaczenie — dlatego przy warunkach zawsze pisz podwójny.

PRZYKŁAD Z OMÓWIENIEM

Kwalifikacja na kurs prawa jazdy

Program sprawdza, czy kandydat spełnia warunki zapisu. Zasady: ukończone 17 lat i 9 miesięcy, ważne badania lekarskie oraz albo zgoda opiekuna (dla niepełnoletnich), albo ukończone 18 lat.

Program.cs
// Dane kandydata
int wiekMiesiace = 213;          // 17 lat i 9 miesięcy
bool maBadania = true;
bool maZgodeOpiekuna = true;

const int MinimalnyWiekMiesiace = 213;    // 17 lat i 9 miesiecy
const int PelnoletnoscMiesiace = 216;     // 18 lat

// Warunki cząstkowe — każdy nazwany, więc kod tłumaczy się sam
bool wiekOk = wiekMiesiace >= MinimalnyWiekMiesiace;
bool czyPelnoletni = wiekMiesiace >= PelnoletnoscMiesiace;
bool zgodaOk = czyPelnoletni || maZgodeOpiekuna;

// Warunek końcowy
bool mozeSieZapisac = wiekOk && maBadania && zgodaOk;

Console.WriteLine($"Wiek w miesiacach:  {wiekMiesiace}  ({wiekMiesiace / 12} lat {wiekMiesiace % 12} mies.)");
Console.WriteLine($"Wiek wystarczajacy: {wiekOk}");
Console.WriteLine($"Pelnoletni:         {czyPelnoletni}");
Console.WriteLine($"Badania:            {maBadania}");
Console.WriteLine($"Zgoda w porzadku:   {zgodaOk}");
Console.WriteLine();
Console.WriteLine($"MOZE SIE ZAPISAC:   {mozeSieZapisac}");
wynik w konsoli
Wiek w miesiacach:  213  (17 lat 9 mies.)
Wiek wystarczajacy: True
Pelnoletni:         False
Badania:            True
Zgoda w porzadku:   True

MOZE SIE ZAPISAC:   True

Omówienie

FragmentDlaczego tak
Wiek liczony w miesiącach Warunek „17 lat i 9 miesięcy” nie da się sensownie zapisać na latach. Sprowadzenie wszystkiego do jednej jednostki upraszcza całą resztę — to częsty zabieg przy datach i czasie.
Rozbicie na warunki cząstkowe Można było napisać jedno wielkie wyrażenie. Byłoby krótsze, ale nieczytelne i trudne do sprawdzenia. Nazwane warunki pośrednie są kluczową techniką — pozwalają też wypisać, który dokładnie warunek zawiódł.
czyPelnoletni || maZgodeOpiekuna Alternatywa, bo wystarczy jedno z dwojga. Pełnoletni nie potrzebuje zgody; niepełnoletni musi ją mieć.
wiekOk && maBadania && zgodaOk Koniunkcja, bo tu wszystkie trzy warunki są obowiązkowe. Zwróć uwagę, że jeden z nich sam w sobie jest alternatywą — warunki można zagnieżdżać dowolnie.
{wiekMiesiace / 12} i {wiekMiesiace % 12} Znajomy duet z poprzedniej lekcji: dzielenie daje pełne lata, reszta — miesiące.

Spróbuj sam

Zmień maBadania na false i uruchom program. Potem ustaw maZgodeOpiekuna = false. Zobacz, jak każda zmiana wpływa na warunek końcowy — to najlepszy sposób, żeby poczuć różnicę między && a ||.

CZĘSTE BŁĘDY

Na co uważać

KodProblem
if (x = 5) Jeden znak równości to przypisanie. W C# to błąd kompilacji — warunek musi być typu bool.
18 <= wiek <= 65 Zapis matematyczny nie działa. Trzeba wiek >= 18 && wiek <= 65.
if (liczba) W C# liczba nie jest wartością logiczną. Trzeba if (liczba != 0).
a == b dla double Bywa zawodne. Porównuj przez tolerancję.
tekst == "tak" dla danych od użytkownika Wpisanie Tak albo TAK da false. Użyj porównania ignorującego wielkość liter.
if (a & b) zamiast && Zadziała dla wartości bool, ale bez skracania — obie strony zostaną sprawdzone. Traci się zabezpieczenie przed błędem.
liczba / dzielnik > 10 && dzielnik != 0 Zła kolejność. Zabezpieczenie musi być pierwsze, inaczej ocena skrócona nie zdąży zadziałać.
a || b && c bez nawiasów Kompiluje się, ale && wiąże mocniej — łatwo o pomyłkę w interpretacji. Postaw nawiasy.
tekst1 < tekst2 Napisów nie da się porównać operatorem <. Służy do tego string.Compare.
ZADANIA

Zadania

ZAD 1Przewidz wynik★☆☆

Dla int a = 5, b = 10; i bool t = true, f = false; zapisz wynik każdej linii.

co wypisze?
Console.WriteLine(a < b && b < 20);
Console.WriteLine(a > b || b > 20);
Console.WriteLine(!(a == 5));
Console.WriteLine(t && f || t);
Console.WriteLine(a + 5 == b);
Console.WriteLine(a * 2 != b);
pokaż rozwiązanie
True
False
False
True
True
False

Linia 4&& wiąże mocniej, więc czytamy to jako (t && f) || t, czyli false || true, czyli true.

Linie 5 i 6 — arytmetyka wykonuje się przed porównaniem: najpierw 5 + 5, potem sprawdzenie równości z 10.

ZAD 2Zapisz warunki★☆☆

Zapisz w C# poniższe warunki, używając zmiennych wiek, ocena, maZaswiadczenie:

  • wiek mieści się w przedziale od 15 do 19 lat włącznie
  • ocena jest niedostateczna (mniejsza niż 2) albo brakuje zaświadczenia
  • uczeń nie jest ani zbyt młody (poniżej 15), ani zbyt stary (powyżej 19)
pokaż rozwiązanie
bool wPrzedziale = wiek >= 15 && wiek <= 19;

bool problem = ocena < 2 || !maZaswiadczenie;

bool odpowiedniWiek = !(wiek < 15 || wiek > 19);
// to samo prawami De Morgana:
bool odpowiedniWiek2 = wiek >= 15 && wiek <= 19;

Trzeci warunek to dokładnie ten sam przedział co pierwszy, tylko zapisany „od drugiej strony”. Prawa De Morgana pozwalają zamienić jeden zapis na drugi — i zwykle wersja bez zaprzeczenia jest czytelniejsza.

ZAD 3Uprość zaprzeczenia★★☆

Zastosuj prawa De Morgana, aby usunąć zewnętrzną negację z każdego wyrażenia:

do uproszczenia
!(zalogowany && maUprawnienia)
!(pusty || uszkodzony)
!(wiek >= 18 && maDokument)
pokaż rozwiązanie
!zalogowany || !maUprawnienia

!pusty && !uszkodzony

wiek < 18 || !maDokument

Zwróć uwagę na trzecie wyrażenie: zaprzeczeniem warunku wiek >= 18 nie jest !(wiek >= 18) zapisane dosłownie, tylko po prostu wiek < 18. Każdy operator porównania ma swoje przeciwieństwo:

>=<    ><=    ==!=

Korzystanie z tego zamiast wstawiania wykrzyknika daje dużo czytelniejszy kod.

ZAD 4Rok przestępny★★☆

Napisz warunek sprawdzający, czy rok jest przestępny. Zasada: rok jest przestępny, jeśli dzieli się przez 4, ale nie przez 100 — chyba że dzieli się przez 400. Sprawdź go dla lat 2024, 1900 i 2000.

pokaż rozwiązanie
int rok = 2024;

bool przestepny = (rok % 4 == 0 && rok % 100 != 0) || rok % 400 == 0;

Console.WriteLine($"{rok}: {przestepny}");
2024: True
1900: False
2000: True

2024 — dzieli się przez 4, nie dzieli przez 100 → pierwszy nawias daje true.

1900 — dzieli się przez 4, ale też przez 100, więc pierwszy nawias daje false. Przez 400 też się nie dzieli, więc całość jest fałszywa.

2000 — pierwszy nawias daje false (dzieli się przez 100), ale drugi warunek ratuje sytuację: 2000 dzieli się przez 400.

Nawias wokół pierwszej części jest tu konieczny. Bez niego && i tak wiązałoby mocniej i wynik byłby przypadkiem ten sam — ale nikt nie chce polegać na przypadku przy tak zawiłej regule.

ZAD 5Operacje bitowe★★☆

Dla liczb a = 20 i b = 6 wypisz ich zapis dwójkowy oraz wyniki działań &, |, ^, a także a << 2 i a >> 2. Sprawdź, czy przesunięcia rzeczywiście odpowiadają mnożeniu i dzieleniu przez 4.

pokaż rozwiązanie
int a = 20;      // 10100
int b = 6;       //   110

Console.WriteLine($"a       = {a,3}  {Convert.ToString(a, 2)}");
Console.WriteLine($"b       = {b,3}  {Convert.ToString(b, 2)}");
Console.WriteLine($"a & b   = {a & b,3}  {Convert.ToString(a & b, 2)}");
Console.WriteLine($"a | b   = {a | b,3}  {Convert.ToString(a | b, 2)}");
Console.WriteLine($"a ^ b   = {a ^ b,3}  {Convert.ToString(a ^ b, 2)}");
Console.WriteLine($"a << 2  = {a << 2,3}   (a * 4 = {a * 4})");
Console.WriteLine($"a >> 2  = {a >> 2,3}   (a / 4 = {a / 4})");
a       =  20  10100
b       =   6  110
a & b   =   4  100
a | b   =  22  10110
a ^ b   =  18  10010
a << 2  =  80   (a * 4 = 80)
a >> 2  =   5   (a / 4 = 5)

Wyrównaj bity, żeby zobaczyć, co się dzieje. 20 to 10100, 6 to 00110:

& zostawia jedynkę tylko tam, gdzie obie liczby miały jedynkę — czyli na trzeciej pozycji od prawej: 00100 = 4.

^ daje jedynkę tam, gdzie bity się różnią: 10010 = 18.

Przesunięcia potwierdzają regułę: o 2 pozycje to mnożenie lub dzielenie przez 2² = 4.

ZAD 6System uprawnień★★★

Zbuduj maskę bitową dla czterech uprawnień: odczyt (1), zapis (2), usuwanie (4), udostępnianie (8). Nadaj użytkownikowi odczyt i udostępnianie, sprawdź wszystkie cztery uprawnienia, następnie odbierz udostępnianie i sprawdź ponownie.

pokaż rozwiązanie
const int Odczyt = 1;          // 0001
const int Zapis = 2;           // 0010
const int Usuwanie = 4;        // 0100
const int Udostepnianie = 8;   // 1000

// Nadanie dwóch uprawnien
int uprawnienia = Odczyt | Udostepnianie;      // 1001 = 9

Console.WriteLine($"Maska: {uprawnienia}  ({Convert.ToString(uprawnienia, 2)})");
Console.WriteLine($"  odczyt:        {(uprawnienia & Odczyt) != 0}");
Console.WriteLine($"  zapis:         {(uprawnienia & Zapis) != 0}");
Console.WriteLine($"  usuwanie:      {(uprawnienia & Usuwanie) != 0}");
Console.WriteLine($"  udostepnianie: {(uprawnienia & Udostepnianie) != 0}");

// Odebranie uprawnienia: iloczyn z zanegowana maska
uprawnienia = uprawnienia & ~Udostepnianie;    // 0001 = 1

Console.WriteLine();
Console.WriteLine($"Po odebraniu udostepniania: {uprawnienia}  ({Convert.ToString(uprawnienia, 2)})");
Console.WriteLine($"  udostepnianie: {(uprawnienia & Udostepnianie) != 0}");
Maska: 9  (1001)
  odczyt:        True
  zapis:         False
  usuwanie:      False
  udostepnianie: True

Po odebraniu udostepniania: 1  (1)
  udostepnianie: False

Trzy operacje warto zapamiętać jako zestaw:

  • Nadanie uprawnienia: maska | flaga
  • Sprawdzenie: (maska & flaga) != 0
  • Odebranie: maska & ~flaga — negacja bitowa robi maskę z jedynkami wszędzie poza tym jednym bitem, więc iloczyn zeruje właśnie jego

Ten wzorzec spotkasz w systemach plików, uprawnieniach w Windowsie i w wielu formatach binarnych. W C# istnieje wygodniejszy sposób zapisania takich flag — typ wyliczeniowy z atrybutem [Flags] — ale wymaga on znajomości typów wyliczeniowych, które omawiamy w bloku 9.

PODSUMOWANIE

Co trzeba zapamiętać

  • Operatory porównania zwracają bool. = przypisuje, == porównuje.
  • Przedział zapisujemy dwoma warunkami: x >= 18 && x <= 65. Zapis matematyczny nie działa.
  • Na napisach == porównuje treść, ale rozróżnia wielkość liter. Dla danych od użytkownika użyj ToLower() albo StringComparison.OrdinalIgnoreCase.
  • Typu double nie porównuj przez == — użyj tolerancji.
  • && wymaga wszystkich warunków, || wystarczy jeden, ! odwraca.
  • Ocena skrócona: gdy wynik jest przesądzony, reszta się nie wykonuje. Warunek zabezpieczający stawiaj pierwszy.
  • && wiąże mocniej niż ||. Przy mieszaniu obu zawsze stawiaj nawiasy.
  • Prawa De Morgana: !(A && B) to !A || !B, a !(A || B) to !A && !B.
  • Operatory bitowe & | ^ ~ << >> działają na pojedynczych bitach — nie myl ich z logicznymi.