تُنفَّذ العمليات البتية (أو الثنائية) على مستوى البت من البيانات وذلك باستعمال العوامل التالية:
|
- عامِل OR البِتِّي (Bitwise OR)
إليك المثال التالي:
int a = 5; // 0101b (0x05) int b = 12; // 1100b (0x0C) int c = a | b; // 1101b (0x0D) std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
يكون ناتج الشيفرة السابقة هو:
a = 5, b = 12, c = 13
وسبب ذلك أن عامِل OR
يعمل على مستوى البِتّْ (bit level)، ويستخدم جدول الصحة البوليني أو المنطقي (Boolean Truth Table) التالي:
true OR true = true true OR false = true false OR false = false
عندما يُطبق عامل OR
على القيمتين البتّيّتين a
(أي 0101
) و b
(أي 1100
) ، سنحصل على القيمة البتّيّة 1101
:
int a = 0 1 0 1 int b = 1 1 0 0 | --------- int c = 1 1 0 1
لا يغير عامِل OR
القيم الأصلية للمعامَلات إلا في حالة استخدام عامِل الإسناد البِتِّي المركَّب =|
(Bitwise Assignment Compound Operator) لإسناد تغيير تلك القيم:
int a = 5; // 0101b (0x05) a |= 12; // a = 0101b | 1101b
^
- عامل XOR البِتِّي (Bitwise XOR)
إليك المثال التالي:
int a = 5; // 0101b (0x05) int b = 9; // 1001b (0x09) int c = a ^ b; // 1100b (0x0C) std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
يكون ناتج الشيفرة السابقة:
a = 5, b = 9, c = 12
وسبب ذلك أن عامِل XOR
يعمل على مستوى البِتّْ (bit level)، ويستخدم جدول الصحة البولياني التالي:
true OR true = false true OR false = true false OR false = false
لاحظ أنه في عملية XOR، يكون لدينا true XOR true = false
، على خلاف العمليتين AND و OR، إذ تحققان: true AND/OR true = true
، وذلك هو وصف الطبيعة الحصرية لعملية XOR. عندما يُطبّق عامِل XOR
على القيمتين البتّيّتين a
(أي 0101
) و b
(أي 1001
) ، سنحصل على القيمة البتّيّة 1101
:
int a = 0 1 0 1 int b = 1 0 0 1 ^ --------- int c = 1 1 0 0
لا يغير عامِل XOR
القيم الأصلية للمعامَلات إلا في حالة استخدام عامِل الإسناد البِتِّي المركَّب =^
(Bitwise Assignment Compound Operator) لإسناد تغيير تلك القيم:
int a = 5; // 0101b (0x05) a ^= 9; // a = 0101b ^ 1001b
يمكن استخدام عامِل XOR بعدة طرق، وغالبًا ما يُستخدَم في عمليات البتات المُقنّعة (bit mask operations) لتشفير البيانات وضغطها.
لاحظ أن المثال التالي توضيحي، ولا ينبغي أن يُستخدم في الشيفرة النهائية (هناك طرق أفضل لتحقيق نفس النتيجة، مثل std::swap()
). تستطيع استخدام عملية XOR لمبادلة متغيرين:
int a = 42; int b = 64; // XOR المبادلة عبر a ^= b; b ^= a; a ^= b; std::cout << "a = " << a << ", b = " << b << "\n";
لإنجاز هذا، تحتاج إلى التحقق من إمكانية استخدامه.
void doXORSwap(int & a, int & b) { // ينبغي إضافة عبارة للتحقق من أنك لا تبادل المتغير مع نفسه // وإلا ستجعل القيمة صفرية if ( & a != & b) { // XOR مبادلة a ^= b; b ^= a; a ^= b; } }
ورغم أنها تبدو طريقة مناسبة للعزل إلا أنها ليست مفيدة في شيفرة الإنتاج، ذلك أن xor ليس عملية منطقية أساسية، بل مزيج من عمليات أخرى: a^c=~(a&c)&(a|c)
.
كذلك ، فإن المُصرفات التي صدرت بعد 2015 أتاحت إسناد قيم ثنائية إلى المتغيرات:
int cn=0b0111;
&
- عامل AND البتّيّ
إليك المثال التالي:
int a = 6; // 0110b (0x06) int b = 10; // 1010b (0x0A) int c = a & b; // 0010b (0x02) std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
يكون ناتج الشيفرة السابقة:
a = 6, b = 10, c = 2
وسبب ذلك أن عامل AND
يعمل على مستوى البت، ويستخدم جدول الصحة البوليني التالي:
TRUE AND TRUE = TRUE TRUE AND FALSE = FALSE FALSE AND FALSE = FALSE
عندما يُطبق عامِل AND
على القيمتين البتّيّتين a
(أي 0110
) و b
(أي 1010
) ، سنحصل على القيمة البتّيّة 0010
:
int a = 0 1 1 0 int b = 1 0 1 0 & --------- int c = 0 0 1 0
لا يغير عامِل AND قيمة المعامَل الأصلي إلا عند استخدام عامِل الإسناد البِتِّي المركب &=
:
int a = 5; // 0101b (0x05) a &= 10; // a = 0101b & 1010b
<<
- عملية الإزاحة اليسارية (left shift)
إليك المثال التالي:
int a = 1; // 0001b int b = a << 1; // 0010b std::cout << "a = " << a << ", b = " << b << std::endl;
يكون ناتج ذلك هو:
a = 1, b = 2
وتفسير ذلك أن عامِل الإزاحة اليساري يزيح بتَّات القيمة الموجودة على الجانب الأيسر (a
) بالعدد المحدد على اليمين (1
)، مع ملأ البتات الأقل أهمية (least significant bits) بأصفار، فمثلًا في حال إزاحة قيمة العدد 5
(تمثيله الثنائي 0000 0101
) إلى اليسار أربعة منازل (على سبيل المثال 5 <<4
)، سوف نحصل على القيمة 80 (تمثيلها الثنائي 0101 0000
). قد تلاحظ أنّ إزاحة قيمة إلى اليسار بمنزلة واحدة يكافئ ضرب تلك القيمة بالعدد 2، على سبيل المثال:
int a = 7; while (a < 200) { std::cout << "a = " << a << std::endl; a <<= 1; } a = 7; while (a < 200) { std::cout << "a = " << a << std::endl; a *= 2; }
ولكن تجدر الإشارة إلى أنّ عملية الإزاحة اليسارية سوف تزيح جميع البتات إلى اليسار، بما في ذلك بتّ الإشارة، انظر إلى المثال التالي:
int a = 2147483647; // 0111 1111 1111 1111 1111 1111 1111 1111 int b = a << 1; // 1111 1111 1111 1111 1111 1111 1111 1110 std::cout << "a = " << a << ", b = " << b << std::endl;
سيكون الناتج:
a = 2147483647, b = -2
وفي حين أنّ بعض المُصرِّفات ستعيد نتائج قد تبدو متوقعة غير أنه يجب ملاحظة أن إزاحة عددٍ ذا إشارة (signed number) بحيث تتأثر بتة الإشارة (sign bit)، فستكون النتيجة غير محددة (undefined). كذلك في حال كان عدد البتات التي ترغب في إزاحتها عددًا سالبًا، أو كان أكبر من عدد البتات التي يمكن أن يحملها النوع الموجود على اليسار، انظر:
int a = 1; int b = a << -1; // سلوك غير محدد char c = a << 20; // سلوك غير محدد
لا تغير الإزاحة البتّيّة اليسارية قيمة المعامَلات إلا في حال استخدام عامِل الإسناد البِتِّي المركب <<=
:
int a = 5; // 0101b a <<= 1; // a = a << 1;
>>
- الإزاحة اليمينية (right shift)
إليك المثال التالي:
int a = 2; // 0010b int b = a >> 1; // 0001b std::cout << "a = " << a << ", b = " << b << std::endl;
سيكون الناتج:
a = 2, b = 1
وتفسير ذلك أن عامِل الإزاحة اليميني يزيح بتِّات القيمة الموجودة على الجانب الأيسر (a
) بالعدد المحدد على اليمين (1
)؛ وتجدر الإشارة إلى أنه رغم أنّ عملية الإزاحة اليمينية قياسية، فإنّ ما يحدث لبتات الإزاحة اليمينية في الأعداد السالبة ذات الإشارة (signed negative number) يختلف بحسب الاستخدام، ومن ثم لا يمكن ضمان كونها قابلة للنقل (portable)، انظر:
int a = -2; int b = a >> 1; // على المصرّف b تعتمد قيمة
كذلك سيكون ذلك السلوك غير محدد إن كان عدد البتات التي ترغب في إزاحتها سالبًا، على سبيل المثال:
int a = 1; int b = a >> -1; // سلوك غير محدد
لا يغيّر عامل الإزاحة اليميني قيمة المعامَل الأصلي إلا في حال استخدام عامِل الإسناد البِتِّي المركب >>=
:
int a = 2; // 0010b a >>= 1; // a = a >> 1;
هذا الدرس جزء من سلسلة دروس عن C++.
ترجمة -وبتصرّف- للفصل Chapter 5: Bit Operators من كتاب C++ Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.