سلسلة ++c للمحترفين الدرس 3: أسبقية العمليات في ++C


محمد الميداوي

العمليات المنطقية

العامل المنطقي && له الأسبقية على ||، وذلك يعني أن الأقواس توضع لتقييم ما سيتم تقييمه مع غيره، وتستخدم C++‎ تقييم الدارة القصيرة (short-circuit evaluation) في تقييم && و || من أجل تجنّب إجراء العمليات غير الضرورية. أيضًا، إذا أعاد الجانب الأيسر من || القيمة true، فلن تكون هناك حاجة إلى تقييم الجانب الأيمن.

في المثال التالي، سنقيّم ثلاثة قيم منطقية بالعامِليْن || و && من أجل توضيح الأسبقية، لا تعني الأسبقية أن && ستقيَّم أولًا وإنما توضح أين ستوضع الأقواس:

#include <iostream>
#include <string>

using namespace std;

bool True(string id) {
    cout << "True" << id << endl;
    return true;
}

bool False(string id) {
    cout << "False" << id << endl;
    return false;
}

int main() {
    bool result;

    // حالة 1
    result =
        False("A") || False("B") && False("C"); 
  		// eq. False("A") || (False("B") && False("C"))
    //FalseA
    //FalseB


	// حالة 2
    result =
        True("A") || False("B") && False("C");
                // eq. True("A") || (False("B") && False("C"))
  
    cout << result << " :=====================" << endl;
    //TrueA
}

في تقييم الدارة القصيرة، يجري تخطي C في الحالة الأولى، فبما أن A خطأ فيجب تقييم الجانب الأيمن من العامِل ||؛ تكون B خطأ أيضًا، لذا ليس هنالك حاجة إلى تقييم  C مهما كان تقييمها سواءً خطأ False أم صحيح True.

في الحالة الثانية، يجري تخطي B و C وفقًا لتقييم الدارة القصيرة، إذ قيمة A هي True صحيحة فلا داع لتقييم الجانب الأيمن من المعامل ||. إن كان للعامِل || أسبقية على && فإن التقييم المكافئ يكون: (True("A") || False("B")) && False("C") والذي سيطبع TrueA للجزء الأول الموضوع بين أقواس والذي سيتخطى B فقط آنذاك في تقييم الدارة القصيرة ولكن بوجود العامل && في الطرف الأيمن من الأقواس، ستكون النتيجة النهائية FalseC. بما أنّ الأقواس موضوعة بشكل مختلف، فإنّ الأجزاء التي ستُقيّم ستكون مختلفة ما يجعل النتيجة النهائية في هذه الحالة هي False، لأن تقييم C هو False.

العاملان AND و OR لهما الأسبقية المعتادة في C++‎، أي أنّ AND تسبق OR دومًا.

// يمكنك القيادة برخصة أجنبية لستِّين يوما
bool can_drive = has_domestic_license || has_foreign_license && num_days <= 60;

هذه الشيفرة مكافئة لما يلي:

// يمكنك القيادة برخصة أجنبية لستّين يوما
bool can_drive = has_domestic_license || (has_foreign_license && num_days <= 60);

لا تؤدي إضافة الأقواس إلى تغيير السلوك، ولكنها تحسّن إمكانية القراءة، وترفع اللُّبس عن نية كاتب الشيفرة.

العمليات الأحادية

تُطبَّق العمليات الأحادية على الكائن الذي استُدعيت عليه وسميت أحادية لأن العملية تطبَّق على طرف واحد فقط باستعمال عامل أحادي (Unary Operator)، ولها أسبقية عالية أي تأخذ الأولوية دومًا ضمن المعاملات. عند استخدام العامِل الأحادي بعد الكائن -أي postfix-، فلن يُتَّخذ إجراء إلا بعد تقييم العملية بأكملها، وهذا مفيد في بعض العمليات الحسابية. إليك المثال التالي:

int a = 1;
++a;                // 2
a--;                // 1

int minusa = -a;    // -1
bool b = true;
!b;                 // true

a = 4;
int c = a++/2;      // 4 / 2 = 2
cout << a << endl;  // 5

int d = ++a / 2;    // 6 / 2 = 3

int arr[4] =    {1,2,3,4};

إليك مثال آخر أكثر تتعقيدًا حول تطبيق عملية أحادية باستعمال العامل ++ على المؤشرات:

int arr[4] =    {1,2,3,4};

int * ptr1 = & arr[0];
int * ptr2 = ptr1++;

std::cout << * ptr1++ << std::endl; // 2

// قبل زيادته arr[0] الحصول على قيمة العنصر
int e = arr[0]++;      

std::cout << e << std::endl;        // 1
std::cout << * ptr2 << std::endl;   // 2

يشير المؤشر ptr1 إلى عنوان [arr[0 والتي تخزن القيمة 1 بينما يشير المؤشر ptr2 إلى المؤشر prt1 ثم يزيد من قيمة الأخير مقدار 1 ليشير بذلك إلى العنصر [arr[1.

العمليات الحسابية

العمليات الحسابية في C++‎ لها نفس الأسبقية التي لها في الرياضيات. فعمليتا الضرب والقسمة لهما تجميع يساري (left associativity) -بمعنى أنهما تُقيَّمان من اليسار إلى اليمين-، ولهما أسبقية أعلى من الجمع والطرح، واللتان لهما تجميع يساري كذلك. كما يمكننا أيضًا التحكم في أسبقية التعابير باستخدام الأقواس () كما نفعل في الرياضيات العادية. إليك المثال التالي:

// 4 pi R^3 - 4 pi r^3 = حجم السطح الكروي
double vol = 4.0 * pi * R * R * R / 3.0 - 4.0 * pi * r * r * r / 3.0;

// الإضافة
int a = 2 + 4 / 2;          // =    2+(4/2) = 4
int b = (3 + 3) / 2;        // =    (3+3)/2 = 3

// مع الضرب
int c = 3 + 4 / 2 * 6;      // =    3+((4/2)*6) = 15
int d = 3 * (3 + 6) / 9;    // =    (3*(3+6))/9 = 3

// القسمة والباقي  
int g = 3 - 3 % 1;          // =    3 % 1 = 0  3 - 0 = 3
int h = 3 - (3 % 1);        // =    3 % 1 = 0  3 - 0 = 3
int i = 3 - 3 / 1 % 3;      // =    3 / 1 = 3  3 % 3 = 0  3 - 0 = 3
int l = 3 - (3 / 1) % 3;    // =    3 / 1 = 3  3 % 3 = 0  3 - 0 = 3
int m = 3 - (3 / (1 % 3));  // =    1 % 3 = 1  3 / 1 = 3  3 - 3 = 0

هذا الدرس جزء من سلسلة دروس عن C++‎.

ترجمة -وبتصرّف- للفصل Chapter 3: operator precedence من كتاب C++ Notes for Professionals

اقرأ أيضا





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن