الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

التعامل مع البتات والبايتات والعمليات المنطقية عليها

بدأه Mr.B في 7 سبتمبر 2012 · 16 رد · 5,377 مشاهدة · في لغة C و ++C
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم ورحمة الله وبركاته

سنتحدّث في هذا الموضوع عن أهم تطبيقات العمليات على البت والبايت وفوائد الإزاحة << و >> والعمليات المنطقية مثل AND, OR, XOR, NOT وكيف تُستخدم في العالم الحقيقي. موضوع سهل إلا أنه ليس بديهي ولن تكتشفه الا بالتجربة والممارسة وتحتاجه إذا كنت تعمل في مجال البرمجة المتقدمة كبرمجة النظم والعتاد وحتى بعض مجالات البرمجة العادية.

سنتطرق أولاً لموضوعات أخرى كمقدمة لهذا الموضوع وتعتبر أساسيات فقط لإزالة بعض الأفكار الخاطئة وتسليط الضوء على بعض المفاهيم. سأسخدم الـC لإنها أقرب لغة للإنسان وللحاسوب, في الوسط, الا أنه فعياً يُمكنك التطبيق بأي لغة ولا علاقة لهذا الموضوع بالـC بل مرتبط بمجال علوم الحاسب.

سنتحدث عن :

* كيفية تقطيع البتات, كتجزئة 0x11223344 إلى أربع بايتات منفصلة 0x11, 0x22, 0x33, 0x44.

* كيفية وصل البتات, كوصل أربع بايتات منفصلة 0x11, 0x22, 0x33, 0x44 لنحصل على dword تساوي 0x11223344.

* كيفية الحذف البايتات, كحذف الجزء 0x22 من 0x11223344 ليصبح 0x11003344.

* كيفية جعل متغير unsigned char يحمل 8 معلومات مختلفة و unsigned char long يحمل 32 معلومة مختلفة.

* نظرة على حقول البتات bit fields.

* مفهوم الرايات flags وإستخداماته.

لنتحدّث أولاً عن أشهر أنظمة العد, لن أدقق فيها بل فقط لتسليط الضوء على بعض الأشياء حولها. هناك أربع أنظمة عدّ مستخدمة في الحواسيب وهي على الترتيب حسب شيوع إستخدامها :

* نظام العد العشري. وأعداده الأساسية :

0, 1, 2, 3, 4, 5, 6, 7, 8, 9

* نظام العد السداسي عشري. وأعداده الأساسية :

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f

* نظام العد الثنائي. ويُمثل بـ:

0, 1

* والنظام الأقل إستخدام النظام الثماني. وأعداده :

0, 1, 2, 3, 4, 5, 6, 7

إذا أردت التحويل بين أنظمة العد هذه يمكنك إستخدام الحاسبة في ويندوز لذلك :

post-231926-086142400 1347015382_thumb.p

نحن كبشر نعرف النظام العشري منذ طفولتنا فلاداعي للحديث عنه والنظام الثماني نادر الإستخدام ولم يسبق أن إضطررت للتعامل معه فدائماً مايوفي النظام السداسي العشري عنه, لذا لن أتحدث عنه. يهمنا كثيراً السداسي عشري والنظام الثنائي.

النظام الثنائي ليس بنظام حقيقي وغير مُستخدم عند مستوى البرامج بل يُستخدمه العتاد المادي. ما أريد قوله أنك لاتتوقع في يوم أن تكتب في برنامج:

01000001

ويطبع لك حرف الـA. ولكن يُمكن طباعته لو فتحت الحاسوب و عبثت في الذاكرة بحيث تشحن مكثف وفرغت شحنة خمس مكثفات بعده وتشحنت المكثف السابع وفرغت شحنة المكثف الثامن. ولو أردت إرسال رسالة تحمل الحرف A عبر الشبكة فسترسل نبضة كهربائية وبعد خمس ثوانٍ سترسل نبظة أخرى وتتأخر ثانية واحدة. ربما سيظهر لصديقك حينها حرف الـA.

كما رأيت أنه في الحقيقة لايوجد 0 ولايوجد 1 . فالصفر إمّا "يُعبر" عن عدم وجود الشحنة الكهربائية أو حالة عدم مرور تيار كهربائي خلال مدة زمنية محددة.والواحد "يُعبر" عن وجود الشحنة الكهربائية أو حالة مُرور تيار كهربائي خلال مُدة زمنية محددة.

النظام السداسي عشري أو الـhex مهم. فهو صورة أبسط وأقرب للنظام الثنائي. في النظام السداسي عشري, كل خانة تقابل 4 بتات bits من النظام الثنائي. مثلاً لو كتبت (سأستخدم 0x من الآن وصاعداً لتمثيل الأعداد السداي عشرية) :

0xf1

فبدون أن أحسب وأعمل شيء أعرف أنه يقابل 8 بتات "على الأكثر" في النظام الثنائي والنتيجة تؤكد ذلك بعد الحساب:

0xf1 = 11110001

حيث أن كل خانة تقابل 4 بتات ولدينا خانتين أي مجموعها 8 بتات :

0xf  = 1111
0x1  = 0001

هناك أرقام مُميزة رجاء تذكرهما, وهما 0 و الـ1. فالصفر نفسه صفر في جميع أنظمة العد والواحد كذلك. أيضاً تذكر هذه المُتستلة (أسميها متسلسة أحجام الذاكرة) :

[math]2^n[/math]

لو أخذنا أول ثمان أرقام مثلا من هذه المُتسلسلة ونظرنا لها بعدّة أنظمة :

+---------+-----+-----+-----+-----+------+-------+--------+---------+
| Base 10 | 0   | 1   | 2   | 4   | 8    | 6     | 32     | 64      |
+---------+-----+-----+-----+-----+------+-------+--------+---------+
| Base 16 | 0x0 | 0x1 | 0x2 | 0x4 | 0x8  | 0x10  | 0x20   | 0x40    |
+---------+-----+-----+-----+-----+------+-------+--------+---------+
| Base 2  | 0   | 1   | 10  | 100 | 1000 | 10000 | 100000 | 1000000 |
+---------+-----+-----+-----+-----+------+-------+--------+---------+

لابد أنك لاحظت النمط خصوصاً في النظام الثنائي, فهناك واحد وفي كل مرة يزيد صفر على اليمين. تذكر هذه المتسلسلة جيداً فسنستخدمها لاحقا.

لنتحدث الآن عن أشهر وحدات القياس في معالجات أنتل الـ32 بت والمساماة 86x, جميع الأحجام التي سأذكرها تقابل الضعف في حواسيب الـ64 بت. هناك أربع وحدات شهيرة وهي :

* البت bit وتقابل خانة واحدة في الثنائي:

1

* الحرف أوالبايت byte وتقابل 8 بتات في الثنائي :

11111111

* الكلمة word وتقابل 16 بايت في الثنائي وتقابل أيضا حرفين\بايتين :

11111111 11111111

* الكلمة المضاعفة double word أو فقط dword وتقابل 32 بايت في الثنائي وأيضاً 4 حروف\بايتات :

11111111 11111111 11111111 11111111

يُمكن تمثيل تلك الأحجام في C , بإسثناء البت , بإستخدام أنواعها الأساسية, char, short, int, long. فلكل نوع من تلك الأنواع حجم محدد يقابل تلك الأحجام :

#include <stdio.h>

int main(int argc, char **argv)
{

    printf("char  = %d byte\n",  sizeof(char));
    printf("short = %d bytes\n", sizeof(short));
    printf("int   = %d bytes\n", sizeof(int));
    printf("long  = %d bytes\n", sizeof(long));

    return 0;
}

* حجم char يساوي بايت byte واحد.

* حجم short يساوي بايتين أي أنه يساوي word.

* حجم int "قد يساوي" في بعض المُصرفات بايتين word وقد يساوي أيضاً أربع بايتات dword (لذا سنتجنبها).

* حجم long يساوي أربع بايتات أي dword, وهو المناسب لتمثيل عنوانين الذاكرة والمسجلات في حواسيب 32بت.

إذا أردت تمثيل البايت فإستخدم unsigned char ولمتثيل الكلمة إستخدم unsigned short ولتمثيل الكلمة المضاعفة إستخدم unsigned long.

سبب إستخدمنا لـunsigned أنها تُمكننا من إستخدام كامل البتات لهذا المتغير. مثلاً unsigned char. يمكون مجالة من صفر 0 :

00000000

إلى 255 0xff :

11111111

في حال إستخدمت char فقط, والتي فعلياً تكافئ signed char, فإن البت الأخير من اليسار فسيستخدم للتحديد الإشارة ,يُسمى هذا البت بـmost significant bit أو بت تحديد الإشارة كما أفضل تسميته, فإذا كان 1 فيعني أن العدد سالب وإذا كان 0 فسيعني أن العدد موجب. هذا يعني أيضاً أنك ستحصل فقط على 7 بتتات ليصبح المدى من -127 :

1 | 1111111

إلى 127 :

0 | 1111111

ونفس الشيء ينطبق على الأنواع الأخرى. إنظر لهذا البرنامج :

#include <stdio.h>

int main(int argc, char **argv)
{

    unsigned long i  =  1;
    signed long j    = -1;

    if( j > i )
        printf("-1 is greater than 1\n");
    else
        printf("1 is greater than -1\n");

    return 0;
}

من البديهي أنّ 1 أكبر من -1, إلا أن البرنامج يخطئ وسيقول أنّ -1 أكبر من 1. حولها للنظام الثنائي وستفهم السبب :

i  = 1 = 0000 0000 0000 0000 0000 0000 0000 0001
j = -1 = 1000 0000 0000 0000 0000 0000 0000 0001

طبعاً هذا البرنامج خاطئ ولا تحاول أن تقارن بين نوعين أحدهما unsigned والآخر signed لأنهما فعلياً نوعين مختلفين ولو تشابها بكونهما long.

قد تجد في بعض البرامج شيء مثل :

#include <stdio.h>

int main(int argc, char **argv)
{
    unsigned char number = (unsigned char) -1;

    printf("0x%.2x\n", number);

    return 0;
}

هذه طريقة لملئ جميع البتات والحصول على القيمة القصوى للمتغير, سيكون 0xff أو 255 في حالتنا. لكن لاتنسى أن تعمل casting للمتغير, لهذا وضعنا (unsigned char).

الآن بعد هذا الحديث أعتقد أنك أصبحت جاهز لعمليات تقطيع ووصل وحذف البتتات والبايتات وبعضاً من تطبيقاته.

تقطيع ووصل وحذف البتتات والبايتات تطبيق للإزاحة لليمين >> والإزاحة لليسار << و العمليات المنطقية :

* العملية AND وجدولها:

+---+---+---------+
| x | y | x AND y |
+---+---+---------+
| 0 | 0 |       0 |
| 0 | 1 |       0 |
| 1 | 0 |       0 |
| 1 | 1 |       1 |
+---+---+---------+

تعطي العملية AND الرقم 1 إذا كان المتغيير الأول يساوي 1 "و" الثاني يساوي واحد.

رمزها & في C :

unsigned long = 1 & 0;

* العملية OR وجدولها :

+---+---+---------+
| x | y | x OR  y |
+---+---+---------+
| 0 | 0 |       0 |
| 0 | 1 |       1 |
| 1 | 0 |       1 |
| 1 | 1 |       1 |
+---+---+---------+

تعطي العملية OR إذا كان المتغيير الأول يساوي 1 "أو" الثاني يساوي واحد أو كان كلاهما يساوي 1.

رمزها | في C :

unsigned long = 1 | 0;

* العملية XOR وجدولها :

+---+---+---------+
| x | y | x XOR y |
+---+---+---------+
| 0 | 0 |       0 |
| 0 | 1 |       1 |
| 1 | 0 |       1 |
| 1 | 1 |       0 |
+---+---+---------+

العملية XOR تعطي 1 إذا إختلف أحد المتغييرين عن الأخر. كأن يكون الأول 1 والثاني 0 أو العكس. إذا كانا متشابهيين فستعطي 0.

رمزها ^ في C :

unsigned long = 1 ^ 0;

* العملية NOT وجدولها :

+---+-------------+
| x | NOT x       |
+---+-------------+
| 0 |     1       |
| 1 |     0       |
+---+-------------+

العملية NOT تعكس أي رقم يأتيها.

رمزها ~ في C :

unsigned long = ~0;

لنبدأ مع إزاحة البتات. هناك نوعين من الإزاحة, الإزاحة لليمين << وللإزاحة لليسار >>. عندما نكتب :

#include <stdio.h>

int main(int argc, char **argv)
{
    unsigned char number = 0xff;

    number >>= 1; /* OR number = number >> 1 */

    printf("0x%.2x\n", number);

    return 0;
}

فسيطبع البرنامج 0x7f. تقابل 0xff :

11111111

عندما أزحنا الرقم بت واحد لليمين أصبح يساوي 0x7f وبالثنائي :

01111111

وكأن المعالج قام بحذف بت من اليمين وعوّض مكانه في اليسار بصغر. كذلك بالنسبة للإزاحه لليسار, إلا أن المعالج يقوم بالعكس. مثلاً هذا البرنامج :

#include <stdio.h>

int main(int argc, char **argv)
{
    unsigned char number = 0xff;

    number <<= 1; /* OR number = number << 1 */

    printf("0x%.2x\n", number);

    return 0;
}

سيطبع 0xfe. حيث أزاح الرقم 0xff لليسار ليصبح 0xfe :

11111110

لنأخذ أول تطبيق عملي لتقطيع البتات. لو كان لدينا هذا الرقم 0x1122 ونريد أن نقطعه ليصبح 0x11 فقط. يُمكننا إزاحته بايت واحد لليسار , أي 8 بتات :

#include <stdio.h>

int main(int argc, char **argv)
{
    unsigned short number = 0x1122;

    number >>= 8; /* OR number = number >> 8 */

    printf("0x%.2x\n", number);

    return 0;
}

لو شغلناه فسيطبع 0x11. لو أردنا أن نقطع 0x22 فقط, هنا يأتي دور العملية AND.

تقوم العملية AND بتطبيق الجدول :

+---+---+---------+
| x | y | x AND y |
+---+---+---------+
| 0 | 0 |       0 |
| 0 | 1 |       0 |
| 1 | 0 |       0 |
| 1 | 1 |       1 |
+---+---+---------+

على كامل المتغير. مثلاً لو كان لدينا متغيرين unsigned short أحدهما يحتوي 0x1100 وقمنا بتطبيق AND مع آخر يحتوي 0x11 فستعطي 0x00. تقابل 0x1100 في الثنائي (تذكر أننا نتعمل مع متغير unsigned short حجمه 16بت وأن كل خانتين من النظام السداسي عشري يقابل بايت أو 8 بتات) :

00010001 00000000

وتقابل 0x11 :

00000000 00010001

سيتم تطبيق العلاقة AND على كامل الحيز في الذاكرة :

 00010001 00000000
 00000000 00010001 AND
 -----------------
 00000000 00000000

يُمكننا الإستفادة من AND لقطع 0x22 من 0x1122 بالطريقة التالية 0x1122 & 0xff :

#include <stdio.h>

int main(int argc, char **argv)
{
    unsigned short number = 0x1122;

    number &= 0xff; /* OR number = number & 0xff */

    printf("0x%.2x\n", number);

    return 0;
}

وستعطي 0x22. كيف حدث هذا؟ تقابل 0x1122 في الثنائي :

00010001 00100010

وتقابل 0xff :

00000000 11111111

عندما قمنا بتطبيق العلاقة AND بينهما :

 00010001 00100010
 00000000 11111111 AND
 -----------------
 00000000 00100010

لم يتأثر الشق الأول بينما أصبح الشق الأيسر أصفار والصفر على اليسار لامعنى له.

إنظر لهذا البرنامج والذي يقوم بتقطيع كلمة مضاعفة 0x11223344 إلى أربع بايتات 0x11 و0x22 و0x33 و0x44 وذلك بالإستفادة من الإزاحة << وعملية AND :

#include <stdio.h>

int main(int argc, char **argv)
{
    const unsigned long number = 0x11223344;

    unsigned char part_one;
    unsigned char part_two;
    unsigned char part_three;
    unsigned char part_four;

    // 0x11223344 >> 24  = 0x00000011
    part_one    =  number >> 24;

    // 0x11223344 >> 16  = 0x00001122
    // 0x00001122 & 0xff = 0x00000022
    part_two    = (number >> 16) & 0xff; 

    // 0x11223344 >> 12  = 0x00112233
    // 0x00112233 & 0xff = 0x00000033
    part_three  = (number >> 12) & 0xff;

    // 0x11223344 & 0xff  = 0x00000044
    part_four   =  number & 0xff;

    printf("Part 1 = 0x%.2x\n", part_one);
    printf("Part 2 = 0x%.2x\n", part_two);
    printf("Part 3 = 0x%.2x\n", part_three);
    printf("Part 4 = 0x%.2x\n", part_four);

    return 0;
}

لو قمنا بتشغيله :

> a.exe
Part 1 = 0x11
Part 2 = 0x22
Part 3 = 0x33
Part 4 = 0x44
>

لو أردت تقطيع كلمة تحتوي 0x1234 إلى بايتات كل منها 0x1 و0x2 و0x3 و0x4 فيُمكنك التعديل عليه قليلاً ليصبح :

#include <stdio.h>

int main(int argc, char **argv)
{
    const unsigned short number = 0x1234;

    unsigned char part_one;
    unsigned char part_two;
    unsigned char part_three;
    unsigned char part_four;

    // 0x1234 >> 12   = 0x00001
    part_one    =  number >> 12;

    // 0x1234 >> 8    = 0x00012
    // 0x00012 & 0xf  = 0x00002
    part_two    = (number >> 8) & 0xf; 

    // 0x1234 >> 4    = 0x0123
    // 0x0123 & 0xf   = 0x0003
    part_three  = (number >> 4) & 0xf;

    // 0x1234 & 0xf   = 0x0004
    part_four   =  number & 0xf;

    printf("Part 1 = 0x%.1x\n", part_one);
    printf("Part 2 = 0x%.1x\n", part_two);
    printf("Part 3 = 0x%.1x\n", part_three);
    printf("Part 4 = 0x%.1x\n", part_four);

    return 0;
}

لو قمنا بتشغيله :

> a.exe
Part 1 = 0x1
Part 2 = 0x2
Part 3 = 0x3
Part 4 = 0x4
>

ماذا لو أردنا مثلاً وصل رقمين مثل 0x11 و0x22 ليصبحا 0x1122 ؟ هنا يأتي دور العملية OR. ويُمكننا عمل ذلك بالطريقة التالية :

#include <stdio.h>

int main(int argc, char **argv)
{

    unsigned short number = 0x0000;

    number =  number | 0x11;        /* OR number |= 0x11 */
    number = (number << 8) | 0x22;

    printf("0x%.4x\n", number);

    return 0;
}

لو قمت بتشغيله فسيطبع 0x1122. في البداية number يساوي صفر. عندما قُمنا بعمل AND بينه وبين 0x11 :

 00000000 00000000      ; 0x0000
 00000000 00100100 OR   ; 0x0011
 --------------------
 00000000 00010001      ; 0x0011

لم نقم بعمل شيء فعلياً, فـAND س مع صفر ستعطي س. ولكن الجزء الحقيقي يبدأ هنا :

number = (number << 8) | 0x22;

قُمنا بإزاحة number والتي تحمل الآن القيمة 0x11 بايت واحد لليمين , أو 8 بتات , لتصبح 0x1100 :

00000000 00010001 << 8 = 00010001 00000000 ; 0x1100

وذلك لعمل مساحة لـ0x22. الآن قُمنا بوصل 0x1100 مع 0x22 بإستخدام العلاقة OR :

 00010001 00000000      ; 0x1100
 00000000 00100010 OR   ; 0x0022
 --------------------
 00010001 00100010      ; 0x1122

ليصبح لدينا 0x1122.

إنظر كيف نقُوم بوصل 0x11 و0x22 و0x33 و0x44 لتصبح 0x11223344.

#include <stdio.h>

int main(int argc, char **argv)
{

    unsigned long number = 0x00000000;

    // 0x00000000 | 0x00000011 = 0x00000011
    number =  number | 0x11;

    // 0x00000011 << 8   = 0x00001100
    // 0x00001100 | 0x22 = 0x00001122
    number = (number << 8) | 0x22;

    // 0x00001122 << 8   = 0x00112200
    // 0x00112200 | 0x33 = 0x00112233
    number = (number << 8) | 0x33;

    // 0x00112233 << 8   = 0x11223300
    // 0x11223300 | 0x44 = 0x11223344
    number = (number << 8) | 0x44;

    printf("0x%.8lx\n", number);

    return 0;
}

لنرى تطبيقات هذه العمليات في العالم الحقيقي.

لو كنت تستخدم برامج تحرير الصور والرسم, مثل برنامج الرسام أو فوتوشوب, ستلاحظ أنه يُمكنك إنشاء العديد من الألوان من ثلاث ألوان : الأحمر والأخضر والأزرق :

post-231926-093650800 1347015451_thumb.p

هذا النظام يُسمىّ نظام الـRGB إختصاراً لـ Red Green Blue. حيث يكون فيه اللون الأسود (أقل قيمة للون 0) :

Red = 0, Green = 0, Blue = 0

اللون الأبيض (أقصى قيمة للون 255):

Red = 255, Green = 255, Blue = 255

عينة للألوان الأساسية التي يُمكن تشكيلها من هذا النظام :

post-231926-013819100 1347015512_thumb.p

ويمكن عمل ألوان كثيرة أخرى بتعديل كثافة كُل لون , بشرط أن يكون الرقم بين 0-255 ,أو 0xff-0x00. يُخزّن الحاسوب هذه الألوان في 3 بايتات, 24 بت. حيث يكون البايت الأول من اليمين للون الأزرق ويليه اللون الأخضر ويليه الأحمر. مثلاً سبدو الأزرق , Red = 0, Green = 0, Blue = 255 , في الذاكرة هكذا 0x0000ff.

يظهر هذين الشكلين كثيراً لمن يتعاملون مع تطبيقات الوب. فإذا أراد مبرمج ويب أن يجعل لون خلفية الصفحة زرقاء فيُمكنه إستخدام إحدى التنسيقين : إما تنسيق الـhex أو السداسي عشري (يمكنك إنشاء ملف إسمه مثلاً foo.html ووضع هذا الكود فيه وفتحه بالمتصفح) :

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>Colors</title>
  <style>
    body {
        background-color: #0000ff;
    }  
  </style>
</head>
<body>
</body>
<html>

أو تنسيق الـrgb :

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>Colors</title>
  <style>
    body {
        background-color: rgb(0, 0, 255);
    }  
  </style>
</head>
<body>
</body>
<html>

post-231926-073371000 1347015558_thumb.p

الشكل الأول #0000ff أقرب للحاسوب والشكل الآخر أقرب للإنسان. إذا أردنا التحويل من الصيغة الأولى للثانية, فُنستخدم طريقة تقطيع البتات :

color = 0x0000ff

red   = color >> 8
green = (color >> 8) & 0xff
blue  = color & 0xff

وإذا أردنا التحويل من الـrgb للـhex :

red   = 0
green = 0
blue  = 255

color = (red << 16) | (green << 8) | blue

إنظر لهذا البرنامج بـC , فهناك الدالة color_rgb_to_hex للتحويل للـhex والدالة color_rgb_to_hex للتحويل للـrgb :

#include <stdio.h>

typedef struct _RgbObject {
    unsigned char red;
    unsigned char green;
    unsigned char blue;
} RgbObject;

static unsigned long color_rgb_to_hex(unsigned long red,
                                      unsigned long green,
                                      unsigned long blue);

static void color_hex_to_rgb(RgbObject *rgb,
                             unsigned long hex);

int main(int argc, char **argv)
{

    unsigned long hex;
    RgbObject rgb;

    // rgb(0, 0, 255) = #0000ff
    hex = color_rgb_to_hex(0, 0, 255);
    printf("rgb(0, 0, 255) = #%.6lx\n", hex);

    // #0000ff        = rgb(0, 0, 255)
    color_hex_to_rgb(&rgb, 0x0000ff);
    printf("#0000ff        = rgb(%u, %u, %u)\n", rgb.red,
                                                 rgb.green,
                                                 rgb.blue);

    return 0;
}

static unsigned long color_rgb_to_hex(unsigned long red,
                                      unsigned long green,
                                      unsigned long blue)
{
    return (red << 16) | (green << 8) | blue;
}

static void color_hex_to_rgb(RgbObject *rgb,
                             unsigned long hex)
{
    rgb->red    =  hex >> 8;
    rgb->green  = (hex >> 8) & 0xff;
    rgb->blue   =  hex & 0xff;
}

هناك أيضاً مجال آخر يستخدم فكرة تقطيع ووصل البتات وهو العناوين كعناوين الـIP والـMAC address. لنأخذ الـIP كمثال.

لو كان لدينا عنوان الـIP هذا 192.0.10.0. هذه الصيغة هي الصيغة المقروءة والأكثر فهم للإنسان والا فالحاسوب يخزنها في متغير حجمة 4 بايت , 32بت, هكذا 0xc0000a00. فإذا أردنا التحويل من الصيغة الأولى للصيغة الثانية فيمكننا عمل ذلك هكذا :

part_one   = 192
part_two   = 0
part_three = 10
part_four  = 0

ip = (part_one << 24) | (part_two << 16) | (part_three << 8) | part_four

وللتحويل من الصيغة 0xc0000a00 للصيغة المقروءة :

ip = 0xc0000a00

part_one   =  ip >> 24
part_two   = (ip >> 16) & 0xff
part_three = (ip >> 8 ) & 0xff,
part_four  =  ip & 0xff;

إنظر لهذا البرنامج وفيه الدالة convert_string_to_ip تقوم بتحويل الـIP من نص مثل "192.0.10.0" إلى الصيغة 32بت 0xc0000a00 والدالة convert_ip_to_string التي تقوم بالعكس:

#include <stdio.h>

static unsigned long convert_string_to_ip(char *buffer);
static void convert_ip_to_string(char *buffer, unsigned long ip);

int main(int argc, char **argv)
{
    unsigned long ip_hex;
    char ip_string[16];

    ip_hex = convert_string_to_ip("192.0.10.0");
    printf("192.0.10.0  = 0x%.8lx\n", ip_hex);

    convert_ip_to_string(&ip_string[0], 0xc0000a00);
    printf("0xc0000a00  = %s\n", ip_string);

    return 0;
}

static unsigned long convert_string_to_ip(char *buffer)
{
    unsigned long part_one    = 0;
    unsigned long part_two    = 0;
    unsigned long part_three  = 0;
    unsigned long part_four   = 0;

    sscanf(buffer, "%03lu.%03lu.%03lu.%03lu", &part_one, 
                                              &part_two,
                                              &part_three,
                                              &part_four);

    return (part_one << 24) | (part_two << 16) | (part_three << 8) | part_four;
}

static void convert_ip_to_string(char *buffer, unsigned long ip)
{
    sprintf(buffer, "%lu.%lu.%lu.%lu", ip >> 24,
                                      (ip >> 16) & 0xff,
                                      (ip >> 8 ) & 0xff,
                                       ip & 0xff);
}

لنفترض أن لدينا هذا البرنامج :

#include <stdio.h>

int main(int argc, char **argv)
{
    unsigned short number = 0x1122;

    printf("0x%.4x\n", number);

    return 0;
}

سيطبع البرنامج 0x1122. السؤال هنا ماذا لو أردنا أن "نحذف" 0x11 فقط لتصبح 0x0022 ؟ هُنا يأتي دور العملية NOT.

لو كان لدينا متغير حجمة بايتين, 16 بت, ويحتوي 0x00ff وقُمنا بتطبيق العملية NOT عليه فسيصبح 0xff00 :

#include <stdio.h>

int main(int argc, char **argv)
{
    unsigned short number = 0x00ff;

    number = ~number;

    printf("0x%.4x\n", number); /* 0xff00 */

    return 0;
}

ماحدث ببساطة أن كل 1 قلبت إلى 0 وكل 0 قلبت إلى 1 :

 00000000 11111111  NOT  ; 0x00ff
 -----------------------
 11111111 00000000       ; 0xff00

لو أردنا حذف 0x11 من 0x1122 فسنقوم بإزاحة 0x1122 إلى اليمين 8 بتات, بايت :

 00010001 00100010 >> 8 ; 0x1122 >> 8
 ----------------------
 00000000 00010001      ; 0x0011

لإقتطاع 0x11 ومن ثُم نُزيحها لليسار 8 بتات :

 00000000 00010001 << 8  ; 0x0011 << 8
 -----------------------
 00010001 00000000       ; 0x1100

تلك الخطوتين نعرفها وليست ضرورية في مثالنا هذا لأننا نعرف أن number يحمل 0x1122. الا أنه في العالم الحقيقي لاتعرف ماقيمة ألمتغير number فقد تكون أي رقم, لذا قد تحتاج تلك الخطوتين.

الآن يأتي دور NOT وستنتج لنا الرقم المضاد من 0x1100 والذي إذا طبقنا 0x1122 معه ستُحذف 0x11 :

 00010001 00000000 NOT   ; ~0x1100
 -----------------------
 11101110 11111111       ; 0xeeff

الرقم المُضاد هو 0xeeff. الآن لنُطبقه مع 0x1122 :

 00010001 00100010       ; 0x1122
 11101110 11111111 AND   ; 0xeeff
 -----------------------
 00000000 00100010       ; 0x0022

كما ترى, حُذفت 0x11 وبقي 0x0022. لنرى البرنامج :

#include <stdio.h>

int main(int argc, char **argv)
{
    unsigned short number = 0x1122;

    number &= ~((number >> 8) << 8);

    printf("0x%.4x\n", number); /* 0x0022 */

    return 0;
}

سيطبع الآن 0x0022. فعلياً يُمكننا إختصار آخر خطوتين, NOT و AND , بالعميلة XOR, الآن أتى دورها. فهي تُكافئ NOT وAND معاً حيث أنّ:

x XOR y == x AND (NOT y)

لنرى البرنامج :

#include <stdio.h>

int main(int argc, char **argv)
{
    unsigned short number = 0x1122;

    number ^= (number >> 8) << 8;

    printf("0x%.4x\n", number); /* 0x0022 */

    return 0;
}

هنُاك إستخدامات أخرى جميلة لـXOR, فهي تُستخدم لعمل تشفير خفيف (لاتستخدمه لإخفاء بيانات حساسة فهو تشفير ضعيف ويُمكن كسره بسهولة نسبية). لتشفير مثلاً 0x1234 بالمفتاح السري 0x3344 يُمكننا عمل :

message = 0x1234
key     = 0x3344
cipher  = message ^ key ; 0x2170

وستعطينا البيانات المشفرة 0x2170. ولفك التشفير, يُمكننا عكس العملية بإستخدام البيانات المشفرة والمفتاح السري :

cipher  = 0x2170
key     = 0x3344
message = cipher ^ key  ; 0x1234

لتحصل على الرسالة الأصلية 0x1234. إنظر لهذا البرنامج الذي يُشفر رسالة ويفك تشفيرها :

#include <stdio.h>
#include <string.h>

static void message_encrypt(char *message, unsigned long len, char key);
static void message_decrypt(char *cipher, unsigned long len, char key);

int main(int argc, char **argv)
{
    char buffer[]   = "This is a secret message!";
    char key = 0x12;

    message_encrypt(&buffer[0], strlen(&buffer[0]), key);
    printf("Encrypted message = %s\n", buffer);

    message_decrypt(&buffer[0], strlen(&buffer[0]), key);
    printf("Decrypted message = %s\n", buffer);

    return 0;
}

static void message_encrypt(char *message, unsigned long len, char key)
{
    unsigned long i;
    for( i = 0 ; i < len ; i++ )
        message = message ^ key;
}

static void message_decrypt(char *cipher, unsigned long len, char key)
{
    unsigned long i;
    for( i = 0 ; i < len ; i++ )
        cipher = cipher ^ key;
}

عند تشغيله :

> a.exe
Encrypted message = Fz{a2{a2s2awq`wf2⌂waasuw3
Decrypted message = This is a secret message!
>

لنعود لموضوعنا.

يُمكنك أيضاً بإستخدام العمليات على البتات أن تجعل متغير واحد مثل unsigned char يحمل 8 معلومات مختلفة. مثلاً لنفترض أنك مبرمج عتاد ولديك 8 مصابيح LED مختلفة وتريد أن تسجل حالة كُل مصباح. يُمكنك أن تستخدم بتات متغير من نوع unsigned char بحيث يُرمز كل بت من هذا المتغير إلى حالة المُصباح , 1 إذا كان مُشغل و 0 إذا كان مغلق :

#include <stdio.h>

#define LED_GET(led, order) (led >> (order - 1))
#define LED_ON(led, order)  (led |= ( 0x1 << (order - 1) ) )
#define LED_OFF(led, order) (led ^= ( 0x1 << (order - 1) ) )

int main(int argc, char **argv)
{
    unsigned char leds = 0;

    LED_ON(leds, 2);
    printf("%d\n", LED_GET(leds, 2));

    LED_OFF(leds, 2);
    printf("%d\n", LED_GET(leds, 2));

    return 0;
}

الا أن هناك طريقة أخرى شائعة الإستخدام أيضاً وهي بإستخدام "خانات البتات" bit fields. نفس البرنامج السابق يُمكن كتابته هكذا :

#include <stdio.h>
#include <string.h>

#define LED_ON(led) (led = 0);
#define LED_OFF(led) (led = 1);

typedef struct _LedGroup {
    unsigned char led1: 1,
                  led2: 1,
                  led3: 1,
                  led4: 1,
                  led5: 1,
                  led6: 1,
                  led7: 1,
                  led8: 1;
} LedGroup;

int main(int argc, char **argv)
{

    LedGroup ledgroup;

    LED_ON(ledgroup.led2);
    printf("%d\n", ledgroup.led2);

    LED_OFF(ledgroup.led2);
    printf("%d\n", ledgroup.led2);

    return 0;
}

قُمنا بإنشاء بُنية تحتوي على عضو unsigned char حجمه طبعاً 8بتات. وُقُمنا بتوزيع تلك البتات على ثمانية مُتغيرات ledX . فالرمز ":" يُحدد كم بت ستحجز من unsigned char لهذا المُتغير. الآن يُمكن وضعه صفر بأي من هذه المُتغيرات هكذا ledgroup.led2 = 0 أو 1 هكذا ledgroup.led2 = 1. طبعاً خجم أيّ من ledX يساوي 1 بت تماماً وهذا يعني أنك إما أن تضع 0 أو 1 فقط.

هُناك إستخدام آخر شائع للعمليات على البتات ومهم جداً خصوصاً في مجال برمجة النظم وهي "الرايات" flags. فرضاً لو كان لديك نظام تصاريح يُوزع التصاريح على كائنات , مثلاً ملفات أو عمليات أو مجموعات مستخدمين وغيرها, وأردت أن تعطي هذا الكائن أي من هذه التصاريح :

* تصريح التنفيذ للسماح له بتشغيل برنامج مثلاً.

* تصريح قراءة للسماح له بقراءة ملف مثلاً.

* تصريح للكتابة قراءة للسماح له بالكتابة في ملف مثلاً.

بدل إنشاء 3 متيغيرات مستقله وكتابة أكواد طويلة لتتبع كل تصريح, فُيمكنك إستغلال العمليات على البتات وتحديداً AND و OR لعمل هذا الشيء بسهولة :

#include <stdio.h>

typedef enum _PermissionFlags {
    PERMISSION_EXECUTE  = 1 << 0,    // 1 = 001
    PERMISSION_READ     = 1 << 1,    // 2 = 010
    PERMISSION_WRITE    = 1 << 2,    // 4 = 100
} PermissionFlags;

static void check_permitions(PermissionFlags flag);

int main(int argc, char **argv)
{
    check_permitions(PERMISSION_READ);

    return 0;
}

static void check_permitions(PermissionFlags flag)
{
    if( (flag & PERMISSION_EXECUTE) != 0)
        printf("PERMISSION_EXECUTE is passed\n");

    if( (flag & PERMISSION_READ)    != 0 )
        printf("PERMISSION_READ is passed\n");

    if( (flag & PERMISSION_WRITE)   != 0 )
        printf("PERMISSION_WRITE is passed\n");
}

أويمكن كتابة البرنامج هكذا, بدل إستخدام مجموعة الثوابت enum نعرف الثوابت بإستخدام define :

#include <stdio.h>

#define PERMISSION_EXECUTE  (1 << 0)    // 1 = 001
#define PERMISSION_READ     (1 << 1)    // 2 = 010
#define PERMISSION_WRITE    (1 << 2)    // 4 = 100

static void check_permitions(unsigned int flag);

int main(int argc, char **argv)
{
    check_permitions(PERMISSION_EXECUTE | PERMISSION_READ);

    return 0;
}

static void check_permitions(unsigned int flag)
{
    if( (flag & PERMISSION_EXECUTE) != 0)
        printf("PERMISSION_EXECUTE is passed\n");

    if( (flag & PERMISSION_READ)    != 0 )
        printf("PERMISSION_READ is passed\n");

    if( (flag & PERMISSION_WRITE)   != 0 )
        printf("PERMISSION_WRITE is passed\n");
}

الا أنني أفضل كثيراً إستخدام الطريقة الأولى وحصر الثوابت في مجموعة لتنسيق البرنامج وليظهر بشكل أفضل.

ماقمنا به أننا أنشأنا مجموعة ثوابت إسمها PermissionFlags وتحتوي على أسماء نوع كل تصريح وقيمته :

* تصريح التنفيذ PERMISSION_EXECUTE وقيمته :

1 << 0

والتي تساوي أيضاً 1 أو بالثنائي 1.

* تصريح القراءة PERMISSION_READ وقيمته :

1 << 1

والتي تساوي أيضاً 2 أو بالثنائي 10.

* تصريح الكتابة PERMISSION_WRITE وقيمته :

1 << 2

والتي تساوي أيضاً 4 أو بالثنائي 100.

طبعاً لم نختر تلك القيم عشوائياً, لاحظ أن الرقم الثنائي في كُل مرة يزيد صفر على اليمين. لو شغلت البرنامج فسيطبع :

> a.exe
PERMISSION_READ is passed
>

لو أردنا إعطاء صلاحية القراءة في برنامجنا مثلاً, فسنستخدم طريقة مشابهة لطريقة الدالة check_permitions ونمرر لها راية flag , في حالتنا مررنا PERMISSION_READ. حيث ستطبق الدالة علاقة AND بين الرقم المُمر وبين كل الرايات الذي يُمكن تمريرها. إذا لم يُعطي ناتج العلاقة بين الراية وهذا الرقم الممر صفر, فهذا يعني أن الراية مُررت.

نحن مررنا PERMISSION_READ والتي تُساوي في الثنائي 010, عندما طبقت الدالة العلاقة بين 010 وقيمة PERMISSION_EXECUTE والتي تساوي 001 أعطت 000 :

 010     ; PERMISSION_READ
 001 AND ; PERMISSION_EXECUTE
 -------
 000     ; == 0

طالما أعطت صفر, فهذا يعني أن الراية المُمررة ليست بـPERMISSION_EXECUTE. ولكن عندما قامت الدالة بتطبيق علاقة الـAND مع الراية التي مررناها و PERMISSION_READ, فلم تُعطي الصفر :

 010     ; PERMISSION_READ
 010 AND ; PERMISSION_READ
 -------
 010     ; != 0

وهذا يعني أنّ الراية PERMISSION_READ مُررت. عندما طبقت العلاقة مع PERMISSION_WRITE لم تعطي صفر أي أنها لم تُمرر.

هذه الطريقة تُمكننا من تمرير أكثر من راية وليس راية واحدة. إنظر هنا, لُنمرر الرايتين PERMISSION_EXECUTE وPERMISSION_READ للدالة هكذا :

#include <stdio.h>

typedef enum _PermissionFlags {
    PERMISSION_EXECUTE  = 1 << 0,    // 1 = 001
    PERMISSION_READ     = 1 << 1,    // 2 = 010
    PERMISSION_WRITE    = 1 << 2,    // 4 = 100
} PermissionFlags;

static void check_permitions(PermissionFlags flag);

int main(int argc, char **argv)
{
    check_permitions(PERMISSION_EXECUTE | PERMISSION_READ);

    return 0;
}

static void check_permitions(PermissionFlags flag)
{
    if( (flag & PERMISSION_EXECUTE) != 0)
        printf("PERMISSION_EXECUTE is passed\n");

    if( (flag & PERMISSION_READ)    != 0 )
        printf("PERMISSION_READ is passed\n");

    if( (flag & PERMISSION_WRITE)   != 0 )
        printf("PERMISSION_WRITE is passed\n");
}

لاحظ أننا قُمنا بعمل علاقة AND بين الرايتين :

check_permitions(PERMISSION_EXECUTE | PERMISSION_READ);

أي :

 001     ; PERMISSION_EXECUTE
 010  OR ; PERMISSION_READ
 -------
 011     ; PERMISSION_EXECUTE | PERMISSION_READ

الآن لو شغلت البرنامج فسيطبع :

> a.exe
PERMISSION_EXECUTE is passed
PERMISSION_READ is passed
>

ماحدث مع الدالة أنّها عملت علاقة AND بين 011 وPERMISSION_EXECUTE فأعطت 001, طالما أنها لم تعطي صفر فهذا يعني أن الراية PERMISSION_EXECUTE مُررت :

 011     ; PERMISSION_EXECUTE | PERMISSION_READ
 001 AND ; PERMISSION_EXECUTE
 -------
 001     ; != 0

قامت أيضاً بعمل علاقة AND بين 011 وPERMISSION_READ وأعطت 010 ممايفيد بأن الراية PERMISSION_READ مررت لأننا لم نحصل على صفر :

 011     ; PERMISSION_EXECUTE | PERMISSION_READ
 010 AND ; PERMISSION_READ
 -------
 010     ; != 0

ولكن عندما قامت الدالة بعمل علاقة AND بين 011 وPERMISSION_WRITE فاناتج كان صفر :

 011     ; PERMISSION_EXECUTE | PERMISSION_READ
 100 AND ; PERMISSION_WRITE
 -------
 000     ; == 0

وهذا يعني أنّ الراية PERMISSION_WRITE لم تُمرر.

هناك الكثير من الدوال حولنا والتي تأخذ رايات كمعاملات مثل الدالة MessageBox والتي تُظهر الرسائل في ويندوز, هذه عينة من الرايات التي يُمكننا تمريرها للدالة MessageBox:

post-231926-070073700 1347015578_thumb.p

يُمكن تمرير الراية MB_OK للدالة MessageBox كي يظهر زر OK هكذا :

#include <windows.h>

int main(int argc, char **argv)
{
    MessageBox(NULL, TEXT("Hello there"), TEXT("Flags"), MB_OK);

    return 0;
}

ويُمكن أيضاً إضافة الراية MB_ICONWARNING لإظهار أيقونة تحذير :

#include <windows.h>

int main(int argc, char **argv)
{
    MessageBox(NULL, TEXT("Hello there"), TEXT("Flags"), MB_OK | MB_ICONWARNING);

    return 0;
}

وأيضاً يُمكن إضافة الراية MB_HELP لإضافة زرّ المساعدة HELP :

#include <windows.h>

int main(int argc, char **argv)
{
    MessageBox(NULL, TEXT("Hello there"), TEXT("Flags"), MB_OK | MB_ICONWARNING | MB_HELP);

    return 0;
}

تحدثنا عن الكثير من الموضوعات حول العمل مع البتات والبايتات والتلاعب بها. أو أن أنبه على أنك إذا رأيت شرح يتضمّن العمل على البتات كعند العمل مع العتاد أو الخوزميات والتشفير والتعامل مع الملفات فأغلب الظن أنك ستحتاج للجوء للعمليات التي ذكرناها.

أتمنى أن تكون إستفدت من المقال.

بالتوفيق.

المرفقات
1.png2.png3.png4.png5.png

تم تعديل هذه المشاركة بواسطة Mr.B في 8 سبتمبر 2012 في 00:49

20
#2

ما شاء الله عمل متعوب عليه ... قرأته كاملاً ... ولكن للأسف فهو موجه للمستوى تحت المبتدئ ... على كل حال مفيد جدا لهم ...

ولكن هناك عدة ملاحظات أرجو منك تقبلها بصدر رحب ...

أولا : أخطاء إملائية :

اقتباس
اقتباس
هناك ثلاث أربع أنظمة عدّ
اقتباس

* والنظام الأقل إستخدام النظام الثماني. وأعداده :

0, 1, 2, 3, 4, 5, 6, 7, 8

أظن أن الخطأ واضح لديك ... جزاك الله خيراً ...

ثانياً: أخطاء علمية :

اقتباس
النظام الثنائي ليس بنظام حقيقي وغير مُستخدم عند مستوى البرامج

هذه أو لمرة أسمع مثل هذا الكلام .؟؟

اقتباس
تشفير خفيف (لاتستخدمه لإخفاء بيانات حساسة فهو تشفير ضعيف ويُمكن كسره بسهولة نسبية).

غريب ..قرأت موضوعاً للأخ خالد الشايع .. يصمم فيه تشفير غير قابل للكسر بxor ..

اقتباس
typedef struct _LedGroup {
    unsigned char led1: 1,
                  led2: 1,
                  led3: 1,
                  led4: 1,
                  led5: 1,
                  led6: 1,
                  led7: 1,
                  led8: 1;
} LedGroup;

ما هي هذه الطريقة في كتابة المتغيرات !!!

في النهاية .. أشكرك لتقبل الملاحظات .. وأشكرك على المقال... ولكن حبذا لو تم تدارك النقطة التالية :

فهرسة المقال : على الأقل أخبرنا ما هي النقاط التي ستشرحها في مقالك .. حتى لا نضطر لقراءة المقال كاملا دون فائدة ..

جزاك الله خيراً ... الموضوع ممتاز لمن لا يعرف شيئاً عن العمليات المنطقية ...

والسلام عليكم

تم تعديل هذه المشاركة بواسطة مصطفى 36a2 في 7 سبتمبر 2012 في 21:04

#3

+1

موضوع مفيد جدا, تم التقييم بخمس نجوم.

بارك الله فيك.

#4

(1- +2) موضوع جميل أخي جاري القرائة

#5

شرح رائع ومفصلcool.gif

استفدت كثيرا من هذا الكورس و صححت بعض الأخطاء و أزلت الكثير من اللبس في بعض الأشياء التي كانت مبهمة بالنسبة لي في السابقwink.gif

+1

i love you mama

#6

@مصطفى : يارجل أي مستوى تحت المبتدئ :lol: ؟ كثير من المبرمجين المتمكنين لايعرفون تلك العمليات. أعد قراءة الموضوع بتركيز وستجد أنه أكثر من كونه عمليات منطقية.

- بالنسبة لموضوع الصفر والواحد فهو "رمز" لوجود التيار أو عدمه, فلا يوجد صفر أو واحد, شرحت ماعنيه بعد الإقتباس. مثلاً يُمكننا إستبدال الصفر والواحد بأي رمز كـ"+" و "-" أو "صواب" أو "خطأ" ولن يختلف الموضوع.

- موضوع التشفير ففعلاً تشفير الـxor ضعيف جداً وكلما كبر الملف أو الرسالة زادت قابلية كسرة. يكسر بطرق أعرف منها طريقة

. هناك خورزميات أكثر تعقيد وقوة ,مثل DES, ورغم ذلك تعد الآن ضعيفة.

- موضوع الـbit fields بينت طريقة تلك البنية بعد الكود. يمكنك التوسع به بالقراءة أحد هذه المقالات :

/index.php?showtopic=225234

http://msdn.microsoft.com/en-us/library/yszfawxh%28v=vs.80%29.aspx

http://publications.gbdirect.co.uk/c_book/chapter6/bitfields.html

أشكرك على تنبيهك لم أنتبه لتلك الأخطأ, صحت ذلك ووضعت فهرس بسيط في مقدمة الموضوع.

حياكم الله شباب وإن شاء الله إستفدتم.

#7
اقتباس
بالنسبة لموضوع الصفر والواحد فهو "رمز" لوجود التيار أو عدمه, فلا يوجد صفر أو واحد, شرحت ماعنيه بعد الإقتباس

وكذلك أخي فإن جميع الأرقام هي مجرد رموز ..مثلاً لا يوجد شيء اسمه 9 إنه رمز للدلالة على الكمية .. ويمكن التعامل معه كقيمة مجردة ... ولكن بالنهاية هي رموز يمكن استبدالها ..

اقتباس
الآن يُمكن وضعه صفر بأي من هذه المُتغيرات هكذا ledgroup.led2 = 0 أو 1 هكذا ledgroup.led2 = 1. طبعاً خجم أيّ من ledX يساوي 1 بت تماماً وهذا يعني أنك إما أن تضع 0 أو 1 فقط.

رائع ... أسحب كلامي السابق ... هذه المقالة تنقل من هم دون المبتدئ (في العمليات المنطقية)إلى فوق المتوسط على الأقل (في استخدام العمليات المنطقية )

جزاك الله خيراً... وشكراً لأنك تحملتني ...

بخصوص التشفير هذا هو موضوع الأخ خالد الذي حدثتك عنه ...

استفدنا جزاك الله خيراً ..

والسلام عليكم

تم تعديل هذه المشاركة بواسطة مصطفى 36a2 في 8 سبتمبر 2012 في 08:18

#8

الموضوع فعلا يستحق التقييم ب 5 نجوم

صراحة Mr.B امتعتنا بعدة مواضيع من العيار الثقيل فى الفترة الأخيرة

لك الشكر على هذا المجهود ولكننا لم نكتفى بعد وننتظر المزيد :)


﴿وَمَا كَانَ اللَّهُ لِيُعَذِّبَهُمْ وَأَنْتَ فِيهِمْ ۚ وَمَا كَانَ اللَّهُ مُعَذِّبَهُمْ وَهُمْ يَسْتَغْفِرُونَ﴾

الأنفال ۳۳                                  
 

#9

موضوع جيد, أزال كثيرا من اللبس كنت أقع فيه

+1

لكن لم أستطع تقييم الموضوع بالنجوم :blink:

[#10345] You do not have permission to rate this topic.

[سبحان الله و بحمده, سبحان الله العظيم]

#10

+1

 

موضوع رائع من انسان رائع 

 

تحياتي أستاذ

−1
#11

فعلا درس رائع , بارك الله فيك

عندي استفسار بسيط

انت قلت في بعض البرامج تجد شىء مثل

unsigned char number = (unsigned char) -1;

و الذي سيعطينا القيمة القصوى لهذا المتغيير في هذا المثال يعني 1111 1111, حسب كلامك -1  تعني 0001 1000

كيف 1111 1111 = 0001 1000

 

شكرا على الدرس

#12
اقتباس
حسب كلامك -1  تعني 0001 1000

 

يوجد أكثر من نظام لتمثيل الأعداد السالبة و المشهور هو Two complement  حيث تقوم بعكس البتات التى يستهلكها الرقم داخل مساحة محددة و من ثم تقوم بإضافة واحد لهم.

 

سنأخذ النوع signed char على سبيل المثال حيث مساحته قد تكون 8 بت و بالتالي الرقم 510 سيكون 01012 0000 و عملية تحويل 510 إلى 10-5 ستتم كالتالي:

-> 5         ; 5 in decimal
-> 0000 0101 ; 5 in binary
-> 1111 1010 ; flip bits
-> 1111 1011 ; add one, this is -5 in binary

و لتحويل 110 إلى 10-1:

-> 1         ; 1 in decimal
-> 0000 0001 ; 1 in binary
-> 1111 1110 ; flip bits
-> 1111 1111 ; add one, this is -1 in binary

و الله ولي التوفيق

2

مدونتي: C++ Tips and Tricks

#13

شكرا C++er 

الان اتضحت الصورة

#14

السلام عليكم

موضوع مميز يا استاد

درست تقريبا الشطر الأول من الدرس

لدي بعض الاسفسارات

بخصوص علمية النفي not دكرت انها تقوم بنفي اي عدد

فمتلا ماهو نفي العدد 5؟؟ انا اعرف انها تنفي 0 الى 1 و تنفي اي عدد الى 0

ورمزها في السي ! وليس رمزها ~ 

المرجو التوضيح

+

اقتباس
لم نقم بعمل شيء فعلياً, فـAND س مع صفر ستعطي س. ولكن الجزء الحقيقي يبدأ هنا : 

 

على ما اضن تتكلم عن Or وليس and

#15
اقتباس

ورمزها في السي ! وليس رمزها ~

النفي تتم على المتغيرات المنطقية ذات النوع bool و تقوم بتحويل true إلى false و تقوم بتحويل false إلى true و المعامل ! يسمى logical-not.

المعامل ~ يستخدم للحصول على معكوس البتات داخل المتغيرات ذات الأنواع الرقمية الصحيحه و يسمي bitwise-not أو bitwise-negation  أو complement.

 

و الله ولي التوفيق

تم تعديل هذه المشاركة بواسطة C++er في 2 أبريل 2014 في 01:47

2

مدونتي: C++ Tips and Tricks

#16

يعني لو كان لدينا متغير يحتوي على القيمة

0xff

وطبقنا عليه عملية النفي

ستصبح قيمته

0x0

؟؟؟

#17
اقتباس

يعني لو كان لدينا متغير يحتوي على القيمة

0xff

وطبقنا عليه عملية النفي

ستصبح قيمته

0x0

؟؟؟

أولا: كما ذكرت لك إسمها ليس النفي و إنما معكوس القيمه.

ثانيا: إذا كان حجم نوع المتغير 8 بت فالإجابة نعم و إذا كانت مساحته أكبر من 8 بت حينها ستصبح اول 8 بت بصفر و بقية البتات بواحد.

 

 

و الله ولي التوفيق

1

مدونتي: C++ Tips and Tricks

مواضيع مشابهة

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…