← Kursa Dön
📄 Text · 22 min

Bit Manipulation — Bitwise Operators, Bit Masks ve std::bitset

Bilgisayarın en küçük veri birimi bit — 0 veya 1. Her şey, tüm veriler, tüm programlar sonunda bitlere indirgenir. Bit manipülasyonu, bu en alt seviyede çalışmayı öğrenmektir. İlk bakışta "neden bununla uğraşayım?" diyebilirsin, ama gerçek şu ki: izin sistemleri, renk işleme, ağ protokolleri, oyun motorları, kriptografi ve performans-kritik kodlar hep bitlerle dans eder.

Bu ders, bitwise operatörleri, bit mask pattern'lerini ve std::bitset'i sıfırdan öğretecek. Sonunda, bitleri okumak senin için doğal hale gelecek.


Bitlerin Temeli: Hızlı Hatırlatma

Bir byte 8 bitten oluşur. Her bit bir pozisyona sahiptir ve sağdan sola (0'dan başlayarak) numaralandırılır.

Pozisyon:  7   6   5   4   3   2   1   0
Değer:   128  64  32  16   8   4   2   1
Örnek:     0   0   1   0   1   0   1   1  = 43 (32+8+2+1)

Her pozisyon, 2'nin o pozisyondaki kuvvetine karşılık gelir. Yani bit 0 = 2⁰ = 1, bit 3 = 2³ = 8, bit 7 = 2⁷ = 128.

Bunu bir ışık paneli gibi düşün: 8 anahtar var, her biri bağımsız olarak açık (1) veya kapalı (0). Farklı kombinasyonlar farklı anlamlar taşıyor. Bir anahtarı açmak, kapatmak veya kontrol etmek istiyorsan — işte bitwise operatörler tam bunu yapıyor.


Bitwise Operatörler

C++ altı bitwise operatör sunar. Bunlar, sayıların her bir biti üzerinde bağımsız olarak çalışır.

AND Operatörü: &

İki bitin ikisi de 1 ise sonuç 1, aksi halde 0.

Truth Table (Doğruluk Tablosu):
  A | B | A & B
  --|---|------
  0 | 0 |  0
  0 | 1 |  0
  1 | 0 |  0
  1 | 1 |  1

AND operatörü "her ikisi de doğru mu?" sorusunu sorar. Bir kapının iki kilidi var — ikisi de açıksa kapı açılır. Sadece birini açmak yetmez.

#include <iostream>
#include <bitset>

int main() {
    uint8_t a = 0b11001010;  // 202
    uint8_t b = 0b10101100;  // 172

    uint8_t sonuc = a & b;

    std::cout << "  a:      " << std::bitset<8>(a) << " (" << +a << ")" << std::endl;
    std::cout << "  b:      " << std::bitset<8>(b) << " (" << +b << ")" << std::endl;
    std::cout << "  a & b:  " << std::bitset<8>(sonuc) << " (" << +sonuc << ")" << std::endl;
    // 11001010
    // 10101100
    // --------
    // 10001000 = 136

    return 0;
}

AND'in en yaygın kullanımı: belirli bir bitin değerini kontrol etmek (maskeleme).

OR Operatörü: |

İki bitten en az biri 1 ise sonuç 1.

Truth Table:
  A | B | A | B
  --|---|------
  0 | 0 |  0
  0 | 1 |  1
  1 | 0 |  1
  1 | 1 |  1

OR "en az biri doğru mu?" sorusunu sorar. Bir oda iki kapıdan giriliyorsa, herhangi birinden girmen yeterli.

#include <iostream>
#include <bitset>

int main() {
    uint8_t a = 0b11001010;
    uint8_t b = 0b10101100;

    uint8_t sonuc = a | b;

    std::cout << "  a:      " << std::bitset<8>(a) << std::endl;
    std::cout << "  b:      " << std::bitset<8>(b) << std::endl;
    std::cout << "  a | b:  " << std::bitset<8>(sonuc) << std::endl;
    // 11001010
    // 10101100
    // --------
    // 11101110 = 238

    return 0;
}

OR'un en yaygın kullanımı: belirli bir biti 1'e ayarlamak (set etmek).

XOR Operatörü: ^

İki bit farklıysa 1, aynıysa 0. "Exclusive OR" — "münhasır veya" demek.

Truth Table:
  A | B | A ^ B
  --|---|------
  0 | 0 |  0
  0 | 1 |  1
  1 | 0 |  1
  1 | 1 |  0

XOR'un harika bir özelliği: kendisiyle XOR'lanan bir değer sıfır olur, ve XOR geri dönüşümlüdür (reversible). A ^ B ^ B = A.

#include <iostream>
#include <bitset>

int main() {
    uint8_t a = 0b11001010;
    uint8_t b = 0b10101100;

    uint8_t sonuc = a ^ b;

    std::cout << "  a:      " << std::bitset<8>(a) << std::endl;
    std::cout << "  b:      " << std::bitset<8>(b) << std::endl;
    std::cout << "  a ^ b:  " << std::bitset<8>(sonuc) << std::endl;
    // 11001010
    // 10101100
    // --------
    // 01100110 = 102

    // XOR ile swap (geçici değişken olmadan)
    uint8_t x = 15, y = 27;
    std::cout << "\nSwap oncesi: x=" << +x << " y=" << +y << std::endl;
    x = x ^ y;
    y = x ^ y;
    x = x ^ y;
    std::cout << "Swap sonrasi: x=" << +x << " y=" << +y << std::endl;

    return 0;
}

XOR'un en yaygın kullanımları: bit toggle (tersine çevirme), basit şifreleme, swap.

NOT Operatörü: ~

Tek operandlı (unary) operatör. Tüm bitleri tersine çevirir: 0 → 1, 1 → 0.

Truth Table:
  A | ~A
  --|---
  0 |  1
  1 |  0
#include <iostream>
#include <bitset>

int main() {
    uint8_t a = 0b11001010;  // 202

    uint8_t sonuc = ~a;

    std::cout << "  a:  " << std::bitset<8>(a) << " (" << +a << ")" << std::endl;
    std::cout << "  ~a: " << std::bitset<8>(sonuc) << " (" << +sonuc << ")" << std::endl;
    // 11001010
    // --------
    // 00110101 = 53

    // Tüm bitleri 1 olan mask oluşturmak:
    uint8_t all_ones = ~uint8_t(0);  // 11111111 = 255
    std::cout << "  ~0: " << std::bitset<8>(all_ones) << " (" << +all_ones << ")" << std::endl;

    return 0;
}

NOT'un yaygın kullanımı: bit mask'ı tersine çevirmek — "bu bitler HARİCİNDE hepsini seç" demek.

Left Shift Operatörü: <<

Bitleri sola kaydırır. Sağdan sıfır doldurulur. Her kaydırma, sayıyı 2 ile çarpmaya eşdeğerdir.

  00001010 << 2 = 00101000
  (10)             (40)
  10 * 2² = 10 * 4 = 40
#include <iostream>
#include <bitset>

int main() {
    uint8_t a = 0b00001010;  // 10

    std::cout << "  a:      " << std::bitset<8>(a)      << " (" << +a      << ")" << std::endl;
    std::cout << "  a << 1: " << std::bitset<8>(a << 1) << " (" << +(uint8_t)(a << 1) << ")" << std::endl;
    std::cout << "  a << 2: " << std::bitset<8>(a << 2) << " (" << +(uint8_t)(a << 2) << ")" << std::endl;
    std::cout << "  a << 3: " << std::bitset<8>(a << 3) << " (" << +(uint8_t)(a << 3) << ")" << std::endl;
    // a << 1: 00010100 (20)  — 10 * 2
    // a << 2: 00101000 (40)  — 10 * 4
    // a << 3: 01010000 (80)  — 10 * 8

    // Tek bir bit pozisyonuna mask oluşturmak
    uint8_t bit3 = 1 << 3;  // 00001000 — 3. bit'in mask'ı
    uint8_t bit5 = 1 << 5;  // 00100000 — 5. bit'in mask'ı
    std::cout << "\n  1 << 3: " << std::bitset<8>(bit3) << std::endl;
    std::cout << "  1 << 5: " << std::bitset<8>(bit5) << std::endl;

    return 0;
}

Right Shift Operatörü: >>

Bitleri sağa kaydırır. Her kaydırma, sayıyı 2'ye bölmeye eşdeğerdir.

  00101000 >> 2 = 00001010
  (40)             (10)
  40 / 2² = 40 / 4 = 10

Unsigned türlerde soldan sıfır doldurulur. Signed türlerde davranış implementasyona bağlıdır (genellikle sign bit korunur — arithmetic shift).

#include <iostream>
#include <bitset>

int main() {
    uint8_t a = 0b01010000;  // 80

    std::cout << "  a:      " << std::bitset<8>(a) << " (" << +a << ")" << std::endl;
    std::cout << "  a >> 1: " << std::bitset<8>(a >> 1) << " (" << +(uint8_t)(a >> 1) << ")" << std::endl;
    std::cout << "  a >> 2: " << std::bitset<8>(a >> 2) << " (" << +(uint8_t)(a >> 2) << ")" << std::endl;
    std::cout << "  a >> 3: " << std::bitset<8>(a >> 3) << " (" << +(uint8_t)(a >> 3) << ")" << std::endl;
    // a >> 1: 00101000 (40)  — 80 / 2
    // a >> 2: 00010100 (20)  — 80 / 4
    // a >> 3: 00001010 (10)  — 80 / 8

    return 0;
}

⚠️ Dikkat: Signed integer'larla shift operatörü kullanırken çok dikkatli ol! Negatif bir sayıyı sola kaydırmak (left shift) C++'ta undefined behavior (tanımsız davranış) olabilir. Negatif sayıyı sağa kaydırmak ise implementation-defined — yani derleyiciye bağlı. Bit manipülasyonu için her zaman unsigned türler (uint8_t, uint16_t, uint32_t, uint64_t) kullan.

// TEHLİKELİ — signed shift
int8_t neg = -1;            // 11111111 (two's complement)
int8_t shifted = neg << 1;  // UNDEFINED BEHAVIOR!

// GÜVENLİ — unsigned shift
uint8_t val = 0xFF;          // 11111111
uint8_t shifted = val << 1;  // 11111110 = 254 — tanımlı davranış

Bit Mask Pattern'leri

Bit mask (bit maskesi), belirli bitleri seçmek, değiştirmek veya kontrol etmek için kullanılan bir kalıptır. Dört temel operasyon vardır: set, clear, toggle, check.

Bit Set (Açma): OR ile

Belirli bir biti 1 yapmak için OR kullanılır.

uint8_t flags = 0b00000000;  // Tüm bitler kapalı

// Bit 3'ü aç
flags = flags | (1 << 3);    // flags |= (1 << 3);
// 00000000 | 00001000 = 00001000

// Bit 0 ve bit 5'i aç
flags |= (1 << 0) | (1 << 5);
// 00001000 | 00100001 = 00101001

Mantık: x | 1 = 1 (her zaman 1 yapar), x | 0 = x (olduğu gibi bırakır). Mask'taki 1'ler "burayı 1 yap", 0'lar "dokunma" demek.

Bit Clear (Kapatma): AND + NOT ile

Belirli bir biti 0 yapmak için AND ile ters mask kullanılır.

uint8_t flags = 0b00101001;

// Bit 3'ü kapat
flags = flags & ~(1 << 3);   // flags &= ~(1 << 3);
// 00101001 & 11110111 = 00100001

// Bit 0'ı kapat
flags &= ~(1 << 0);
// 00100001 & 11111110 = 00100000

Mantık: x & 0 = 0 (her zaman 0 yapar), x & 1 = x (olduğu gibi bırakır). ~(1 << n) ifadesi, n. bit hariç tüm bitleri 1 olan bir mask oluşturur.

Bit Toggle (Tersine Çevirme): XOR ile

Belirli bir bitin değerini tersine çevirmek (0 → 1, 1 → 0) için XOR kullanılır.

uint8_t flags = 0b00100000;

// Bit 5'i toggle et
flags = flags ^ (1 << 5);    // flags ^= (1 << 5);
// 00100000 ^ 00100000 = 00000000  (1 idi, 0 oldu)

flags ^= (1 << 5);
// 00000000 ^ 00100000 = 00100000  (0 idi, 1 oldu — geri döndü)

Mantık: x ^ 1 = ~x (tersini alır), x ^ 0 = x (olduğu gibi bırakır).

Bit Check (Kontrol): AND ile

Belirli bir bitin 1 olup olmadığını kontrol etmek için AND kullanılır.

uint8_t flags = 0b00101001;

// Bit 3 açık mı?
bool bit3_acik = (flags & (1 << 3)) != 0;
// 00101001 & 00001000 = 00001000 → sıfır değil → true

// Bit 2 açık mı?
bool bit2_acik = (flags & (1 << 2)) != 0;
// 00101001 & 00000100 = 00000000 → sıfır → false

if (flags & (1 << 3)) {
    std::cout << "Bit 3 acik!" << std::endl;
}

Özet Tablosu

OperasyonKodAçıklama
Set (aç)`flags \= (1 << n)`n. biti 1 yap
Clear (kapat)flags &= ~(1 << n)n. biti 0 yap
Toggle (çevir)flags ^= (1 << n)n. biti tersine çevir
Check (kontrol)flags & (1 << n)n. bit 1 mi?

Flags Pattern: İzin Sistemi Örneği

Bit mask'ların en klasik kullanımı, birden fazla boolean değeri tek bir sayıda saklamaktır. Unix dosya izinleri, oyun entity özellikleri, GUI widget durumları — hepsi bu pattern'i kullanır.

#include <iostream>
#include <cstdint>

// Her izin bir bit pozisyonuna karşılık gelir
constexpr uint8_t PERM_READ    = 1 << 0;  // 00000001 = 1
constexpr uint8_t PERM_WRITE   = 1 << 1;  // 00000010 = 2
constexpr uint8_t PERM_EXECUTE = 1 << 2;  // 00000100 = 4
constexpr uint8_t PERM_DELETE  = 1 << 3;  // 00001000 = 8
constexpr uint8_t PERM_ADMIN   = 1 << 4;  // 00010000 = 16

// Yaygın kombinasyonlar
constexpr uint8_t PERM_RW       = PERM_READ | PERM_WRITE;         // 00000011 = 3
constexpr uint8_t PERM_ALL      = PERM_READ | PERM_WRITE | PERM_EXECUTE | PERM_DELETE;
constexpr uint8_t PERM_READONLY = PERM_READ;

void printPermissions(uint8_t perms) {
    std::cout << "[";
    if (perms & PERM_READ)    std::cout << "R";
    if (perms & PERM_WRITE)   std::cout << "W";
    if (perms & PERM_EXECUTE) std::cout << "X";
    if (perms & PERM_DELETE)  std::cout << "D";
    if (perms & PERM_ADMIN)   std::cout << "A";
    std::cout << "]" << std::endl;
}

int main() {
    // Kullanıcıya okuma ve yazma izni ver
    uint8_t userPerms = PERM_READ | PERM_WRITE;
    std::cout << "Baslangic: ";
    printPermissions(userPerms);  // [RW]

    // Çalıştırma izni ekle
    userPerms |= PERM_EXECUTE;
    std::cout << "Execute eklendi: ";
    printPermissions(userPerms);  // [RWX]

    // Yazma iznini kaldır
    userPerms &= ~PERM_WRITE;
    std::cout << "Write kaldirildi: ";
    printPermissions(userPerms);  // [RX]

    // Admin yetkisi var mı kontrol et
    if (userPerms & PERM_ADMIN) {
        std::cout << "Admin yetkisi VAR" << std::endl;
    } else {
        std::cout << "Admin yetkisi YOK" << std::endl;
    }

    // Birden fazla izni aynı anda kontrol et
    if ((userPerms & PERM_RW) == PERM_RW) {
        std::cout << "Hem okuma hem yazma yetkisi var" << std::endl;
    } else {
        std::cout << "Okuma+yazma yetkisi eksik" << std::endl;
    }

    return 0;
}

Bu pattern neden bu kadar popüler? Çünkü:

  • Bellek tasarrufu: 8 boolean = 8 byte yerine 1 byte

  • Hız: Tek bir CPU talimatıyla birden fazla flag kontrol edilebilir

  • Birleştirilebilirlik: PERM_READ | PERM_WRITE gibi doğal ve okunaklı

Enum ile Daha Temiz Flags

C++11 ile enum class kullanarak daha tip-güvenli flags tanımlayabilirsin. Ancak enum class bitwise operatörleri doğrudan desteklemez, bu yüzden operatörleri overload etmen gerekir:

#include <iostream>
#include <cstdint>

enum class Permission : uint8_t {
    None    = 0,
    Read    = 1 << 0,
    Write   = 1 << 1,
    Execute = 1 << 2,
    Delete  = 1 << 3,
    Admin   = 1 << 4
};

// Bitwise operatörleri overload et
Permission operator|(Permission a, Permission b) {
    return static_cast<Permission>(
        static_cast<uint8_t>(a) | static_cast<uint8_t>(b)
    );
}

Permission operator&(Permission a, Permission b) {
    return static_cast<Permission>(
        static_cast<uint8_t>(a) & static_cast<uint8_t>(b)
    );
}

Permission operator~(Permission a) {
    return static_cast<Permission>(~static_cast<uint8_t>(a));
}

bool hasPermission(Permission perms, Permission check) {
    return (perms & check) == check;
}

int main() {
    Permission userPerms = Permission::Read | Permission::Write;

    if (hasPermission(userPerms, Permission::Read)) {
        std::cout << "Okuma yetkisi var" << std::endl;
    }

    if (!hasPermission(userPerms, Permission::Admin)) {
        std::cout << "Admin yetkisi yok" << std::endl;
    }

    return 0;
}

std::bitset Kullanımı

<bitset> header'ındaki std::bitset, sabit boyutlu bir bit dizisi sunar. Bitlerle çalışmayı çok daha okunaklı ve güvenli hale getirir.

#include <iostream>
#include <bitset>
#include <string>

int main() {
    // Farklı oluşturma yolları
    std::bitset<8> a;                      // 00000000 (varsayılan: tümü 0)
    std::bitset<8> b(42);                  // 00101010 (integer'dan)
    std::bitset<8> c(std::string("11001010")); // String'den
    std::bitset<8> d(0xFF);                // 11111111

    std::cout << "a: " << a << std::endl;  // 00000000
    std::cout << "b: " << b << std::endl;  // 00101010
    std::cout << "c: " << c << std::endl;  // 11001010
    std::cout << "d: " << d << std::endl;  // 11111111

    return 0;
}

Temel Metotlar

#include <iostream>
#include <bitset>

int main() {
    std::bitset<8> flags(0b00101010);

    // Tek bit operasyonları
    flags.set(0);        // Bit 0'ı 1 yap    → 00101011
    flags.reset(3);      // Bit 3'ü 0 yap    → 00100011
    flags.flip(7);       // Bit 7'yi toggle et → 10100011
    bool bit5 = flags.test(5);  // Bit 5 açık mı?  → true

    // Tüm bitler üzerinde
    flags.set();         // Tüm bitleri 1 yap → 11111111
    flags.reset();       // Tüm bitleri 0 yap → 00000000
    flags.flip();        // Tüm bitleri çevir  → 11111111

    // Sorgulama
    std::bitset<8> example(0b00101010);
    std::cout << "Deger:      " << example << std::endl;
    std::cout << "Acik bit:   " << example.count() << std::endl;    // 3 (kaç tane 1 var)
    std::cout << "Toplam bit: " << example.size() << std::endl;     // 8
    std::cout << "Hepsi 0 mi: " << example.none() << std::endl;     // false
    std::cout << "En az 1 mi: " << example.any() << std::endl;      // true
    std::cout << "Hepsi 1 mi: " << example.all() << std::endl;      // false

    // Dönüşüm
    unsigned long sayi = example.to_ulong();       // 42
    std::string str    = example.to_string();      // "00101010"
    std::cout << "Sayi:  " << sayi << std::endl;
    std::cout << "String: " << str << std::endl;

    return 0;
}

std::bitset vs Manuel Bit Manipulation

ÖzellikManuel (uint32_t + operatörler)std::bitset
OkunabilirlikDüşükYüksek
GüvenlikDüşük (yanlış mask riski)Yüksek (sınır kontrolü)
PerformansÇok yüksekGenellikle aynı (derleyici optimize eder)
Dinamik boyutHayırHayır (derleme zamanı sabit)
YazdırmaManuel format gerekliDoğrudan cout'a basılabilir
Boyut esnekliği8, 16, 32, 64 bitHerhangi bir boyut (128, 256...)

Pratikte, performans kritik bölümlerde ve API'lerde genellikle unsigned integer + bitwise operatörler kullanılır. Debug, prototipleme ve 64 bit'ten büyük bit dizileri için std::bitset daha uygun.


Gerçek Dünya Örnekleri

RGB Renk Manipülasyonu

Bilgisayar grafiklerinde bir renk genellikle 32-bit bir tam sayıda saklanır: 0xAARRGGBB (Alpha, Red, Green, Blue — her biri 8 bit / 1 byte).

#include <iostream>
#include <cstdint>
#include <bitset>

// Renk bileşenlerini birleştir
uint32_t makeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) {
    return (static_cast<uint32_t>(a) << 24) |
           (static_cast<uint32_t>(r) << 16) |
           (static_cast<uint32_t>(g) << 8)  |
           (static_cast<uint32_t>(b));
}

// Renk bileşenlerini çıkar
uint8_t getAlpha(uint32_t color) { return (color >> 24) & 0xFF; }
uint8_t getRed(uint32_t color)   { return (color >> 16) & 0xFF; }
uint8_t getGreen(uint32_t color) { return (color >> 8)  & 0xFF; }
uint8_t getBlue(uint32_t color)  { return  color        & 0xFF; }

void printColor(uint32_t color) {
    std::cout << "Renk: #" << std::hex
              << +getRed(color)
              << +getGreen(color)
              << +getBlue(color)
              << std::dec
              << " (R=" << +getRed(color)
              << " G="  << +getGreen(color)
              << " B="  << +getBlue(color)
              << " A="  << +getAlpha(color) << ")" << std::endl;
}

int main() {
    // Kırmızı: R=255, G=0, B=0
    uint32_t red = makeColor(255, 0, 0);
    printColor(red);

    // Turuncu: R=255, G=165, B=0
    uint32_t orange = makeColor(255, 165, 0);
    printColor(orange);

    // Rengi koyulaştır (her bileşeni yarıya böl)
    uint32_t dark_orange = makeColor(
        getRed(orange) >> 1,
        getGreen(orange) >> 1,
        getBlue(orange) >> 1
    );
    std::cout << "Koyu ";
    printColor(dark_orange);

    return 0;
}

Bu pattern, oyun motorlarından web tarayıcılarına kadar her yerde kullanılır. >> 16, & 0xFF gibi operasyonlar ilk başta korkutucu görünebilir, ama aslında çok basit: kaydır ve maskele.

IP Adresi ve Subnet Mask

Ağ programlamada IP adresleri 32-bit sayılar olarak saklanır ve subnet mask'lar bitwise AND ile uygulanır.

#include <iostream>
#include <cstdint>

// IP adresini string olarak yazdır
void printIP(uint32_t ip) {
    std::cout << ((ip >> 24) & 0xFF) << "."
              << ((ip >> 16) & 0xFF) << "."
              << ((ip >> 8) & 0xFF)  << "."
              << (ip & 0xFF);
}

// IP adresini byte'lardan oluştur
uint32_t makeIP(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
    return (static_cast<uint32_t>(a) << 24) |
           (static_cast<uint32_t>(b) << 16) |
           (static_cast<uint32_t>(c) << 8)  |
           static_cast<uint32_t>(d);
}

int main() {
    uint32_t ip   = makeIP(192, 168, 1, 100);
    uint32_t mask = makeIP(255, 255, 255, 0);  // /24 subnet

    // Network adresi = IP AND Mask
    uint32_t network = ip & mask;

    // Broadcast adresi = Network OR (~Mask)
    uint32_t broadcast = network | ~mask;

    // Host kısmı = IP AND (~Mask)
    uint32_t host = ip & ~mask;

    std::cout << "IP:        "; printIP(ip);        std::cout << std::endl;
    std::cout << "Mask:      "; printIP(mask);      std::cout << std::endl;
    std::cout << "Network:   "; printIP(network);   std::cout << std::endl;
    std::cout << "Broadcast: "; printIP(broadcast); std::cout << std::endl;
    std::cout << "Host:      "; printIP(host);      std::cout << std::endl;
    // Çıktı:
    // IP:        192.168.1.100
    // Mask:      255.255.255.0
    // Network:   192.168.1.0
    // Broadcast: 192.168.1.255
    // Host:      0.0.0.100

    return 0;
}

Subnet maskeleme, tam anlamıyla bitwise AND operatörünün doğrudan uygulamasıdır. Her ağ cihazı, yönlendirici, firewall bu operasyonu saniyede milyonlarca kez yapar.

Oyun Geliştirmede Entity Flags

#include <iostream>
#include <cstdint>

// Entity özellikleri — her biri bir bit
constexpr uint16_t FLAG_VISIBLE    = 1 << 0;   // Görünür
constexpr uint16_t FLAG_SOLID      = 1 << 1;   // Fiziksel çarpışma
constexpr uint16_t FLAG_MOVABLE    = 1 << 2;   // Hareket edebilir
constexpr uint16_t FLAG_DAMAGEABLE = 1 << 3;   // Hasar alabilir
constexpr uint16_t FLAG_HOSTILE    = 1 << 4;   // Düşman
constexpr uint16_t FLAG_NPC        = 1 << 5;   // NPC
constexpr uint16_t FLAG_PLAYER     = 1 << 6;   // Oyuncu

// Yaygın presetler
constexpr uint16_t PRESET_WALL   = FLAG_VISIBLE | FLAG_SOLID;
constexpr uint16_t PRESET_ENEMY  = FLAG_VISIBLE | FLAG_SOLID | FLAG_MOVABLE |
                                   FLAG_DAMAGEABLE | FLAG_HOSTILE;
constexpr uint16_t PRESET_PLAYER = FLAG_VISIBLE | FLAG_SOLID | FLAG_MOVABLE |
                                   FLAG_DAMAGEABLE | FLAG_PLAYER;

struct Entity {
    std::string name;
    uint16_t flags;

    bool isVisible() const    { return flags & FLAG_VISIBLE; }
    bool isSolid() const      { return flags & FLAG_SOLID; }
    bool isHostile() const    { return flags & FLAG_HOSTILE; }
    bool isDamageable() const { return flags & FLAG_DAMAGEABLE; }

    void makeInvisible() { flags &= ~FLAG_VISIBLE; }
    void makeVisible()   { flags |= FLAG_VISIBLE; }
};

int main() {
    Entity player{"Hero", PRESET_PLAYER};
    Entity goblin{"Goblin", PRESET_ENEMY};
    Entity wall{"Stone Wall", PRESET_WALL};

    // Çarpışma kontrolü: ikisi de solid mı?
    if (player.isSolid() && wall.isSolid()) {
        std::cout << player.name << " ve " << wall.name
                  << " carpisiyor!" << std::endl;
    }

    // Düşman mı kontrol et
    if (goblin.isHostile() && goblin.isDamageable()) {
        std::cout << goblin.name << " dusmandir ve hasar alabilir" << std::endl;
    }

    // Görünmezlik büyüsü
    player.makeInvisible();
    std::cout << player.name << " gorunur mu: "
              << (player.isVisible() ? "evet" : "hayir") << std::endl;

    return 0;
}

Performans: Bitwise vs Normal Operatörler

Bitwise operatörler neden hızlı? Çünkü doğrudan CPU talimatlarına karşılık gelirler. Bir AND, OR veya shift işlemi genellikle tek bir clock cycle'da tamamlanır.

Çarpma/Bölme Yerine Shift

// 2'nin kuvvetleriyle çarpma/bölme:
int a = 10;

// Bu ikisi aynı sonucu verir:
int b = a * 8;      // Çarpma
int c = a << 3;     // 3 bit sola kaydır (2^3 = 8)

// Bu ikisi de aynı:
int d = a / 4;      // Bölme
int e = a >> 2;     // 2 bit sağa kaydır (2^2 = 4)

Modern derleyiciler bu tür optimizasyonları otomatik yapar — yani a * 8 yazsan bile derleyici onu a << 3'e çevirir. Bu yüzden okunabilirliği feda etmeye gerek yok. Ama embedded sistemlerde veya çok eski derleyicilerde fark yaratabileceği durumlar olabilir.

Tek/Çift Kontrolü

// Klasik: modulo
bool isEven1 = (n % 2 == 0);

// Bitwise: son bit kontrolü
bool isEven2 = (n & 1) == 0;

// Tek sayıların son biti her zaman 1'dir:
// 3 = 011, 5 = 101, 7 = 111
// Çift sayıların son biti her zaman 0'dır:
// 2 = 010, 4 = 100, 6 = 110

2'nin Kuvveti Kontrolü

// Bir sayı 2'nin kuvveti mi? (1, 2, 4, 8, 16, 32, ...)
// 2'nin kuvvetlerinin binary'de tek bir 1 biti vardır:
// 1 = 001, 2 = 010, 4 = 100, 8 = 1000

bool isPowerOfTwo(uint32_t n) {
    return n > 0 && (n & (n - 1)) == 0;
}

// Nasıl çalışır:
// n = 8:  1000 & 0111 = 0000 → true
// n = 6:  0110 & 0101 = 0100 → false
// n = 16: 10000 & 01111 = 00000 → true

Bu, çok bilinen ve zarif bir bit manipulation trick'idir.

💡 İpucu: Bit manipulation trick'leri "akıllı" görünür ama okunabilirliği düşürebilir. Eğer performans kritik değilse, açık ve anlaşılır kod yaz. Eğer gerçekten optimizasyon gerekiyorsa, yorum satırıyla ne yaptığını açıkla. isPowerOfTwo gibi fonksiyonları bir kere yazıp güzel isimlendirmen, kodun her yerinde (n & (n-1)) == 0 yazmaktan çok daha iyi.


Ek Bitwise Teknikler

Belirli Bitleri İzole Etme

uint32_t value = 0xABCD1234;

// Alt 8 bit'i al
uint8_t low_byte = value & 0xFF;           // 0x34

// 8-15 arası bitleri al
uint8_t second_byte = (value >> 8) & 0xFF; // 0x12

// 16-23 arası bitleri al
uint8_t third_byte = (value >> 16) & 0xFF; // 0xCD

// Üst 8 bit'i al
uint8_t high_byte = (value >> 24) & 0xFF;  // 0xAB

Bit Sayma (Population Count)

#include <iostream>
#include <bitset>

// Manuel bit sayma
int countBits(uint32_t n) {
    int count = 0;
    while (n) {
        count += n & 1;  // Son bit 1 mi?
        n >>= 1;         // Bir bit sağa kaydır
    }
    return count;
}

// Brian Kernighan'ın algoritması (daha hızlı)
int countBitsKernighan(uint32_t n) {
    int count = 0;
    while (n) {
        n &= (n - 1);  // En düşük 1 bitini sıfırla
        count++;
    }
    return count;
}

int main() {
    uint32_t val = 0b10110101;  // 5 tane 1 biti var
    std::cout << "Manuel:    " << countBits(val) << std::endl;        // 5
    std::cout << "Kernighan: " << countBitsKernighan(val) << std::endl; // 5

    // std::bitset ile:
    std::cout << "bitset:    " << std::bitset<8>(val).count() << std::endl; // 5

    return 0;
}

Swap Bit Grupları

// Üst 4 bit ve alt 4 bit'i yer değiştir (nibble swap)
uint8_t nibbleSwap(uint8_t val) {
    return (val << 4) | (val >> 4);
}

// Örnek: 0xAB → 0xBA
// 10101011 << 4 = 10110000
// 10101011 >> 4 = 00001010
// OR:              10111010

Compound Assignment Operatörleri

Bitwise operatörlerin hepsinin compound (birleşik) assignment versiyonları vardır:

uint8_t flags = 0;

flags |= 0x0F;   // flags = flags | 0x0F
flags &= 0xF0;   // flags = flags & 0xF0
flags ^= 0xFF;   // flags = flags ^ 0xFF
flags <<= 2;     // flags = flags << 2
flags >>= 1;     // flags = flags >> 1

Bu versiyonlar daha kısa ve yaygındır. Okunabilirlik açısından da tercih edilir — flags = flags | mask yerine flags |= mask yazmak hem daha kısa hem de niyeti daha net ifade eder.


Operatör Önceliği Uyarısı

Bitwise operatörlerin önceliği, karşılaştırma operatörlerinden düşüktür. Bu çok yaygın bir hata kaynağıdır:

// YANLIŞ — & önceliği == den düşük!
if (flags & PERM_READ == PERM_READ) { ... }
// Derleyici bunu şöyle okur: flags & (PERM_READ == PERM_READ) → flags & 1

// DOĞRU — parantez kullan
if ((flags & PERM_READ) == PERM_READ) { ... }

// Basit kontrol için zaten bu yeterli:
if (flags & PERM_READ) { ... }

Altın kural: bitwise operatörlerle karşılaştırma yaparken her zaman parantez kullan. Emin değilsen parantez ekle — fazla parantez hata yapmaz, eksik parantez yapar.


Özet

  • Bitwise operatörler (&, |, ^, ~, <<, >>) sayıların her bir biti üzerinde bağımsız çalışır. AND maskeleme, OR açma, XOR toggle, NOT tersine çevirme, shift kaydırma yapar.

  • Bit mask pattern'leri dört temel operasyondan oluşur: set (|=), clear (&= ~), toggle (^=), check (&). Bu pattern ile tek bir tam sayıda birden fazla boolean flag saklanabilir.

  • Flags pattern (izin sistemi gibi) bellek tasarrufu ve performans sağlar. Her flag bir bit pozisyonuna karşılık gelir (1, 2, 4, 8, 16...) ve | ile birleştirilebilir.

  • std::bitset, sabit boyutlu bit dizileri için güvenli ve okunaklı bir araçtır. set(), reset(), flip(), test(), count() gibi metotlarla bitleri kolayca yönetirsin.

  • Gerçek dünya uygulamaları her yerdedir: RGB renk işleme (shift + mask), IP subnet hesaplama (AND), dosya izinleri, oyun entity flag'leri ve kriptografi.

  • Signed integer'larla shift yapmaktan kaçın — undefined behavior riski taşır. Bit manipülasyonu için her zaman uint8_t, uint16_t, uint32_t, uint64_t gibi unsigned türler kullan.