این آموزش، جلسه ی سومی هست که میخواهیم در مورد عملگرهای زبان PHP صحبت کنیم.
شاید تا قبل از این جلسات، تصور نمیکردید که بحث عملگرها در زبان PHP، این همه جزئیات داشته باشد.
در دو جلسه ی قبلی عملگرهای ریاضی، انتساب، رشته، مقایسه ای، کنترل خطا، بیتی، منطقی را معرفی کردیم.
این جلسه میخواهیم، عملگرهای باقیمانده ی زبان PHP را بررسی میکنیم و چندین مثال ترکیبی با عملگرهایی که تا کنون یاد گرفته ایم بیاوریم.
* اولویت عملگرها:
احتمالاً اکثرتان اولویت عملگرها را بشناسید اما در این جلسه میخواهیم کمی با جزئیات آنها آشنا شویم.
ضمناً گاهی اوقات در زبان PHP آنچیزی که در مورد اولویتها در ذهنتان داشته اید اتفاق نمیافتد ولی بخاطر گنگ نشدن موضوع آنها را فعلاً بیان نمیکنیم.
ترتیب اولویت ها مشخص میکنند که بین چندین عملگر، نحوه ی اجرا چگونه است.
نحوه ی اجرا از روی گروه بندی(بسته بندی) عبارت مشخص میشود.
برای اینکه خوب متوجه شوید منظورمان چیست، باید چند مثال بیاوریم.
برای مثال 1 + 2 * 3
را در نظر بگیرید، در نگاه اول میگویید، جواب 9 است، در حالی که جواب صحیح 7 میباشد.
اما علت مربوط به ترتیب اولویت ها میباشد.
در مقاله ی 14، عملگرهای ریاضی را معرفی کردیم و در آنجا دیدید که عملگرهای +
و *
بین دو عملوند اتفاق میافتند.
حالا در عبارت 1 + 2 * 3
عملوندهای هر کدام، چه اعدادی هستند؟
به کمک ترتیب اولویت عملگرها میتوان فهمید که عبارت 1 + 2 * 3
چگونه اجرا میشود.
عملگر *
اولویت بالاتری نسبت به +
دارد.
پس با این دانسته میدانیم که در این عبارت، ابتدا 2 * 3
انجام میگیرد.
بنابراین اگر بخواهیم با این دانسته، عبارت 1 + 2 * 3
را گروه بندی(بسته بندی) کنیم، عبارت به شکل 1 + (2 * 3)
در میآید.
میبینید که با دانستن ترتیب اولویت توانستیم، عملوندها را مشخص کنیم و هر کدام که اولویت بالاتری دارد را در یک گروه قرار بدهیم.
عملوند های *
اعداد 3 و 2 شدند و عملوند های +
عدد 1 و نتیجه ی 2 * 3
یعنی 6 شده است.
پس ابتدا ضرب انجام میشود و سپس 1 + (6)
که نتیجه ی آن برابر 7 خواهد بود.
این پرانتز گذاریها را جهت تفهیم مطلب به کار بردیم وگرنه اگر دنبال جواب 7 باشید دیگر نیازی به پرانتز گذاری هم نیست.
اما اگر مقصود شما جواب 9 است باید حتماً عبارت را بصورت (1 + 2) * 3
عبارت را بنویسید و پرانتز گذاری کنید.
+ همانطور که ملاحظه کردید، اولویت عملگرها، گروه بندی یک عبارت را مشخص میکند.
حالا ببنیم واقعا در پشت صحنه ی PHP هم در مثال بالا، ابتدا ضرب انجام میگیرد یا جمع!!
opcode عبارت 1 + 2 * 3
به شکل زیر است:
line # op return operands
----------------------------------------------------
2 0 MUL ~0 2, 3
1 ADD ~1 1, ~0
2 FREE ~1
3 RETURN 1
شاید در ابتدا کمی گنگ بنظر برسد ولی کافیست فقط به خط #0
و #1
نگاه بیندازید. خواهید دید که ابتدا بین اعداد 2 و 3 عمل MUL
یا همان Multiply(ضرب) صورت گرفته است و در خط دوم بین عدد 1 و جواب خط قبلی (منظور عدد 6 است) عمل ADD
صورت گرفته است.
این نمونه از دستورالعمل، نشان میدهد که چه عملی ابتدا صورت گرفته است.
+ ( در مقاله ی بررسی تاریخچه ی زبان PHP، کمی در مورد opcodeها هم صحیت کرده ایم.)
نکته: با پرانتز گذاری میتوانید عبارات را گروه بندی کنید تا عبارتهای درون یک گروه، حتماً با یکدیگر محاسبه شوند.
+ (با این کار تأکید میکنیم که چه عباراتی با یکدیگر سنجیده شوند.)
پس طبیعتاً هر گروه دارای اولویتهای درون گروهی هم خواهد بود.
برای مثال عبارت 1 + (2 + 3 * 4)
را اگر ببینید، خود عبارت داخل پرانتز، بصورت مجزا دارای ترتیب اولویت میباشد: 2 + (3 * 4)
پس اگر بخواهیم به شما نحوه ی اجرا را نشان دهیم، کل عبارت به این شکل در میآید: 1 + (2 + (3 * 4))
یعنی 1 + (2 + (12))
خواهد بود.
سؤال: اگر عبارت به شکل 1 + 2 + 3
باشد، نحوه ی اجرا به چه شکل است؟
پاسخ: چون ترتیب اولویتها یکسان است(و عملگرهای دارای اولویت یکسان یکی پس از دیگری آمده است)، بنابراین با توجه به لیست اولویت ها، قانون Associativity یا شرکت پذیری مشخص میکند که عملگرهای با ترتیب اولویت یکسان، چگونه گروه بندی شوند. عملگر +
از سمت چپ به راست و به ترتیب در نظر گرفته میشوند.
برای مثال اگر بخواهیم 1 + 2 + 3
را پرانتز گذاری کنیم، به این شکل در میآید: (1 + 2) + 3
سؤال: اگر عبارت به شکل 1 + 4 - 3 - 2
بود، نحوه ی اجرا به چه شکل میشد؟
پاسخ: عملگرهای +
و -
دارای اولویت یکسانی هستند.
همچنین این دو عملگر از قانون شرکت پذیری برخوردار هستند. پس طبق لیست ترتیب اولویت ها، در این مورد هم، عبارت از سمت چپ به راست در نظر گرفته میشود.
اگر عبارت را پرانتز گذاری کنیم، به شکل روبرو در میآید: ((1 + 4) - 3) - 2
حالا لیست ترتیب اولویتهای زبان PHP را ببینید:
Associativity Operators Additional Information
**************************************************************************
non-associative clone new clone and new
--------------------------------------------------------------------------
left to right [ array()
--------------------------------------------------------------------------
right to left ** arithmetic
--------------------------------------------------------------------------
right to left ++ -- ~
(int) (float)
(string) (array)
(object) (bool) @ types and increment/decrement
--------------------------------------------------------------------------
non-associative instanceof types
--------------------------------------------------------------------------
right to left ! logical
--------------------------------------------------------------------------
left to right * / % arithmetic
--------------------------------------------------------------------------
left to right + - . arithmetic and string
--------------------------------------------------------------------------
left to right << >> bitwise
--------------------------------------------------------------------------
non-associative < <= > >= comparison
--------------------------------------------------------------------------
non-associative == != ===
!== <> <=> comparison
--------------------------------------------------------------------------
left to right & bitwise and references
--------------------------------------------------------------------------
left to right ^ bitwise
--------------------------------------------------------------------------
left to right | bitwise
--------------------------------------------------------------------------
left to right && logical
--------------------------------------------------------------------------
left to right || logical
--------------------------------------------------------------------------
right to left ?? comparison
--------------------------------------------------------------------------
left to right ? : ternary
--------------------------------------------------------------------------
right to left = += -= *= **= /= .=
%= &= |= ^= <<= >>= assignment
--------------------------------------------------------------------------
left to right and logical
--------------------------------------------------------------------------
left to right xor logical
--------------------------------------------------------------------------
left to right or logical
نکته: در جدول بالا Associativityها نشان میدهند که عملگرهای دارای اولویت یکسان در عبارت، چگونه و از چه سمتی گروه بندی شوند.
توجه: قانون شرکت پذیری مشخص میکند که عملگرهای با اولویت یکسان، اگر یکی پس از دیگری اتفاق ببفتد از چه سمتی در نظر گرفته شود.(پس حداقل 2 عملگر با اولویت یکسان میبایست وجود داشته باشد.)
میخواهیم برای تثبیت مطلب، چندین مثال بزنیم:
سؤال: عبارت 1 < 2 > 1
چگونه گروه بندی و محاسبه میشود؟
پاسخ: اگر به جدول ترتیب اولویتها نگاهی بیندازید، عملگرهای <
و >
دارای اولویت یکسانی هستند امـــا دارای شرکت پذیری نیستند.
بنابراین این عبارت در زبان PHP غیر مجاز میباشد.
سؤال: عبارت 1 <= 1 == 1
چطور؟
پاسخ: این عبارت مجاز است. چون عملگر ==
دارای اولویت پایینتری نسبت به عملگر <=
است.
عبارت بصورت روبرو گروه بندی میشود: (1 <= 1) == 1
سؤال: عبارت $a = $b = $c = 0;
چطور؟
پاسخ: ترتیب اولویتشان که برابر است و از طرف دیگر اگر جدول را نگاه کنید، قانون شرکت پذیری برای این عملگر، ترتیب را از راست به چپ نشان میدهد.
پس به این شکل در میآید: $a = ($b = ($c = 0));
سؤال: عبارت $a = 1 + 2 * 3;
چطور؟
پاسخ: عملکر =
دارای اولویت پایینتری نسبت به دو عملگر +
و *
است.
همچنین عملگر +
هم دارای اولویت پایینتری نسبت به عملگر *
است.
بنابراین اگر بخواهیم نشان دهیم که چگونه گروه بندی میشوند، به این شکل است: $a = (1 + (2 * 3));
سؤال: چرا خروجی دستور زیر، برابر 2 است؟
<?php
$x = 3;
echo 'Result: ' . $x + 2; // 2
پاسخ: شاید انتظار داشته باشید، خروجی دستور بالا به شکل Result: 5
باشد.
اما اگر به جدول اولویتها نگاه کنید، میبینید که عملگرهای .
و +
دارای اولویت یکسانی هستند و از سمت چپ به راست ارزیابی میشوند.
پس اگر بخواهیم پرانتز بندی کنیم، نحوه ی اجرای دستور بالا به شکل echo ('Result: ' . $x) + 2;
خواهد بود.
پس عبارت echo 'Result: 3' + 2;
را خواهیم داشت.
حالا چیزی که میبینیم، جمع بین یک string و یک integer است. با توجه به مباحث جلسات گذشته، این string به integer تبدیل میشود یا اصطلاحاً cast میشود.
پس خواهیم داشت echo 0 + 2
و در نتیجه در خروجی، عدد 2 چاپ خواهد شد.
اما اگر مقصودتان این باشد که در خروجی Result: 5
چاپ شود، باید دستور بالا را به شکل echo 'Result: ' . ($x + 2)
بنویسید.
OpCode: برای اینکه خودتان هم ترتیب opcodeهای مثال بالا را ببینید:
compiled vars: !0 = $x
line #* op return operands
----------------------------------------------------------------
2 0 ASSIGN !0, 3
3 1 CONCAT ~1 'Result%3A+', !0
2 ADD ~2 ~1, 2
3 ECHO ~2
4 RETURN 1
اگر این قسمت را متوجه نشدید اهمیتی ندارد. فقط اگر توجه کنید میبینید اول $x = 3
انجام میشود که در opcodeها با دستور ASSIGN
مشخص شده است.
در ادامه دستور CONCAT
نشان میدهد که قسمت 'Result: ' . $x
انجام میشود.
بعد از آن، دستور ADD
نشان میدهد که 'Result: 3' + 2
شده است و ECHO
شده است.
این opcodeها در موتور Zend تفسیر خواهند شد.
حالا به یک سؤال مهم توجه کنید:
سؤال: عبارت 1 > 2 && 3 > 1
چگونه اجرا میشود؟
پاسخ: اولویت >
بالاتر از اولویت &&
میباشد. اگر بخواهیم عبارت را پرانتز گذاری کنیم به این شکل در میآید: (1 > 2) && (3 > 1)
اگر یادتان باشد، در جلسه ی پیش گفتیم که عملگرهای منطقی، خاصیت short-circuit
دارند بنابراین در عملگر &&
اگر شرط اول true نباشد، دیگر شرط دوم چک نمیشود و نتیجه ی &&
برابر false خواهد بود. خب تا اینجا درست.
حالا شاید برای بعضیها سؤال پیش بیاید که مگر اولویت >
بالاتر از &&
نیست، پس چطور میشود که طرف دوم چک نشود؟ یعنی میگویند اول شرطهای دو طرف &&
چک میشود و در آخر &&
محاسبه میشود.
+ گفتیم اولویتها گروه بندی رو مشخص میکنند ولی ترتیب اجرای دستور العملها بستگی به مفسر PHP داره.(یکی از موارد گیج کننده شاید باشه.)
برای پاسخ به این سؤال، بهتره از روی opcodeهای دستور بالا بفهمیم که چطور تفسیر خواهد شد.
opcodeها به ما نشان خواهند داد که ترتیب اجرای دستور بالا، چگونه است:
line # op return operands
-------------------------------------------------------------------------------------
2 0 IS_SMALLER ~0 2, 1
1 JMPZ_EX ~0 ~0, ->4
2 IS_SMALLER ~1 1, 3
3 BOOL ~0 ~1
4 FREE ~0
5 RETURN 1
با توجه به شماره خط #
دستورالعملهای بالا را بررسی میکنیم:#0
: دستور IS_SMALLER
چک میکند که آیا عدد 2 از عدد 1 کوچکتر است یا نه، و جوابش boolean است.#1
: قسمتی که دنبال جواب هستیم، همین دستور JMPZ_EX
است. این دستور عمل JUMP را انجام میدهد. یعنی ابتدا چک میکند که جواب 1 > 2
false است یا true(جواب در #0
بدست آمده است) و اگر جواب false(یا صفر) بود به ردیف #4
جامپ خواهد کرد. و ردیف #4
هم فضایهای اختصاص داده به برنامه را آزاد میکند و در نهایت برنامه به پایان میرسد.
برای درک بهتر به شبیه سازی زیر توجه کنید:(منظور opcodeهای بالا مثل کد زیر است)
<?php
if(!(1 > 2)) goto end;
if(!(3 > 1)) goto end;
echo 'done';
end: return 0;
همانطور که میبینید، با توجه به جمله ی شرطی ما و وجود &&
در عبارت، ترتیب دستورالعملها به شکلی قرار گرفته است که اگر شرط اول برقرار نباشد، عمل JUMP صورت بگیرد و از کل آن عبارت بگذرد و در نتیجه شرط دوم و هر چند شرط دیگر که باشد، چک نشود.
همچنین به چند مثال دیگر میتوانید نگاه بیندازید که چطور گروه بندی شده اند:
<?php
$a = $i = 1;
$b = $j = 2;
//------------------------------//
$a = $b += 3; // $a = ($b += 3); -> $a = 5, $b = 5
//------------------------------//
$a = 3 * 3 % 5; // $a = ((3 * 3) % 5); -> 4
//------------------------------//
$a = true && 'true' ? 0 : true ? 1 : 2; // $a = ((true && true ? 0 : true) ? 1 : 2); -> 2
//------------------------------//
$a = $i & $j >> 1; // $a = ($i & ($j >> 1)); -> 1
//------------------------------//
$a = $i >= 0 && $i < $j; // $a = ($i >= 0) && ($i < $j); -> true
//------------------------------//
$a = !($i and $i + $j ** 2 && $j % 2);
/*
$a = !( $i and ( ($i + ($j ** 2)) && ($j % 2) ) ) ->
$a = !(false) ->
($a = true)
*/
همانطور که در مثالها دیدید، ترتیب اولویتها کمک کرد تا بتوانیم عبارتها را گروه بندی کنیم.
توصیه: حتماً عبارت هایی که چندین عملگر با اولویتهای متفاوت در کنار یکدیگر قرار گرفته اند را پرانتز گذاری کنید تا هم خوانایی کدتان بالا رود و هم آنچه که از انتظار دارید، تفسیر شود. چون این احتمال وجود دارد اگر گروه بندی نکرده باشید، در ورژنهای مختلف PHP نتایج متفاوتی در خروجی اتفاق بیفتد.
بحث ترتیب اولویتها تمام است، حالا دیگر عملگرهای باقیمانده را معرفی خواهیم کرد.
* عملگر اجرا: (Execution Operator)
برای اجرای commandهای خود میتوانید از عملگر ` `
بهره ببرید.
دقت داشته باشید که علامت single-quote نمیباشد و نامش backtick است.
زمانی که محتوایی در بین دو backtick قرار میدهید، PHP آنرا در خط فرمان سیستم عاملتان اجرا میکند و خروجی را برای شما برمیگرداند.
این عملگر مانند تابع shell_exec()
عمل میکند.
حالا اگر سیستم عامل شما windows است، میتوانید دستور زیر را تست کنید:
<?php
$output = `dir`;
echo '<pre>' . $output . '</pre>';
یا اگر سیستم عامل شما linux است:
<?php
$output = `ls`;
echo '<pre>' . $output . '</pre>';
نکته: عملگر backtick در داخل double-quote و single-quote هیچ معنی خاصی نمیدهد و بعنوان جزئی از رشته محسوب میشود.