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 | 1AND 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 | 1OR "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 | 0XOR'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 = 10Unsigned 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 = 00101001Mantı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 = 00100000Mantı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
| Operasyon | Kod | Açı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_WRITEgibi 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
| Özellik | Manuel (uint32_t + operatörler) | std::bitset |
|---|---|---|
| Okunabilirlik | Düşük | Yüksek |
| Güvenlik | Düşük (yanlış mask riski) | Yüksek (sınır kontrolü) |
| Performans | Çok yüksek | Genellikle aynı (derleyici optimize eder) |
| Dinamik boyut | Hayır | Hayır (derleme zamanı sabit) |
| Yazdırma | Manuel format gerekli | Doğrudan cout'a basılabilir |
| Boyut esnekliği | 8, 16, 32, 64 bit | Herhangi 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 = 1102'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 → trueBu, ç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.
isPowerOfTwogibi fonksiyonları bir kere yazıp güzel isimlendirmen, kodun her yerinde(n & (n-1)) == 0yazmaktan ç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; // 0xABBit 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: 10111010Compound 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 >> 1Bu 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_tgibi unsigned türler kullan.
AI Asistan
Sorularını yanıtlamaya hazır